@lalalic/markcut 2.8.0 → 3.0.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 (41) hide show
  1. package/B] +2 -0
  2. package/README.md +29 -0
  3. package/package.json +1 -1
  4. package/skills/markcut/SKILL.md +12 -45
  5. package/skills/markcut/docs/components.md +89 -0
  6. package/skills/markcut/docs/markdown-descriptive.md +17 -2
  7. package/skills/markcut/docs/sound-effects.md +45 -0
  8. package/src/components/Markdown.tsx +138 -24
  9. package/src/components/Mermaid.tsx +223 -22
  10. package/src/config.mjs +2 -2
  11. package/src/context/EventContext.tsx +3 -0
  12. package/src/descriptive/compiler.ts +68 -29
  13. package/src/descriptive/markdown.ts +20 -0
  14. package/src/player/browser.tsx +95 -5
  15. package/src/player/bundle/player.js +1078 -629
  16. package/src/player/components/EditControls.tsx +6 -3
  17. package/src/player/components/EditMessagePanel.tsx +96 -0
  18. package/src/player/components/HeaderBar.tsx +9 -11
  19. package/src/player/components/index.ts +1 -0
  20. package/src/player/pipeline.mjs +72 -21
  21. package/src/player/server-shared.mjs +4 -1
  22. package/src/player/server.mjs +202 -42
  23. package/src/render/cli.mjs +1 -1
  24. package/src/schema/index.ts +4 -1
  25. package/src/types/Component.tsx +27 -1
  26. package/src/types/Effect.tsx +13 -6
  27. package/src/types/Folder.tsx +1 -1
  28. package/src/types/Map.tsx +51 -1
  29. package/src/utils/index.ts +14 -2
  30. package/tests/fixtures/md/animate-diagrams.md +40 -0
  31. package/tests/fixtures/md/electricity-grow.md +130 -0
  32. package/tests/tmp/vision-1785081637127-video/videos/.normalized/segments/test-clip_0to3_seg_1100to3000.mp4 +0 -0
  33. package/tests/tmp/vision-1785081637127-video/videos/.normalized/test-clip_0to3.mp4 +0 -0
  34. package/tests/tmp/vision-1785081637127-video/videos/.normalized/test-clip_audio.mp3 +0 -0
  35. package/tests/tmp/vision-1785081637127-video/videos/metadata.json +9 -0
  36. package/tests/tmp/vision-1785081637127-video/videos/test-clip.mp4 +0 -0
  37. package/tests/tmp/vision-1785081637127-video/videos/test-clip.vtt +5 -0
  38. package/tests/tmp/vision-1784830584961/images/.normalized/test-photo_384.jpg +0 -0
  39. package/tests/tmp/vision-1784830584961/images/metadata.json +0 -8
  40. package/tests/tmp/vision-1784830584961/images/test-photo.png +0 -0
  41. package/tmp/frontmatter-test.ts +0 -21
