import { HorizontalLine } from "components/HorizontalLine"; import { Ico, Icon } from "components/Ico"; import { Img } from "components/Img"; import { InsetBox } from "components/InsetBox"; import { ToolTip } from "components/ToolTip"; import { useAppLayout } from "contexts/AppLayoutContext"; import { slugify } from "helpers/formatters"; import { getAssetURL, ImageQuality } from "helpers/img"; import { Immutable } from "helpers/types"; import { useLightBox } from "hooks/useLightBox"; import Markdown from "markdown-to-jsx"; import { useRouter } from "next/router"; import React from "react"; import ReactDOMServer from "react-dom/server"; interface Props { className?: string; text: string; } export function Markdawn(props: Immutable): JSX.Element { const appLayout = useAppLayout(); // eslint-disable-next-line no-irregular-whitespace const text = `${preprocessMarkDawn(props.text)}​`; const router = useRouter(); const [openLightBox, LightBox] = useLightBox(); if (text) { return ( <> { if (compProps.href.startsWith("/")) { return ( router.push(compProps.href)}> {compProps.children} ); } return ( {compProps.children} ); }, }, h1: { component: (compProps: { id: string; style: React.CSSProperties; children: React.ReactNode; }) => (

{compProps.children}

), }, h2: { component: (compProps: { id: string; style: React.CSSProperties; children: React.ReactNode; }) => (

{compProps.children}

), }, h3: { component: (compProps: { id: string; style: React.CSSProperties; children: React.ReactNode; }) => (

{compProps.children}

), }, h4: { component: (compProps: { id: string; style: React.CSSProperties; children: React.ReactNode; }) => (

{compProps.children}

), }, h5: { component: (compProps: { id: string; style: React.CSSProperties; children: React.ReactNode; }) => (
{compProps.children}
), }, h6: { component: (compProps: { id: string; style: React.CSSProperties; children: React.ReactNode; }) => (
{compProps.children}
), }, SceneBreak: { component: (compProps: { id: string }) => (
* * *
), }, IntraLink: { component: (compProps: { children: React.ReactNode; target?: string; page?: string; }) => { const slug = compProps.target ? slugify(compProps.target) : slugify(compProps.children?.toString()); return ( router.replace( `${compProps.page ? compProps.page : ""}#${slug}` ) } > {compProps.children} ); }, }, player: { component: () => ( {appLayout.playerName ? appLayout.playerName : ""} ), }, Transcript: { component: (compProps) => (
{compProps.children}
), }, Line: { component: (compProps) => ( <> {compProps.name}

{compProps.children}

), }, InsetBox: { component: (compProps) => ( {compProps.children} ), }, li: { component: (compProps: { children: React.ReactNode }) => (
  • {compProps.children} ).length > 100 ? "my-4" : "" } > {compProps.children}
  • ), }, Highlight: { component: (compProps: { children: React.ReactNode }) => ( {compProps.children} ), }, footer: { component: (compProps: { children: React.ReactNode }) => ( <>
    {compProps.children}
    ), }, blockquote: { component: (compProps: { children: React.ReactNode; cite?: string; }) => (
    {compProps.cite ? ( <> “{compProps.children}” — {compProps.cite} ) : ( compProps.children )}
    ), }, img: { component: (compProps: { alt: string; src: string; width?: number; height?: number; caption?: string; name?: string; }) => (
    { openLightBox([ compProps.src.startsWith("/uploads/") ? getAssetURL(compProps.src, ImageQuality.Large) : compProps.src, ]); }} >
    ), }, }, }} > {text}
    ); } return <>; } function HeaderToolTip(props: { id: string }) { return ( { navigator.clipboard.writeText( `${process.env.NEXT_PUBLIC_URL_SELF + window.location.pathname}#${ props.id }` ); }} /> ); } function typographicRules(text: string): string { let newText = text; newText = newText.replace(/--/gu, "—"); /* * newText = newText.replace(/\.\.\./gu, "…"); * newText = newText.replace(/(?:^|[\s{[(<'"\u2018\u201C])(")/gu, " “"); * newText = newText.replace(/"/gu, "”"); * newText = newText.replace(/(?:^|[\s{[(<'"\u2018\u201C])(')/gu, " ‘"); * newText = newText.replace(/'/gu, "’"); */ return newText; } export function preprocessMarkDawn(text: string): string { if (!text) return ""; let preprocessed = typographicRules(text); let scenebreakIndex = 0; const visitedSlugs: string[] = []; preprocessed = preprocessed .split("\n") .map((line) => { if (line === "* * *" || line === "---") { scenebreakIndex += 1; return ``; } if (line.startsWith("# ")) { return markdawnHeadersParser(HeaderLevels.H1, line, visitedSlugs); } if (line.startsWith("## ")) { return markdawnHeadersParser(HeaderLevels.H2, line, visitedSlugs); } if (line.startsWith("### ")) { return markdawnHeadersParser(HeaderLevels.H3, line, visitedSlugs); } if (line.startsWith("#### ")) { return markdawnHeadersParser(HeaderLevels.H4, line, visitedSlugs); } if (line.startsWith("##### ")) { return markdawnHeadersParser(HeaderLevels.H5, line, visitedSlugs); } if (line.startsWith("###### ")) { return markdawnHeadersParser(HeaderLevels.H6, line, visitedSlugs); } return line; }) .join("\n"); return preprocessed; } enum HeaderLevels { H1 = 1, H2 = 2, H3 = 3, H4 = 4, H5 = 5, H6 = 6, } function markdawnHeadersParser( headerLevel: HeaderLevels, line: string, visitedSlugs: string[] ): string { const lineText = line.slice(headerLevel + 1); const slug = slugify(lineText); let newSlug = slug; let index = 2; while (visitedSlugs.includes(newSlug)) { newSlug = `${slug}-${index}`; index += 1; } visitedSlugs.push(newSlug); return `${lineText}`; }