@kadal/deck 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/LICENSE +661 -0
  2. package/LICENSING.md +40 -0
  3. package/README.md +170 -2
  4. package/dist/_virtual/_rolldown/runtime.js +13 -0
  5. package/dist/audience/index.d.ts +14 -0
  6. package/dist/audience/index.js +680 -0
  7. package/dist/backgrounds/index.d.ts +65 -0
  8. package/dist/backgrounds/index.js +604 -0
  9. package/dist/backgrounds/shared.d.ts +7 -0
  10. package/dist/backgrounds/shared.js +89 -0
  11. package/dist/cli/bin.d.ts +1 -0
  12. package/dist/cli/bin.js +8 -0
  13. package/dist/cli/host.js +111 -0
  14. package/dist/cli/index.d.ts +4 -0
  15. package/dist/cli/index.js +140 -0
  16. package/dist/cli/login.js +56 -0
  17. package/dist/cli/publish.js +159 -0
  18. package/dist/cli/vite.js +39 -0
  19. package/dist/compiler/code-meta.d.ts +33 -0
  20. package/dist/compiler/code-meta.js +81 -0
  21. package/dist/compiler/index.d.ts +8 -0
  22. package/dist/compiler/index.js +8 -0
  23. package/dist/compiler/magic-move.d.ts +26 -0
  24. package/dist/compiler/magic-move.js +33 -0
  25. package/dist/compiler/plain-text.js +7 -0
  26. package/dist/compiler/rehype-code.d.ts +18 -0
  27. package/dist/compiler/rehype-code.js +66 -0
  28. package/dist/compiler/rehype-glossary.d.ts +35 -0
  29. package/dist/compiler/rehype-glossary.js +211 -0
  30. package/dist/compiler/slots.d.ts +3 -0
  31. package/dist/compiler/slots.js +36 -0
  32. package/dist/compiler/split.d.ts +63 -0
  33. package/dist/compiler/split.js +195 -0
  34. package/dist/components/Background.d.ts +15 -0
  35. package/dist/components/Background.js +24 -0
  36. package/dist/components/Click.d.ts +54 -0
  37. package/dist/components/Click.js +129 -0
  38. package/dist/components/Code.d.ts +27 -0
  39. package/dist/components/Code.js +124 -0
  40. package/dist/components/Motion.d.ts +25 -0
  41. package/dist/components/Motion.js +45 -0
  42. package/dist/components/Sketch.d.ts +44 -0
  43. package/dist/components/Sketch.js +141 -0
  44. package/dist/components/Term.d.ts +63 -0
  45. package/dist/components/Term.js +492 -0
  46. package/dist/components/bling.d.ts +75 -0
  47. package/dist/components/bling.js +146 -0
  48. package/dist/components/content.d.ts +232 -0
  49. package/dist/components/content.js +637 -0
  50. package/dist/components/index.d.ts +10 -0
  51. package/dist/components/index.js +88 -0
  52. package/dist/core/clicks.d.ts +45 -0
  53. package/dist/core/clicks.js +75 -0
  54. package/dist/core/hash.d.ts +33 -0
  55. package/dist/core/hash.js +49 -0
  56. package/dist/core/keyboard.d.ts +17 -0
  57. package/dist/core/keyboard.js +198 -0
  58. package/dist/core/layout.d.ts +16 -0
  59. package/dist/core/layout.js +25 -0
  60. package/dist/core/model.d.ts +259 -0
  61. package/dist/core/model.js +377 -0
  62. package/dist/core/navigation.d.ts +74 -0
  63. package/dist/core/navigation.js +244 -0
  64. package/dist/core/poster.js +30 -0
  65. package/dist/core/store.d.ts +20 -0
  66. package/dist/core/store.js +64 -0
  67. package/dist/core/touch.js +27 -0
  68. package/dist/draw/DrawingLayer.js +315 -0
  69. package/dist/draw/strokes.js +188 -0
  70. package/dist/export/index.d.ts +68 -0
  71. package/dist/export/index.js +251 -0
  72. package/dist/export/run.js +81 -0
  73. package/dist/glossary/model.d.ts +81 -0
  74. package/dist/glossary/model.js +156 -0
  75. package/dist/glossary/parse.d.ts +27 -0
  76. package/dist/glossary/parse.js +90 -0
  77. package/dist/index.d.ts +31 -0
  78. package/dist/index.js +30 -0
  79. package/dist/live/LiveProvider.d.ts +20 -0
  80. package/dist/live/LiveProvider.js +450 -0
  81. package/dist/live/analytics.d.ts +15 -0
  82. package/dist/live/analytics.js +103 -0
  83. package/dist/live/client.d.ts +51 -0
  84. package/dist/live/client.js +193 -0
  85. package/dist/live/components.d.ts +64 -0
  86. package/dist/live/components.js +471 -0
  87. package/dist/live/context.d.ts +15 -0
  88. package/dist/live/context.js +28 -0
  89. package/dist/live/index.d.ts +7 -0
  90. package/dist/live/index.js +7 -0
  91. package/dist/live/protocol.d.ts +284 -0
  92. package/dist/live/protocol.js +42 -0
  93. package/dist/live/turnstile.js +49 -0
  94. package/dist/presenter/index.d.ts +20 -0
  95. package/dist/presenter/index.js +1211 -0
  96. package/dist/react/AutoAnimate.js +92 -0
  97. package/dist/react/Backgrounds.d.ts +11 -0
  98. package/dist/react/Backgrounds.js +154 -0
  99. package/dist/react/ConfettiLayer.js +85 -0
  100. package/dist/react/Deck.d.ts +35 -0
  101. package/dist/react/Deck.js +983 -0
  102. package/dist/react/Frame.d.ts +16 -0
  103. package/dist/react/Frame.js +174 -0
  104. package/dist/react/Hints.d.ts +3 -0
  105. package/dist/react/Hints.js +201 -0
  106. package/dist/react/Lightbox.js +54 -0
  107. package/dist/react/Overview.js +93 -0
  108. package/dist/react/PrintView.js +128 -0
  109. package/dist/react/ScrollView.js +128 -0
  110. package/dist/react/SlideFrame.d.ts +51 -0
  111. package/dist/react/SlideFrame.js +323 -0
  112. package/dist/react/Stage.js +151 -0
  113. package/dist/react/ThemeMenu.d.ts +5 -0
  114. package/dist/react/ThemeMenu.js +124 -0
  115. package/dist/react/Zoom.js +23 -0
  116. package/dist/react/chrome.js +339 -0
  117. package/dist/react/context.d.ts +165 -0
  118. package/dist/react/context.js +19 -0
  119. package/dist/react/hooks.d.ts +33 -0
  120. package/dist/react/hooks.js +70 -0
  121. package/dist/react/layouts.d.ts +21 -0
  122. package/dist/react/layouts.js +104 -0
  123. package/dist/react/mdx.d.ts +4 -0
  124. package/dist/react/mdx.js +33 -0
  125. package/dist/react/terms.d.ts +6 -0
  126. package/dist/react/terms.js +66 -0
  127. package/dist/renderers/Island.d.ts +57 -0
  128. package/dist/renderers/Island.js +223 -0
  129. package/dist/renderers/contract.d.ts +173 -0
  130. package/dist/renderers/contract.js +152 -0
  131. package/dist/renderers/index.d.ts +12 -0
  132. package/dist/renderers/index.js +16 -0
  133. package/dist/renderers/react.d.ts +16 -0
  134. package/dist/renderers/react.js +32 -0
  135. package/dist/renderers/web-components.d.ts +4 -0
  136. package/dist/renderers/web-components.js +103 -0
  137. package/dist/sketch/Canvas.js +49 -0
  138. package/dist/sketch/dsl.d.ts +131 -0
  139. package/dist/sketch/dsl.js +281 -0
  140. package/dist/sketch/index.d.ts +4 -0
  141. package/dist/sketch/index.js +3 -0
  142. package/dist/sketch/palette.d.ts +21 -0
  143. package/dist/sketch/palette.js +66 -0
  144. package/dist/sketch/render.d.ts +43 -0
  145. package/dist/sketch/render.js +198 -0
  146. package/dist/themes/base.css +1704 -0
  147. package/dist/themes/bling.css +592 -0
  148. package/dist/themes/glossary.css +317 -0
  149. package/dist/themes/live.css +1557 -0
  150. package/dist/themes/minimal.css +83 -0
  151. package/dist/themes/rla.css +92 -0
  152. package/dist/themes/sketch.css +121 -0
  153. package/dist/themes/stack.css +62 -0
  154. package/dist/vite/index.d.ts +15 -0
  155. package/dist/vite/index.js +320 -0
  156. package/package.json +159 -4
  157. package/tools/verify-island.mjs +150 -0