package/B] ADDED
@@ -0,0 +1,2 @@
1
+ [main 2b839d7] mermaid.animateEdges=true|[A-
2
+ 5 files changed, 162 insertions(+), 18 deletions(-)
package/README.md CHANGED
@@ -48,6 +48,7 @@ storyboard.md ──[parse]──▶ DescriptiveRoot ──[compile]──▶
48
48
  | **Built-in Mermaid** | `<Mermaid source="..." theme="dark" />` renders Mermaid diagrams as inline SVG. No imports needed |
49
49
  | **Styling** | Inline `style` strings on any node for CSS. JSX components use inline React styles |
50
50
  | **Live edit** | `--edit` watches the input file, re-runs pipeline, auto-reloads player |
51
+ | **Player language** | Player UI supports English/Chinese. Use `?lang=en` or `?lang=zh` |
51
52
  | **Storyboard** | `--storyboard` fast structure preview: replaces TTI/TTV prompts with placeholder components, skips slow generation. Implies `--edit` |
52
53
  | **Label mode** | `--label` interactive player with per-scene label input, saves to labels.json |
53
54
  | **CLI** | `render`, `preview` commands for MP4 export and Remotion Studio |
@@ -159,6 +160,34 @@ Compound variant labels (like `zh-tiktok`) are split on `-` to form a **variant
159
160
 
160
161
  The browser player reads `window.VARIANT` from the URL path and fetches the correct compiled data via `/api/video-data?variant=<name>`. A **variant switcher bar** at the top of the player lets you jump between variants instantly.
161
162
 
163
+ ### Player UI Language (EN / ZH)
164
+
165
+ The player UI (edit panel, placeholders, tooltips) supports English and Chinese.
166
+
167
+ - `?lang=en` for English
168
+ - `?lang=zh` for Chinese
169
+
170
+ Examples:
171
+
172
+ - `http://localhost:3001?lang=en`
173
+ - `http://localhost:3001?lang=zh`
174
+
175
+ If `lang` is not provided, the player falls back to browser language (Chinese browsers default to `zh`, otherwise `en`).
176
+
177
+ ### Map Language (EN / ZH)
178
+
179
+ `map` nodes support `language`/`lang` and `region` keys.
180
+
181
+ ```md
182
+ - map duration:3 lang:zh region:CN waypoints:[31.23,121.47,"上海";39.90,116.40,"北京"]
183
+ - map duration:3 lang:en region:US waypoints:[37.77,-122.41,"SF";34.05,-118.24,"LA"]
184
+ ```
185
+
186
+ Locale shorthand normalization:
187
+
188
+ - `lang:zh` / `language:zh` → `zh-CN` (default region `CN`)
189
+ - `lang:en` / `language:en` → `en` (default region `US`)
190
+
162
191
  ## `.markcut/` Directory Layout
163
192
 
164
193
  When you run `preview` or `render`, all generated artifacts live under `.markcut/` next to the source file. With multiple variants, each gets its own subdirectory:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lalalic/markcut",
3
- "version": "2.8.0",
3
+ "version": "3.0.0",
4
4
  "description": "Markdown-to-video engine. Describe scenes in markdown, get a rendered video.",
5
5
  "bin": {
6
6
  "markcut": "bin/markcut"
@@ -80,52 +80,15 @@ some common issues (photo or video can't be displayed, audio missing), take belo
80
80
  | Topic | File |
81
81
  |-------|------|
82
82
  | Markdown descriptive format (primary authoring format) | [docs/markdown-descriptive.md](docs/markdown-descriptive.md) |
83
+ | Built-in components & common npm packages | [docs/components.md](docs/components.md) |
84
+ | Sound effects | [docs/sound-effects.md](docs/sound-effects.md) |
83
85
 
84
86
 
85
- ## Built-in Components (no imports needed)
86
87
 
87
- These components are available directly in `jsx:".."` fields or importable from the `~~~js imports` block via `@lalalic/markcut/components`:
88
+ ## Built-in Components
88
89
 
89
- ### `<Markdown />` — render markdown content
90
- ```md
91
- - component jsx:"<Markdown source='# Hello\n\n**bold** text.' />"
92
- ```
93
- - Uses `react-markdown` + `remark-gfm` (tables, strikethrough, task lists)
94
- - Supports `plugins` and `components` props: define custom renderers in imports block and pass them in
95
- - ` ```mermaid ` code fences inside markdown are automatically rendered as Mermaid diagrams
96
- - Importable: `import {Markdown} from "@lalalic/markcut/components"`
97
-
98
- ### `<Mermaid />` — render Mermaid diagrams as SVG
99
- ```md
100
- - component jsx:"<Mermaid source='graph TD; A-->B; A-->C; B-->D;' />"
101
- ```
102
- - Theme prop: `theme="default" | "dark" | "forest" | "neutral"` (default: `dark`)
103
- - Uses `delayRender`/`continueRender` for async rendering — diagram is ready before Remotion captures the frame
104
- - Errors shown inline in the output
105
- - Importable: `import {Mermaid} from "@lalalic/markcut/components"`
106
-
107
- ### Wrapping built-ins in custom components
108
- ```js
109
- import {Markdown, Mermaid} from "@lalalic/markcut/components"
110
-
111
- export function SuperMarkdown({ source }) {
112
- return (
113
- <Markdown
114
- source={source}
115
- components={{
116
- li: ({children}) => <li style={{color:'#ffd700'}}>{children}</li>,
117
- }}
118
- />
119
- )
120
- }
121
- ```
90
+ Built-in components available via `@lalalic/markcut/components`. See [docs/components.md](docs/components.md) for full reference.
122
91
 
123
- ## Common npm packages (used via imports block)
124
- - `react-markdown` + `remark-gfm` — already bundled as built-in `<Markdown />` above
125
- - `remark-toc` — generate table of contents
126
- - `remark-math` + `rehype-katex` — render math formulas with KaTeX
127
- - `@remotion/shapes` — render shapes like arrows, circles, rectangles, etc
128
- - `@remotion/starburst` — render starburst animations
129
92
 
130
93
  ## Golden rule
131
94
  - always check stream start and duration to avoid
@@ -133,8 +96,12 @@ export function SuperMarkdown({ source }) {
133
96
  - video cut off
134
97
  - subtitle mismatch
135
98
  - sync issues between audio, video, and subtitles
136
- - don't set duration for script or stream's duration depending on audio script
137
- - markcut resolver will automatically calculate the duration based on the audio script length
138
- - **don't** rm `.markcut` directory, which served as cache for all generated content. cache will auto update according to the content change. rm `.markcut` will cause all content to be regenerated, which is time consuming and wasteful.
99
+ according to the content change. rm `.markcut` will cause all content to be regenerated, which is time consuming and wasteful.
139
100
  - put all manual assets in `assets` folder, such as bgm, logo, watermark, etc. don't put them in `.markcut` folder, which is auto generated and will be deleted when `markcut clean` command is run.
140
- - `npx @lalalic/markcut preview` stuck until user close the preview window.
101
+
102
+ ### Don'ts
103
+ - **don't** set duration for script or stream's duration depending on audio script
104
+ - markcut resolver will automatically calculate the duration based on the audio script length
105
+ - **don't** rm `.markcut` directory, which served as cache for all generated content. cache will auto update
106
+ - **don't** set timeout for `preview`, `vision`, `render` markcut commands, which may take long time to generate medias.
107
+ - **don't** use skill to understand vision media. use `npx @lalalic/markcut vision <folder>`.
@@ -0,0 +1,89 @@
1
+ ## Built-in Components (no imports needed)
2
+
3
+ These components are available directly in `jsx:".."` fields or importable from the `~~~js imports` block via `@lalalic/markcut/components`:
4
+
5
+ ### `<Markdown />` — render markdown content
6
+ ```md
7
+ - component jsx:"<Markdown source='# Hello\n\n**bold** text.' />"
8
+ ```
9
+ - Uses `react-markdown` + `remark-gfm` (tables, strikethrough, task lists)
10
+ - Supports `plugins` and `components` props: define custom renderers in imports block and pass them in
11
+ - ` ```mermaid ` code fences inside markdown are automatically rendered as Mermaid diagrams
12
+ - **`highlight`** — array of 0-based list item indices to highlight (e.g., `highlight={[0,2]}`).
13
+ Matching `<li>` elements receive CSS class `highlight-list-item` with a default golden
14
+ left-border and background tint. Works for both ordered and unordered lists.
15
+ ```md
16
+ <Markdown highlight={[0,2]}
17
+ source={"1. First item\n2. Second item\n3. Third item\n4. Fourth item"} />
18
+ ```
19
+ - Importable: `import {Markdown} from "@lalalic/markcut/components"`
20
+
21
+ ### `<Mermaid />` — render Mermaid diagrams as SVG
22
+ ```md
23
+ - component jsx:"<Mermaid source='graph TD; A-->B; A-->C; B-->D;' />"
24
+ ```
25
+ - Theme prop: `theme="default" | "dark" | "forest" | "neutral"` (default: `dark`)
26
+ - **`highlight`** — node name(s) to highlight. String or array of strings.
27
+ Toggles CSS class `highlight` on matching SVG elements. The diagram source
28
+ must define the class via `classDef`:
29
+ ```md
30
+ classDef highlight fill:#ffd700,stroke:#ff6600,stroke-width:3px,color:#000
31
+ ```
32
+ - **`animateEdges`** — edge(s) to animate with a flowing dash effect.
33
+ `true` = animate all edges. `string[]` = specific edges by source→target alias:
34
+ ```md
35
+ animateEdges={["A->B","D->F","F->G"]}
36
+ ```
37
+ Animates matching edge `<path>` elements via CSS `stroke-dasharray` + `stroke-dashoffset` keyframes.
38
+ - Source from code fences: use `~~~mermaid` to define the diagram source in a
39
+ separate block, then reference it as `source={mermaid}` in the JSX:
40
+ ```md
41
+ - component id:flowChart duration:12
42
+ ~~~jsx
43
+ <Mermaid highlight={highlight} animateEdges={animateEdges}
44
+ theme='dark' source={mermaid}/>
45
+ ~~~
46
+ ~~~mermaid
47
+ graph TD
48
+ A["Receive Request"] --> B["Validate Input"]
49
+ B --> C{"Valid?"}
50
+ classDef highlight fill:#ffd700,stroke:#ff6600,stroke-width:3px
51
+ ~~~
52
+ highlight:"A"
53
+ animateEdges:true
54
+ ```
55
+ - Dynamic highlight/edges via events: define `on` specs that mutate the
56
+ component's registered id:
57
+ ```md
58
+ - event duration:3 start:3 on:(start, flowChart.highlight="B")
59
+ - event duration:3 start:6 on:(start, flowChart.highlight="C")
60
+ - event duration:3 start:9 on:(start, flowChart.highlight=["D","G"])
61
+ ```
62
+ - Errors shown inline in the output
63
+ - Importable: `import {Mermaid} from "@lalalic/markcut/components"`
64
+
65
+ ### Wrapping built-ins in custom components
66
+ ```js
67
+ import {Markdown, Mermaid} from "@lalalic/markcut/components"
68
+
69
+ export function SuperMarkdown({ source }) {
70
+ return (
71
+ <Markdown
72
+ source={source}
73
+ components={{
74
+ li: ({children}) => <li style={{color:'#ffd700'}}>{children}</li>,
75
+ }}
76
+ />
77
+ )
78
+ }
79
+ ```
80
+
81
+ ## Common npm packages (used via imports block)
82
+ - `react-markdown` + `remark-gfm` — already bundled as built-in `<Markdown />` above
83
+ - `remark-toc` — generate table of contents
84
+ - `remark-math` + `rehype-katex` — render math formulas with KaTeX
85
+ - `@remotion/shapes` — render shapes like arrows, circles, rectangles, etc
86
+ - `@remotion/starburst` — render starburst animations
87
+ - `react-webcam-pro` — render webcam video
88
+ - `react-chartjs-2` — render charts with Chart.js at `https://react-chartjs-2.js.org/components`
89
+ - `@xyflow/react` - render diagrams with `https://reactflow.dev/api-reference`
@@ -69,6 +69,7 @@ Where:
69
69
  | `audio` | `audio` | Voiceover, BGM, SFX | `src` + (`duration` or `endAt`) |
70
70
  | `script "..."` | `audio` | Narration/TTS (shorthand for audio with script) | `script` text (the `"..."` is the primary content) |
71
71
  | `component` | `component` | JSX React component | `jsx` (inline or code fence) + `duration` (unless `isBackground`) |
72
+ | `event` | `component` (stub) | **Event-only** — renders nothing, fires JS on registered components at a specific frame | `on:(when, state)` + `start` + `duration` |
72
73
  | `rhythm` | `rhythm` | Beat-synced audio with timed children | `src`, `spots`, `children` |
73
74
  | `map` | `map` | Animated route visualization | `duration`, `waypoints` |
74
75
  | `include` | `include` | Embed external `.md` sub-video | `src` (path to another `.md` file) |
@@ -112,10 +113,13 @@ Fire JS expression at a specific frame to mutate component state.
112
113
  ```
113
114
  - script "Narration" on:(start, slide.current=0)
114
115
  - script "Beat" on:(50%, slide.current++)
116
+ - event duration:3 start:6 on:(start, slide.current=1)
115
117
  ```
116
118
 
117
119
  `when`: `start`/`end`/`50%` (percent) / `2.5s` (seconds value). `state`: any JS expression.
118
120
 
121
+ Use the `event` type token to create an **event-only stub** — a node that renders nothing and exists solely to fire an event at a specific time. This avoids boilerplate like `- component duration:3 jsx:"<></>" start:6 on:(...)`.
122
+
119
123
  #### Lists — `spots:[n,n,n]`, `start:n`
120
124
 
121
125
  ```
@@ -160,8 +164,7 @@ When a property value is too long for a single line (JSX, prompts, scripts, mark
160
164
  | `~~~jsx jsx` or `~~~jsx` | `jsx` | Component JSX expression |
161
165
  | `~~~prompt prompt` | `prompt` | TTI/TTV generation prompt |
162
166
  | `~~~script script` or `~~~script` | `script` | Narration text on audio nodes |
163
- | `~~~css stylesheet` | `stylesheet` | Global CSS (only valid at root level) |
164
- | `~~~md <key>` | arbitrary | Markdown content for a specific key (e.g., `source` for `react-markdown`) |
167
+ | `~~~css stylesheet` | `stylesheet` | Global CSS (only valid at root level) || `~~~mermaid` | `mermaid` | Mermaid diagram source (referenced as `{mermaid}` in JSX) || `~~~md <key>` | arbitrary | Markdown content for a specific key (e.g., `source` for `react-markdown`) |
165
168
 
166
169
  ### 7. Scene Metadata Block
167
170
 
@@ -331,6 +334,9 @@ Subtitles are configured at the root level as a VTT overlay. Set via `subtitle:`
331
334
  | `zoom` | int (default 10) | map |
332
335
  | `center` | `{lat:n,lng:n}` JSON | map |
333
336
  | `mapType` | `roadmap\|satellite\|hybrid\|terrain` | map |
337
+ | `language` | map label/UI language. Supports `en`, `zh`, `zh-CN`, etc. | map |
338
+ | `lang` | alias of `language` (e.g. `lang:zh`) | map |
339
+ | `region` | region code hint (e.g. `CN`, `US`) | map |
334
340
  | `routeMarker` | emoji string e.g. `"🚗"` | map |
335
341
  | `title` | display title | scene |
336
342
  | `instruction` | visual intent / style / any prompt; NOT rendered | any |
@@ -433,6 +439,15 @@ When: animated route. Required: `duration`, `waypoints`.
433
439
 
434
440
  `- map duration:3 travelMode:DRIVING waypoints:[37.77,-122.41,"SF";34.05,-118.24,"LA"]`
435
441
 
442
+ Language and region are optional:
443
+
444
+ `- map duration:3 language:zh region:CN waypoints:[31.23,121.47,"上海";39.90,116.40,"北京"]`
445
+
446
+ Shorthand aliases:
447
+
448
+ - `language:zh` or `lang:zh` → normalized to `zh-CN` with `region:CN` default
449
+ - `language:en` or `lang:en` → keeps English labels with `region:US` default
450
+
436
451
  ### `include`
437
452
 
438
453
  When: embed an external markdown file as a sub-video. The sub-video is
@@ -0,0 +1,45 @@
1
+ # 🎧 Remotion Sound Effects
2
+
3
+ ## 1. URL Construction
4
+
5
+ All sound effect URLs follow this pattern:
6
+
7
+ `https://remotion.media/<name>.wav`
8
+
9
+ such as `https://remotion.media/sanctuary-guardian-what.wav`
10
+ ---
11
+
12
+ ## 2. Available Names
13
+
14
+ whip
15
+ whoosh
16
+ page-turn
17
+ switch
18
+ mouse-click
19
+ shutter-modern
20
+ shutter-old
21
+ ding
22
+ bruh
23
+ vine-boom
24
+ windows-xp-error
25
+ fah
26
+ spongebob-fail
27
+ omg-hell-nah
28
+ price-is-right-fail
29
+ romance-meme
30
+ bone-crack
31
+ anime-wow
32
+ yippee
33
+ loading-lag
34
+ wilhelm-scream
35
+ mac-quack
36
+ skedaddle
37
+ snapchat-notification
38
+ nelly-ahh
39
+ sanctuary-guardian-what
40
+ minecraft-hurt
41
+ oh-my-god-vine
42
+ illuminati-confirmed
43
+ dramatic-boomer
44
+ triggered
45
+ record-scratch
@@ -11,39 +11,153 @@ import { Mermaid } from "./Mermaid";
11
11
  *
12
12
  * The `source` prop is the markdown string to render.
13
13
  *
14
+ * `highlight` is an array of 0-based list item indices to highlight.
15
+ * Works for both ordered (`ol`) and unordered (`ul`) lists. Matching
16
+ * `<li>` elements get CSS class `highlight-list-item` (intended for use
17
+ * with a `classDef` or a custom stylesheet).
18
+ *
14
19
  * Built-in — no imports or frontmatter registration needed.
15
20
  */
16
21
  export interface MarkdownProps {
17
- children?: string;
22
+ children?: string;
18
23
  source?: string;
19
24
  className?: string;
20
25
  plugins?: any[];
21
26
  components?: any;
27
+ /** 0-based indices of list items to highlight. */
28
+ highlight?: number[];
29
+ }
30
+
31
+ // Context to pass highlightList down to list renderers
32
+ const ListCtx = React.createContext<number[]>([]);
33
+
34
+ function OrderedList({
35
+ children,
36
+ ...props
37
+ }: React.ComponentPropsWithoutRef<"ol">) {
38
+ const hl = React.useContext(ListCtx);
39
+ const items = React.Children.toArray(children).filter(
40
+ (c): c is React.ReactElement => React.isValidElement(c) && c.type === "li",
41
+ );
42
+ return (
43
+ <ol {...props}>
44
+ {items.map((child, i) => {
45
+ if (hl.includes(i)) {
46
+ return React.cloneElement(child, { className: "highlight-list-item", key: (child as any).key } as any);
47
+ }
48
+ return child;
49
+ })}
50
+ </ol>
51
+ );
52
+ }
53
+
54
+ function UnorderedList({
55
+ children,
56
+ ...props
57
+ }: React.ComponentPropsWithoutRef<"ul">) {
58
+ const hl = React.useContext(ListCtx);
59
+ const items = React.Children.toArray(children).filter(
60
+ (c): c is React.ReactElement => React.isValidElement(c) && c.type === "li",
61
+ );
62
+ return (
63
+ <ul {...props}>
64
+ {items.map((child, i) => {
65
+ if (hl.includes(i)) {
66
+ return React.cloneElement(child, { className: "highlight-list-item", key: (child as any).key } as any);
67
+ }
68
+ return child;
69
+ })}
70
+ </ul>
71
+ );
22
72
  }
23
73
 
24
- export function Markdown({ children, source=children, className, plugins, components }: MarkdownProps) {
74
+ export function Markdown({
75
+ children,
76
+ source = children,
77
+ className,
78
+ plugins,
79
+ components: propComponents,
80
+ highlight,
81
+ }: MarkdownProps) {
82
+ const hl = React.useMemo(() => highlight ?? [], [highlight]);
83
+
84
+ // Inject default styles once
85
+ React.useEffect(() => {
86
+ const id = "markcut-markdown-defaults";
87
+ if (document.getElementById(id)) return;
88
+ const el = document.createElement("style");
89
+ el.id = id;
90
+ el.textContent = `
91
+ .highlight-list-item {
92
+ background: rgba(255, 215, 0, 0.15);
93
+ border-left: 3px solid #ffd700;
94
+ padding-left: 8px;
95
+ border-radius: 0 4px 4px 0;
96
+ }
97
+ .slide {
98
+ display: flex;
99
+ flex-direction: column;
100
+ align-items: center;
101
+ justify-content: center;
102
+ padding: 24px;
103
+ text-align: center;
104
+ }
105
+ .slide h1 { font-size: 2em; margin: 0.4em 0; font-weight: 700; }
106
+ .slide h2 { font-size: 1.6em; margin: 0.35em 0; font-weight: 600; }
107
+ .slide h3 { font-size: 1.3em; margin: 0.3em 0; font-weight: 600; }
108
+ .slide p { margin: 0.6em 0; line-height: 1.6; }
109
+ .slide ul, .slide ol { margin: 0.5em 0; padding-left: 1.5em; text-align: left; }
110
+ .slide li { margin: 0.3em 0; }
111
+ .slide blockquote {
112
+ margin: 0.6em 0;
113
+ padding: 0.4em 1em;
114
+ border-left: 3px solid rgba(255,255,255,.3);
115
+ font-style: italic;
116
+ opacity: .85;
117
+ }
118
+ .slide code {
119
+ background: rgba(255,255,255,.08);
120
+ padding: 0.15em 0.4em;
121
+ border-radius: 4px;
122
+ font-size: 0.9em;
123
+ }
124
+ .slide pre { margin: 0.6em 0; text-align: left; width: 100%; }
125
+ .slide a { color: #4a9eff; text-decoration: none; }
126
+ .slide a:hover { text-decoration: underline; }
127
+ `;
128
+ document.head.appendChild(el);
129
+ return () => { document.getElementById(id)?.remove(); };
130
+ }, []);
131
+
132
+ const mergedComponents = React.useMemo(
133
+ () => ({
134
+ ...propComponents,
135
+ ol: OrderedList,
136
+ ul: UnorderedList,
137
+ pre: ({ children: preChildren }: { children: React.ReactNode }) => {
138
+ const code = React.Children.toArray(preChildren)[0] as React.ReactElement<any>;
139
+ if (code?.props?.className === "language-mermaid") {
140
+ return <Mermaid source={String(code.props.children)} />;
141
+ }
142
+ if (propComponents?.pre) {
143
+ return (propComponents.pre as any)({ children: preChildren });
144
+ }
145
+ return <pre>{preChildren}</pre>;
146
+ },
147
+ }),
148
+ [propComponents],
149
+ );
150
+
25
151
  return (
26
- <div className={className}>
27
- <ReactMarkdown
28
- remarkPlugins={React.useMemo(() => [remarkGfm, ...(plugins || [])], [plugins])}
29
- components={React.useMemo(
30
- () => ({
31
- ...components,
32
- pre: ({ children }) => {
33
- const code = React.Children.toArray(children)[0] as React.ReactElement<any>;
34
- if (code?.props?.className === 'language-mermaid') {
35
- return <Mermaid source={String(code.props.children)} />
36
- } else if (components.pre) {
37
- return components.pre({ children });
38
- }
39
- return <pre>{children}</pre>;
40
- },
41
- }),
42
- [components]
43
- )}
44
- >
45
- {source}
46
- </ReactMarkdown>
47
- </div>
152
+ <ListCtx.Provider value={hl}>
153
+ <div className={className}>
154
+ <ReactMarkdown
155
+ remarkPlugins={React.useMemo(() => [remarkGfm, ...(plugins || [])], [plugins])}
156
+ components={mergedComponents}
157
+ >
158
+ {source}
159
+ </ReactMarkdown>
160
+ </div>
161
+ </ListCtx.Provider>
48
162
  );
49
163
  }