ttcMD: adds frontmatter extraction and token,

fixes inline-code... again
This commit is contained in:
Emmaline Autumn 2024-03-14 09:26:15 -06:00
parent 606a90b050
commit 0d839fbf37
5 changed files with 96 additions and 10 deletions

View File

@ -31,7 +31,7 @@
} }
.heading { .heading {
@apply pb-8 border-b border-b-primary-500 dark:border-b-dark-500 min-w-full; @apply pb-6 border-b border-b-primary-500 dark:border-b-dark-500 min-w-full;
} }
.heading h1 { .heading h1 {
@apply text-5xl font-bold; @apply text-5xl font-bold;
@ -68,7 +68,7 @@
@apply rounded-t-md; @apply rounded-t-md;
} }
.accordion:has(+ .accordion) { .accordion:has(+ .accordion) {
@apply border-b border-black/20; @apply border-b border-black/20 dark:border-white/20;
} }
.accordion:not(:has(+ .accordion)) { .accordion:not(:has(+ .accordion)) {
@apply rounded-b-md; @apply rounded-b-md;

View File

@ -1,7 +1,8 @@
"use client"; "use client";
import { TTCMD, TTCMDRenderer } from "@/components/ttcmd"; import { TTCMD, TTCMDRenderer } from "@/components/ttcmd";
import { FC, use, useCallback, useState } from "react"; import { extractFrontMatter } from "@/lib/tcmd";
import { FC, use, useCallback, useEffect, useState } from "react";
export const HelpClient: FC<{ body: Promise<string>; title: string }> = ({ export const HelpClient: FC<{ body: Promise<string>; title: string }> = ({
body: bodyPromise, body: bodyPromise,
@ -14,21 +15,47 @@ export const HelpClient: FC<{ body: Promise<string>; title: string }> = ({
return true; return true;
}, []); }, []);
const [frontMatter, setFrontMatter] = useState<FrontMatter>({});
const [cleanBody, setBody] = useState<string>("");
useEffect(() => {
if (!body) return;
const [frontmatter, clean] = extractFrontMatter(body);
setFrontMatter(frontmatter);
setBody(clean);
}, [body]);
return ( return (
<> <>
<section className="heading"> <section className="heading">
<h2 className="strapline">Help</h2> <h2 className="strapline">Help</h2>
<h1>{decodeURIComponent(title)}</h1> <h1>{frontMatter.title || decodeURIComponent(title)}</h1>
<div className="text-white/50">
{!!frontMatter.date && (
<div className="text-white/50">
Published: {frontMatter.date}
{!!frontMatter.updated && (
<span className="text-white/50">
, Last updated: {frontMatter.updated}
</span>
)}
</div>
)}
{!!frontMatter.author && (
<div className="italic text-white/50">By: {frontMatter.author}</div>
)}
</div>
</section> </section>
<section className="grid grid-cols-3 gap-x-8 gap-y-6 my-6"> <section className="grid grid-cols-3 gap-x-8 gap-y-6 my-6">
<div className="col-span-2"> <div className="col-span-2">
<TTCMD <TTCMD
body={body} body={cleanBody}
escapeTOC={escapeTOC} escapeTOC={escapeTOC}
/> />
</div> </div>
{toc && ( {toc && (
<div className="sticky top-8 h-min"> <div className="sticky top-6 h-min">
<TTCMDRenderer tokens={toc} /> <TTCMDRenderer tokens={toc} />
</div> </div>
)} )}

View File

@ -1,3 +1,5 @@
import { parse } from "path";
type TokenIdentifier = { type TokenIdentifier = {
rx: RegExp; rx: RegExp;
parse: (s: string) => Token; parse: (s: string) => Token;
@ -63,8 +65,13 @@ TokenIdentifiers.set("card", {
return search(s, start, end, rx, crx); return search(s, start, end, rx, crx);
}, },
parse(s) { parse(s) {
const rx = /\[{2}(!?)\n+([\s\S]*)\n+\]{2}/; const rx = /\[{2}(!?)\s*?\n+([\s\S]*)\n+\]{2}/;
const [_, isBlock, content] = s.match(rx) || ["", "Unable to parse card"]; const match = s.match(rx);
if (!match) debugger;
const [_, isBlock, content] = match ||
["", "", s];
// if (!_) debugger;
return { return {
content: content.trim(), content: content.trim(),
@ -74,6 +81,7 @@ TokenIdentifiers.set("card", {
}, },
type: "card", type: "card",
uuid: crypto.randomUUID(), uuid: crypto.randomUUID(),
rendersChildrenOnly,
}; };
}, },
}); });
@ -187,7 +195,7 @@ TokenIdentifiers.set("anchor", {
}, },
}); });
TokenIdentifiers.set("inline-code", { TokenIdentifiers.set("inline-code", {
rx: /\s?`(.{3,}?|[a-z0-9]*?)`[^`a-z0-9\n]/gi, rx: /(?<=\s|^)`(.*?)`(?=[\s,.!?)]|$)/gi,
parse(s) { parse(s) {
return { return {
// content: inline, // content: inline,
@ -307,6 +315,21 @@ TokenIdentifiers.set("comment", {
}, },
}); });
TokenIdentifiers.set("frontmatter", {
rx: /^---([\s\S]*?)---/g,
parse(s) {
return {
content: "",
metadata: {
frontmatterString: s.match(this.rx)?.at(0) || "",
},
raw: "",
type: "frontmatter",
uuid: "frontmatter",
};
},
});
function findMatchingClosedParenthesis( function findMatchingClosedParenthesis(
str: string, str: string,
openRegex: RegExp, openRegex: RegExp,

View File

@ -155,9 +155,20 @@ const contentToChildren = (token: Token) => {
content: c.replaceAll("\n", ""), content: c.replaceAll("\n", ""),
metadata: {}, metadata: {},
raw: c, raw: c,
type: "text", type: token.rendersChildrenOnly ? "p" : "text",
uuid: crypto.randomUUID(), uuid: crypto.randomUUID(),
rendersContentOnly: true, rendersContentOnly: true,
children: token.rendersChildrenOnly && c.replaceAll("\n", "")
? [
{
content: c.replaceAll("\n", ""),
metadata: {},
raw: c,
type: "text",
uuid: crypto.randomUUID(),
},
]
: undefined,
})), })),
token.children || [], token.children || [],
).filter((c) => c.children?.length || (c.rendersContentOnly && c.content)); ).filter((c) => c.children?.length || (c.rendersContentOnly && c.content));
@ -239,3 +250,26 @@ function processChunks(chunks: Token[][]) {
// Filter out chunks that were merged into others // Filter out chunks that were merged into others
return chunks.filter((c) => !mergedChunks.find((c2) => c === c2)); return chunks.filter((c) => !mergedChunks.find((c2) => c === c2));
} }
/**
* @description Extracts the frontmatter of a markdown document and returns it as an object and with the body with the frontmatter removed from it
*
* @returns a tuple containing the body and the frontmatter object
*/
export function extractFrontMatter(body: string): [FrontMatter, string] {
const rx = /^---([\s\S]*?)---/;
const [_, frontmatterString] = body.match(rx) || ["", ""];
body = body.replace(rx, "");
const frontMatter: FrontMatter = {};
for (const line of frontmatterString.split("\n")) {
if (!line) continue;
const [key, value] = line.split(": ");
frontMatter[key] = value;
}
return [frontMatter, body];
}

2
types.d.ts vendored
View File

@ -58,6 +58,8 @@ type TokenMarker = {
token: Token; token: Token;
}; };
type FrontMatter = Record<string, string>;
type MultilineCfg = { type MultilineCfg = {
rx: RegExp; rx: RegExp;
closeRx?: RegExp; closeRx?: RegExp;