diff --git a/.eslintrc.json b/.eslintrc.json index bffb357..55f8beb 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,3 +1,23 @@ { - "extends": "next/core-web-vitals" -} + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint" + ], + "extends": [ + "next/core-web-vitals" + ], + "rules": { + "@typescript-eslint/no-unused-vars": [ + "error", + { + "args": "all", + "argsIgnorePattern": "^_", + "caughtErrors": "all", + "caughtErrorsIgnorePattern": "^_", + "destructuredArrayIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "ignoreRestSiblings": true + } + ] + } +} \ No newline at end of file diff --git a/app/help/[article]/error.tsx b/app/help/[article]/error.tsx index 9ecd2d8..e0df3d7 100644 --- a/app/help/[article]/error.tsx +++ b/app/help/[article]/error.tsx @@ -2,7 +2,6 @@ export default function Error({ error, - reset, }: { error: Error & { digest?: string }; reset: () => void; diff --git a/app/page.tsx b/app/page.tsx index ad5ded3..4540704 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,6 +1,4 @@ import { readMD } from "@/actions/readMD"; -import { TTCMD } from "@/components/ttcmd"; -import { readFile } from "fs/promises"; import { Suspense } from "react"; import { HomeClient } from "./client"; import { MDSkeletonLoader } from "@/components/loader"; diff --git a/bun.lockb b/bun.lockb index 173a35b..04c620b 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/lib/poppables/components/poppable-content.tsx b/lib/poppables/components/poppable-content.tsx index e507caf..4b305e4 100644 --- a/lib/poppables/components/poppable-content.tsx +++ b/lib/poppables/components/poppable-content.tsx @@ -4,7 +4,6 @@ import { PropsWithChildren, SetStateAction, useCallback, - useEffect, useState, } from "react"; import { bulkRound } from "../../utils/bulkRound"; @@ -24,7 +23,7 @@ interface IProps { setHover: Dispatch>; } -type position = { top: number; left: number; width?: number }; +type position = { top: number; left: number; width?: number; }; export const PoppableContent: FC> = ( { @@ -35,7 +34,6 @@ export const PoppableContent: FC> = ( spacing = 10, setHover, isClosing, - isClosed, }, ) => { const [popRef, setPopRef] = useState(); @@ -52,7 +50,7 @@ export const PoppableContent: FC> = ( relHeight: number, popWidth: number, popHeight: number, - edge: edge, + _edge: edge, align: alignment, ): position => { const pos = { diff --git a/lib/poppables/components/poppable.tsx b/lib/poppables/components/poppable.tsx index e80fd62..8d78b86 100644 --- a/lib/poppables/components/poppable.tsx +++ b/lib/poppables/components/poppable.tsx @@ -1,13 +1,6 @@ "use client"; -import { - FC, - PropsWithChildren, - ReactNode, - useCallback, - useEffect, - useState, -} from "react"; +import { FC, PropsWithChildren, ReactNode, useCallback, useState } from "react"; import { PoppableContent } from "./poppable-content"; import { useDebounce } from "../../../hooks/useDebounce"; diff --git a/lib/tcmd/TokenIdentifiers.tsx b/lib/tcmd/TokenIdentifiers.tsx index 1e2848d..727f401 100644 --- a/lib/tcmd/TokenIdentifiers.tsx +++ b/lib/tcmd/TokenIdentifiers.tsx @@ -3,48 +3,14 @@ import Link from "next/link"; import { Fragment } from "react"; import { Poppable } from "../poppables/components/poppable"; import { Accordion, AccordionContent } from "../accordion"; -import { metadata } from "@/app/layout"; -type SearchFunction = (s: string, start: number, end: number) => { - start: number; - end: number; - text: string; - lastIndex: number; -}; - -type TokenIdentifier = { - rx: RegExp; - parse: (s: string) => Token; - search?: SearchFunction; -}; - -type TokenIdentifierMap = Map< - string, - TokenIdentifier ->; - -export const TokenRenderers = new Map< - string, - TokenRenderer ->(); - -type IdentifierRegistration = >( - type: string, - match: RegExp, - parseFunction: (s: string, rx: RegExp) => IdentifiedToken, - renderFunction: TokenRenderer, - openTagRx?: RegExp, - closeTagRx?: RegExp, -) => void; +export const TokenRenderers = new Map>(); export function buildIdentifierMap(): [ TokenIdentifierMap, IdentifierRegistration, ] { - const TokenIdentifiers = new Map< - string, - TokenIdentifier - >(); + const TokenIdentifiers = new Map>(); function registerIdentifier( type: string, @@ -79,7 +45,7 @@ export function buildIdentifierMap(): [ return { ...token, ...identifiedToken } as Token; }, - search: (openTagRx && closeTagRx) + search: openTagRx && closeTagRx ? (s, start, end) => { return search( s, @@ -120,8 +86,11 @@ export const buildOnlyDefaultElements = () => { /(? { const rx = /((?:\[\])+)\n+([\s\S]*)\n+\/\[\]/; - const [_, columns, content] = s.match(rx) || - ["", "..", "Unable to parse grid"]; + const [_, columns, content] = s.match(rx) || [ + "", + "..", + "Unable to parse grid", + ]; return { content, raw: s, @@ -133,7 +102,7 @@ export const buildOnlyDefaultElements = () => { }; }, (token) => { - const { content, children, metadata, uuid } = token; + const { children, metadata } = token; return (
{ } as React.CSSProperties} className="grid grid-cols-dynamic gap-x-8 gap-y-6 mb-6" > - {children?.map((c) => ( -
- {c.render(c)} -
- ))} + {children?.map((c) =>
{c.render(c)}
)}
); }, @@ -173,17 +138,14 @@ export const buildOnlyDefaultElements = () => { }; }, (token) => { - const { children, metadata, uuid } = token; + const { children, metadata } = token; return (
- {children?.map((e) => ( - - {e.render(e)} - - ))} + {children?.map((e) => {e.render(e)} + )}
); }, @@ -192,22 +154,26 @@ export const buildOnlyDefaultElements = () => { ); // fenced code block - registerIdentifier("code", /`{3}\n+((?:.|\n)*?)\n+`{3}/g, (s, rx) => { - return { - content: s.match(new RegExp(rx, ""))?.at(1) || - "Unable to parse code", - raw: s, - metadata: {}, - uuid: crypto.randomUUID(), - rendersContentOnly, - }; - }, (token) => { - return ( -
-      {token.content}
-      
- ); - }); + registerIdentifier( + "code", + /`{3}\n+((?:.|\n)*?)\n+`{3}/g, + (s, rx) => { + return { + content: s.match(new RegExp(rx, ""))?.at(1) || "Unable to parse code", + raw: s, + metadata: {}, + uuid: crypto.randomUUID(), + rendersContentOnly, + }; + }, + (token) => { + return ( +
+          {token.content}
+        
+ ); + }, + ); // list registerIdentifier( @@ -215,20 +181,18 @@ export const buildOnlyDefaultElements = () => { /^\s*-\s([\s\S]*?)\n\n/gm, (s, rx) => { return { - content: s.match(new RegExp(rx, ""))?.at(1) || - "Unable to parse list", + content: s.match(new RegExp(rx, ""))?.at(1) || "Unable to parse list", raw: s, metadata: { initialDepth: - s.replace("\n", "").split("-").at(0)?.length.toString() || - "1", + s.replace("\n", "").split("-").at(0)?.length.toString() || "1", }, uuid: crypto.randomUUID(), rendersChildrenOnly, }; }, (token) => { - const { children, metadata, uuid } = token; + const { children, metadata } = token; return ( <>
    { > {children?.map((c) => { return ( -
  • +
  • {c.children?.map((c: Token) => ( {c.render(c)} ))} @@ -264,19 +225,15 @@ export const buildOnlyDefaultElements = () => { raw: s, metadata: { initialDepth: - s.replace("\n", "").split("-").at(0)?.length.toString() || - "1", + s.replace("\n", "").split("-").at(0)?.length.toString() || "1", }, uuid: crypto.randomUUID(), }; }, (token) => { - const { children, metadata, uuid } = token; + const { children, metadata } = token; return ( -
  • +
  • {children?.map((c) => ( (c.render(c)) @@ -288,104 +245,120 @@ export const buildOnlyDefaultElements = () => { ); // heading - registerIdentifier("heading", /^#+\s(.*?)$/gm, (s, rx) => { - const content = s.match(new RegExp(rx, ""))?.at(1) || - "Unable to parse heading"; - return { - content: content, - raw: s, - metadata: { - strength: s.match(/#/g)?.length.toString() || "1", - id: generateId(content, usedIds), - }, - uuid: crypto.randomUUID(), - rendersContentOnly, - }; - }, (token) => { - return ( -
    { + const content = s.match(new RegExp(rx, ""))?.at(1) || + "Unable to parse heading"; + return { + content: content, + raw: s, + metadata: { + strength: s.match(/#/g)?.length.toString() || "1", + id: generateId(content, usedIds), + }, + uuid: crypto.randomUUID(), + rendersContentOnly, + }; + }, + (token) => { + return ( +
    - {token.content} -
    - ); - }); - - // image - registerIdentifier("image", /\!\[(.*?)\]\((.*?)\)/g, (s, rx) => { - const [_, title, src] = s.match(new RegExp(rx, ""))!; - - return { - // content: inline, - content: title.trim(), - raw: s, - metadata: { - src, - }, - uuid: crypto.randomUUID(), - rendersContentOnly, - }; - }, (token) => { - const { metadata } = token; - metadata.src = metadata.src as string; - if (metadata.src.startsWith(" + {token.content}
    ); - } - // eslint-disable-next-line @next/next/no-img-element - return {token.content}; - }); + }, + ); + + // image + registerIdentifier( + "image", + /\!\[(.*?)\]\((.*?)\)/g, + (s, rx) => { + const [_, title, src] = s.match(new RegExp(rx, ""))!; + + return { + // content: inline, + content: title.trim(), + raw: s, + metadata: { + src, + }, + uuid: crypto.randomUUID(), + rendersContentOnly, + }; + }, + (token) => { + const { metadata } = token; + metadata.src = metadata.src as string; + if (metadata.src.startsWith(" + + ); + } + // eslint-disable-next-line @next/next/no-img-element + return {token.content}; + }, + ); // anchor - registerIdentifier("anchor", /(? { - let preset, [_, title, href] = s.match(new RegExp(rx, ""))!; - const match = title.match(/`{3}(cta|button)?(.*)/); + registerIdentifier( + "anchor", + /(? { + let preset, + [_, title, href] = s.match(new RegExp(rx, ""))!; + const match = title.match(/`{3}(cta|button)?(.*)/); - if (match) { - [_, preset, title] = match; - } + if (match) { + [_, preset, title] = match; + } - const classes = { - button: "btn-primary inline-block", - cta: "btn-secondary inline-block uppercase", - }; - return { - content: title.trim(), - raw: s, - metadata: { - href, - classes: classes[preset as keyof typeof classes], - }, - uuid: crypto.randomUUID(), - rendersContentOnly, - }; - }, (token) => { - const { metadata } = token; - return ( - - {token.content} - - ); - }); + const classes = { + button: "btn-primary inline-block", + cta: "btn-secondary inline-block uppercase", + }; + return { + content: title.trim(), + raw: s, + metadata: { + href, + classes: classes[preset as keyof typeof classes], + }, + uuid: crypto.randomUUID(), + rendersContentOnly, + }; + }, + (token) => { + const { metadata } = token; + return ( + + {token.content} + + ); + }, + ); // inline-code registerIdentifier( @@ -411,70 +384,75 @@ export const buildOnlyDefaultElements = () => { ); // bold - registerIdentifier("bold", /\*{2}(.*?)\*{2}/g, (s, rx) => { - return { - content: s.match(new RegExp(rx, "i"))?.at(1) || - "Unable to parse bold", - raw: s, - metadata: {}, - uuid: crypto.randomUUID(), - rendersContentOnly, - }; - }, (token) => { - return ( - - {token.content} - - ); - }); + registerIdentifier( + "bold", + /\*{2}(.*?)\*{2}/g, + (s, rx) => { + return { + content: s.match(new RegExp(rx, "i"))?.at(1) || "Unable to parse bold", + raw: s, + metadata: {}, + uuid: crypto.randomUUID(), + rendersContentOnly, + }; + }, + (token) => { + return {token.content}; + }, + ); // italic - registerIdentifier("italic", /(? { - return { - content: s.match(new RegExp(rx, "i"))?.at(1) || - "Unable to parse italic", - raw: s, - metadata: {}, - uuid: crypto.randomUUID(), - rendersContentOnly, - }; - }, (token) => { - return ( - - {token.content} - - ); - }); + registerIdentifier( + "italic", + /(? { + return { + content: s.match(new RegExp(rx, "i"))?.at(1) || + "Unable to parse italic", + raw: s, + metadata: {}, + uuid: crypto.randomUUID(), + rendersContentOnly, + }; + }, + (token) => { + return {token.content}; + }, + ); // popover - registerIdentifier("popover", /\^\[(.*?)\]\<<(.*?)\>>/g, (s, rx) => { - const [_, title, content] = s.match(new RegExp(rx, ""))!; + registerIdentifier( + "popover", + /\^\[(.*?)\]\<<(.*?)\>>/g, + (s, rx) => { + const [_, title, content] = s.match(new RegExp(rx, ""))!; - return { - content, - raw: s, - metadata: { title }, - uuid: crypto.randomUUID(), - rendersContentOnly, - }; - }, (token) => { - const { children, metadata, uuid } = token; - return ( - ( - {c.render(c)} - )) || - token.content} - preferredAlign="centered" - preferredEdge="bottom" - className="cursor-pointer mx-2" - > - - {metadata.title} - - - ); - }); + return { + content, + raw: s, + metadata: { title }, + uuid: crypto.randomUUID(), + rendersContentOnly, + }; + }, + (token) => { + const { children, metadata, uuid } = token; + return ( + ( + {c.render(c)} + )) || token.content} + preferredAlign="centered" + preferredEdge="bottom" + className="cursor-pointer mx-2" + > + + {metadata.title} + + + ); + }, + ); registerIdentifier( "accordion", @@ -490,17 +468,13 @@ export const buildOnlyDefaultElements = () => { }; }, (token) => { - const { children, metadata, uuid } = token; + const { children, metadata } = token; return (
    - + - {children?.map((e, i) => ( - - {e.render(e)} - + {children?.map((e) => ( + {e.render(e)} ))} @@ -509,169 +483,193 @@ export const buildOnlyDefaultElements = () => { }, ); - registerIdentifier("p", /(?<=\n\n)([\s\S]*?)(?=\n\n)/g, (s) => { - return { - content: s.replace("\n", " "), - raw: s, - metadata: {}, - uuid: crypto.randomUUID(), - }; - }, (token) => { - const { children, uuid } = token; + registerIdentifier( + "p", + /(?<=\n\n)([\s\S]*?)(?=\n\n)/g, + (s) => { + return { + content: s.replace("\n", " "), + raw: s, + metadata: {}, + uuid: crypto.randomUUID(), + }; + }, + (token) => { + const { children } = token; - debugger; + debugger; - return ( -
    - {children?.map((e) => { - console.log(e); - return ( - - {e.render(e)} - - ); - })} -
    - ); - }); + return ( +
    + {children?.map((e) => { + console.log(e); + return {e.render(e)}; + })} +
    + ); + }, + ); - registerIdentifier("hr", /^-{3,}$/gm, (s, rx) => { - return { - content: s, - raw: s, - metadata: {}, - uuid: crypto.randomUUID(), - rendersContentOnly, - }; - }, (token) => { - return
    ; - }); + registerIdentifier( + "hr", + /^-{3,}$/gm, + (s) => { + return { + content: s, + raw: s, + metadata: {}, + uuid: crypto.randomUUID(), + rendersContentOnly, + }; + }, + () => { + return
    ; + }, + ); - registerIdentifier("comment", //g, (s, rx) => { - return { - content: "", - metadata: { comment: s }, - raw: "", - uuid: crypto.randomUUID(), - rendersContentOnly, - }; - }, (token) => { - return <>; - }); + registerIdentifier( + "comment", + //g, + (s) => { + return { + content: "", + metadata: { comment: s }, + raw: "", + uuid: crypto.randomUUID(), + rendersContentOnly, + }; + }, + () => { + return <>; + }, + ); - registerIdentifier("frontmatter", /^---([\s\S]*?)---/g, (s, rx) => { - return { - content: "", - metadata: { - frontmatterString: s.match(rx)?.at(0) || "", - }, - raw: "", - uuid: "frontmatter", - }; - }, (token) => { - return <>{token.raw}; - }); + registerIdentifier( + "frontmatter", + /^---([\s\S]*?)---/g, + (s, rx) => { + return { + content: "", + metadata: { + frontmatterString: s.match(rx)?.at(0) || "", + }, + raw: "", + uuid: "frontmatter", + }; + }, + (token) => { + return <>{token.raw}; + }, + ); - registerIdentifier("table", /^\|\s[\s\S]*?\|(?=(\n\n)|$)/g, (s, rx) => { - const rowSections = s.split(/^\|[|-\s]+\|$/gm).map((s) => - s.split("\n").filter((r) => !!r).map((r) => - r.split("|").map((c) => c.trim()).filter((c) => !!c) - ) - ); + registerIdentifier( + "table", + /^\|\s[\s\S]*?\|(?=(\n\n)|$)/g, + (s) => { + const rowSections = s.split(/^\|[|-\s]+\|$/gm).map((s) => + s + .split("\n") + .filter((r) => !!r) + .map((r) => + r + .split("|") + .map((c) => c.trim()) + .filter((c) => !!c) + ) + ); - let headerRows: string[][] = [], - bodyRows: string[][] = [], - footerRows: string[][] = []; + let headerRows: string[][] = [], + bodyRows: string[][] = [], + footerRows: string[][] = []; - switch (rowSections.length) { - case 1: - bodyRows = rowSections[0]; - break; - case 2: - headerRows = rowSections[0]; - bodyRows = rowSections[1]; - break; - case 3: - headerRows = rowSections[0]; - bodyRows = rowSections[1]; - footerRows = rowSections[3]; - break; - } + switch (rowSections.length) { + case 1: + bodyRows = rowSections[0]; + break; + case 2: + headerRows = rowSections[0]; + bodyRows = rowSections[1]; + break; + case 3: + headerRows = rowSections[0]; + bodyRows = rowSections[1]; + footerRows = rowSections[3]; + break; + } - const maxColumns = Math.max( - ...[...headerRows, ...bodyRows, ...footerRows].map((r) => r.length), - ); + const maxColumns = Math.max( + ...[...headerRows, ...bodyRows, ...footerRows].map((r) => r.length), + ); - return { - content: s, - raw: s, - metadata: { - headerRows: headerRows, - bodyRows: bodyRows, - footerRows: footerRows, - columns: maxColumns, - }, - uuid: crypto.randomUUID(), - }; - }, (t) => { - const { headerRows, bodyRows, footerRows, columns } = t.metadata; + return { + content: s, + raw: s, + metadata: { + headerRows: headerRows, + bodyRows: bodyRows, + footerRows: footerRows, + columns: maxColumns, + }, + uuid: crypto.randomUUID(), + }; + }, + (t) => { + const { headerRows, bodyRows, footerRows, columns } = t.metadata; - return ( - - {!!headerRows && ( - - {headerRows.map((r, i) => ( - - {r.concat(Array(columns - r.length).fill("")).map((c) => { - const child = t.children?.find((child) => child.raw === c); - return ( - - ); - })} - - ))} - - )} - {!!bodyRows && ( - - {bodyRows.map((r, i) => ( - - {r.concat(Array(columns - r.length).fill("")).map((c) => { - const child = t.children?.find((child) => child.raw === c); - return ( - - ); - })} - - ))} - - )} - {!!footerRows && ( - - {footerRows.map((r, i) => ( - - {r.concat(Array(columns - r.length).fill("")).map((c) => { - const child = t.children?.find((child) => child.raw === c); - return ( - - ); - })} - - ))} - - )} -
    - {child?.render(child) || - c} -
    - {child?.render(child) || - c} -
    - {child?.render(child) || - c} -
    - ); - }); + return ( + + {!!headerRows && ( + + {headerRows.map((r, i) => ( + + {r.concat(Array(columns - r.length).fill("")).map((c) => { + const child = t.children?.find((child) => child.raw === c); + return ( + + ); + })} + + ))} + + )} + {!!bodyRows && ( + + {bodyRows.map((r, i) => ( + + {r.concat(Array(columns - r.length).fill("")).map((c) => { + const child = t.children?.find((child) => child.raw === c); + return ( + + ); + })} + + ))} + + )} + {!!footerRows && ( + + {footerRows.map((r, i) => ( + + {r.concat(Array(columns - r.length).fill("")).map((c) => { + const child = t.children?.find((child) => child.raw === c); + return ( + + ); + })} + + ))} + + )} +
    + {child?.render(child) || c} +
    + {child?.render(child) || c} +
    + {child?.render(child) || c} +
    + ); + }, + ); return TokenIdentifiers; }; @@ -694,14 +692,16 @@ function findMatchingClosedParenthesis( const openingMatch = openRegex.exec(str); const closingMatch = closedRegex.exec(str); - if ((openingMatch && !closingMatch)) { + if (openingMatch && !closingMatch) { throw Error("Things have gone horribly wrong"); } // if ((!openingMatch && closingMatch) || (!openingMatch && !closingMatch)) break; if ( - openingMatch && closingMatch && openingMatch.index < closingMatch.index + openingMatch && + closingMatch && + openingMatch.index < closingMatch.index ) { openings++; lastOpeningSuccessIndex = openingMatch.index + openingMatch[0].length; @@ -758,10 +758,11 @@ function search( // Finds a unique id for things like headings function generateId(t: string, usedIds: string[]) { - let id = t.toLowerCase().replace(/[^a-z\s]/ig, "").trim().replaceAll( - " ", - "-", - ); + let id = t + .toLowerCase() + .replace(/[^a-z\s]/gi, "") + .trim() + .replaceAll(" ", "-"); let idNum = 1; while (usedIds.includes(id)) { id = id.replace(/-[0-9]+$/g, ""); diff --git a/lib/tcmd/index.ts b/lib/tcmd/index.ts index 7d6f869..7611be6 100644 --- a/lib/tcmd/index.ts +++ b/lib/tcmd/index.ts @@ -118,7 +118,7 @@ function isAcceptableChild(parentType: string, childType: string): boolean { return acceptableChildren ? acceptableChildren.includes(childType) : true; } -// Occasionally, some P blocks start exactly at the same point as another block (a side effect of needing to exclude preceding linebreaks from the regex while also having the only clear delineation being those linebreaks) so we just remove those P blocks so that when searching for a parent, it doesn't need to figure out if the P block is valid or not. This doesn't cause issues during rendering since each block handles its own container element +// Occasionally, some P blocks start exactly at the same point as another block (a side effect of needing to exclude preceding line-breaks from the regex while also having the only clear delineation being those line-breaks) so we just remove those P blocks so that when searching for a parent, it doesn't need to figure out if the P block is valid or not. This doesn't cause issues during rendering since each block handles its own container element function filterOverlappingPBlocks(blocks: TokenMarker[]): TokenMarker[] { return blocks.filter((block) => { if (block.type !== "p") { diff --git a/package.json b/package.json index 439a382..406ce02 100644 --- a/package.json +++ b/package.json @@ -16,14 +16,15 @@ "react-dom": "^18" }, "devDependencies": { - "typescript": "^5", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", "autoprefixer": "^10.0.1", + "eslint": "^8", + "eslint-config-next": "14.1.0", "postcss": "^8", "tailwindcss": "^3.3.0", - "eslint": "^8", - "eslint-config-next": "14.1.0" + "typescript": "^5", + "typescript-eslint": "^7.2.0" } } diff --git a/types.d.ts b/types.d.ts index 4a7c9fe..3e73d59 100644 --- a/types.d.ts +++ b/types.d.ts @@ -26,3 +26,31 @@ type TokenMarker> = { }; type FrontMatter = Record; + +type SearchFunction = ( + s: string, + start: number, + end: number, +) => { + start: number; + end: number; + text: string; + lastIndex: number; +}; + +type TokenIdentifier = { + rx: RegExp; + parse: (s: string) => Token; + search?: SearchFunction; +}; + +type TokenIdentifierMap = Map>; + +type IdentifierRegistration = >( + type: string, + match: RegExp, + parseFunction: (s: string, rx: RegExp) => IdentifiedToken, + renderFunction: TokenRenderer, + openTagRx?: RegExp, + closeTagRx?: RegExp, +) => void;