@@ -0,0 +1,81 @@
1
+ //#region src/compiler/code-meta.ts
2
+ function parseLineSet(spec, total) {
3
+ const s = spec.trim();
4
+ if (s === "" || s === "all" || s === "*") return null;
5
+ if (s === "none" || s === "hide") return /* @__PURE__ */ new Set();
6
+ const out = /* @__PURE__ */ new Set();
7
+ for (const part of s.split(",")) {
8
+ const p = part.trim();
9
+ if (!p) continue;
10
+ const range = /^(\d+)\s*-\s*(\d+)?$/.exec(p);
11
+ if (range) {
12
+ const a = Number(range[1]);
13
+ const b = range[2] ? Number(range[2]) : total ?? a;
14
+ for (let i = Math.min(a, b); i <= Math.max(a, b); i++) out.add(i);
15
+ continue;
16
+ }
17
+ const n = Number(p);
18
+ if (Number.isFinite(n)) out.add(n);
19
+ }
20
+ return out;
21
+ }
22
+ function parseOptions(text, into) {
23
+ for (const pair of text.split(",")) {
24
+ const m = /^\s*([\w-]+)\s*:\s*(.+?)\s*$/.exec(pair);
25
+ if (!m) continue;
26
+ const key = m[1];
27
+ const raw = m[2].replace(/^['"]|['"]$/g, "");
28
+ if (key === "lines") into.lines = raw !== "false";
29
+ else if (key === "startLine") into.startLine = Number(raw) || 1;
30
+ else if (key === "maxHeight") into.maxHeight = raw;
31
+ }
32
+ }
33
+ function parseCodeMeta(meta) {
34
+ const out = {
35
+ steps: null,
36
+ lines: false,
37
+ startLine: 1,
38
+ title: null,
39
+ maxHeight: null,
40
+ hideUntilStep: false
41
+ };
42
+ if (!meta) return out;
43
+ let rest = meta.trim();
44
+ const reveal = /data-line-numbers(?:="([^"]*)")?/.exec(rest);
45
+ if (reveal) {
46
+ out.lines = true;
47
+ if (reveal[1]) out.steps = reveal[1].split("|").map((s) => parseLineSet(s));
48
+ rest = rest.replace(reveal[0], "");
49
+ const start = /data-ln-start-from="(\d+)"/.exec(rest);
50
+ if (start) out.startLine = Number(start[1]);
51
+ }
52
+ const title = /\[([^\]]+)\]/.exec(rest);
53
+ if (title) {
54
+ out.title = title[1].trim();
55
+ rest = rest.replace(title[0], "");
56
+ }
57
+ const braces = [...rest.matchAll(/\{([^}]*)\}/g)].map((m) => m[1]);
58
+ if (braces.length) {
59
+ const first = braces[0];
60
+ if (!/^\s*[\w-]+\s*:/.test(first)) {
61
+ const stages = first.split("|").map((s) => s.trim());
62
+ if (stages[0] === "hide") {
63
+ out.hideUntilStep = true;
64
+ stages.shift();
65
+ }
66
+ out.steps = stages.map((s) => parseLineSet(s));
67
+ if (out.steps.length === 0) out.steps = null;
68
+ } else parseOptions(first, out);
69
+ for (const extra of braces.slice(1)) parseOptions(extra, out);
70
+ }
71
+ return out;
72
+ }
73
+ /** Serialise steps for a `data-steps` attribute: `2,3|5|` (empty = all). */
74
+ function serialiseSteps(steps) {
75
+ return steps.map((s) => s === null ? "*" : s.size === 0 ? "none" : [...s].sort((a, b) => a - b).join(",")).join("|");
76
+ }
77
+ function deserialiseSteps(text) {
78
+ return text.split("|").map((s) => parseLineSet(s));
79
+ }
80
+ //#endregion
81
+ export { deserialiseSteps, parseCodeMeta, parseLineSet, serialiseSteps };
@@ -0,0 +1,8 @@
1
+ import { Frontmatter, RawSlide, SplitOptions, SplitResult, levelOf, splitDeck, titleOf } from "./split.js";
2
+ import { CodeMeta, LineSet, deserialiseSteps, parseCodeMeta, parseLineSet, serialiseSteps } from "./code-meta.js";
3
+ import { MagicMoveBlock, MagicMoveStep, findMagicMoves } from "./magic-move.js";
4
+ import { rehypeDeckCode } from "./rehype-code.js";
5
+ import { RehypeGlossaryOptions, rehypeGlossary } from "./rehype-glossary.js";
6
+ import { ParsedGlossary, parseGlossary } from "../glossary/parse.js";
7
+ import { applySlots } from "./slots.js";
8
+ export { type CodeMeta, type Frontmatter, type LineSet, type MagicMoveBlock, type MagicMoveStep, type ParsedGlossary, type RawSlide, type RehypeGlossaryOptions, type SplitOptions, type SplitResult, applySlots, deserialiseSteps, findMagicMoves, levelOf, parseCodeMeta, parseGlossary, parseLineSet, rehypeDeckCode, rehypeGlossary, serialiseSteps, splitDeck, titleOf };
@@ -0,0 +1,8 @@
1
+ import { deserialiseSteps, parseCodeMeta, parseLineSet, serialiseSteps } from "./code-meta.js";
2
+ import { levelOf, splitDeck, titleOf } from "./split.js";
3
+ import { findMagicMoves } from "./magic-move.js";
4
+ import { rehypeDeckCode } from "./rehype-code.js";
5
+ import { rehypeGlossary } from "./rehype-glossary.js";
6
+ import { parseGlossary } from "../glossary/parse.js";
7
+ import { applySlots } from "./slots.js";
8
+ export { applySlots, deserialiseSteps, findMagicMoves, levelOf, parseCodeMeta, parseGlossary, parseLineSet, rehypeDeckCode, rehypeGlossary, serialiseSteps, splitDeck, titleOf };
@@ -0,0 +1,26 @@
1
+ //#region src/compiler/magic-move.d.ts
2
+ /**
3
+ * Shiki Magic Move, the Slidev syntax: a Markdown fence of four backticks with the language `md`
4
+ * and the word `magic-move`, holding one code fence per step. The block morphs from step to step
5
+ * on each click, Keynote-style. We find the block in the raw slide text (before MDX sees it),
6
+ * precompute the keyed tokens for every step at build time with shiki-magic-move's core, and hand
7
+ * the slide a `<MagicMove steps={…} />` in its place — the runtime then animates without shipping
8
+ * a highlighter to the browser.
9
+ */
10
+ export interface MagicMoveStep {
11
+ code: string;
12
+ lang: string;
13
+ /** The fence meta, e.g. `{1|2-3}{lines:true}` — highlight steps within this step. */
14
+ meta: string;
15
+ }
16
+ export interface MagicMoveBlock {
17
+ /** `{at:4, lines: true, duration: 500}`-style options on the outer fence. */
18
+ options: Record<string, string>;
19
+ title: string | null;
20
+ steps: MagicMoveStep[];
21
+ }
22
+ export declare function findMagicMoves(text: string): {
23
+ text: string;
24
+ blocks: MagicMoveBlock[];
25
+ };
26
+ //#endregion
@@ -0,0 +1,33 @@
1
+ //#region src/compiler/magic-move.ts
2
+ const OUTER = /^([ \t]*)````+\s*md\s+magic-move([^\n]*)\n([\s\S]*?)\n\1````+[ \t]*$/gm;
3
+ const INNER = /```([\w+-]*)([^\n]*)\n([\s\S]*?)\n```/g;
4
+ function findMagicMoves(text) {
5
+ const blocks = [];
6
+ return {
7
+ text: text.replace(OUTER, (_m, _indent, header, body) => {
8
+ const options = {};
9
+ const braces = /\{([^}]*)\}/.exec(header);
10
+ if (braces) for (const pair of braces[1].split(",")) {
11
+ const kv = /^\s*([\w-]+)\s*:\s*(.+?)\s*$/.exec(pair);
12
+ if (kv) options[kv[1]] = kv[2].replace(/^['"]|['"]$/g, "");
13
+ }
14
+ const title = /\[([^\]]+)\]/.exec(header);
15
+ const steps = [];
16
+ for (const m of body.matchAll(INNER)) steps.push({
17
+ lang: m[1] || "text",
18
+ meta: m[2].trim(),
19
+ code: m[3]
20
+ });
21
+ const id = blocks.length;
22
+ blocks.push({
23
+ options,
24
+ title: title ? title[1].trim() : null,
25
+ steps
26
+ });
27
+ return `\n<MagicMove block={${id}} />\n`;
28
+ }),
29
+ blocks
30
+ };
31
+ }
32
+ //#endregion
33
+ export { findMagicMoves };
@@ -0,0 +1,7 @@
1
+ //#region src/compiler/plain-text.ts
2
+ /** A slide's MDX reduced to searchable text: no tags, no fence noise, no attribute braces. */
3
+ function plainText(mdx) {
4
+ return mdx.replace(/```[^\n]*\n?/g, "").replace(/\{\/\*[\s\S]*?\*\/\}/g, "").replace(/<[^>]+>/g, " ").replace(/\{[^}]*\}/g, " ").replace(/!?\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/[#*_`>~|]+/g, " ").replace(/\s+/g, " ").trim();
5
+ }
6
+ //#endregion
7
+ export { plainText };
@@ -0,0 +1,18 @@
1
+ import { Highlighter } from "shiki";
2
+ import { Root } from "hast";
3
+ //#region src/compiler/rehype-code.d.ts
4
+ /**
5
+ * Turn every fenced code block into highlighted HTML at build time, and carry the fence meta over
6
+ * as data attributes the runtime `<Code>` reads: which lines light up on which click, whether to
7
+ * number lines, the title, the max height. Highlighting is Shiki with a light and a dark theme at
8
+ * once; the CSS picks by the deck's colour scheme.
9
+ */
10
+ export interface RehypeCodeOptions {
11
+ highlighter: Highlighter;
12
+ themes: {
13
+ light: string;
14
+ dark: string;
15
+ };
16
+ }
17
+ export declare function rehypeDeckCode(options: RehypeCodeOptions): (tree: Root) => void;
18
+ //#endregion
@@ -0,0 +1,66 @@
1
+ import { parseCodeMeta, serialiseSteps } from "./code-meta.js";
2
+ import { visit } from "unist-util-visit";
3
+ //#region src/compiler/rehype-code.ts
4
+ function langOf(node) {
5
+ const cls = node.properties?.className;
6
+ const list = Array.isArray(cls) ? cls : typeof cls === "string" ? [cls] : [];
7
+ for (const c of list) {
8
+ const m = /^language-([\w+-]+)$/.exec(String(c));
9
+ if (m) return m[1];
10
+ }
11
+ return "text";
12
+ }
13
+ function textOf(node) {
14
+ let out = "";
15
+ const walk = (n) => {
16
+ if (n.type === "text") out += n.value;
17
+ for (const c of n.children ?? []) walk(c);
18
+ };
19
+ walk(node);
20
+ return out;
21
+ }
22
+ function rehypeDeckCode(options) {
23
+ const { highlighter, themes } = options;
24
+ return (tree) => {
25
+ visit(tree, "element", (node, index, parent) => {
26
+ if (node.tagName !== "pre" || !parent || index === void 0) return;
27
+ const code = node.children.find((c) => c.type === "element" && c.tagName === "code");
28
+ if (!code) return;
29
+ const lang = langOf(code);
30
+ if (lang === "math") return;
31
+ const meta = parseCodeMeta(code.data?.meta);
32
+ const source = textOf(code).replace(/\n$/, "");
33
+ const useLang = highlighter.getLoadedLanguages().includes(lang) ? lang : "text";
34
+ const pre = highlighter.codeToHast(source, {
35
+ lang: useLang,
36
+ themes,
37
+ defaultColor: false,
38
+ transformers: [{ line(el, line) {
39
+ el.properties["data-line"] = String(line + meta.startLine - 1);
40
+ } }]
41
+ }).children.find((c) => c.type === "element" && c.tagName === "pre");
42
+ if (!pre) return;
43
+ pre.properties = {
44
+ ...pre.properties,
45
+ ...dataAttributes(meta, lang, source.split("\n").length)
46
+ };
47
+ pre.properties.className = ["deck-code", "shiki"];
48
+ parent.children[index] = pre;
49
+ });
50
+ };
51
+ }
52
+ function dataAttributes(meta, lang, totalLines) {
53
+ const out = {
54
+ "data-lang": lang,
55
+ "data-total": String(totalLines)
56
+ };
57
+ if (meta.steps) out["data-steps"] = serialiseSteps(meta.steps);
58
+ if (meta.lines) out["data-lines"] = "true";
59
+ if (meta.startLine !== 1) out["data-start"] = String(meta.startLine);
60
+ if (meta.title) out["data-title"] = meta.title;
61
+ if (meta.maxHeight) out["data-max-height"] = meta.maxHeight;
62
+ if (meta.hideUntilStep) out["data-hide-until-step"] = "true";
63
+ return out;
64
+ }
65
+ //#endregion
66
+ export { rehypeDeckCode };
@@ -0,0 +1,35 @@
1
+ import { TermMatcher } from "../glossary/model.js";
2
+ import { Root } from "hast";
3
+ //#region src/compiler/rehype-glossary.d.ts
4
+ /**
5
+ * Links a slide's key terms to the deck's glossary: each mention the matcher finds is wrapped in
6
+ * `<Term id auto>`, which shows the definition in a card after a short hover. The same idea as
7
+ * the pass that links Paadam's notes (apps/paadam/mdx/rehype-glossary.ts), retuned for slides:
8
+ *
9
+ * · A slide is the unit. With `repeat: "first"` a term is linked once per slide — every slide
10
+ * stands alone in front of an audience, nobody can scroll back to where it was first defined.
11
+ * · Body text goes first and headings only pick up what the body never mentions (`headings:
12
+ * "fallback"`): a slide whose title is its only mention of UTM still gets the link, but a
13
+ * title is not underlined when the sentence below it can carry the term instead.
14
+ * · Code, maths, links, controls and the components that animate or paint their own text
15
+ * (GradientText, Typewriter, Counter…) are left alone; `className="no-terms"` opts a block out.
16
+ *
17
+ * Explicit `<Term>` elements are settled here as well, so the author hears about a typo at build
18
+ * time: `<Term>UTM</Term>` gets its `id`, `<Term def="…">` gets its definition rendered (with
19
+ * KaTeX, which the browser never has to load), and both count as the slide's mention of the term.
20
+ */
21
+ export interface RehypeGlossaryOptions {
22
+ matcher: TermMatcher;
23
+ /** False settles the slide's explicit `<Term>`s and links nothing else (`terms: false`). */
24
+ auto?: boolean;
25
+ repeat: "first" | "all";
26
+ headings: "fallback" | boolean;
27
+ /** Extra component names to leave alone, from the headmatter. */
28
+ skip?: string[];
29
+ /** Term ids this slide does not link (`terms: { skip: [...] }` in its frontmatter). */
30
+ exclude?: Set<string>;
31
+ math?: (tex: string) => string;
32
+ warn?: (message: string) => void;
33
+ }
34
+ export declare function rehypeGlossary(options: RehypeGlossaryOptions): (tree: Root) => void;
35
+ //#endregion
@@ -0,0 +1,211 @@
1
+ import { adhocId, inlineHtml } from "../glossary/model.js";
2
+ //#region src/compiler/rehype-glossary.ts
3
+ const HEADINGS = /* @__PURE__ */ new Set([
4
+ "h1",
5
+ "h2",
6
+ "h3",
7
+ "h4",
8
+ "h5",
9
+ "h6"
10
+ ]);
11
+ const SKIP_TAGS = /* @__PURE__ */ new Set([
12
+ "code",
13
+ "pre",
14
+ "a",
15
+ "script",
16
+ "style",
17
+ "svg",
18
+ "math",
19
+ "kbd",
20
+ "abbr",
21
+ "button",
22
+ "select",
23
+ "option",
24
+ "textarea",
25
+ "iframe",
26
+ "video",
27
+ "audio"
28
+ ]);
29
+ const SKIP_CLASSES = /* @__PURE__ */ new Set([
30
+ "no-terms",
31
+ "katex",
32
+ "math",
33
+ "math-inline",
34
+ "math-display"
35
+ ]);
36
+ const SKIP_JSX = /* @__PURE__ */ new Set([
37
+ "Code",
38
+ "MagicMove",
39
+ "Tex",
40
+ "Math",
41
+ "Mermaid",
42
+ "Sketch",
43
+ "Notes",
44
+ "Link",
45
+ "Anchor",
46
+ "Toc",
47
+ "Image",
48
+ "Video",
49
+ "Audio",
50
+ "Iframe",
51
+ "Youtube",
52
+ "Tweet",
53
+ "Kbd",
54
+ "Counter",
55
+ "Typewriter",
56
+ "Encrypted",
57
+ "Timer",
58
+ "FitText",
59
+ "Qr",
60
+ "QrJoin",
61
+ "Background",
62
+ "GradientText",
63
+ "Shimmer",
64
+ "Icon",
65
+ "Poll",
66
+ "Quiz",
67
+ "Questions",
68
+ "Leaderboard",
69
+ "WordCloud",
70
+ "Presence",
71
+ "Reactions",
72
+ "Pace"
73
+ ]);
74
+ const isJsx = (n) => n.type === "mdxJsxTextElement" || n.type === "mdxJsxFlowElement";
75
+ function attr(el, name) {
76
+ return el.attributes.find((a) => a.type === "mdxJsxAttribute" && a.name === name);
77
+ }
78
+ const stringAttr = (el, name) => {
79
+ const v = attr(el, name)?.value;
80
+ return typeof v === "string" ? v : null;
81
+ };
82
+ function setAttr(el, name, value) {
83
+ const existing = attr(el, name);
84
+ if (existing) existing.value = value;
85
+ else el.attributes.push({
86
+ type: "mdxJsxAttribute",
87
+ name,
88
+ value
89
+ });
90
+ }
91
+ function classesOf(node) {
92
+ if (node.type === "element") {
93
+ const cls = node.properties?.className;
94
+ return Array.isArray(cls) ? cls.map(String) : typeof cls === "string" ? cls.split(/\s+/) : [];
95
+ }
96
+ if (isJsx(node)) return (stringAttr(node, "className") ?? stringAttr(node, "class") ?? "").split(/\s+/);
97
+ return [];
98
+ }
99
+ function textOf(node) {
100
+ if (node.type === "text") return node.value;
101
+ return (node.children ?? []).map(textOf).join("");
102
+ }
103
+ function rehypeGlossary(options) {
104
+ const { matcher, repeat, headings, exclude, math, warn } = options;
105
+ const skipJsx = /* @__PURE__ */ new Set([...SKIP_JSX, ...options.skip ?? []]);
106
+ return (tree) => {
107
+ const seen = new Set(exclude ?? []);
108
+ const jobs = [];
109
+ const settle = (el) => {
110
+ const def = stringAttr(el, "def");
111
+ if (def !== null) {
112
+ setAttr(el, "html", inlineHtml(def.replace(/\s+/g, " ").trim(), math));
113
+ const more = stringAttr(el, "more");
114
+ if (more !== null) setAttr(el, "moreHtml", inlineHtml(more.replace(/\s+/g, " ").trim(), math));
115
+ if (stringAttr(el, "id") === null) setAttr(el, "id", adhocId(`${textOf(el)}|${def}`));
116
+ return;
117
+ }
118
+ if (attr(el, "def")) return;
119
+ const wanted = stringAttr(el, "id") ?? textOf(el);
120
+ const entry = matcher.resolve(wanted);
121
+ if (!entry) {
122
+ warn?.(`<Term> "${wanted.trim()}" is not in the glossary and has no def`);
123
+ return;
124
+ }
125
+ setAttr(el, "id", entry.id);
126
+ seen.add(entry.id);
127
+ };
128
+ const collect = (node, heading) => {
129
+ let inHeading = heading;
130
+ if (node.type === "element") {
131
+ const tag = node.tagName;
132
+ if (SKIP_TAGS.has(tag)) return;
133
+ if (HEADINGS.has(tag)) inHeading = true;
134
+ } else if (isJsx(node)) {
135
+ if (node.name === "Term") return settle(node);
136
+ if (node.name && (skipJsx.has(node.name) || SKIP_TAGS.has(node.name))) return;
137
+ if (node.name && HEADINGS.has(node.name)) inHeading = true;
138
+ } else if (node.type !== "root") return;
139
+ if (classesOf(node).some((c) => SKIP_CLASSES.has(c))) return;
140
+ for (const child of node.children ?? []) if (child.type === "text") {
141
+ if (child.value.trim()) jobs.push({
142
+ node: child,
143
+ heading: inHeading
144
+ });
145
+ } else collect(child, inHeading);
146
+ };
147
+ collect(tree, false);
148
+ const replacements = /* @__PURE__ */ new Map();
149
+ const link = (job) => {
150
+ const src = job.node.value;
151
+ const out = [];
152
+ let last = 0;
153
+ for (const m of matcher.find(src)) {
154
+ if (exclude?.has(m.entry.id)) continue;
155
+ if (repeat === "first" && seen.has(m.entry.id)) continue;
156
+ seen.add(m.entry.id);
157
+ if (m.index > last) out.push({
158
+ type: "text",
159
+ value: src.slice(last, m.index)
160
+ });
161
+ out.push({
162
+ type: "mdxJsxTextElement",
163
+ name: "Term",
164
+ attributes: [{
165
+ type: "mdxJsxAttribute",
166
+ name: "id",
167
+ value: m.entry.id
168
+ }, {
169
+ type: "mdxJsxAttribute",
170
+ name: "auto",
171
+ value: null
172
+ }],
173
+ children: [{
174
+ type: "text",
175
+ value: m.text
176
+ }]
177
+ });
178
+ last = m.index + m.text.length;
179
+ }
180
+ if (!out.length) return;
181
+ if (last < src.length) out.push({
182
+ type: "text",
183
+ value: src.slice(last)
184
+ });
185
+ replacements.set(job.node, out);
186
+ };
187
+ if (options.auto === false) return;
188
+ if (headings === "fallback") {
189
+ jobs.filter((j) => !j.heading).forEach(link);
190
+ jobs.filter((j) => j.heading).forEach(link);
191
+ } else jobs.filter((j) => headings || !j.heading).forEach(link);
192
+ if (!replacements.size) return;
193
+ const rebuild = (node) => {
194
+ const kids = node.children;
195
+ if (!kids) return;
196
+ const next = [];
197
+ for (const child of kids) {
198
+ const swap = child.type === "text" ? replacements.get(child) : void 0;
199
+ if (swap) next.push(...swap);
200
+ else {
201
+ rebuild(child);
202
+ next.push(child);
203
+ }
204
+ }
205
+ node.children = next;
206
+ };
207
+ rebuild(tree);
208
+ };
209
+ }
210
+ //#endregion
211
+ export { rehypeGlossary };
@@ -0,0 +1,3 @@
1
+ //#region src/compiler/slots.d.ts
2
+ export declare function applySlots(body: string): string;
3
+ //#endregion
@@ -0,0 +1,36 @@
1
+ //#region src/compiler/slots.ts
2
+ /**
3
+ * Slidev's named slots for layouts: a line `::right::` (or any `::name::`) splits the slide body
4
+ * into slots. Each slot becomes a `<div data-slot="name">` so a layout's CSS can place it — the
5
+ * two-cols layout puts `default` in the left column and `right` in the right one. A body with no
6
+ * markers is left untouched, so plain slides pay nothing for this.
7
+ */
8
+ const MARKER = /^::([a-z][\w-]*)::[ \t]*$/;
9
+ function applySlots(body) {
10
+ const lines = body.split("\n");
11
+ if (!lines.some((l) => MARKER.test(l))) return body;
12
+ const slots = [{
13
+ name: "default",
14
+ lines: []
15
+ }];
16
+ let fence = null;
17
+ for (const line of lines) {
18
+ const f = line.match(/^\s*(`{3,}|~{3,})/);
19
+ if (f) {
20
+ if (!fence) fence = f[1];
21
+ else if (line.trim().startsWith(fence[0]) && line.trim().length >= fence.length) fence = null;
22
+ }
23
+ const m = !fence && line.match(MARKER);
24
+ if (m) {
25
+ slots.push({
26
+ name: m[1],
27
+ lines: []
28
+ });
29
+ continue;
30
+ }
31
+ slots[slots.length - 1].lines.push(line);
32
+ }
33
+ return slots.filter((s) => s.lines.join("\n").trim()).map((s) => `<div data-slot="${s.name}" className="deck-slot deck-slot-${s.name}">\n\n${s.lines.join("\n").trim()}\n\n</div>`).join("\n\n");
34
+ }
35
+ //#endregion
36
+ export { applySlots };
@@ -0,0 +1,63 @@
1
+ //#region src/compiler/split.d.ts
2
+ /**
3
+ * ─────────────────────────────────────────────────────────────────────────────
4
+ * THE DECK FILE
5
+ * ─────────────────────────────────────────────────────────────────────────────
6
+ *
7
+ * A deck is one MDX file, `deck.mdx`, read the way Slidev reads its Markdown and reveal.js reads
8
+ * its external Markdown — as text first, slides second:
9
+ *
10
+ * --- a line of three dashes starts the next slide (horizontal)
11
+ * ---- a line of four or more dashes starts a slide BENEATH the previous one
12
+ * (a vertical stack, the reveal.js idea: a deep dive the audience may skip)
13
+ * --- the block right after a separator is that slide's FRONTMATTER when it is YAML
14
+ * key: value and closes with another line of three dashes; a fenced block at the very top
15
+ * --- of the file is the HEADMATTER, which configures the whole deck
16
+ * <Notes>…</Notes> anywhere in a slide: speaker notes, compiled as MDX of their own
17
+ * src: ./file.mdx a frontmatter-only slide that pulls the slides of another file in
18
+ *
19
+ * Everything in between is MDX: Markdown, JSX, the engine's components. This module only cuts
20
+ * the text; the Vite plugin turns the pieces into modules.
21
+ * ─────────────────────────────────────────────────────────────────────────────
22
+ */
23
+ export type Frontmatter = Record<string, unknown>;
24
+ export interface RawSlide {
25
+ /** Flat index in authored order, 0-based. */
26
+ index: number;
27
+ /** Horizontal column and vertical position in it. */
28
+ h: number;
29
+ v: number;
30
+ frontmatter: Frontmatter;
31
+ /** The slide's MDX with notes and frontmatter removed. */
32
+ content: string;
33
+ /** The notes' MDX, or null when the slide has none. */
34
+ notes: string | null;
35
+ /** Where the slide starts, for error messages and the editor. */
36
+ source: {
37
+ file: string;
38
+ line: number;
39
+ };
40
+ }
41
+ export interface SplitResult {
42
+ headmatter: Frontmatter;
43
+ slides: RawSlide[];
44
+ }
45
+ export interface SplitOptions {
46
+ file: string;
47
+ /** Returns the text of an included file (the `src:` frontmatter), or null when it is missing. */
48
+ resolveInclude?: (path: string, from: string) => {
49
+ file: string;
50
+ text: string;
51
+ } | null;
52
+ }
53
+ /**
54
+ * Split a deck file into slides. Includes (`src:`) are expanded in place; a piece that is only
55
+ * frontmatter with a `src` contributes the included file's slides, with the included file's own
56
+ * headmatter and the piece's remaining frontmatter merged into the first of them.
57
+ */
58
+ export declare function splitDeck(text: string, options: SplitOptions): SplitResult;
59
+ /** The title a slide reports to the table of contents: frontmatter `title`, else its first heading. */
60
+ export declare function titleOf(slide: Pick<RawSlide, "content" | "frontmatter">): string | null;
61
+ /** The level of the title, for the table of contents: frontmatter `level`, else the heading depth. */
62
+ export declare function levelOf(slide: Pick<RawSlide, "content" | "frontmatter">): number;
63
+ //#endregion