egaki 0.6.0 → 0.7.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.
- package/CHANGELOG.md +140 -9
- package/dist/cli/cache-utils.d.ts +33 -0
- package/dist/cli/cache-utils.d.ts.map +1 -0
- package/dist/cli/cache-utils.js +157 -0
- package/dist/cli/cached-generate.d.ts +105 -0
- package/dist/cli/cached-generate.d.ts.map +1 -0
- package/dist/cli/cached-generate.js +208 -0
- package/dist/cli/cached-generate.test.d.ts +11 -0
- package/dist/cli/cached-generate.test.d.ts.map +1 -0
- package/dist/cli/cached-generate.test.js +431 -0
- package/dist/cli/cartesia-provider.d.ts +16 -0
- package/dist/cli/cartesia-provider.d.ts.map +1 -0
- package/dist/cli/cartesia-provider.js +184 -0
- package/dist/cli/chatgpt-auth.d.ts.map +1 -0
- package/dist/cli/cli.d.ts +4 -0
- package/dist/cli/cli.d.ts.map +1 -0
- package/dist/{cli.js → cli/cli.js} +626 -600
- package/dist/cli/clone-generate.d.ts +26 -0
- package/dist/cli/clone-generate.d.ts.map +1 -0
- package/dist/cli/clone-generate.js +148 -0
- package/dist/cli/credentials.d.ts.map +1 -0
- package/dist/{credentials.js → cli/credentials.js} +20 -0
- package/dist/cli/elevenlabs-speech-provider.d.ts +26 -0
- package/dist/cli/elevenlabs-speech-provider.d.ts.map +1 -0
- package/dist/cli/elevenlabs-speech-provider.js +199 -0
- package/dist/cli/generate.d.ts +140 -0
- package/dist/cli/generate.d.ts.map +1 -0
- package/dist/cli/generate.js +604 -0
- package/dist/cli/generate.test.d.ts +2 -0
- package/dist/cli/generate.test.d.ts.map +1 -0
- package/dist/cli/generate.test.js +131 -0
- package/dist/cli/login.d.ts.map +1 -0
- package/dist/cli/main.d.ts +3 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +7 -0
- package/dist/{model-catalog.d.ts → cli/model-catalog.d.ts} +6 -0
- package/dist/cli/model-catalog.d.ts.map +1 -0
- package/dist/{model-catalog.js → cli/model-catalog.js} +714 -4
- package/dist/{models.d.ts → cli/models.d.ts} +12 -5
- package/dist/cli/models.d.ts.map +1 -0
- package/dist/{models.js → cli/models.js} +58 -57
- package/dist/cli/open-browser.d.ts.map +1 -0
- package/dist/cli/openai-speech-provider.d.ts +3 -0
- package/dist/cli/openai-speech-provider.d.ts.map +1 -0
- package/dist/cli/openai-speech-provider.js +28 -0
- package/dist/cli/separation-generate.d.ts +34 -0
- package/dist/cli/separation-generate.d.ts.map +1 -0
- package/dist/cli/separation-generate.js +117 -0
- package/dist/cli/speech-catalog.d.ts +35 -0
- package/dist/cli/speech-catalog.d.ts.map +1 -0
- package/dist/cli/speech-catalog.js +195 -0
- package/dist/cli/speech-generate.d.ts +86 -0
- package/dist/cli/speech-generate.d.ts.map +1 -0
- package/dist/cli/speech-generate.js +156 -0
- package/dist/cli/speech-models.d.ts +8 -0
- package/dist/cli/speech-models.d.ts.map +1 -0
- package/dist/cli/speech-models.js +109 -0
- package/dist/cli/speech-provider.test.d.ts +2 -0
- package/dist/cli/speech-provider.test.d.ts.map +1 -0
- package/dist/cli/speech-provider.test.js +197 -0
- package/dist/cli/subscription.d.ts.map +1 -0
- package/dist/cli/transcription-catalog.d.ts +35 -0
- package/dist/cli/transcription-catalog.d.ts.map +1 -0
- package/dist/cli/transcription-catalog.js +192 -0
- package/dist/cli/transcription-generate.d.ts +62 -0
- package/dist/cli/transcription-generate.d.ts.map +1 -0
- package/dist/cli/transcription-generate.js +144 -0
- package/dist/cli/transcription-models.d.ts +9 -0
- package/dist/cli/transcription-models.d.ts.map +1 -0
- package/dist/cli/transcription-models.js +100 -0
- package/dist/cli/transcription-providers.d.ts +22 -0
- package/dist/cli/transcription-providers.d.ts.map +1 -0
- package/dist/cli/transcription-providers.js +290 -0
- package/dist/cli/video-model-catalog.d.ts.map +1 -0
- package/dist/cli/xai-auth.d.ts.map +1 -0
- package/dist/{xai-auth.js → cli/xai-auth.js} +1 -1
- package/dist/vite/angled-screen.d.ts +57 -0
- package/dist/vite/angled-screen.d.ts.map +1 -0
- package/dist/vite/angled-screen.js +115 -0
- package/dist/vite/app.d.ts +51 -0
- package/dist/vite/app.d.ts.map +1 -0
- package/dist/vite/app.js +150 -0
- package/dist/vite/bands-shader.d.ts +47 -0
- package/dist/vite/bands-shader.d.ts.map +1 -0
- package/dist/vite/bands-shader.js +267 -0
- package/dist/vite/code-block.d.ts +107 -0
- package/dist/vite/code-block.d.ts.map +1 -0
- package/dist/vite/code-block.js +641 -0
- package/dist/vite/components.d.ts +36 -0
- package/dist/vite/components.d.ts.map +1 -0
- package/dist/vite/components.js +113 -0
- package/dist/vite/dispersion-rings-shader.d.ts +44 -0
- package/dist/vite/dispersion-rings-shader.d.ts.map +1 -0
- package/dist/vite/dispersion-rings-shader.js +282 -0
- package/dist/vite/easing-curves.d.ts +129 -0
- package/dist/vite/easing-curves.d.ts.map +1 -0
- package/dist/vite/easing-curves.js +584 -0
- package/dist/vite/easing-curves.test.d.ts +2 -0
- package/dist/vite/easing-curves.test.d.ts.map +1 -0
- package/dist/vite/easing-curves.test.js +200 -0
- package/dist/vite/generated-media-client.d.ts +35 -0
- package/dist/vite/generated-media-client.d.ts.map +1 -0
- package/dist/vite/generated-media-client.js +76 -0
- package/dist/vite/keyframes.d.ts +129 -0
- package/dist/vite/keyframes.d.ts.map +1 -0
- package/dist/vite/keyframes.js +194 -0
- package/dist/vite/layout-editor.d.ts +45 -0
- package/dist/vite/layout-editor.d.ts.map +1 -0
- package/dist/vite/layout-editor.js +587 -0
- package/dist/vite/layout-transition.d.ts +136 -0
- package/dist/vite/layout-transition.d.ts.map +1 -0
- package/dist/vite/layout-transition.js +581 -0
- package/dist/vite/liquid-gradient-shader.d.ts +62 -0
- package/dist/vite/liquid-gradient-shader.d.ts.map +1 -0
- package/dist/vite/liquid-gradient-shader.js +413 -0
- package/dist/vite/mdx-client.d.ts +15 -0
- package/dist/vite/mdx-client.d.ts.map +1 -0
- package/dist/vite/mdx-client.js +265 -0
- package/dist/vite/mdx-parse.d.ts +102 -0
- package/dist/vite/mdx-parse.d.ts.map +1 -0
- package/dist/vite/mdx-parse.js +251 -0
- package/dist/vite/mdx-provided-components.d.ts +18 -0
- package/dist/vite/mdx-provided-components.d.ts.map +1 -0
- package/dist/vite/mdx-provided-components.js +14 -0
- package/dist/vite/mdx-video.d.ts +327 -0
- package/dist/vite/mdx-video.d.ts.map +1 -0
- package/dist/vite/mdx-video.js +596 -0
- package/dist/vite/mdx-video.test.d.ts +9 -0
- package/dist/vite/mdx-video.test.d.ts.map +1 -0
- package/dist/vite/mdx-video.test.js +1945 -0
- package/dist/vite/media-components.d.ts +45 -0
- package/dist/vite/media-components.d.ts.map +1 -0
- package/dist/vite/media-components.js +355 -0
- package/dist/vite/media-duration-store.d.ts +66 -0
- package/dist/vite/media-duration-store.d.ts.map +1 -0
- package/dist/vite/media-duration-store.js +204 -0
- package/dist/vite/player-page.d.ts +35 -0
- package/dist/vite/player-page.d.ts.map +1 -0
- package/dist/vite/player-page.js +720 -0
- package/dist/vite/render-client.d.ts +18 -0
- package/dist/vite/render-client.d.ts.map +1 -0
- package/dist/vite/render-client.js +67 -0
- package/dist/vite/sdk.d.ts +221 -0
- package/dist/vite/sdk.d.ts.map +1 -0
- package/dist/vite/sdk.js +355 -0
- package/dist/vite/server-components.d.ts +55 -0
- package/dist/vite/server-components.d.ts.map +1 -0
- package/dist/vite/server-components.js +156 -0
- package/dist/vite/server-mdx.d.ts +74 -0
- package/dist/vite/server-mdx.d.ts.map +1 -0
- package/dist/vite/server-mdx.js +259 -0
- package/dist/vite/shader-renderer.d.ts +48 -0
- package/dist/vite/shader-renderer.d.ts.map +1 -0
- package/dist/vite/shader-renderer.js +411 -0
- package/dist/vite/store-hooks.d.ts +12 -0
- package/dist/vite/store-hooks.d.ts.map +1 -0
- package/dist/vite/store-hooks.js +93 -0
- package/dist/vite/store.d.ts +75 -0
- package/dist/vite/store.d.ts.map +1 -0
- package/dist/vite/store.js +52 -0
- package/dist/vite/store.test.d.ts +6 -0
- package/dist/vite/store.test.d.ts.map +1 -0
- package/dist/vite/store.test.js +172 -0
- package/dist/vite/tweakpane-hook.d.ts +94 -0
- package/dist/vite/tweakpane-hook.d.ts.map +1 -0
- package/dist/vite/tweakpane-hook.js +450 -0
- package/dist/vite/vite-plugin.d.ts +28 -0
- package/dist/vite/vite-plugin.d.ts.map +1 -0
- package/dist/vite/vite-plugin.js +538 -0
- package/dist/vite/wave-gradient-shader.d.ts +41 -0
- package/dist/vite/wave-gradient-shader.d.ts.map +1 -0
- package/dist/vite/wave-gradient-shader.js +207 -0
- package/package.json +101 -24
- package/skills/egaki/SKILL.md +162 -1
- package/src/cli/cache-utils.ts +169 -0
- package/src/cli/cached-generate.test.ts +531 -0
- package/src/cli/cached-generate.ts +348 -0
- package/src/cli/cartesia-provider.ts +249 -0
- package/src/{cli.ts → cli/cli.ts} +836 -884
- package/src/cli/clone-generate.ts +202 -0
- package/src/{credentials.ts → cli/credentials.ts} +20 -0
- package/src/cli/elevenlabs-speech-provider.ts +243 -0
- package/src/cli/generate.test.ts +166 -0
- package/src/cli/generate.ts +912 -0
- package/src/cli/main.ts +8 -0
- package/src/{model-catalog.ts → cli/model-catalog.ts} +787 -58
- package/src/{models.ts → cli/models.ts} +65 -86
- package/src/cli/openai-speech-provider.ts +34 -0
- package/src/cli/separation-generate.ts +187 -0
- package/src/cli/speech-catalog.ts +253 -0
- package/src/cli/speech-generate.ts +275 -0
- package/src/cli/speech-models.ts +146 -0
- package/src/cli/speech-provider.test.ts +226 -0
- package/src/cli/transcription-catalog.ts +250 -0
- package/src/cli/transcription-generate.ts +214 -0
- package/src/cli/transcription-models.ts +136 -0
- package/src/cli/transcription-providers.ts +427 -0
- package/src/{xai-auth.ts → cli/xai-auth.ts} +1 -1
- package/src/vite/angled-screen.tsx +214 -0
- package/src/vite/app.tsx +197 -0
- package/src/vite/bands-shader.tsx +319 -0
- package/src/vite/code-block.tsx +939 -0
- package/src/vite/components.tsx +278 -0
- package/src/vite/dispersion-rings-shader.tsx +331 -0
- package/src/vite/easing-curves.test.ts +232 -0
- package/src/vite/easing-curves.ts +743 -0
- package/src/vite/generated-media-client.tsx +121 -0
- package/src/vite/keyframes.tsx +347 -0
- package/src/vite/layout-editor.tsx +709 -0
- package/src/vite/layout-transition.tsx +706 -0
- package/src/vite/liquid-gradient-shader.tsx +474 -0
- package/src/vite/mdx-client.tsx +420 -0
- package/src/vite/mdx-parse.ts +343 -0
- package/src/vite/mdx-provided-components.ts +19 -0
- package/src/vite/mdx-video.test.tsx +2219 -0
- package/src/vite/mdx-video.tsx +976 -0
- package/src/vite/media-components.tsx +473 -0
- package/src/vite/media-duration-store.ts +225 -0
- package/src/vite/motion-timing.ts +194 -0
- package/src/vite/player-page.tsx +1107 -0
- package/src/vite/render-client.ts +84 -0
- package/src/vite/sdk.ts +598 -0
- package/src/vite/server-components.tsx +241 -0
- package/src/vite/server-mdx.ts +285 -0
- package/src/vite/shader-renderer.tsx +535 -0
- package/src/vite/store-hooks.ts +117 -0
- package/src/vite/store.test.ts +202 -0
- package/src/vite/store.ts +115 -0
- package/src/vite/styles.css +21 -0
- package/src/vite/tweakpane-hook.tsx +593 -0
- package/src/vite/virtual-modules.d.ts +30 -0
- package/src/vite/vite-plugin.ts +584 -0
- package/src/vite/wave-gradient-shader.tsx +253 -0
- package/README.md +0 -370
- package/dist/antigravity-auth.d.ts +0 -15
- package/dist/antigravity-auth.d.ts.map +0 -1
- package/dist/antigravity-auth.js +0 -544
- package/dist/antigravity-image.d.ts +0 -11
- package/dist/antigravity-image.d.ts.map +0 -1
- package/dist/antigravity-image.js +0 -285
- package/dist/chatgpt-auth.d.ts.map +0 -1
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/credentials.d.ts.map +0 -1
- package/dist/gateway-model-candidates.generated.d.ts +0 -11
- package/dist/gateway-model-candidates.generated.d.ts.map +0 -1
- package/dist/gateway-model-candidates.generated.js +0 -64
- package/dist/login.d.ts.map +0 -1
- package/dist/model-catalog.d.ts.map +0 -1
- package/dist/models.d.ts.map +0 -1
- package/dist/open-browser.d.ts.map +0 -1
- package/dist/subscription.d.ts.map +0 -1
- package/dist/video-model-catalog.d.ts.map +0 -1
- package/dist/xai-auth.d.ts.map +0 -1
- /package/dist/{chatgpt-auth.d.ts → cli/chatgpt-auth.d.ts} +0 -0
- /package/dist/{chatgpt-auth.js → cli/chatgpt-auth.js} +0 -0
- /package/dist/{credentials.d.ts → cli/credentials.d.ts} +0 -0
- /package/dist/{login.d.ts → cli/login.d.ts} +0 -0
- /package/dist/{login.js → cli/login.js} +0 -0
- /package/dist/{open-browser.d.ts → cli/open-browser.d.ts} +0 -0
- /package/dist/{open-browser.js → cli/open-browser.js} +0 -0
- /package/dist/{subscription.d.ts → cli/subscription.d.ts} +0 -0
- /package/dist/{subscription.js → cli/subscription.js} +0 -0
- /package/dist/{video-model-catalog.d.ts → cli/video-model-catalog.d.ts} +0 -0
- /package/dist/{video-model-catalog.js → cli/video-model-catalog.js} +0 -0
- /package/dist/{xai-auth.d.ts → cli/xai-auth.d.ts} +0 -0
- /package/src/{chatgpt-auth.ts → cli/chatgpt-auth.ts} +0 -0
- /package/src/{login.ts → cli/login.ts} +0 -0
- /package/src/{open-browser.ts → cli/open-browser.ts} +0 -0
- /package/src/{subscription.ts → cli/subscription.ts} +0 -0
- /package/src/{video-model-catalog.ts → cli/video-model-catalog.ts} +0 -0
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Client-side MDX application.
|
|
5
|
+
*
|
|
6
|
+
* All MDX processing happens in the browser: parsing, section splitting,
|
|
7
|
+
* user module resolution, and safe-mdx rendering. Because rendering runs on
|
|
8
|
+
* the client there is no RSC serialization boundary between MDX content and
|
|
9
|
+
* the components — expression props can be functions (easing={x => x}),
|
|
10
|
+
* imported values can be anything, and user components don't need a
|
|
11
|
+
* 'use client' directive.
|
|
12
|
+
*
|
|
13
|
+
* The server (app.tsx) only passes the raw MDX source string through the
|
|
14
|
+
* RSC flight payload. Entry MDX edits flow server → client via rsc:update;
|
|
15
|
+
* user .tsx/.ts/.mdx edits flow through the client module graph: this
|
|
16
|
+
* module accepts HMR updates of virtual:egaki-modules directly via
|
|
17
|
+
* import.meta.hot.accept(dep, cb) and pushes the fresh map into React
|
|
18
|
+
* through useSyncExternalStore.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { createContext, useContext, useEffect, useMemo } from 'react'
|
|
22
|
+
import type { ReactNode } from 'react'
|
|
23
|
+
import { SafeMdxRenderer } from 'safe-mdx'
|
|
24
|
+
import { mdxParse, extractImports, resolveModulePath } from 'safe-mdx/parse'
|
|
25
|
+
import type { EagerModules } from 'safe-mdx/parse'
|
|
26
|
+
import { eagerModules as initialModules } from 'virtual:egaki-modules'
|
|
27
|
+
import { splitIntoSections, calculateTotalDuration, resolveAutoDurations, parseFrontmatter, buildMdxScope } from './mdx-parse.ts'
|
|
28
|
+
import { filterImportNodesToModules } from './server-mdx.ts'
|
|
29
|
+
import { PlayerPage } from './player-page.tsx'
|
|
30
|
+
import { MDX_BUILTIN_COMPONENTS, Img, ServerSlotsContext, type ServerSlots } from './mdx-video.tsx'
|
|
31
|
+
import { MdxCodeBlockWrapper } from './code-block.tsx'
|
|
32
|
+
import { egakiStore } from './store.ts'
|
|
33
|
+
import { useModules, useMediaDurations } from './store-hooks.ts'
|
|
34
|
+
import { resetSectionDurations } from './media-duration-store.ts'
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// MDX components map
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
const FONT_SANS =
|
|
41
|
+
'-apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", sans-serif'
|
|
42
|
+
const FONT_MONO =
|
|
43
|
+
'"SF Mono", ui-monospace, SFMono-Regular, "Cascadia Code", monospace'
|
|
44
|
+
|
|
45
|
+
function buildVideoMdxComponents(): Record<string, any> {
|
|
46
|
+
return {
|
|
47
|
+
...MDX_BUILTIN_COMPONENTS,
|
|
48
|
+
|
|
49
|
+
// Reserved: server component slot marker. The server renders the
|
|
50
|
+
// original children; this client component splices the slot in by
|
|
51
|
+
// matching its data-markdown-line against the slot keys.
|
|
52
|
+
Server,
|
|
53
|
+
|
|
54
|
+
// Standard element overrides
|
|
55
|
+
p: ({ children }: { children: ReactNode }) => (
|
|
56
|
+
<div style={{
|
|
57
|
+
fontSize: 'clamp(1.5rem, 2.5vw, 3rem)', fontWeight: 400,
|
|
58
|
+
color: '#a1a1aa', fontFamily: FONT_SANS, textAlign: 'center',
|
|
59
|
+
letterSpacing: '-0.02em', lineHeight: 1.4, maxWidth: '80%',
|
|
60
|
+
}}>{children}</div>
|
|
61
|
+
),
|
|
62
|
+
strong: ({ children }: { children: ReactNode }) => (
|
|
63
|
+
<span style={{ color: '#fafafa', fontWeight: 600 }}>{children}</span>
|
|
64
|
+
),
|
|
65
|
+
em: ({ children }: { children: ReactNode }) => (
|
|
66
|
+
<span style={{ fontStyle: 'italic' }}>{children}</span>
|
|
67
|
+
),
|
|
68
|
+
a: ({ children }: { children: ReactNode; href?: string }) => (
|
|
69
|
+
<span style={{ color: '#818cf8', textDecoration: 'underline' }}>{children}</span>
|
|
70
|
+
),
|
|
71
|
+
h1: () => null, h2: () => null, h3: () => null,
|
|
72
|
+
h4: () => null, h5: () => null, h6: () => null,
|
|
73
|
+
blockquote: () => null,
|
|
74
|
+
pre: MdxCodeBlockWrapper,
|
|
75
|
+
inlineCode: ({ children }: { children: ReactNode }) => (
|
|
76
|
+
<span style={{
|
|
77
|
+
fontFamily: FONT_MONO, fontSize: '0.875em', color: '#e4e4e7',
|
|
78
|
+
background: 'rgba(255, 255, 255, 0.06)', borderRadius: '0.25em',
|
|
79
|
+
padding: '0.1em 0.4em',
|
|
80
|
+
}}>{children}</span>
|
|
81
|
+
),
|
|
82
|
+
ul: ({ children }: { children: ReactNode }) => (
|
|
83
|
+
<div style={{
|
|
84
|
+
fontSize: 'clamp(1.25rem, 2vw, 2rem)', color: '#a1a1aa',
|
|
85
|
+
fontFamily: FONT_SANS, textAlign: 'left', display: 'flex',
|
|
86
|
+
flexDirection: 'column', gap: '0.4em',
|
|
87
|
+
}}>{children}</div>
|
|
88
|
+
),
|
|
89
|
+
ol: ({ children }: { children: ReactNode }) => (
|
|
90
|
+
<div style={{
|
|
91
|
+
fontSize: 'clamp(1.25rem, 2vw, 2rem)', color: '#a1a1aa',
|
|
92
|
+
fontFamily: FONT_SANS, textAlign: 'left', display: 'flex',
|
|
93
|
+
flexDirection: 'column', gap: '0.4em',
|
|
94
|
+
}}>{children}</div>
|
|
95
|
+
),
|
|
96
|
+
li: ({ children }: { children: ReactNode }) => (
|
|
97
|
+
<div style={{ display: 'flex', gap: '0.5em' }}>
|
|
98
|
+
<span style={{ color: '#52525b' }}>•</span>
|
|
99
|
+
<span>{children}</span>
|
|
100
|
+
</div>
|
|
101
|
+
),
|
|
102
|
+
img: ({ src, alt }: { src?: string; alt?: string }) => (
|
|
103
|
+
<Img src={src} alt={alt || ''} style={{
|
|
104
|
+
maxWidth: '80%', maxHeight: '70%', objectFit: 'contain', borderRadius: '0.5em',
|
|
105
|
+
}} />
|
|
106
|
+
),
|
|
107
|
+
hr: () => (
|
|
108
|
+
<div style={{ width: '40%', height: 1, background: 'rgba(255, 255, 255, 0.1)' }} />
|
|
109
|
+
),
|
|
110
|
+
table: ({ children }: { children: ReactNode }) => (
|
|
111
|
+
<div style={{ fontSize: 'clamp(0.875rem, 1.2vw, 1.125rem)', fontFamily: FONT_SANS, color: '#a1a1aa' }}>
|
|
112
|
+
{children}
|
|
113
|
+
</div>
|
|
114
|
+
),
|
|
115
|
+
thead: ({ children }: { children: ReactNode }) => <div>{children}</div>,
|
|
116
|
+
tbody: ({ children }: { children: ReactNode }) => <div>{children}</div>,
|
|
117
|
+
tr: ({ children }: { children: ReactNode }) => (
|
|
118
|
+
<div style={{ display: 'flex', gap: '1em', borderBottom: '1px solid rgba(255,255,255,0.06)', padding: '0.5em 0' }}>
|
|
119
|
+
{children}
|
|
120
|
+
</div>
|
|
121
|
+
),
|
|
122
|
+
td: ({ children }: { children: ReactNode }) => <div style={{ flex: 1 }}>{children}</div>,
|
|
123
|
+
th: ({ children }: { children: ReactNode }) => (
|
|
124
|
+
<div style={{ flex: 1, fontWeight: 600, color: '#e4e4e7' }}>{children}</div>
|
|
125
|
+
),
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Built once — the map is static.
|
|
130
|
+
const mdxComponents = buildVideoMdxComponents()
|
|
131
|
+
|
|
132
|
+
// Enable function expressions in MDX attribute props. safe-mdx evaluates
|
|
133
|
+
// them with a safe AST interpreter (no eval), so `easing={x => x}` works.
|
|
134
|
+
const evaluateOptions = { functions: true }
|
|
135
|
+
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// User modules — initial value from static import, HMR updates via store
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
egakiStore.setState({ modules: initialModules as EagerModules })
|
|
141
|
+
|
|
142
|
+
if (import.meta.hot) {
|
|
143
|
+
import.meta.hot.accept('virtual:egaki-modules', (next) => {
|
|
144
|
+
if (next?.eagerModules) {
|
|
145
|
+
egakiStore.setState({ modules: next.eagerModules as EagerModules })
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Composition building (parse → sections → JSX)
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
// Slots travel via React context (provided by MdxClientApp around
|
|
155
|
+
// PlayerPage) rather than a safe-mdx renderNode hook: safe-mdx only calls
|
|
156
|
+
// renderNode in its top-level mdast traversal, while JSX elements nested
|
|
157
|
+
// inside other JSX elements go through jsxTransformer which resolves the
|
|
158
|
+
// components map directly. A real `Server` component in the map works at
|
|
159
|
+
// any nesting depth; it matches its slot via the data-markdown-line prop
|
|
160
|
+
// that safe-mdx injects (line numbers are identical on server and client
|
|
161
|
+
// because blankServerContents preserves line positions).
|
|
162
|
+
|
|
163
|
+
function Server(props: { 'data-markdown-line'?: number }) {
|
|
164
|
+
const slots = useContext(ServerSlotsContext)
|
|
165
|
+
const key = String(props['data-markdown-line'])
|
|
166
|
+
if (key in slots) return slots[key]
|
|
167
|
+
// No slot: <Server> inside an imported .mdx file (not scanned, v1
|
|
168
|
+
// limitation) or a stale flight payload.
|
|
169
|
+
console.warn(`[egaki] <Server> at line ${key} has no server-rendered slot; rendering nothing`)
|
|
170
|
+
return null
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
// Parse error recovery
|
|
175
|
+
//
|
|
176
|
+
// On MDX syntax errors, buildComposition returns the last successful result
|
|
177
|
+
// instead of crashing React. An error overlay section replaces the content
|
|
178
|
+
// so the user sees what went wrong. When the syntax is fixed, the next HMR
|
|
179
|
+
// cycle parses successfully and the real content reappears seamlessly.
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
type CompositionResult = {
|
|
183
|
+
sections: { heading: string | null; durationInFrames: number | null; jsx: ReactNode }[]
|
|
184
|
+
preamble: ReactNode | undefined
|
|
185
|
+
frontmatter: ReturnType<typeof parseFrontmatter>
|
|
186
|
+
parseError?: string
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let lastGoodComposition: CompositionResult | null = null
|
|
190
|
+
|
|
191
|
+
function makeErrorComposition(error: Error): CompositionResult {
|
|
192
|
+
// If we have a cached good result, reuse its frontmatter and show the
|
|
193
|
+
// error as a single-section overlay so the player stays alive.
|
|
194
|
+
const frontmatter = lastGoodComposition?.frontmatter ?? { fps: 30, bpm: 120, width: 1920, height: 1080, scale: 1 }
|
|
195
|
+
return {
|
|
196
|
+
sections: [{
|
|
197
|
+
heading: 'Parse Error',
|
|
198
|
+
durationInFrames: frontmatter.fps * 5,
|
|
199
|
+
jsx: (
|
|
200
|
+
<div style={{
|
|
201
|
+
display: 'flex', flexDirection: 'column', alignItems: 'center',
|
|
202
|
+
justifyContent: 'center', width: '100%', height: '100%',
|
|
203
|
+
padding: '2rem', fontFamily: 'ui-monospace, SFMono-Regular, "Cascadia Code", monospace',
|
|
204
|
+
}}>
|
|
205
|
+
<div style={{ color: '#ef4444', fontSize: 'clamp(1rem, 2vw, 1.5rem)', fontWeight: 600, marginBottom: '1rem' }}>
|
|
206
|
+
MDX Parse Error
|
|
207
|
+
</div>
|
|
208
|
+
<div style={{
|
|
209
|
+
color: '#fca5a5', fontSize: 'clamp(0.75rem, 1.5vw, 1rem)',
|
|
210
|
+
background: 'rgba(239, 68, 68, 0.1)', borderRadius: '0.5rem',
|
|
211
|
+
padding: '1rem 1.5rem', maxWidth: '80%', whiteSpace: 'pre-wrap',
|
|
212
|
+
lineHeight: 1.6, border: '1px solid rgba(239, 68, 68, 0.2)',
|
|
213
|
+
}}>
|
|
214
|
+
{error.message}
|
|
215
|
+
</div>
|
|
216
|
+
<div style={{ color: '#71717a', fontSize: 'clamp(0.625rem, 1vw, 0.875rem)', marginTop: '1rem' }}>
|
|
217
|
+
Fix the syntax error and save. The video will recover automatically.
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
),
|
|
221
|
+
}],
|
|
222
|
+
preamble: lastGoodComposition?.preamble,
|
|
223
|
+
frontmatter,
|
|
224
|
+
parseError: error.message,
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function buildComposition(mdxSource: string, modules: EagerModules): CompositionResult {
|
|
229
|
+
let ast: ReturnType<typeof mdxParse>
|
|
230
|
+
try {
|
|
231
|
+
ast = mdxParse(mdxSource)
|
|
232
|
+
} catch (e) {
|
|
233
|
+
console.warn('[egaki] MDX parse error:', (e as Error).message)
|
|
234
|
+
return makeErrorComposition(e as Error)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Compute FPS and BEAT from frontmatter early so they're available as
|
|
238
|
+
// scope variables for all SafeMdxRenderer calls (including imported MDX).
|
|
239
|
+
const { fps, bpm } = parseFrontmatter(ast)
|
|
240
|
+
const mdxScope = buildMdxScope(fps, bpm)
|
|
241
|
+
|
|
242
|
+
// Render imported .mdx/.md files into React components so safe-mdx can
|
|
243
|
+
// resolve `import Intro from './intro.mdx'` and render `<Intro />` via
|
|
244
|
+
// React composition. Each imported MDX gets its own SafeMdxRenderer pass
|
|
245
|
+
// with the same components map.
|
|
246
|
+
const moduleKeys = Object.keys(modules)
|
|
247
|
+
const mergedModules: EagerModules = { ...modules }
|
|
248
|
+
const imports = extractImports(ast)
|
|
249
|
+
for (const imp of imports) {
|
|
250
|
+
if (!/\.mdx?$/.test(imp.source)) continue
|
|
251
|
+
const key = resolveModulePath(imp.source, './', moduleKeys)
|
|
252
|
+
if (!key || !mergedModules[key]) continue
|
|
253
|
+
const rawContent = mergedModules[key].default
|
|
254
|
+
if (typeof rawContent !== 'string') continue
|
|
255
|
+
let importedAst
|
|
256
|
+
try {
|
|
257
|
+
importedAst = mdxParse(rawContent)
|
|
258
|
+
} catch (e) {
|
|
259
|
+
// Imported MDX has a syntax error. Skip it instead of crashing the
|
|
260
|
+
// whole composition; the main file's content still renders.
|
|
261
|
+
console.warn(`[egaki] parse error in imported MDX (${imp.source}):`, (e as Error).message)
|
|
262
|
+
continue
|
|
263
|
+
}
|
|
264
|
+
const renderedJsx = (
|
|
265
|
+
<SafeMdxRenderer
|
|
266
|
+
markdown={rawContent}
|
|
267
|
+
mdast={importedAst}
|
|
268
|
+
components={mdxComponents}
|
|
269
|
+
modules={mergedModules}
|
|
270
|
+
baseUrl="./"
|
|
271
|
+
scope={mdxScope}
|
|
272
|
+
evaluateOptions={evaluateOptions}
|
|
273
|
+
onError={(e) => console.warn('[egaki] imported MDX:', e.message)}
|
|
274
|
+
/>
|
|
275
|
+
)
|
|
276
|
+
// Replace the raw string module with a component that returns the
|
|
277
|
+
// pre-rendered JSX. safe-mdx reads mod.default for default imports.
|
|
278
|
+
mergedModules[key] = { default: () => renderedJsx }
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const result = splitIntoSections(ast)
|
|
282
|
+
|
|
283
|
+
// Extract import nodes (mdxjsEsm) from the full mdast. Section splitting
|
|
284
|
+
// drops them, but SafeMdxRenderer needs them to resolve imported components
|
|
285
|
+
// from the modules map. Prepend to every section's nodes.
|
|
286
|
+
//
|
|
287
|
+
// Imports not resolvable in the client modules map are stripped: those
|
|
288
|
+
// are server-only files (used exclusively inside <Server> blocks, which
|
|
289
|
+
// the server already rendered into slots) excluded from the client map
|
|
290
|
+
// by the vite plugin's inference.
|
|
291
|
+
const importNodes = filterImportNodesToModules(
|
|
292
|
+
ast.children.filter((node: any) => node.type === 'mdxjsEsm'),
|
|
293
|
+
Object.keys(mergedModules),
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
const renderNodes = (nodes: any[]) => (
|
|
297
|
+
<SafeMdxRenderer
|
|
298
|
+
markdown={mdxSource}
|
|
299
|
+
mdast={{ type: 'root', children: [...importNodes, ...nodes] } as any}
|
|
300
|
+
components={mdxComponents}
|
|
301
|
+
modules={mergedModules}
|
|
302
|
+
baseUrl="./"
|
|
303
|
+
scope={mdxScope}
|
|
304
|
+
addMarkdownLineNumbers
|
|
305
|
+
evaluateOptions={evaluateOptions}
|
|
306
|
+
onError={(e) => console.warn('[egaki] MDX:', e.message)}
|
|
307
|
+
/>
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
const sections = result.sections.map((section) => ({
|
|
311
|
+
heading: section.heading,
|
|
312
|
+
durationInFrames: section.durationInFrames,
|
|
313
|
+
jsx: renderNodes(section.nodes),
|
|
314
|
+
}))
|
|
315
|
+
|
|
316
|
+
// Preamble: content before the first heading, rendered at composition
|
|
317
|
+
// level (outside Series) so it spans the full video duration.
|
|
318
|
+
const preamble = result.preamble.length > 0
|
|
319
|
+
? renderNodes(result.preamble)
|
|
320
|
+
: undefined
|
|
321
|
+
|
|
322
|
+
const composition: CompositionResult = { sections, preamble, frontmatter: result.frontmatter }
|
|
323
|
+
|
|
324
|
+
// Cache successful result for parse error recovery
|
|
325
|
+
lastGoodComposition = composition
|
|
326
|
+
|
|
327
|
+
return composition
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ---------------------------------------------------------------------------
|
|
331
|
+
// App component
|
|
332
|
+
// ---------------------------------------------------------------------------
|
|
333
|
+
|
|
334
|
+
const EMPTY_SLOTS: ServerSlots = {}
|
|
335
|
+
|
|
336
|
+
export function MdxClientApp({
|
|
337
|
+
mdx,
|
|
338
|
+
serverSlots = EMPTY_SLOTS,
|
|
339
|
+
entryPath,
|
|
340
|
+
availableEntries = [],
|
|
341
|
+
currentRoute = '',
|
|
342
|
+
}: {
|
|
343
|
+
mdx: string
|
|
344
|
+
/** Server-rendered <Server> subtrees keyed by node start line, produced
|
|
345
|
+
* in app.tsx and delivered via RSC flight. Consumed by the Server
|
|
346
|
+
* component through ServerSlotsContext. */
|
|
347
|
+
serverSlots?: ServerSlots
|
|
348
|
+
/** Absolute path of the MDX entry file, for copy prompts. */
|
|
349
|
+
entryPath: string
|
|
350
|
+
/** All available MDX entry route paths for navigation. */
|
|
351
|
+
availableEntries?: string[]
|
|
352
|
+
/** Current route path ('' for default entry). */
|
|
353
|
+
currentRoute?: string
|
|
354
|
+
}) {
|
|
355
|
+
const modules = useModules()
|
|
356
|
+
|
|
357
|
+
// Subscribe to media duration reports. When Audio/Video components report
|
|
358
|
+
// their durations (via mediabunny metadata fetch), this re-renders and
|
|
359
|
+
// sections without explicit duration= get auto-sized to their media content.
|
|
360
|
+
// The map is keyed by section index (as string) → max media duration in
|
|
361
|
+
// seconds, derived from the src-keyed persistent cache.
|
|
362
|
+
const sectionDurations = useMediaDurations()
|
|
363
|
+
|
|
364
|
+
// Split into two memos to avoid an infinite loop:
|
|
365
|
+
// 1. buildComposition creates JSX (depends on mdx + modules only).
|
|
366
|
+
// If this re-ran on sectionDurations changes, it would create new JSX
|
|
367
|
+
// identity → React unmounts Audio/Video → clearSectionDuration fires →
|
|
368
|
+
// snapshot changes → sectionDurations changes → infinite loop.
|
|
369
|
+
// 2. resolveAutoDurations fills in null durations from the section store.
|
|
370
|
+
// This memo is cheap and can re-run on every sectionDurations change
|
|
371
|
+
// without touching JSX identity.
|
|
372
|
+
const composed = useMemo(() => buildComposition(mdx, modules), [mdx, modules])
|
|
373
|
+
|
|
374
|
+
// Reset section duration reports when the composition changes (HMR,
|
|
375
|
+
// MDX edit, module update). Media components will re-report on mount.
|
|
376
|
+
// Does NOT clear the raw src cache so cached durations resolve instantly.
|
|
377
|
+
useEffect(() => {
|
|
378
|
+
resetSectionDurations()
|
|
379
|
+
}, [composed])
|
|
380
|
+
|
|
381
|
+
const { sections, totalDuration, preamble, hasUnresolvedDurations } = useMemo(() => {
|
|
382
|
+
const { fps, bpm } = composed.frontmatter
|
|
383
|
+
|
|
384
|
+
// MDX files without headings (e.g. imported partials rendered standalone)
|
|
385
|
+
// have 0 sections. Synthesize one from the preamble with null duration
|
|
386
|
+
// BEFORE resolving auto-durations, so media auto-sizing still works.
|
|
387
|
+
let sourceSections = composed.sections
|
|
388
|
+
let finalPreamble = composed.preamble
|
|
389
|
+
if (sourceSections.length === 0 && finalPreamble) {
|
|
390
|
+
sourceSections = [{ heading: null, durationInFrames: null, jsx: finalPreamble }]
|
|
391
|
+
finalPreamble = undefined
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const resolved = resolveAutoDurations(sourceSections, fps, bpm, sectionDurations)
|
|
395
|
+
const hasUnresolved = sourceSections.some((s, i) =>
|
|
396
|
+
s.durationInFrames === null && sectionDurations[String(i)] === undefined,
|
|
397
|
+
)
|
|
398
|
+
return {
|
|
399
|
+
sections: resolved,
|
|
400
|
+
totalDuration: Math.max(1, calculateTotalDuration(resolved)),
|
|
401
|
+
preamble: finalPreamble,
|
|
402
|
+
hasUnresolvedDurations: hasUnresolved,
|
|
403
|
+
}
|
|
404
|
+
}, [composed, sectionDurations])
|
|
405
|
+
|
|
406
|
+
return (
|
|
407
|
+
<ServerSlotsContext.Provider value={serverSlots}>
|
|
408
|
+
<PlayerPage
|
|
409
|
+
sections={sections}
|
|
410
|
+
totalDuration={totalDuration}
|
|
411
|
+
preamble={preamble}
|
|
412
|
+
entryPath={entryPath}
|
|
413
|
+
hasUnresolvedDurations={hasUnresolvedDurations}
|
|
414
|
+
frontmatter={composed.frontmatter}
|
|
415
|
+
availableEntries={availableEntries}
|
|
416
|
+
currentRoute={currentRoute}
|
|
417
|
+
/>
|
|
418
|
+
</ServerSlotsContext.Provider>
|
|
419
|
+
)
|
|
420
|
+
}
|