this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor: simplify templates and rendering pipeline

+199 -302
+7 -2
cli/build.ts
··· 1 1 import { Command } from "@cliffy/command"; 2 - import { buildStatic } from "../core/renderer.ts"; 2 + import { renderPresentationHtml } from "../core/renderer.ts"; 3 3 4 4 export const build = new Command() 5 5 .arguments("<source:string>") ··· 9 9 .description("Build static presentation") 10 10 .action(async (options, source: string) => { 11 11 const { output } = options; 12 - await buildStatic(source, output); 12 + await Deno.mkdir(output, { recursive: true }); 13 + 14 + const html = await renderPresentationHtml(source); 15 + 16 + const outputPath = `${output}/index.html`; 17 + await Deno.writeTextFile(outputPath, html); 13 18 console.log(`Static build complete! Output: ${output}/index.html`); 14 19 });
+5 -5
core/markdoc-config.ts
··· 1 - import Markdoc, { Config } from "@markdoc/markdoc"; 1 + import { Config } from "@markdoc/markdoc"; 2 2 import { createFenceNode } from "./nodes/fence.ts"; 3 + import type { Includes } from "./types.ts"; 3 4 4 5 export function createMarkdocConfig() { 5 - const scripts = new Set<string>(); 6 - 6 + const includes: Includes = new Set(); 7 7 const config: Config = { 8 8 tags: { 9 9 slide: { ··· 14 14 }, 15 15 }, 16 16 nodes: { 17 - fence: createFenceNode(scripts), 17 + fence: createFenceNode(includes), 18 18 }, 19 19 }; 20 20 21 - return { scripts, config }; 21 + return { includes, config }; 22 22 }
+13 -7
core/nodes/fence.ts
··· 1 - import Markdoc, { nodes, RenderableTreeNode, Tag } from "@markdoc/markdoc"; 1 + import { 2 + Config, 3 + Node, 4 + nodes, 5 + RenderableTreeNode, 6 + Schema, 7 + Tag, 8 + } from "@markdoc/markdoc"; 2 9 import { codeToHast } from "shiki"; 3 10 import { isElement } from "hast-util-is-element"; 4 - import type { Node as HastNode } from "@types/hast"; 11 + import type { Node as HastNode } from "hast"; 12 + import type { Includes } from "../types.ts"; 5 13 6 14 function hastToRenderNode(node: HastNode): RenderableTreeNode { 7 15 let tag: Tag; ··· 27 35 return tag; 28 36 } 29 37 30 - export function createFenceNode(scripts: Set<string>) { 38 + export function createFenceNode(includes: Includes): Schema { 31 39 return { 32 40 render: "pre", 33 41 attributes: { 34 42 content: { type: "String", render: false }, 35 43 language: { type: "String", render: false }, 36 44 }, 37 - async transform(node: any, config: any) { 45 + async transform(node: Node, config: Config) { 38 46 const { content, language = "text" } = node.attributes; 39 47 40 48 if (language === "mermaid") { 41 - scripts.add( 42 - "https://cdn.jsdelivr.net/npm/mermaid@11.9.0/dist/mermaid.min.js", 43 - ); 49 + includes.add("mermaid"); 44 50 45 51 // Bail out for Mermaid to handle later 46 52 const base = (nodes.fence.transform!)(node, config);
+15 -27
core/renderer.ts
··· 1 1 import Markdoc, { Node } from "@markdoc/markdoc"; 2 2 import { createMarkdocConfig } from "./markdoc-config.ts"; 3 3 import { Eta } from "@eta-dev/eta"; 4 + import type { RenderOptions } from "./types.ts"; 4 5 5 6 const eta = new Eta({ views: Deno.cwd() + "/templates" }); 6 7 7 - export async function renderBody(path: string) { 8 + export async function renderPresentationHtml( 9 + path: string, 10 + options?: RenderOptions, 11 + ) { 8 12 const file = await Deno.readTextFile(path); 9 13 const ast = Markdoc.parse(file); 10 14 ··· 28 32 ], "slide") 29 33 ); 30 34 31 - const { config, scripts } = createMarkdocConfig(); 35 + const { config, includes } = createMarkdocConfig(); 32 36 const tree = Markdoc.transform(ast, config); 33 37 34 - return { 35 - scripts, 36 - body: Markdoc.renderers.html(await Promise.resolve(tree)), 37 - }; 38 - } 38 + if (options?.devMode) { 39 + includes.add("live-reload"); 40 + } 39 41 40 - export async function buildStatic(sourcePath: string, outputDir: string) { 41 - // Ensure output directory exists 42 - await Deno.mkdir(outputDir, { recursive: true }); 43 - 44 - // Get the presentation title from the first heading 45 - const sourceContent = await Deno.readTextFile(sourcePath); 46 - const titleMatch = sourceContent.match(/^# (.+)$/m); 42 + const titleMatch = file.match(/^# (.+)$/m); 47 43 const title = titleMatch ? titleMatch[1] : "Presentation"; 48 - 49 - // Render the presentation 50 - const { body, scripts } = await renderBody(sourcePath); 51 - 52 - // Generate static HTML 53 - const html = eta.render("static", { 54 - body, 55 - scripts: Array.from(scripts), 56 - title 44 + 45 + return eta.render("presentation", { 46 + body: Markdoc.renderers.html(await Promise.resolve(tree)), 47 + title, 48 + includes, 57 49 }); 58 - 59 - // Write to output file 60 - const outputPath = `${outputDir}/index.html`; 61 - await Deno.writeTextFile(outputPath, html); 62 50 }
+8
core/types.ts
··· 1 + export interface RenderOptions { 2 + devMode?: boolean; 3 + } 4 + 5 + // String identifiers for output partials; things like CDN links, styles, and web components 6 + export type Includes = Set< 7 + "live-reload" | "mermaid" 8 + >;
+1 -1
deno.json
··· 8 8 "@eta-dev/eta": "jsr:@eta-dev/eta@^3.5.0", 9 9 "@markdoc/markdoc": "https://esm.sh/@markdoc/markdoc@0.5.2", 10 10 "@std/assert": "jsr:@std/assert@1", 11 - "@types/hast": "npm:@types/hast@^3.0.4", 11 + "hast": "npm:@types/hast@^3.0.4", 12 12 "hast-util-is-element": "npm:hast-util-is-element@^3.0.0", 13 13 "shiki": "npm:shiki@^3.8.1" 14 14 }
+3 -6
server/dev.ts
··· 1 - import { Eta } from "@eta-dev/eta"; 2 - import { renderBody } from "../core/renderer.ts"; 3 - 4 - const eta = new Eta({ views: Deno.cwd() + "/templates" }); 1 + import { renderPresentationHtml } from "../core/renderer.ts"; 5 2 6 3 export async function startDevServer(source: string) { 7 4 await Promise.all([ ··· 45 42 return response; 46 43 } 47 44 48 - const { body, scripts } = await renderBody(path); 45 + const html = await renderPresentationHtml(path, { devMode: true }); 49 46 50 47 return new Response( 51 - eta.render("live-reload", { body, scripts }), 48 + html, 52 49 { 53 50 status: 200, 54 51 headers: {
-133
templates/live-reload.eta
··· 1 - <!doctype html> 2 - <html> 3 - <head> 4 - <title>Dev</title> 5 - <link rel="preconnect" href="https://fonts.googleapis.com"> 6 - <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 7 - <link href="https://fonts.googleapis.com/css2?family=Recursive:slnt,wght,CASL,CRSV,MONO@-15..0,300..1000,0..1,0..1,0..1&display=swap" rel="stylesheet"> 8 - 9 - <style> 10 - body { 11 - margin: 0; 12 - box-sizing: border-box; 13 - font-family: "Recursive", sans-serif; 14 - background-color: #191724; 15 - color: #908caa; 16 - } 17 - 18 - h1, h2, h3, h4, h5, h6 { 19 - display: block; 20 - color: #e0def4; 21 - margin-bottom: 2cqi; 22 - } 23 - 24 - p, ul, ol { 25 - font-size: 2.5cqi; 26 - margin: 0; 27 - } 28 - 29 - pre { 30 - margin: 0; 31 - font-variation-settings: "MONO" 1; 32 - font-family: "Recursive", monospace; 33 - } 34 - 35 - code { 36 - font-variation-settings: "MONO" 1; 37 - font-family: "Recursive", monospace; 38 - } 39 - 40 - article { 41 - width: 100vw; 42 - height: 100vh; 43 - overflow-y: scroll; 44 - scroll-behavior: smooth; 45 - scroll-snap-type: y mandatory; 46 - } 47 - 48 - section { 49 - width: 100%; 50 - height: 100%; 51 - scroll-snap-align: center; 52 - position: relative; 53 - } 54 - 55 - section > div { 56 - container: slide / inline-size; 57 - box-sizing: border-box; 58 - position: absolute; 59 - inset: 0; 60 - margin: auto; 61 - aspect-ratio: 16 / 9; 62 - max-width: 100%; 63 - max-height: 100%; 64 - 65 - display: flex; 66 - flex-direction: column; 67 - justify-content: center; 68 - align-items: center; 69 - 70 - padding: 6cqmin; 71 - background: #1f1d2e; 72 - } 73 - 74 - h1 { 75 - font-size: 8cqi; 76 - margin: 0; 77 - font-weight: 900; 78 - } 79 - 80 - h2 { 81 - font-size: 6cqi; 82 - margin-top: 0; 83 - margin-bottom: 3cqi; 84 - font-weight: 900; 85 - } 86 - 87 - h1:first-of-type { 88 - font-size: 10cqi; 89 - margin-bottom: 3cqi; 90 - font-weight: 1000; 91 - line-height: 0.8em; 92 - } 93 - 94 - h1:first-of-type ~ :is(h2, h3, h4, h5, h6, p) { 95 - font-size: 2.8cqi; 96 - font-weight: 700; 97 - margin: 0; 98 - } 99 - 100 - div:has(h1:first-of-type) { 101 - display: flex; 102 - flex-direction: column; 103 - justify-content: center; 104 - } 105 - 106 - .shiki { 107 - padding: 2cqi; 108 - font-size: 2cqi; 109 - border-radius: 1em; 110 - } 111 - </style> 112 - </head> 113 - <body> 114 - <%~ it.body %> 115 - <script> 116 - const src = new WebSocket("/live-reload") 117 - 118 - src.addEventListener("message", e => { 119 - if (e.data === "reload") { 120 - src.close() 121 - location.reload() 122 - } 123 - }) 124 - 125 - src.addEventListener("open", () => { 126 - console.log("live reload connected") 127 - }) 128 - </script> 129 - <% it.scripts.forEach(src => { %> 130 - <script src="<%= src %>"></script> 131 - <% }) %> 132 - </body> 133 - </html>
+14
templates/partials/live-reload.eta
··· 1 + <script> 2 + const src = new WebSocket("/live-reload") 3 + 4 + src.addEventListener("message", e => { 5 + if (e.data === "reload") { 6 + src.close() 7 + location.reload() 8 + } 9 + }) 10 + 11 + src.addEventListener("open", () => { 12 + console.log("live reload connected") 13 + }) 14 + </script>
+7
templates/partials/mermaid.eta
··· 1 + <script type="module"> 2 + import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11.9.0/+esm" 3 + 4 + mermaid.initialize({ 5 + theme: "base" 6 + }) 7 + </script>
+107
templates/partials/slide-styles.eta
··· 1 + <link rel="preconnect" href="https://fonts.googleapis.com"> 2 + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 3 + <link href="https://fonts.googleapis.com/css2?family=Recursive:slnt,wght,CASL,CRSV,MONO@-15..0,300..1000,0..1,0..1,0..1&display=swap" rel="stylesheet"> 4 + 5 + <style> 6 + body { 7 + margin: 0; 8 + box-sizing: border-box; 9 + font-family: "Recursive", sans-serif; 10 + background-color: #191724; 11 + color: #908caa; 12 + } 13 + 14 + h1, h2, h3, h4, h5, h6 { 15 + display: block; 16 + color: #e0def4; 17 + margin-bottom: 2cqi; 18 + } 19 + 20 + p, ul, ol { 21 + font-size: 2.5cqi; 22 + margin: 0; 23 + } 24 + 25 + pre { 26 + margin: 0; 27 + font-variation-settings: "MONO" 1; 28 + font-family: "Recursive", monospace; 29 + } 30 + 31 + code { 32 + font-variation-settings: "MONO" 1; 33 + font-family: "Recursive", monospace; 34 + } 35 + 36 + article { 37 + width: 100vw; 38 + height: 100vh; 39 + overflow-y: scroll; 40 + scroll-behavior: smooth; 41 + scroll-snap-type: y mandatory; 42 + } 43 + 44 + section { 45 + width: 100%; 46 + height: 100%; 47 + scroll-snap-align: center; 48 + position: relative; 49 + } 50 + 51 + section > div { 52 + container: slide / inline-size; 53 + box-sizing: border-box; 54 + position: absolute; 55 + inset: 0; 56 + margin: auto; 57 + aspect-ratio: 16 / 9; 58 + max-width: 100%; 59 + max-height: 100%; 60 + 61 + display: flex; 62 + flex-direction: column; 63 + justify-content: center; 64 + align-items: center; 65 + 66 + padding: 6cqmin; 67 + background: #1f1d2e; 68 + } 69 + 70 + h1 { 71 + font-size: 8cqi; 72 + margin: 0; 73 + font-weight: 900; 74 + } 75 + 76 + h2 { 77 + font-size: 6cqi; 78 + margin-top: 0; 79 + margin-bottom: 3cqi; 80 + font-weight: 900; 81 + } 82 + 83 + h1:first-of-type { 84 + font-size: 10cqi; 85 + margin-bottom: 3cqi; 86 + font-weight: 1000; 87 + line-height: 0.8em; 88 + } 89 + 90 + h1:first-of-type ~ :is(h2, h3, h4, h5, h6, p) { 91 + font-size: 2.8cqi; 92 + font-weight: 700; 93 + margin: 0; 94 + } 95 + 96 + div:has(h1:first-of-type) { 97 + display: flex; 98 + flex-direction: column; 99 + justify-content: center; 100 + } 101 + 102 + .shiki { 103 + padding: 2cqi; 104 + font-size: 2cqi; 105 + border-radius: 1em; 106 + } 107 + </style>
+19
templates/presentation.eta
··· 1 + <!doctype html> 2 + <html> 3 + <head> 4 + <title><%= it.title || (it.devMode ? "Dev" : "Presentation") %></title> 5 + 6 + <%~ include("./partials/slide-styles") %> 7 + </head> 8 + <body> 9 + <%~ it.body %> 10 + 11 + <% if (it.includes.has("mermaid")) { %> 12 + <%~ include("./partials/mermaid", it) %> 13 + <% } %> 14 + 15 + <% if (it.includes.has("live-reload")) { %> 16 + <%~ include('./partials/live-reload', it) %> 17 + <% } %> 18 + </body> 19 + </html>
-121
templates/static.eta
··· 1 - <!doctype html> 2 - <html> 3 - <head> 4 - <title><%= it.title || "Presentation" %></title> 5 - <link rel="preconnect" href="https://fonts.googleapis.com"> 6 - <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 7 - <link href="https://fonts.googleapis.com/css2?family=Recursive:slnt,wght,CASL,CRSV,MONO@-15..0,300..1000,0..1,0..1,0..1&display=swap" rel="stylesheet"> 8 - 9 - <style> 10 - body { 11 - margin: 0; 12 - box-sizing: border-box; 13 - font-family: "Recursive", sans-serif; 14 - background-color: #191724; 15 - color: #908caa; 16 - } 17 - 18 - h1, h2, h3, h4, h5, h6 { 19 - display: block; 20 - color: #e0def4; 21 - margin-bottom: 2cqi; 22 - } 23 - 24 - p, ul, ol { 25 - font-size: 2.5cqi; 26 - margin: 0; 27 - } 28 - 29 - pre { 30 - margin: 0; 31 - font-variation-settings: "MONO" 1; 32 - font-family: "Recursive", monospace; 33 - } 34 - 35 - code { 36 - font-variation-settings: "MONO" 1; 37 - font-family: "Recursive", monospace; 38 - } 39 - 40 - article { 41 - width: 100vw; 42 - height: 100vh; 43 - overflow-y: scroll; 44 - scroll-behavior: smooth; 45 - scroll-snap-type: y mandatory; 46 - } 47 - 48 - section { 49 - width: 100%; 50 - height: 100%; 51 - scroll-snap-align: center; 52 - position: relative; 53 - } 54 - 55 - section > div { 56 - container: slide / inline-size; 57 - box-sizing: border-box; 58 - position: absolute; 59 - inset: 0; 60 - margin: auto; 61 - aspect-ratio: 16 / 9; 62 - max-width: 100%; 63 - max-height: 100%; 64 - 65 - display: flex; 66 - flex-direction: column; 67 - justify-content: center; 68 - align-items: center; 69 - 70 - padding: 6cqmin; 71 - background: #1f1d2e; 72 - } 73 - 74 - h1 { 75 - font-size: 8cqi; 76 - margin: 0; 77 - font-weight: 900; 78 - } 79 - 80 - h2 { 81 - font-size: 6cqi; 82 - margin-top: 0; 83 - margin-bottom: 3cqi; 84 - font-weight: 900; 85 - } 86 - 87 - h1:first-of-type { 88 - font-size: 10cqi; 89 - margin-bottom: 3cqi; 90 - font-weight: 1000; 91 - line-height: 0.8em; 92 - } 93 - 94 - h1:first-of-type ~ :is(h2, h3, h4, h5, h6, p) { 95 - font-size: 2.8cqi; 96 - font-weight: 700; 97 - margin: 0; 98 - } 99 - 100 - div:has(h1:first-of-type) { 101 - display: flex; 102 - flex-direction: column; 103 - justify-content: center; 104 - } 105 - 106 - .shiki { 107 - padding: 2cqi; 108 - font-size: 2cqi; 109 - border-radius: 1em; 110 - } 111 - </style> 112 - </head> 113 - <body> 114 - <article> 115 - <%~ it.body %> 116 - </article> 117 - <% it.scripts.forEach(src => { %> 118 - <script src="<%= src %>"></script> 119 - <% }) %> 120 - </body> 121 - </html>