promptslide 0.2.4 → 0.2.6
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/dist/index.d.ts +39 -1
- package/dist/index.js +241 -130
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/add.mjs +7 -0
- package/src/commands/create.mjs +10 -37
- package/src/commands/login.mjs +6 -1
- package/src/commands/org.mjs +2 -2
- package/src/commands/publish.mjs +54 -17
- package/src/commands/pull.mjs +228 -0
- package/src/commands/studio.mjs +8 -1
- package/src/core/index.ts +7 -0
- package/src/core/slide-deck.tsx +34 -82
- package/src/core/slide-embed.tsx +141 -0
- package/src/core/slide-renderer.tsx +91 -0
- package/src/index.mjs +6 -0
- package/src/utils/auth.mjs +5 -3
- package/src/utils/prompts.mjs +70 -0
- package/src/utils/registry.mjs +12 -0
- package/src/vite/plugin.mjs +54 -0
- package/templates/default/package.json +1 -1
- package/templates/default/src/layouts/slide-layout-centered.tsx +1 -1
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react"
|
|
2
|
+
|
|
3
|
+
import type { SlideTransitionType } from "./transitions"
|
|
4
|
+
import type { SlideConfig } from "./types"
|
|
5
|
+
|
|
6
|
+
import { SLIDE_DIMENSIONS } from "./animation-config"
|
|
7
|
+
import { SlideRenderer } from "./slide-renderer"
|
|
8
|
+
import { useSlideNavigation } from "./use-slide-navigation"
|
|
9
|
+
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// TYPES
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
interface SlideEmbedProps {
|
|
15
|
+
slides: SlideConfig[]
|
|
16
|
+
transition?: SlideTransitionType
|
|
17
|
+
directionalTransition?: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// =============================================================================
|
|
21
|
+
// COMPONENT
|
|
22
|
+
// =============================================================================
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Headless slide viewer controlled via window.postMessage.
|
|
26
|
+
* Designed for embedding in an iframe (e.g. the registry editor preview).
|
|
27
|
+
*
|
|
28
|
+
* Inbound messages (parent → embed):
|
|
29
|
+
* { type: "navigate", data: { slide: number } }
|
|
30
|
+
* { type: "advance" }
|
|
31
|
+
* { type: "goBack" }
|
|
32
|
+
*
|
|
33
|
+
* Outbound messages (embed → parent):
|
|
34
|
+
* { type: "slideReady" }
|
|
35
|
+
* { type: "slideState", data: { currentSlide, totalSlides, animationStep, totalSteps, titles } }
|
|
36
|
+
* { type: "hmrUpdate" }
|
|
37
|
+
*/
|
|
38
|
+
export function SlideEmbed({ slides, transition, directionalTransition }: SlideEmbedProps) {
|
|
39
|
+
const [scale, setScale] = useState(1)
|
|
40
|
+
|
|
41
|
+
const {
|
|
42
|
+
currentSlide,
|
|
43
|
+
animationStep,
|
|
44
|
+
totalSteps,
|
|
45
|
+
direction,
|
|
46
|
+
showAllAnimations,
|
|
47
|
+
advance,
|
|
48
|
+
goBack,
|
|
49
|
+
goToSlide,
|
|
50
|
+
onTransitionComplete
|
|
51
|
+
} = useSlideNavigation({ slides })
|
|
52
|
+
|
|
53
|
+
// Post slide state to parent whenever it changes
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
const state = {
|
|
56
|
+
currentSlide,
|
|
57
|
+
totalSlides: slides.length,
|
|
58
|
+
animationStep,
|
|
59
|
+
totalSteps,
|
|
60
|
+
titles: slides.map(s => s.title || "")
|
|
61
|
+
}
|
|
62
|
+
window.parent.postMessage({ type: "slideState", data: state }, "*")
|
|
63
|
+
}, [currentSlide, animationStep, totalSteps, slides])
|
|
64
|
+
|
|
65
|
+
// Signal readiness on mount
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
window.parent.postMessage({ type: "slideReady" }, "*")
|
|
68
|
+
}, [])
|
|
69
|
+
|
|
70
|
+
// Listen for Vite HMR updates
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
73
|
+
const hot = (import.meta as any).hot as { on: (event: string, cb: () => void) => void } | undefined
|
|
74
|
+
if (hot) {
|
|
75
|
+
hot.on("vite:afterUpdate", () => {
|
|
76
|
+
window.parent.postMessage({ type: "hmrUpdate" }, "*")
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
}, [])
|
|
80
|
+
|
|
81
|
+
// Listen for inbound messages from parent
|
|
82
|
+
const handleMessage = useCallback(
|
|
83
|
+
(event: MessageEvent) => {
|
|
84
|
+
const { type, data } = event.data || {}
|
|
85
|
+
switch (type) {
|
|
86
|
+
case "navigate":
|
|
87
|
+
if (typeof data?.slide === "number") goToSlide(data.slide)
|
|
88
|
+
break
|
|
89
|
+
case "advance":
|
|
90
|
+
advance()
|
|
91
|
+
break
|
|
92
|
+
case "goBack":
|
|
93
|
+
goBack()
|
|
94
|
+
break
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
[advance, goBack, goToSlide]
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
window.addEventListener("message", handleMessage)
|
|
102
|
+
return () => window.removeEventListener("message", handleMessage)
|
|
103
|
+
}, [handleMessage])
|
|
104
|
+
|
|
105
|
+
// Scale to fill viewport
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
const calculateScale = () => {
|
|
108
|
+
setScale(window.innerWidth / SLIDE_DIMENSIONS.width)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
calculateScale()
|
|
112
|
+
window.addEventListener("resize", calculateScale)
|
|
113
|
+
return () => window.removeEventListener("resize", calculateScale)
|
|
114
|
+
}, [])
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<div className="flex h-screen w-screen items-center justify-center overflow-hidden bg-black">
|
|
118
|
+
<div
|
|
119
|
+
className="relative overflow-hidden bg-black"
|
|
120
|
+
style={{
|
|
121
|
+
width: SLIDE_DIMENSIONS.width,
|
|
122
|
+
height: SLIDE_DIMENSIONS.height,
|
|
123
|
+
transform: `scale(${scale})`,
|
|
124
|
+
transformOrigin: "center center"
|
|
125
|
+
}}
|
|
126
|
+
>
|
|
127
|
+
<SlideRenderer
|
|
128
|
+
slides={slides}
|
|
129
|
+
currentSlide={currentSlide}
|
|
130
|
+
animationStep={animationStep}
|
|
131
|
+
totalSteps={totalSteps}
|
|
132
|
+
direction={direction}
|
|
133
|
+
showAllAnimations={showAllAnimations}
|
|
134
|
+
transition={transition}
|
|
135
|
+
directionalTransition={directionalTransition}
|
|
136
|
+
onTransitionComplete={onTransitionComplete}
|
|
137
|
+
/>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
)
|
|
141
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { AnimatePresence, motion } from "framer-motion"
|
|
2
|
+
|
|
3
|
+
import type { SlideTransitionType } from "./transitions"
|
|
4
|
+
import type { NavigationDirection, SlideConfig } from "./types"
|
|
5
|
+
|
|
6
|
+
import { SLIDE_TRANSITION } from "./animation-config"
|
|
7
|
+
import { AnimationProvider } from "./animation-context"
|
|
8
|
+
import { SlideErrorBoundary } from "./slide-error-boundary"
|
|
9
|
+
import { DEFAULT_SLIDE_TRANSITION, getSlideVariants } from "./transitions"
|
|
10
|
+
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// TYPES
|
|
13
|
+
// =============================================================================
|
|
14
|
+
|
|
15
|
+
export interface SlideRendererProps {
|
|
16
|
+
slides: SlideConfig[]
|
|
17
|
+
currentSlide: number
|
|
18
|
+
animationStep: number
|
|
19
|
+
totalSteps: number
|
|
20
|
+
direction: NavigationDirection
|
|
21
|
+
showAllAnimations: boolean
|
|
22
|
+
transition?: SlideTransitionType
|
|
23
|
+
directionalTransition?: boolean
|
|
24
|
+
onTransitionComplete: () => void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// COMPONENT
|
|
29
|
+
// =============================================================================
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Renders a single slide with animated transitions.
|
|
33
|
+
* Extracted from SlideDeck so it can be reused in SlideEmbed and other contexts.
|
|
34
|
+
*/
|
|
35
|
+
export function SlideRenderer({
|
|
36
|
+
slides,
|
|
37
|
+
currentSlide,
|
|
38
|
+
animationStep,
|
|
39
|
+
totalSteps,
|
|
40
|
+
direction,
|
|
41
|
+
showAllAnimations,
|
|
42
|
+
transition,
|
|
43
|
+
directionalTransition,
|
|
44
|
+
onTransitionComplete
|
|
45
|
+
}: SlideRendererProps) {
|
|
46
|
+
const currentSlideTransition = slides[currentSlide]?.transition
|
|
47
|
+
const transitionType = currentSlideTransition ?? transition ?? DEFAULT_SLIDE_TRANSITION
|
|
48
|
+
const isDirectional = directionalTransition ?? false
|
|
49
|
+
|
|
50
|
+
const slideVariants = getSlideVariants(
|
|
51
|
+
{ type: transitionType, directional: isDirectional },
|
|
52
|
+
direction
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
const CurrentSlideComponent = slides[currentSlide]!.component
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<AnimatePresence initial={false}>
|
|
59
|
+
<motion.div
|
|
60
|
+
key={currentSlide}
|
|
61
|
+
variants={slideVariants}
|
|
62
|
+
initial="enter"
|
|
63
|
+
animate="center"
|
|
64
|
+
exit="exit"
|
|
65
|
+
transition={SLIDE_TRANSITION}
|
|
66
|
+
onAnimationComplete={definition => {
|
|
67
|
+
if (definition === "center") {
|
|
68
|
+
onTransitionComplete()
|
|
69
|
+
}
|
|
70
|
+
}}
|
|
71
|
+
className="absolute inset-0 h-full w-full"
|
|
72
|
+
>
|
|
73
|
+
<AnimationProvider
|
|
74
|
+
currentStep={animationStep}
|
|
75
|
+
totalSteps={totalSteps}
|
|
76
|
+
showAllAnimations={showAllAnimations}
|
|
77
|
+
>
|
|
78
|
+
<SlideErrorBoundary
|
|
79
|
+
slideIndex={currentSlide}
|
|
80
|
+
slideTitle={slides[currentSlide]?.title}
|
|
81
|
+
>
|
|
82
|
+
<CurrentSlideComponent
|
|
83
|
+
slideNumber={currentSlide + 1}
|
|
84
|
+
totalSlides={slides.length}
|
|
85
|
+
/>
|
|
86
|
+
</SlideErrorBoundary>
|
|
87
|
+
</AnimationProvider>
|
|
88
|
+
</motion.div>
|
|
89
|
+
</AnimatePresence>
|
|
90
|
+
)
|
|
91
|
+
}
|
package/src/index.mjs
CHANGED
|
@@ -31,6 +31,7 @@ function printHelp() {
|
|
|
31
31
|
console.log(` add ${dim("<name>")} Install a slide/deck from the registry`)
|
|
32
32
|
console.log(` publish ${dim("[file]")} Publish a slide to the registry`)
|
|
33
33
|
console.log(` update ${dim("[name]")} Check for and apply updates`)
|
|
34
|
+
console.log(` pull Pull the latest deck from the registry`)
|
|
34
35
|
console.log(` remove ${dim("<name>")} Remove an installed item`)
|
|
35
36
|
console.log(` info ${dim("<name>")} Show details about a registry item`)
|
|
36
37
|
console.log(` search ${dim("<query>")} Search the registry`)
|
|
@@ -99,6 +100,11 @@ switch (command) {
|
|
|
99
100
|
await update(args)
|
|
100
101
|
break
|
|
101
102
|
}
|
|
103
|
+
case "pull": {
|
|
104
|
+
const { pull } = await import("./commands/pull.mjs")
|
|
105
|
+
await pull(args)
|
|
106
|
+
break
|
|
107
|
+
}
|
|
102
108
|
case "search": {
|
|
103
109
|
const { search } = await import("./commands/search.mjs")
|
|
104
110
|
await search(args)
|
package/src/utils/auth.mjs
CHANGED
|
@@ -20,7 +20,8 @@ export function loadAuth() {
|
|
|
20
20
|
registry: data.registry || DEFAULT_REGISTRY,
|
|
21
21
|
token: data.token,
|
|
22
22
|
organizationId: data.organizationId || null,
|
|
23
|
-
organizationName: data.organizationName || null
|
|
23
|
+
organizationName: data.organizationName || null,
|
|
24
|
+
organizationSlug: data.organizationSlug || null
|
|
24
25
|
}
|
|
25
26
|
} catch {
|
|
26
27
|
return null
|
|
@@ -30,13 +31,14 @@ export function loadAuth() {
|
|
|
30
31
|
/**
|
|
31
32
|
* Save auth credentials to ~/.promptslide/auth.json.
|
|
32
33
|
*/
|
|
33
|
-
export function saveAuth({ registry, token, organizationId, organizationName }) {
|
|
34
|
+
export function saveAuth({ registry, token, organizationId, organizationName, organizationSlug }) {
|
|
34
35
|
mkdirSync(AUTH_DIR, { recursive: true })
|
|
35
36
|
const data = {
|
|
36
37
|
registry: registry || DEFAULT_REGISTRY,
|
|
37
38
|
token,
|
|
38
39
|
organizationId: organizationId || null,
|
|
39
|
-
organizationName: organizationName || null
|
|
40
|
+
organizationName: organizationName || null,
|
|
41
|
+
organizationSlug: organizationSlug || null
|
|
40
42
|
}
|
|
41
43
|
writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2) + "\n", { encoding: "utf-8", mode: 0o600 })
|
|
42
44
|
}
|
package/src/utils/prompts.mjs
CHANGED
|
@@ -52,3 +52,73 @@ export function confirm(question, defaultYes = true) {
|
|
|
52
52
|
})
|
|
53
53
|
})
|
|
54
54
|
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Arrow-key select prompt. Returns the index of the chosen option.
|
|
58
|
+
* @param {string[]} options - Display labels for each option
|
|
59
|
+
* @param {number} defaultIndex - Initially highlighted index
|
|
60
|
+
* @returns {Promise<number>}
|
|
61
|
+
*/
|
|
62
|
+
export function select(options, defaultIndex = 0) {
|
|
63
|
+
if (!process.stdin.isTTY) return Promise.resolve(defaultIndex)
|
|
64
|
+
|
|
65
|
+
// Pause readline so we can use raw mode
|
|
66
|
+
if (_rl) { _rl.pause() }
|
|
67
|
+
|
|
68
|
+
return new Promise(resolve => {
|
|
69
|
+
let cursor = defaultIndex
|
|
70
|
+
const { stdin, stdout } = process
|
|
71
|
+
|
|
72
|
+
const render = () => {
|
|
73
|
+
// Move up to overwrite previous render (except first time)
|
|
74
|
+
if (render._drawn) stdout.write(`\x1b[${options.length}A`)
|
|
75
|
+
for (let i = 0; i < options.length; i++) {
|
|
76
|
+
const prefix = i === cursor ? " \x1b[36m❯\x1b[0m " : " "
|
|
77
|
+
const label = i === cursor ? `\x1b[1m${options[i]}\x1b[0m` : dim(options[i])
|
|
78
|
+
stdout.write(`\x1b[2K${prefix}${label}\n`)
|
|
79
|
+
}
|
|
80
|
+
render._drawn = true
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
stdin.setRawMode(true)
|
|
84
|
+
stdin.resume()
|
|
85
|
+
render()
|
|
86
|
+
|
|
87
|
+
const onData = (buf) => {
|
|
88
|
+
const key = buf.toString()
|
|
89
|
+
|
|
90
|
+
// Ctrl+C
|
|
91
|
+
if (key === "\x03") {
|
|
92
|
+
stdin.setRawMode(false)
|
|
93
|
+
stdin.removeListener("data", onData)
|
|
94
|
+
if (_rl) { _rl.resume() }
|
|
95
|
+
process.exit(0)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Up arrow or k
|
|
99
|
+
if (key === "\x1b[A" || key === "k") {
|
|
100
|
+
cursor = (cursor - 1 + options.length) % options.length
|
|
101
|
+
render()
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Down arrow or j
|
|
106
|
+
if (key === "\x1b[B" || key === "j") {
|
|
107
|
+
cursor = (cursor + 1) % options.length
|
|
108
|
+
render()
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Enter
|
|
113
|
+
if (key === "\r" || key === "\n") {
|
|
114
|
+
stdin.setRawMode(false)
|
|
115
|
+
stdin.removeListener("data", onData)
|
|
116
|
+
stdin.pause()
|
|
117
|
+
if (_rl) { _rl.resume() }
|
|
118
|
+
resolve(cursor)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
stdin.on("data", onData)
|
|
123
|
+
})
|
|
124
|
+
}
|
package/src/utils/registry.mjs
CHANGED
|
@@ -79,6 +79,18 @@ export function updateLockfileItem(cwd, slug, version, files) {
|
|
|
79
79
|
writeLockfile(cwd, lock)
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Store publish configuration (deckPrefix, deckSlug) in the lockfile.
|
|
84
|
+
* @param {string} cwd
|
|
85
|
+
* @param {{ deckPrefix?: string, deckSlug?: string }} config
|
|
86
|
+
*/
|
|
87
|
+
export function updateLockfilePublishConfig(cwd, config) {
|
|
88
|
+
const lock = readLockfile(cwd)
|
|
89
|
+
if (config.deckPrefix !== undefined) lock.deckPrefix = config.deckPrefix
|
|
90
|
+
if (config.deckSlug !== undefined) lock.deckSlug = config.deckSlug
|
|
91
|
+
writeLockfile(cwd, lock)
|
|
92
|
+
}
|
|
93
|
+
|
|
82
94
|
/**
|
|
83
95
|
* Remove a single item from the lockfile.
|
|
84
96
|
* @param {string} cwd
|
package/src/vite/plugin.mjs
CHANGED
|
@@ -2,6 +2,8 @@ const VIRTUAL_ENTRY_ID = "virtual:promptslide-entry"
|
|
|
2
2
|
const RESOLVED_VIRTUAL_ENTRY_ID = "\0" + VIRTUAL_ENTRY_ID
|
|
3
3
|
const VIRTUAL_EXPORT_ID = "virtual:promptslide-export"
|
|
4
4
|
const RESOLVED_VIRTUAL_EXPORT_ID = "\0" + VIRTUAL_EXPORT_ID
|
|
5
|
+
const VIRTUAL_EMBED_ID = "virtual:promptslide-embed"
|
|
6
|
+
const RESOLVED_VIRTUAL_EMBED_ID = "\0" + VIRTUAL_EMBED_ID
|
|
5
7
|
|
|
6
8
|
function getHtmlTemplate() {
|
|
7
9
|
return `<!doctype html>
|
|
@@ -89,6 +91,45 @@ createRoot(document.getElementById("root")).render(
|
|
|
89
91
|
`
|
|
90
92
|
}
|
|
91
93
|
|
|
94
|
+
function getEmbedHtmlTemplate() {
|
|
95
|
+
return `<!doctype html>
|
|
96
|
+
<html lang="en" class="dark">
|
|
97
|
+
<head>
|
|
98
|
+
<meta charset="UTF-8" />
|
|
99
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
100
|
+
<title>PromptSlide Embed</title>
|
|
101
|
+
</head>
|
|
102
|
+
<body>
|
|
103
|
+
<div id="root"></div>
|
|
104
|
+
<script type="module" src="/@id/${VIRTUAL_EMBED_ID}"></script>
|
|
105
|
+
</body>
|
|
106
|
+
</html>`
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function getEmbedEntryModule(root) {
|
|
110
|
+
return `
|
|
111
|
+
import { StrictMode, createElement } from "react"
|
|
112
|
+
import { createRoot } from "react-dom/client"
|
|
113
|
+
import { SlideEmbed, SlideThemeProvider } from "promptslide"
|
|
114
|
+
import "${root}/src/globals.css"
|
|
115
|
+
import { slides } from "${root}/src/deck-config"
|
|
116
|
+
|
|
117
|
+
let theme = {}
|
|
118
|
+
try {
|
|
119
|
+
const themeMod = await import("${root}/src/theme")
|
|
120
|
+
theme = themeMod.theme || themeMod.default || {}
|
|
121
|
+
} catch {}
|
|
122
|
+
|
|
123
|
+
createRoot(document.getElementById("root")).render(
|
|
124
|
+
createElement(StrictMode, null,
|
|
125
|
+
createElement(SlideThemeProvider, { theme },
|
|
126
|
+
createElement(SlideEmbed, { slides })
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
`
|
|
131
|
+
}
|
|
132
|
+
|
|
92
133
|
export function promptslidePlugin({ root: initialRoot } = {}) {
|
|
93
134
|
let root = initialRoot
|
|
94
135
|
let exportSlidePath = null
|
|
@@ -104,14 +145,27 @@ export function promptslidePlugin({ root: initialRoot } = {}) {
|
|
|
104
145
|
resolveId(id) {
|
|
105
146
|
if (id === VIRTUAL_ENTRY_ID) return RESOLVED_VIRTUAL_ENTRY_ID
|
|
106
147
|
if (id === VIRTUAL_EXPORT_ID) return RESOLVED_VIRTUAL_EXPORT_ID
|
|
148
|
+
if (id === VIRTUAL_EMBED_ID) return RESOLVED_VIRTUAL_EMBED_ID
|
|
107
149
|
},
|
|
108
150
|
|
|
109
151
|
load(id) {
|
|
110
152
|
if (id === RESOLVED_VIRTUAL_ENTRY_ID) return getEntryModule(root)
|
|
111
153
|
if (id === RESOLVED_VIRTUAL_EXPORT_ID) return getExportEntryModule(root, exportSlidePath || "src/slides/slide-title.tsx")
|
|
154
|
+
if (id === RESOLVED_VIRTUAL_EMBED_ID) return getEmbedEntryModule(root)
|
|
112
155
|
},
|
|
113
156
|
|
|
114
157
|
configureServer(server) {
|
|
158
|
+
// Pre-middleware: serve /embed route
|
|
159
|
+
server.middlewares.use(async (req, res, next) => {
|
|
160
|
+
const url = new URL(req.url, "http://localhost")
|
|
161
|
+
if (url.pathname !== "/embed" && url.pathname !== "/embed/") return next()
|
|
162
|
+
|
|
163
|
+
const html = await server.transformIndexHtml("/embed", getEmbedHtmlTemplate())
|
|
164
|
+
res.setHeader("Content-Type", "text/html")
|
|
165
|
+
res.statusCode = 200
|
|
166
|
+
res.end(html)
|
|
167
|
+
})
|
|
168
|
+
|
|
115
169
|
// Pre-middleware: intercept export URLs before Vite's SPA fallback rewrites them
|
|
116
170
|
server.middlewares.use(async (req, res, next) => {
|
|
117
171
|
const url = new URL(req.url, "http://localhost")
|
|
@@ -48,7 +48,7 @@ export function SlideLayoutCentered({
|
|
|
48
48
|
)}
|
|
49
49
|
|
|
50
50
|
{/* Content Area */}
|
|
51
|
-
<div className="min-h-0 w-full flex-1 overflow-hidden
|
|
51
|
+
<div className="flex min-h-0 w-full flex-1 flex-col justify-center overflow-hidden">{children}</div>
|
|
52
52
|
|
|
53
53
|
{/* Footer */}
|
|
54
54
|
{!hideFooter && (
|