tcmd: token uuids, fixes a few issues with poppables
This commit is contained in:
parent
ce83bdf7af
commit
2e8ddd8ed2
@ -42,6 +42,6 @@
|
||||
}
|
||||
|
||||
.poppable {
|
||||
@apply card bg-mixed-300 p-2 rounded-lg transition-opacity data-[visible=true]:z-10 data-[visible=true]:opacity-100 data-[visible=false]:opacity-0 -z-10 max-w-[400px] absolute
|
||||
@apply card dark:bg-mixed-300 bg-primary-600 p-2 rounded-lg transition-opacity data-[visible=true]:z-10 data-[visible=true]:opacity-100 data-[visible=false]:opacity-0 -z-10 max-w-[400px] absolute
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import { Suspense } from "react";
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<section className="pb-8 border-b border-b-dark-500 min-w-full">
|
||||
<section className="pb-8 border-b border-b-primary-500 dark:border-b-dark-500 min-w-full">
|
||||
<h2 className="strapline">Tabletop Commander</h2>
|
||||
<h1 className="text-5xl font-bold">How does it work?</h1>
|
||||
</section>
|
||||
|
@ -3,10 +3,11 @@
|
||||
import { Accordion, AccordionContent } from "@/lib/accordion";
|
||||
import { Poppable } from "@/lib/poppables/components/poppable";
|
||||
import { createElements } from "@/lib/tcmd";
|
||||
import { tokenizeInline } from "@/lib/tcmd/tokenizeInline";
|
||||
import Link from "next/link";
|
||||
import React, { FC, Fragment, ReactNode, use, useMemo } from "react";
|
||||
|
||||
import { sanitize } from "isomorphic-dompurify";
|
||||
|
||||
export const TCMD: FC<{ body: Promise<string> }> = ({ body }) => {
|
||||
const text = use(body);
|
||||
const elements = useMemo(() => createElements(text), [text]);
|
||||
@ -15,9 +16,8 @@ export const TCMD: FC<{ body: Promise<string> }> = ({ body }) => {
|
||||
// <pre>{JSON.stringify(elements,null,2)}</pre>
|
||||
// </div>
|
||||
<div>
|
||||
{elements.map((e, i) => (
|
||||
<Fragment key={"tcmd-block" + i}>{renderBlock(e)}</Fragment>
|
||||
))}
|
||||
{elements.map((e, i) => <Fragment key={e.uuid}>{renderBlock(e)}
|
||||
</Fragment>)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -26,7 +26,7 @@ const renderBlock = (block: BlockChildren): ReactNode => {
|
||||
switch (block.type) {
|
||||
case "block":
|
||||
return block.children.map((e, i) => (
|
||||
<Fragment key={"tcmd-block" + i}>{renderBlock(e)}</Fragment>
|
||||
<Fragment key={e.uuid}>{renderBlock(e)}</Fragment>
|
||||
));
|
||||
case "grid":
|
||||
return (
|
||||
@ -37,7 +37,7 @@ const renderBlock = (block: BlockChildren): ReactNode => {
|
||||
className="grid grid-cols-dynamic gap-x-8 gap-y-6 mb-6"
|
||||
>
|
||||
{block.children.map((c, i) => (
|
||||
<div key={"block-grid" + c.type + i}>
|
||||
<div key={c.uuid}>
|
||||
{renderBlock(c)}
|
||||
</div>
|
||||
))}
|
||||
@ -47,7 +47,7 @@ const renderBlock = (block: BlockChildren): ReactNode => {
|
||||
return (
|
||||
<div className="card">
|
||||
{block.children.map((e, i) => (
|
||||
<Fragment key={"card-block" + i + e.type}>
|
||||
<Fragment key={e.uuid}>
|
||||
{renderBlock(e)}
|
||||
</Fragment>
|
||||
))}
|
||||
@ -60,7 +60,7 @@ const renderBlock = (block: BlockChildren): ReactNode => {
|
||||
>
|
||||
<AccordionContent>
|
||||
{block.children.map((e, i) => (
|
||||
<Fragment key={"accordion" + e.type + "i"}>
|
||||
<Fragment key={e.uuid}>
|
||||
{renderBlock(e)}
|
||||
</Fragment>
|
||||
))}
|
||||
@ -80,7 +80,7 @@ const renderParagraph = (p: ParagraphToken) => {
|
||||
return (
|
||||
<div className="p">
|
||||
{p.content.map((e, i) => (
|
||||
<Fragment key={"p-paragraph" + i + e.type}>
|
||||
<Fragment key={e.uuid}>
|
||||
{renderToken(e)}
|
||||
</Fragment>
|
||||
))}
|
||||
@ -88,7 +88,7 @@ const renderParagraph = (p: ParagraphToken) => {
|
||||
);
|
||||
case "code":
|
||||
return (
|
||||
<pre className="whitespace-pre-wrap">
|
||||
<pre className="whitespace-pre-wrap bg-black/20 p-2 rounded-md">
|
||||
{p.content.map((c) => c.line.toString()).join("\n\n")}
|
||||
</pre>
|
||||
);
|
||||
@ -143,7 +143,7 @@ const renderToken = (t: Token) => {
|
||||
return (
|
||||
<div className="p">
|
||||
{t.lines.map((e, i) => (
|
||||
<Fragment key={"p-line" + i + e.type}>
|
||||
<Fragment key={e.uuid}>
|
||||
{renderInlineToken(e.line)}
|
||||
</Fragment>
|
||||
))}
|
||||
@ -159,7 +159,7 @@ const renderToken = (t: Token) => {
|
||||
case "list1":
|
||||
return <li className="list-disc ml-6">{renderInlineToken(t.line)}</li>;
|
||||
case "list2":
|
||||
return <li className="list-disc ml-8">{renderInlineToken(t.line)}</li>;
|
||||
return <li className="list-disc ml-12">{renderInlineToken(t.line)}</li>;
|
||||
default:
|
||||
return (
|
||||
<div className="text-white bg-red-500 p">
|
||||
@ -174,7 +174,7 @@ const renderInlineToken = (l: Line) => {
|
||||
if (typeof l === "string") return l;
|
||||
|
||||
return l.map((token) => (
|
||||
<Fragment key={"inline_token" + token.content}>
|
||||
<Fragment key={token.uuid}>
|
||||
{(() => {
|
||||
switch (token.type) {
|
||||
case "text":
|
||||
@ -183,13 +183,30 @@ const renderInlineToken = (l: Line) => {
|
||||
return <span className="font-bold">{token.content}</span>;
|
||||
case "anchor":
|
||||
return (
|
||||
<Link className="text-primary-600" href={token.data.href}>
|
||||
<Link
|
||||
className="dark:text-primary-600 underline dark:no-underline"
|
||||
href={token.data.href}
|
||||
>
|
||||
{token.content}
|
||||
</Link>
|
||||
);
|
||||
case "image":
|
||||
case "image": {
|
||||
token.data.src = token.data.src as string;
|
||||
if (token.data.src.startsWith("<svg")) {
|
||||
return (
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: sanitize(token.data.src, {
|
||||
USE_PROFILES: { svg: true },
|
||||
}),
|
||||
}}
|
||||
>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
return <img src={token.data.src} alt={token.content} />;
|
||||
}
|
||||
case "popover":
|
||||
return (
|
||||
<Poppable
|
||||
|
@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
FC,
|
||||
PropsWithChildren,
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { zipArrays } from "../zip";
|
||||
import { singleLineTokens, tokenizeLine } from "./tokenizeLine";
|
||||
import { tokenizeLine } from "./tokenizeLine";
|
||||
import { tokenizeBlock } from "./tokenizeBlock";
|
||||
import { tokenizeInline } from "./tokenizeInline";
|
||||
import { tokenizeParagraph } from "./tokenizeParagraph";
|
||||
|
||||
export const createElements = (body: string) => {
|
||||
@ -43,6 +42,7 @@ const tokenize = (body: string) => {
|
||||
closed: false,
|
||||
metadata: {},
|
||||
type: "block",
|
||||
uuid: crypto.randomUUID(),
|
||||
};
|
||||
blockTokens.push(openBT);
|
||||
}
|
||||
@ -66,6 +66,7 @@ const tokenize = (body: string) => {
|
||||
line: paragraph,
|
||||
raw: paragraph,
|
||||
type: "text",
|
||||
uuid: crypto.randomUUID(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -78,6 +79,7 @@ const tokenize = (body: string) => {
|
||||
content: [],
|
||||
metadata: {},
|
||||
type: "p",
|
||||
uuid: crypto.randomUUID(),
|
||||
};
|
||||
openBT.children.push(openP);
|
||||
paragraphTokens.push(openP);
|
||||
|
@ -26,6 +26,7 @@ const blockTokens: {
|
||||
},
|
||||
children: [],
|
||||
closed: false,
|
||||
uuid: crypto.randomUUID(),
|
||||
};
|
||||
},
|
||||
},
|
||||
@ -38,6 +39,7 @@ const blockTokens: {
|
||||
metadata: {},
|
||||
children: [],
|
||||
closed: false,
|
||||
uuid: crypto.randomUUID(),
|
||||
};
|
||||
},
|
||||
},
|
||||
@ -51,6 +53,7 @@ const blockTokens: {
|
||||
metadata: { title },
|
||||
children: [],
|
||||
closed: false,
|
||||
uuid: crypto.randomUUID(),
|
||||
};
|
||||
},
|
||||
},
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { zipArrays } from "../zip";
|
||||
|
||||
export const tokenizeInline = (line: string, recursive?: boolean) => {
|
||||
if (recursive) console.log("recursive call");
|
||||
export const tokenizeInline = (line: string) => {
|
||||
line = line.trim();
|
||||
const originalLine = line;
|
||||
const insertMarker = "\u{03A9}";
|
||||
@ -43,6 +42,7 @@ export const tokenizeInline = (line: string, recursive?: boolean) => {
|
||||
line.split(new RegExp(insertMarker + "{2,}")).map((t): InlineToken => ({
|
||||
content: t,
|
||||
type: "text",
|
||||
uuid: crypto.randomUUID(),
|
||||
})),
|
||||
tokens,
|
||||
).filter((t) => t.content);
|
||||
@ -69,6 +69,7 @@ export const inlineTokens: {
|
||||
type: "bold",
|
||||
end,
|
||||
start,
|
||||
uuid: crypto.randomUUID(),
|
||||
});
|
||||
},
|
||||
replace(l) {
|
||||
@ -87,6 +88,7 @@ export const inlineTokens: {
|
||||
},
|
||||
start,
|
||||
end,
|
||||
uuid: crypto.randomUUID(),
|
||||
});
|
||||
},
|
||||
replace(l) {
|
||||
@ -106,6 +108,7 @@ export const inlineTokens: {
|
||||
data: {
|
||||
src,
|
||||
},
|
||||
uuid: crypto.randomUUID(),
|
||||
});
|
||||
},
|
||||
replace(l) {
|
||||
@ -116,7 +119,6 @@ export const inlineTokens: {
|
||||
rx: /\^\[(.*?)\]<<(.*?)>>/gm,
|
||||
create(content, start, end, tokens) {
|
||||
const [_, text, popover] = content;
|
||||
// tokenizeInline("", true);
|
||||
tokens.push({
|
||||
content: text,
|
||||
end,
|
||||
@ -125,6 +127,7 @@ export const inlineTokens: {
|
||||
data: {
|
||||
popover: tokenizeInline(popover),
|
||||
},
|
||||
uuid: crypto.randomUUID(),
|
||||
});
|
||||
},
|
||||
replace(l) {
|
||||
|
@ -26,6 +26,7 @@ export const tokenizeLine = (
|
||||
line: tokenizeInline(line),
|
||||
type: "text",
|
||||
raw: line,
|
||||
uuid: crypto.randomUUID(),
|
||||
};
|
||||
};
|
||||
|
||||
@ -33,28 +34,53 @@ export const singleLineTokens: SingleLineCfg[] = [
|
||||
{
|
||||
rx: /^#\s/,
|
||||
create(line) {
|
||||
return ({ type: "h1", line, raw: line, cfg: this });
|
||||
return ({
|
||||
type: "h1",
|
||||
line,
|
||||
raw: line,
|
||||
cfg: this,
|
||||
uuid: crypto.randomUUID(),
|
||||
});
|
||||
},
|
||||
replaceRx: /^#\s/,
|
||||
},
|
||||
{
|
||||
rx: /^##\s/,
|
||||
create(line) {
|
||||
return ({ type: "h2", line, raw: line, cfg: this });
|
||||
return ({
|
||||
type: "h2",
|
||||
line,
|
||||
raw: line,
|
||||
cfg: this,
|
||||
uuid: crypto.randomUUID(),
|
||||
});
|
||||
},
|
||||
replaceRx: /^##\s/,
|
||||
},
|
||||
{
|
||||
rx: /^###\s/,
|
||||
create(line) {
|
||||
return ({ type: "h3", line, raw: line, cfg: this });
|
||||
return ({
|
||||
type: "h3",
|
||||
line,
|
||||
raw: line,
|
||||
cfg: this,
|
||||
uuid: crypto.randomUUID(),
|
||||
});
|
||||
},
|
||||
replaceRx: /^###\s/,
|
||||
},
|
||||
{
|
||||
rx: /^-\s/,
|
||||
create(line) {
|
||||
return ({ type: "list1", line, raw: line, mends: true, cfg: this });
|
||||
return ({
|
||||
type: "list1",
|
||||
line,
|
||||
raw: line,
|
||||
mends: true,
|
||||
cfg: this,
|
||||
uuid: crypto.randomUUID(),
|
||||
});
|
||||
},
|
||||
replaceRx: /^-\s/,
|
||||
shouldMendNextLine: true,
|
||||
@ -62,7 +88,14 @@ export const singleLineTokens: SingleLineCfg[] = [
|
||||
{
|
||||
rx: /^[\t\s]{2}-\s/,
|
||||
create(line) {
|
||||
return ({ type: "list2", line, raw: line, mends: true, cfg: this });
|
||||
return ({
|
||||
type: "list2",
|
||||
line,
|
||||
raw: line,
|
||||
mends: true,
|
||||
cfg: this,
|
||||
uuid: crypto.randomUUID(),
|
||||
});
|
||||
},
|
||||
replaceRx: /^[\t\s]{2}-\s/,
|
||||
shouldMendNextLine: true,
|
||||
|
@ -34,8 +34,10 @@ const blockTokens: {
|
||||
line: line.replace(/```.*?\n/g, "").replace(/\n```/, ""),
|
||||
type: "text",
|
||||
raw: line,
|
||||
uuid: crypto.randomUUID(),
|
||||
}],
|
||||
allowsInline: false,
|
||||
uuid: crypto.randomUUID(),
|
||||
};
|
||||
},
|
||||
},
|
||||
|
@ -10,6 +10,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^2.1.1",
|
||||
"isomorphic-dompurify": "^2.4.0",
|
||||
"next": "14.1.0",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
|
@ -7,7 +7,6 @@ const config: Config = {
|
||||
"./lib/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
|
||||
theme: {
|
||||
extend: {
|
||||
backgroundImage: {
|
||||
|
2
test.md
2
test.md
@ -85,6 +85,8 @@ this is the test of a single accordion
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
[/accordion]
|
||||
|
||||
]]
|
||||
|
4
types.d.ts
vendored
4
types.d.ts
vendored
@ -2,6 +2,7 @@ type InlineToken = {
|
||||
type: "text" | "bold" | "anchor" | "image" | "popover";
|
||||
content: string;
|
||||
data?: any;
|
||||
uuid: string;
|
||||
};
|
||||
|
||||
type InlineTokenInsert = {
|
||||
@ -29,6 +30,7 @@ type SingleLineToken = {
|
||||
raw: string;
|
||||
mends?: boolean;
|
||||
cfg?: SingleLineCfg;
|
||||
uuid: string;
|
||||
};
|
||||
type Token = SingleLineToken | MultilineToken;
|
||||
|
||||
@ -47,6 +49,7 @@ type BlockToken = {
|
||||
children: BlockChildren[];
|
||||
parent?: string;
|
||||
closed: boolean;
|
||||
uuid: string;
|
||||
};
|
||||
|
||||
type BlockChildren = ParagraphToken | BlockToken | SingleLineToken;
|
||||
@ -57,4 +60,5 @@ type ParagraphToken = {
|
||||
type: "p" | "code";
|
||||
metadata: any;
|
||||
closed: boolean;
|
||||
uuid: string;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user