ttcMD: fixes for lists, adds ordered lists

This commit is contained in:
Emmaline Autumn 2024-06-11 05:07:08 -06:00
parent 3a5fe1911a
commit 71bf62b622
2 changed files with 157 additions and 100 deletions

View File

@ -8,7 +8,7 @@ export const TokenRenderers = new Map<string, TokenRenderer<any>>();
export function buildIdentifierMap(): [ export function buildIdentifierMap(): [
TokenIdentifierMap, TokenIdentifierMap,
IdentifierRegistration, IdentifierRegistration
] { ] {
const TokenIdentifiers = new Map<string, TokenIdentifier<any>>(); const TokenIdentifiers = new Map<string, TokenIdentifier<any>>();
@ -16,7 +16,7 @@ export function buildIdentifierMap(): [
type: string, type: string,
match: RegExp, match: RegExp,
parseFunction: (s: string, rx: RegExp) => IdentifiedToken<M>, parseFunction: (s: string, rx: RegExp) => IdentifiedToken<M>,
renderFunction: TokenRenderer<M>, renderFunction: TokenRenderer<M>
): void; ): void;
function registerIdentifier<M>( function registerIdentifier<M>(
type: string, type: string,
@ -24,7 +24,7 @@ export function buildIdentifierMap(): [
parseFunction: (s: string, rx: RegExp) => IdentifiedToken<M>, parseFunction: (s: string, rx: RegExp) => IdentifiedToken<M>,
renderFunction: TokenRenderer<M>, renderFunction: TokenRenderer<M>,
openTagRx: RegExp, openTagRx: RegExp,
closeTagRx: RegExp, closeTagRx: RegExp
): void; ): void;
function registerIdentifier<M = Record<string, string>>( function registerIdentifier<M = Record<string, string>>(
type: string, type: string,
@ -32,7 +32,7 @@ export function buildIdentifierMap(): [
parseFunction: (s: string, rx: RegExp) => IdentifiedToken<M>, parseFunction: (s: string, rx: RegExp) => IdentifiedToken<M>,
renderFunction: TokenRenderer<M>, renderFunction: TokenRenderer<M>,
openTagRx?: RegExp, openTagRx?: RegExp,
closeTagRx?: RegExp, closeTagRx?: RegExp
) { ) {
TokenIdentifiers.set(type, { TokenIdentifiers.set(type, {
rx: match, rx: match,
@ -45,14 +45,15 @@ export function buildIdentifierMap(): [
return { ...token, ...identifiedToken } as Token<M>; return { ...token, ...identifiedToken } as Token<M>;
}, },
search: openTagRx && closeTagRx search:
openTagRx && closeTagRx
? (s, start, end) => { ? (s, start, end) => {
return search( return search(
s, s,
start, start,
end, end,
new RegExp(openTagRx, "g"), new RegExp(openTagRx, "g"),
new RegExp(closeTagRx, "g"), new RegExp(closeTagRx, "g")
); );
} }
: undefined, : undefined,
@ -67,7 +68,6 @@ export const buildOnlyDefaultElements = () => {
const [TokenIdentifiers, registerIdentifier] = buildIdentifierMap(); const [TokenIdentifiers, registerIdentifier] = buildIdentifierMap();
TokenRenderers.set("text", (t: Token<any>) => { TokenRenderers.set("text", (t: Token<any>) => {
debugger;
return ( return (
<span className="whitespace-pre-wrap"> <span className="whitespace-pre-wrap">
{t.content.replaceAll(/\\n ?/g, "\n")} {t.content.replaceAll(/\\n ?/g, "\n")}
@ -105,20 +105,26 @@ export const buildOnlyDefaultElements = () => {
const { children, metadata } = token; const { children, metadata } = token;
return ( return (
<div <div
style={{ style={
{
"--grid-cols": metadata.columns, "--grid-cols": metadata.columns,
} as React.CSSProperties} } as React.CSSProperties
}
className="grid grid-cols-dynamic gap-x-8 mb-6" className="grid grid-cols-dynamic gap-x-8 mb-6"
> >
{children?.map((c) => { {children?.map((c) => {
const Comp = c.metadata.span ? Fragment : "div"; const Comp = c.metadata.span ? Fragment : "div";
return <Comp className="p" key={c.uuid}>{c.render(c)}</Comp>; return (
<Comp className="p" key={c.uuid}>
{c.render(c)}
</Comp>
);
})} })}
</div> </div>
); );
}, },
/(?<!\/)(?:\[\])+/g, /(?<!\/)(?:\[\])+/g,
/\/\[\]/g, /\/\[\]/g
); );
// card // card
@ -149,18 +155,21 @@ export const buildOnlyDefaultElements = () => {
return ( return (
<div <div
data-block={!!metadata.isBlock} data-block={!!metadata.isBlock}
style={{ style={
{
"--v-span": metadata.span || 1, "--v-span": metadata.span || 1,
} as React.CSSProperties} } as React.CSSProperties
}
className="data-[block=false]:card data-[block=false]:mb-6 col-span-2" className="data-[block=false]:card data-[block=false]:mb-6 col-span-2"
> >
{children?.map((e) => <Fragment key={e.uuid}>{e.render(e)} {children?.map((e) => (
</Fragment>)} <Fragment key={e.uuid}>{e.render(e)}</Fragment>
))}
</div> </div>
); );
}, },
/\[\[/g, /\[\[/g,
/\]\]/g, /\]\]/g
); );
// fenced code block // fenced code block
@ -182,16 +191,16 @@ export const buildOnlyDefaultElements = () => {
{token.content} {token.content}
</pre> </pre>
); );
}, }
); );
// list // list
registerIdentifier( registerIdentifier(
"list", "list",
/^\s*-\s([\s\S]*?)\n\n/gm, /(?<=\n\n|^) *-\s([\s\S]*?)(?=\n\n|$)/g,
(s, rx) => { (s, rx) => {
return { return {
content: s.match(new RegExp(rx, ""))?.at(1) || "Unable to parse list", content: s.match(new RegExp(rx, ""))?.at(0) || "Unable to parse list",
raw: s, raw: s,
metadata: { metadata: {
initialDepth: initialDepth:
@ -221,17 +230,60 @@ export const buildOnlyDefaultElements = () => {
</ul> </ul>
</> </>
); );
}, }
); );
// ordered-list
registerIdentifier(
"ordered-list",
/(?<=\n\n|^)\s*\d+\.\s([\s\S]*?)(?=\n\n|$)/g,
(s, rx) => {
// debugger;
return {
content:
s.match(new RegExp(rx, ""))?.at(0) || "Unable to parse ordered list",
raw: s,
metadata: {
// initialDepth:
// s.replace("\n", "").split(/\d+\./).at(0)?.length.toString() || "1",
},
uuid: crypto.randomUUID(),
rendersChildrenOnly,
};
},
(token) => {
const { children } = token;
debugger;
return (
<>
<ol
// data-depth={(Number(metadata.initialDepth) / 2) % 3}
className="ml-6 list-decimal"
>
{children?.map((c) => {
return (
<li key={c.uuid}>
{c.children?.map((c: Token<any>) => (
<Fragment key={c.uuid}>{c.render(c)}</Fragment>
))}
</li>
);
})}
</ol>
</>
);
}
);
// ordered list-item
// list-item // list-item
registerIdentifier( registerIdentifier(
"list-item", "list-item",
/^\s*-\s(.*?)$/gm, /(?<=^|\n) *(?:-|\d+\.)\s(.*?)(?=\n|$)/g,
(s, rx) => { (s, rx) => {
return { return {
content: s.match(new RegExp(rx, ""))?.at(1) || content:
"Unable to parse list-item", s.match(new RegExp(rx, ""))?.at(1) || "Unable to parse list-item",
raw: s, raw: s,
metadata: { metadata: {
initialDepth: initialDepth:
@ -245,13 +297,11 @@ export const buildOnlyDefaultElements = () => {
return ( return (
<li data-depth={metadata.initialDepth} className="ml-2"> <li data-depth={metadata.initialDepth} className="ml-2">
{children?.map((c) => ( {children?.map((c) => (
<Fragment key={c.uuid}> <Fragment key={c.uuid}>{c.render(c)}</Fragment>
(c.render(c))
</Fragment>
))} ))}
</li> </li>
); );
}, }
); );
// heading // heading
@ -259,8 +309,8 @@ export const buildOnlyDefaultElements = () => {
"heading", "heading",
/^#+\s(.*?)$/gm, /^#+\s(.*?)$/gm,
(s, rx) => { (s, rx) => {
const content = s.match(new RegExp(rx, ""))?.at(1) || const content =
"Unable to parse heading"; s.match(new RegExp(rx, ""))?.at(1) || "Unable to parse heading";
return { return {
content: content, content: content,
raw: s, raw: s,
@ -287,7 +337,7 @@ export const buildOnlyDefaultElements = () => {
{token.content} {token.content}
</div> </div>
); );
}, }
); );
// image // image
@ -319,13 +369,12 @@ export const buildOnlyDefaultElements = () => {
USE_PROFILES: { svg: true }, USE_PROFILES: { svg: true },
}), }),
}} }}
> ></div>
</div>
); );
} }
// eslint-disable-next-line @next/next/no-img-element // eslint-disable-next-line @next/next/no-img-element
return <img src={metadata.src} alt={token.content} />; return <img src={metadata.src} alt={token.content} />;
}, }
); );
// anchor // anchor
@ -360,14 +409,16 @@ export const buildOnlyDefaultElements = () => {
const { metadata } = token; const { metadata } = token;
return ( return (
<Link <Link
className={metadata.classes || className={
"dark:text-primary-600 underline dark:no-underline"} metadata.classes ||
"dark:text-primary-600 underline dark:no-underline"
}
href={metadata.href} href={metadata.href}
> >
{token.content} {token.content}
</Link> </Link>
); );
}, }
); );
// inline-code // inline-code
@ -376,8 +427,8 @@ export const buildOnlyDefaultElements = () => {
/(?<=\s|^)`(.*?)`(?=[\s,.!?)]|$)/gi, /(?<=\s|^)`(.*?)`(?=[\s,.!?)]|$)/gi,
(s, rx) => { (s, rx) => {
return { return {
content: s.match(new RegExp(rx, "i"))?.at(1) || content:
"Unable to parse inline-code", s.match(new RegExp(rx, "i"))?.at(1) || "Unable to parse inline-code",
raw: s, raw: s,
metadata: {}, metadata: {},
uuid: crypto.randomUUID(), uuid: crypto.randomUUID(),
@ -390,7 +441,7 @@ export const buildOnlyDefaultElements = () => {
{token.content} {token.content}
</span> </span>
); );
}, }
); );
// bold // bold
@ -408,7 +459,7 @@ export const buildOnlyDefaultElements = () => {
}, },
(token) => { (token) => {
return <span className="font-bold">{token.content}</span>; return <span className="font-bold">{token.content}</span>;
}, }
); );
// italic // italic
@ -417,8 +468,8 @@ export const buildOnlyDefaultElements = () => {
/(?<!\*)\*([^\*]+?)\*(?!\*)/g, /(?<!\*)\*([^\*]+?)\*(?!\*)/g,
(s, rx) => { (s, rx) => {
return { return {
content: s.match(new RegExp(rx, "i"))?.at(1) || content:
"Unable to parse italic", s.match(new RegExp(rx, "i"))?.at(1) || "Unable to parse italic",
raw: s, raw: s,
metadata: {}, metadata: {},
uuid: crypto.randomUUID(), uuid: crypto.randomUUID(),
@ -427,7 +478,7 @@ export const buildOnlyDefaultElements = () => {
}, },
(token) => { (token) => {
return <span className="italic">{token.content}</span>; return <span className="italic">{token.content}</span>;
}, }
); );
// popover // popover
@ -449,9 +500,11 @@ export const buildOnlyDefaultElements = () => {
const { children, metadata, uuid } = token; const { children, metadata, uuid } = token;
return ( return (
<Poppable <Poppable
content={children?.map((c) => ( content={
children?.map((c) => (
<Fragment key={uuid}>{c.render(c)}</Fragment> <Fragment key={uuid}>{c.render(c)}</Fragment>
)) || token.content} )) || token.content
}
preferredAlign="centered" preferredAlign="centered"
preferredEdge="bottom" preferredEdge="bottom"
className="cursor-pointer mx-2" className="cursor-pointer mx-2"
@ -461,7 +514,7 @@ export const buildOnlyDefaultElements = () => {
</span> </span>
</Poppable> </Poppable>
); );
}, }
); );
registerIdentifier( registerIdentifier(
@ -490,7 +543,7 @@ export const buildOnlyDefaultElements = () => {
</Accordion> </Accordion>
</div> </div>
); );
}, }
); );
registerIdentifier( registerIdentifier(
@ -507,8 +560,6 @@ export const buildOnlyDefaultElements = () => {
(token) => { (token) => {
const { children } = token; const { children } = token;
debugger;
return ( return (
<div className="p"> <div className="p">
{children?.map((e) => { {children?.map((e) => {
@ -516,7 +567,7 @@ export const buildOnlyDefaultElements = () => {
})} })}
</div> </div>
); );
}, }
); );
registerIdentifier( registerIdentifier(
@ -533,7 +584,7 @@ export const buildOnlyDefaultElements = () => {
}, },
() => { () => {
return <div className="w-full border-b border-mixed-500 my-3"></div>; return <div className="w-full border-b border-mixed-500 my-3"></div>;
}, }
); );
registerIdentifier( registerIdentifier(
@ -550,7 +601,7 @@ export const buildOnlyDefaultElements = () => {
}, },
() => { () => {
return <></>; return <></>;
}, }
); );
registerIdentifier( registerIdentifier(
@ -568,7 +619,7 @@ export const buildOnlyDefaultElements = () => {
}, },
(token) => { (token) => {
return <>{token.raw}</>; return <>{token.raw}</>;
}, }
); );
registerIdentifier( registerIdentifier(
@ -622,7 +673,7 @@ export const buildOnlyDefaultElements = () => {
} }
const maxColumns = Math.max( const maxColumns = Math.max(
...[...headerRows, ...bodyRows, ...footerRows].map((r) => r.length), ...[...headerRows, ...bodyRows, ...footerRows].map((r) => r.length)
); );
return { return {
@ -680,8 +731,12 @@ export const buildOnlyDefaultElements = () => {
<td <td
key={r.join() + i + c} key={r.join() + i + c}
className="data-[center=true]:text-center" className="data-[center=true]:text-center"
data-center={!!(columnPattern?.at(i) && data-center={
columnPattern.at(i)?.includes("^"))} !!(
columnPattern?.at(i) &&
columnPattern.at(i)?.includes("^")
)
}
> >
{child?.render(child) || c} {child?.render(child) || c}
</td> </td>
@ -709,7 +764,7 @@ export const buildOnlyDefaultElements = () => {
)} )}
</table> </table>
); );
}, }
); );
return TokenIdentifiers; return TokenIdentifiers;
@ -718,7 +773,7 @@ export const buildOnlyDefaultElements = () => {
function findMatchingClosedParenthesis( function findMatchingClosedParenthesis(
str: string, str: string,
openRegex: RegExp, openRegex: RegExp,
closedRegex: RegExp, closedRegex: RegExp
): number | null { ): number | null {
let openings = 0; let openings = 0;
let closings = 0; let closings = 0;
@ -774,7 +829,7 @@ function search(
start: number, start: number,
end: number, end: number,
openRx: RegExp, openRx: RegExp,
closeRx: RegExp, closeRx: RegExp
): SearchResult { ): SearchResult {
const oldEnd = end; const oldEnd = end;
@ -782,7 +837,7 @@ function search(
s, s,
// s.substring(0, end - start), // s.substring(0, end - start),
openRx, openRx,
closeRx, closeRx
); );
if (newEnd === null) throw Error("There was an issue finding a closing tag"); if (newEnd === null) throw Error("There was an issue finding a closing tag");

View File

@ -5,6 +5,7 @@ import { buildOnlyDefaultElements, TokenRenderers } from "./TokenIdentifiers";
export const createElements = (body: string): Token[] => { export const createElements = (body: string): Token[] => {
const tokens = tokenize(body); const tokens = tokenize(body);
console.log(tokens);
return buildAbstractSyntaxTree(tokens).map((t) => t.token); return buildAbstractSyntaxTree(tokens).map((t) => t.token);
}; };
@ -109,7 +110,7 @@ type ParentChildMap = {
}; };
const parentChildMap: ParentChildMap = { const parentChildMap: ParentChildMap = {
"list": ["list-item"], list: ["list-item"],
// Add more mappings as needed... // Add more mappings as needed...
}; };
@ -128,10 +129,8 @@ function filterOverlappingPBlocks(blocks: TokenMarker[]): TokenMarker[] {
for (const otherBlock of blocks) { for (const otherBlock of blocks) {
if ( if (
otherBlock !== block && otherBlock !== block &&
( (otherBlock.start === block.start ||
otherBlock.start === block.start || (otherBlock.end === block.end && otherBlock.start < block.start))
(otherBlock.end === block.end && otherBlock.start < block.start)
)
) { ) {
return false; return false;
} }
@ -155,7 +154,8 @@ const contentToChildren = (token: Token) => {
} }
token.children = zipArrays( token.children = zipArrays(
content.split(splitMarker).map((c): Token => ({ content.split(splitMarker).map(
(c): Token => ({
content: c.replaceAll("\n", " "), content: c.replaceAll("\n", " "),
metadata: {}, metadata: {},
raw: c, raw: c,
@ -163,7 +163,8 @@ const contentToChildren = (token: Token) => {
uuid: crypto.randomUUID(), uuid: crypto.randomUUID(),
rendersContentOnly: token.rendersChildrenOnly ? false : true, rendersContentOnly: token.rendersChildrenOnly ? false : true,
render: TokenRenderers.get(token.rendersChildrenOnly ? "p" : "text")!, render: TokenRenderers.get(token.rendersChildrenOnly ? "p" : "text")!,
children: token.rendersChildrenOnly && c.replaceAll("\n", "") children:
token.rendersChildrenOnly && c.replaceAll("\n", "")
? [ ? [
{ {
content: c.replaceAll("\n", " "), content: c.replaceAll("\n", " "),
@ -176,8 +177,9 @@ const contentToChildren = (token: Token) => {
}, },
] ]
: undefined, : undefined,
})), })
token.children || [], ),
token.children || []
).filter((c) => c.children?.length || (c.rendersContentOnly && c.content)); ).filter((c) => c.children?.length || (c.rendersContentOnly && c.content));
}; };