diff --git a/bun.lock b/bun.lock index f7c9da3..d843b5a 100644 --- a/bun.lock +++ b/bun.lock @@ -6,8 +6,11 @@ "dependencies": { "@proscenium/playbill": "link:@proscenium/playbill", "chalk": "^5.4.1", + "classnames": "^2.5.1", "lodash-es": "^4.17.21", "marked": "^15.0.7", + "react": "^19.0.0", + "react-dom": "^19.0.0", "slugify": "^1.6.6", "yaml": "^2.7.0", "zod": "^3.24.1", @@ -16,6 +19,8 @@ "@biomejs/biome": "^1.9.4", "@types/bun": "latest", "@types/lodash-es": "^4.17.12", + "@types/react": "^19.0.11", + "@types/react-dom": "^19.0.4", }, "peerDependencies": { "typescript": "^5.0.0", @@ -51,16 +56,30 @@ "@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="], + "@types/react": ["@types/react@19.0.11", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-vrdxRZfo9ALXth6yPfV16PYTLZwsUWhVjjC+DkfE5t1suNSbBrWC9YqSuuxJZ8Ps6z1o2ycRpIqzZJIgklq4Tw=="], + + "@types/react-dom": ["@types/react-dom@19.0.4", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg=="], + "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], "bun-types": ["bun-types@1.2.5", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-3oO6LVGGRRKI4kHINx5PIdIgnLRb7l/SprhzqXapmoYkFl5m4j6EvALvbDVuuBFaamB46Ap6HCUxIXNLCGy+tg=="], "chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="], + "classnames": ["classnames@2.5.1", "", {}, "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + "lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], "marked": ["marked@15.0.7", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-dgLIeKGLx5FwziAnsk4ONoGwHwGPJzselimvlVskE9XLN4Orv9u2VA3GWw/lYUqjfA0rUT/6fqKwfZJapP9BEg=="], + "react": ["react@19.0.0", "", {}, "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ=="], + + "react-dom": ["react-dom@19.0.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ=="], + + "scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="], + "slugify": ["slugify@1.6.6", "", {}, "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw=="], "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], diff --git a/package.json b/package.json index cbbb548..600f6f3 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,9 @@ "devDependencies": { "@biomejs/biome": "^1.9.4", "@types/bun": "latest", - "@types/lodash-es": "^4.17.12" + "@types/lodash-es": "^4.17.12", + "@types/react": "^19.0.11", + "@types/react-dom": "^19.0.4" }, "peerDependencies": { "typescript": "^5.0.0" @@ -14,8 +16,11 @@ "dependencies": { "@proscenium/playbill": "link:@proscenium/playbill", "chalk": "^5.4.1", + "classnames": "^2.5.1", "lodash-es": "^4.17.21", "marked": "^15.0.7", + "react": "^19.0.0", + "react-dom": "^19.0.0", "slugify": "^1.6.6", "yaml": "^2.7.0", "zod": "^3.24.1" diff --git a/src/lib/binding.ts b/src/lib/binding.ts index 42d75c6..98aa824 100644 --- a/src/lib/binding.ts +++ b/src/lib/binding.ts @@ -226,6 +226,11 @@ export async function loadFromBinding( playbill.addBlueprint(blueprint); } + // Add rules + for (const rule of rules) { + playbill.addRule(rule); + } + // Add all definitions for (const glossary of glossaries) { for (const [term, definitions] of Object.entries(glossary)) { diff --git a/src/render/html/component/base/react-section.tsx b/src/render/html/component/base/react-section.tsx new file mode 100644 index 0000000..899bc1a --- /dev/null +++ b/src/render/html/component/base/react-section.tsx @@ -0,0 +1,40 @@ +import classNames from "classnames"; +import React from "react"; + +interface SectionProps { + type: string; + title: string; + content: string; + preInfo?: React.ReactNode; + info?: React.ReactNode; + componentId?: string; + leftCornerTag?: React.ReactNode; + rightCornerTag?: React.ReactNode; +} + +export function Section({ + type, + title, + content, + preInfo, + info, + componentId, + leftCornerTag, + rightCornerTag, +}: SectionProps) { + const sectionClasses = classNames("section", type); + const headerClasses = classNames("section-header", `${type}-header`); + const contentClasses = classNames("section-content", `${type}-content`); + + return
+
+ {preInfo} +

{title}

+ {info} +
+
{leftCornerTag}
+
{rightCornerTag}
+
+
+ +} diff --git a/src/render/html/component/rule.ts b/src/render/html/component/rule.ts deleted file mode 100644 index 5f3a2be..0000000 --- a/src/render/html/component/rule.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { Rule } from "@proscenium/playbill"; -import { renderMarkdown } from "#lib"; -import { Section } from "./base/section"; - -export async function RuleSection(rule: Rule): Promise { - const renderedMarkdown = await renderMarkdown(rule.description); - - return Section({ - title: rule.name, - componentId: rule.id, - content: renderedMarkdown, - type: "rule", - }); -} diff --git a/src/render/html/component/rule.tsx b/src/render/html/component/rule.tsx new file mode 100644 index 0000000..b569305 --- /dev/null +++ b/src/render/html/component/rule.tsx @@ -0,0 +1,11 @@ +import type { Rule } from "@proscenium/playbill"; +import { Section } from "./base/react-section"; + +export function RuleSection(rule: Rule) { + return
; +} diff --git a/src/render/html/index.ts b/src/render/html/index.tsx similarity index 89% rename from src/render/html/index.ts rename to src/render/html/index.tsx index 927f363..038d83e 100644 --- a/src/render/html/index.ts +++ b/src/render/html/index.tsx @@ -1,6 +1,6 @@ -import type { Playbill } from "@proscenium/playbill"; +import { Playbill } from "@proscenium/playbill"; import sortBy from "lodash-es/sortBy"; -import { html } from "#lib"; +import { html, renderMarkdown } from "#lib"; import { GlossaryTable } from "./component/glossary"; import { PageHeader } from "./component/header"; import { ItemCard } from "./component/item"; @@ -8,15 +8,21 @@ import { MethodSection } from "./component/method"; import { ResourceSection } from "./component/resource"; import { RuleSection } from "./component/rule"; import { SpeciesSection } from "./component/species"; +import { renderToString } from "react-dom/server"; interface HTMLRenderOptions { styles?: string; } export async function renderPlaybillToHTML( - playbill: Playbill, + playbillToRender: Playbill, opts: HTMLRenderOptions = {}, ): Promise { + // Make a copy of the playbill to avoid modifying the original + // and compile all description markdown + const playbill = Playbill.fromSerialized(playbillToRender.serialize()); + await playbill.processComponents(renderMarkdown); + // Render various resources const resources = ( await Promise.all(Object.values(playbill.resources).map(ResourceSection)) @@ -47,7 +53,7 @@ export async function renderPlaybillToHTML( ).join("\n"); const rulesByOrder = sortBy(Object.values(playbill.rules), "order"); - const rules = (await Promise.all(rulesByOrder.map(RuleSection))).join("\n"); + const rules = rulesByOrder.map(RuleSection); const glossaryHTML = await GlossaryTable(playbill.glossary); // Prepare stylesheet @@ -144,7 +150,7 @@ export async function renderPlaybillToHTML(
- ${rules} + ${renderToString(rules)}