Adds markdown formatting. Layout and style improvements.
This commit is contained in:
parent
8533f63a22
commit
4c2ebdc292
@ -11,7 +11,7 @@
|
||||
<title>Dungeon Master's Companion</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div id="app" class="flex flex-col h-full w-full"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
30
package-lock.json
generated
30
package-lock.json
generated
@ -13,7 +13,9 @@
|
||||
"@tanstack/react-router": "^1.114.3",
|
||||
"@tanstack/react-router-devtools": "^1.114.3",
|
||||
"@tanstack/router-plugin": "^1.114.3",
|
||||
"dompurify": "^3.2.6",
|
||||
"lodash": "^4.17.21",
|
||||
"marked": "^16.1.1",
|
||||
"pocketbase": "^0.26.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
@ -2182,6 +2184,13 @@
|
||||
"@types/react": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@vitejs/plugin-react": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.0.tgz",
|
||||
@ -2754,6 +2763,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz",
|
||||
"integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||
"optionalDependencies": {
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.157",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.157.tgz",
|
||||
@ -3406,6 +3424,18 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "16.1.1",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-16.1.1.tgz",
|
||||
"integrity": "sha512-ij/2lXfCRT71L6u0M29tJPhP0bM5shLL3u5BePhFwPELj2blMJ6GDtD7PfJhRLhJ/c2UwrK17ySVcDzy2YHjHQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||
|
||||
@ -20,7 +20,9 @@
|
||||
"@tanstack/react-router": "^1.114.3",
|
||||
"@tanstack/react-router-devtools": "^1.114.3",
|
||||
"@tanstack/router-plugin": "^1.114.3",
|
||||
"dompurify": "^3.2.6",
|
||||
"lodash": "^4.17.21",
|
||||
"marked": "^16.1.1",
|
||||
"pocketbase": "^0.26.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
|
||||
@ -47,7 +47,7 @@ export function DocumentList<T extends AnyDocument>({
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="w-full max-w-2xl mx-auto">
|
||||
<section className="w-full">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-xl font-bold text-slate-100">{title}</h2>
|
||||
<div className="flex gap-2">
|
||||
@ -78,7 +78,7 @@ export function DocumentList<T extends AnyDocument>({
|
||||
{items.map((item) => (
|
||||
<li
|
||||
key={item.id}
|
||||
className="p-4 bg-slate-800 rounded text-slate-100 flex flex-row justify-between items-center"
|
||||
className="p-2 m-0 border-b-1 last:border-0 border-slate-700 flex flex-row justify-between items-center"
|
||||
>
|
||||
{renderRow(item)}
|
||||
|
||||
|
||||
22
src/components/FormattedText.tsx
Normal file
22
src/components/FormattedText.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import DOMPurify from "dompurify";
|
||||
import * as Marked from "marked";
|
||||
|
||||
export type Props = {
|
||||
value: string;
|
||||
};
|
||||
|
||||
function formatText(text: React.ReactNode): { __html: string } {
|
||||
if (typeof text === "string") {
|
||||
return {
|
||||
__html: DOMPurify.sanitize(
|
||||
Marked.parse(text, { async: false }) as string,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error("Attempted to safe-render a non-string.");
|
||||
}
|
||||
|
||||
export function FormattedText({ children }: React.PropsWithChildren) {
|
||||
return <div dangerouslySetInnerHTML={formatText(children)}></div>;
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
import type { AnyDocument, DocumentId } from "@/lib/types";
|
||||
import type { AnyDocument } from "@/lib/types";
|
||||
import { FormattedText } from "../FormattedText";
|
||||
import { DocumentLink } from "./DocumentLink";
|
||||
|
||||
export type Props = {
|
||||
doc: AnyDocument;
|
||||
title: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
link: (id: DocumentId) => string;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -16,11 +16,11 @@ export const BasicRow = ({ doc, title, description }: Props) => {
|
||||
<div>
|
||||
<DocumentLink
|
||||
childDocId={doc.id}
|
||||
className="text-lg !no-underline text-slate-100 hover:underline hover:text-violet-400"
|
||||
className="!no-underline text-slate-100 hover:underline hover:text-violet-400"
|
||||
>
|
||||
<h4>{title}</h4>
|
||||
{title && <h4 className="font-bold">{title}</h4>}
|
||||
{description && <FormattedText>{description}</FormattedText>}
|
||||
</DocumentLink>
|
||||
{description && <p>{description}</p>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -51,7 +51,7 @@ export const DocumentRow = ({
|
||||
return <SecretToggleRow secret={document} root={root} />;
|
||||
|
||||
case "scene":
|
||||
return <BasicRow doc={document} title={document.data.text} />;
|
||||
return <BasicRow doc={document} description={document.data.text} />;
|
||||
|
||||
case "treasure":
|
||||
return <TreasureToggleRow treasure={document} root={root} />;
|
||||
|
||||
@ -42,14 +42,14 @@ export function DocumentView({
|
||||
<Link
|
||||
to={CampaignRoute.to}
|
||||
params={{ campaignId: doc.campaign }}
|
||||
className="text-slate-400 hover:text-violet-400 text-sm underline underline-offset-2 transition-colors mb-4"
|
||||
className="text-slate-400 hover:text-violet-400 text-sm underline underline-offset-2 transition-colors"
|
||||
>
|
||||
Back to campaign
|
||||
← Back to campaign
|
||||
</Link>
|
||||
<Link
|
||||
to="/document/$documentId/print"
|
||||
params={{ documentId: doc.id }}
|
||||
className="text-slate-400 hover:text-violet-400 text-sm underline underline-offset-2 transition-colors mb-4"
|
||||
className="text-slate-400 hover:text-violet-400 text-sm underline underline-offset-2 transition-colors"
|
||||
>
|
||||
Print
|
||||
</Link>
|
||||
@ -87,7 +87,7 @@ export function DocumentView({
|
||||
/>
|
||||
)
|
||||
}
|
||||
flyout={childDocId && <Flyout docId={childDocId} />}
|
||||
flyout={childDocId && <Flyout key={childDocId} docId={childDocId} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
import type { AnyDocument, Secret } from "@/lib/types";
|
||||
import { useState } from "react";
|
||||
import { DocumentLink } from "../DocumentLink";
|
||||
|
||||
/**
|
||||
* Renders a secret row with a discovered checkbox and secret text.
|
||||
@ -70,7 +71,12 @@ export const SecretToggleRow = ({
|
||||
aria-label="Discovered"
|
||||
disabled={loading}
|
||||
/>
|
||||
{secret.data.text}
|
||||
<DocumentLink
|
||||
childDocId={secret.id}
|
||||
className="!no-underline text-slate-100 hover:underline hover:text-violet-400"
|
||||
>
|
||||
{secret.data.text}
|
||||
</DocumentLink>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -15,25 +15,20 @@ export function TabbedLayout({
|
||||
flyout,
|
||||
}: Props) {
|
||||
return (
|
||||
<div>
|
||||
<div className="">
|
||||
<div>{navigation}</div>
|
||||
<div className="grow p-2 flex flex-col">
|
||||
<div>
|
||||
<div className="flex flex-row gap-2">{navigation}</div>
|
||||
<div>{title}</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-start m-2 max-w-5xl">
|
||||
<div className="grow max-w-2xs p-0">{tabs}</div>
|
||||
<div className="flex flex-row justify-start m-2 grow">
|
||||
<div className="shrink-0 grow-0 w-40 p-0">{tabs}</div>
|
||||
<div
|
||||
className={`grow p-2 bg-slate-800 border-t border-b border-r border-slate-700`}
|
||||
>
|
||||
{content}
|
||||
</div>
|
||||
{flyout && (
|
||||
<div
|
||||
className="w-lg p-2 bg-slate-800 border border-slate-700 shadow-[0_0_15px_0_rgba(0,0,0,0.5)]"
|
||||
style={{
|
||||
"margin-left": "-32rem",
|
||||
}}
|
||||
>
|
||||
<div className="w-md p-2 bg-slate-800 border border-slate-700">
|
||||
{flyout}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
@import "tailwindcss";
|
||||
@tailwind utilities;
|
||||
@import "tailwindcss/utilities";
|
||||
|
||||
html,
|
||||
body {
|
||||
@ -17,6 +17,12 @@ body {
|
||||
min-width: 320px;
|
||||
}
|
||||
|
||||
/* The container for all content */
|
||||
#app {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
code,
|
||||
pre {
|
||||
font-family: "Fira Mono", "Menlo", "Monaco", "Consolas", monospace;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user