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

|

|
||||||
|
|
||||||
|

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