import React, { useEffect, useState, useCallback } from "react"; import { Link, useParams } from "react-router-dom"; import { NoMatch } from "../routing"; // Ingredients import { Prose, ProseWithHeading } from "./ingredients/prose"; import { InteractiveExample } from "./ingredients/interactive-example"; import { Attributes } from "./ingredients/attributes"; import { Examples } from "./ingredients/examples"; import { LinkList, LinkLists } from "./ingredients/link-lists"; import { Specifications } from "./ingredients/specifications"; import { BrowserCompatibilityTable } from "./ingredients/browser-compatibility-table"; // Sub-components import { DocumentTranslations } from "./languages"; import { EditThisPage } from "./editthispage"; // XXX Make this lazy! import { DocumentSpy } from "./spy"; export function Document(props) { const params = useParams(); const slug = params["*"]; const locale = params.locale; const [doc, setDoc] = useState(props.doc || null); const [loading, setLoading] = useState(false); const [loadingError, setLoadingError] = useState( null ); useEffect(() => { if (doc) { document.title = doc.title; if (loading) { setLoading(false); } if (loadingError) { setLoadingError(null); } } }, [doc, loading, loadingError]); useEffect(() => { setLoading(false); }, [loadingError]); const getCurrentDocumentUri = useCallback(() => { let pathname = `/${locale}/docs/${slug}`; // If you're in local development Express will force the trailing / // on any URL. We can't keep that if we're going to compare the current // pathname with the document's mdn_url. if (pathname.endsWith("/")) { pathname = pathname.substring(0, pathname.length - 1); } return pathname; }, [slug, locale]); const fetchDocument = useCallback(async () => { let url = getCurrentDocumentUri(); url += "/index.json"; console.log("OPENING", url); let response: Response; try { response = await fetch(url); } catch (ex) { setLoadingError(ex); return; } if (!response.ok) { console.warn(response); setLoadingError(response); } else { if (response.redirected) { // Fetching that data required a redirect! // XXX perhaps do a route redirect here in React? console.warn(`${url} was redirected to ${response.url}`); } const data = await response.json(); setDoc(data.doc); } }, [getCurrentDocumentUri]); // There are 2 reasons why you'd want to call fetchDocument(): // - The slug/locale combo has *changed* // - The page started with no props.doc useEffect(() => { if ( !props.doc || getCurrentDocumentUri().toLowerCase() !== props.doc.mdn_url.toLowerCase() ) { setLoading(true); fetchDocument(); } }, [slug, locale, props.doc, getCurrentDocumentUri, fetchDocument]); function onMessage(data) { if (data.documentUri === getCurrentDocumentUri()) { // The recently edited document is the one we're currently looking at! fetchDocument(); } } if (loading) { return

Loading...

; } if (loadingError) { // Was it because of a 404? if (typeof window !== "undefined" && loadingError instanceof Response) { return ; } else { return ; } } if (!doc) { return null; } const translations = [...(doc.other_translations || [])]; if (doc.translation_of) { translations.unshift({ locale: "en-US", slug: doc.translation_of, }); } return (

{doc.title}

{translations && !!translations.length && ( )}

{doc.contributors && }
{process.env.NODE_ENV === "development" && ( )}
); } function Breadcrumbs({ parents }) { if (!parents.length) { throw new Error("Empty parents array"); } return (
    {parents.map((parent, i) => { const isLast = i + 1 === parents.length; return (
  1. {parent.title}
  2. ); })}
); } function RenderSideBar({ doc }) { if (!doc.related_content) { if (doc.sidebarHTML) { return
; } return null; } return doc.related_content.map((node) => ( )); } function SidebarLeaf({ parent }) { return (

{parent.title}

    {parent.content.map((node) => { if (node.content) { return (
  • ); } else { return (
  • {node.title}
  • ); } })}
); } function SidebarLeaflets({ node }) { return (
{node.uri ? {node.title} : node.title}
    {node.content.map((childNode) => { if (childNode.content) { return (
  1. ); } else { return (
  2. {childNode.title}
  3. ); } })}
); } /** These prose sections should be rendered WITHOUT a heading. */ const PROSE_NO_HEADING = ["short_description", "overview"]; function RenderDocumentBody({ doc }) { return doc.body.map((section, i) => { if (section.type === "prose") { // Only exceptional few should use the component, // as opposed to . if (!section.value.id || PROSE_NO_HEADING.includes(section.value.id)) { return ( ); } else { return ( ); } } else if (section.type === "interactive_example") { return ( ); } else if (section.type === "attributes") { return ; } else if (section.type === "specifications") { return ( ); } else if (section.type === "browser_compatibility") { return ( ); } else if (section.type === "examples") { return ; } else if (section.type === "info_box") { // XXX Unfinished! // https://github.com/mdn/stumptown-content/issues/106 console.warn("Don't know how to deal with info_box!"); return null; } else if ( section.type === "class_constructor" || section.type === "static_methods" || section.type === "instance_methods" ) { return ( ); } else if (section.type === "link_lists") { return ; } else { console.warn(section); throw new Error(`No idea how to handle a '${section.type}' section`); } }); } function Contributors({ contributors }) { return (
Contributors to this page:
); } function LoadingError({ error }) { return (

Loading Error

{error instanceof window.Response ? (

{error.status} on {error.url}
{error.statusText}

) : (

{error.toString()}

)}
); }