@atom63/slides 0.1.0 → 0.2.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/dist/editor/index.d.ts +50 -32
- package/dist/editor/index.js +116 -61
- package/dist/editor/index.js.map +1 -1
- package/dist/vite/index.d.ts +10 -1
- package/dist/vite/index.js +40 -4
- package/dist/vite/index.js.map +1 -1
- package/package.json +14 -6
- package/src/editor/styles.css +59 -0
package/dist/editor/index.d.ts
CHANGED
|
@@ -40,43 +40,61 @@ declare function stripImports(body: string): string;
|
|
|
40
40
|
declare function compileDeck(source: string): Promise<CompileDeckResult>;
|
|
41
41
|
|
|
42
42
|
interface DeckEditorProps {
|
|
43
|
-
/** Raw deck MDX source (frontmatter + body with `---` slide separators). */
|
|
44
43
|
source: string;
|
|
45
|
-
/**
|
|
46
|
-
* Called with the next source on every edit / template insert. When omitted,
|
|
47
|
-
* the editor manages source internally (uncontrolled).
|
|
48
|
-
*/
|
|
49
44
|
onChange?: (next: string) => void;
|
|
50
|
-
/** Debounce window (ms) before recompiling the preview. Default 300. */
|
|
51
45
|
debounceMs?: number;
|
|
52
46
|
}
|
|
53
47
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* - left: a LIVE preview rendered by `SlidesPlayer`, compiled from the source
|
|
58
|
-
* at runtime via {@link compileDeck} (browser-safe frontmatter split +
|
|
59
|
-
* strip-imports + `@mdx-js/mdx` evaluate, with slide components injected
|
|
60
|
-
* through MDX context).
|
|
61
|
-
* - right: a `<textarea>` editing surface, a registry-driven template palette
|
|
62
|
-
* (click inserts the template's MDX snippet at the end of the source), and a
|
|
63
|
-
* light/dark theme toggle for the preview container.
|
|
64
|
-
*
|
|
65
|
-
* Compilation is debounced and resilient: the LAST good preview is retained on
|
|
66
|
-
* a failed compile and the error is surfaced in a non-blocking banner, so a bad
|
|
67
|
-
* keystroke never blanks or crashes the editor.
|
|
68
|
-
*
|
|
69
|
-
* The editor's own chrome is styled by self-contained plain CSS shipped as a
|
|
70
|
-
* side-effect import — consumers must `import '@atom63/slides/editor/styles'`
|
|
71
|
-
* (the CSS is intentionally NOT bundled into the JS so it isn't orphaned by the
|
|
72
|
-
* build, mirroring how `@atom63/slides` ships `./styles`).
|
|
73
|
-
*
|
|
74
|
-
* A `<textarea>` is intentional
|
|
75
|
-
* for v0.1; CodeMirror (syntax highlight, structured editing) is a future
|
|
76
|
-
* upgrade. Structured per-slide form editing and template-switch-by-form are
|
|
77
|
-
* explicitly v2 — the source textarea is the only editing surface here.
|
|
48
|
+
* Backward-compatible editor: DeckSurface locked to Edit mode. The unified
|
|
49
|
+
* surface ({@link DeckSurface}) is the preferred API; this remains for existing
|
|
50
|
+
* consumers.
|
|
78
51
|
*/
|
|
79
|
-
declare function DeckEditor({ source
|
|
52
|
+
declare function DeckEditor({ source, onChange, debounceMs }: DeckEditorProps): react_jsx_runtime.JSX.Element;
|
|
53
|
+
|
|
54
|
+
type DeckSurfaceMode = 'present' | 'edit';
|
|
55
|
+
interface DeckSurfaceProps {
|
|
56
|
+
/**
|
|
57
|
+
* Raw deck MDX source (frontmatter + `---`-separated slide body). The single
|
|
58
|
+
* canonical input for both Present and Edit modes.
|
|
59
|
+
*
|
|
60
|
+
* **Controlled vs. uncontrolled:**
|
|
61
|
+
* - When `onChange` is provided the surface is **controlled** — the parent
|
|
62
|
+
* owns source state and must feed back the updated string on each change.
|
|
63
|
+
* - When `onChange` is omitted the surface is **uncontrolled** and
|
|
64
|
+
* **present-only**: `source` is read once on mount and later prop changes
|
|
65
|
+
* are ignored. This is the production / read-only contract.
|
|
66
|
+
*/
|
|
67
|
+
source: string;
|
|
68
|
+
/**
|
|
69
|
+
* Called with the next source string on every edit or template insert
|
|
70
|
+
* (in-memory, per-keystroke). Presence of this prop **enables Edit mode**
|
|
71
|
+
* and makes the surface controlled — the parent holds source state.
|
|
72
|
+
* When omitted the surface is present-only (no edit chrome, no keyboard
|
|
73
|
+
* shortcut to enter Edit).
|
|
74
|
+
*/
|
|
75
|
+
onChange?: (next: string) => void;
|
|
76
|
+
/**
|
|
77
|
+
* Called on an **explicit** save action (Save button or Cmd/Ctrl-S in Edit
|
|
78
|
+
* mode). This is the persistence hook, distinct from the per-keystroke
|
|
79
|
+
* `onChange`. Wire it to the dev write-back plugin (or any async persistence
|
|
80
|
+
* layer) to flush the source to disk on demand. No-op / absent in a
|
|
81
|
+
* production build.
|
|
82
|
+
*/
|
|
83
|
+
onSave?: (source: string) => void | Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* The mode the surface starts in. Defaults to `'present'`. Forced to
|
|
86
|
+
* `'present'` when `onChange` is absent (present-only surfaces cannot enter
|
|
87
|
+
* Edit mode).
|
|
88
|
+
*/
|
|
89
|
+
initialMode?: DeckSurfaceMode;
|
|
90
|
+
/**
|
|
91
|
+
* Debounce window in milliseconds before recompiling the preview after a
|
|
92
|
+
* source change. Defaults to `300`. Pass `0` in tests to compile
|
|
93
|
+
* synchronously on the next microtask tick.
|
|
94
|
+
*/
|
|
95
|
+
debounceMs?: number;
|
|
96
|
+
}
|
|
97
|
+
declare function DeckSurface({ source: sourceProp, onChange, onSave, initialMode, debounceMs, }: DeckSurfaceProps): react_jsx_runtime.JSX.Element;
|
|
80
98
|
|
|
81
99
|
/**
|
|
82
100
|
* Synthesize a minimal, valid MDX usage example from a template's schema.
|
|
@@ -93,4 +111,4 @@ declare function toInsertSnippet(t: TemplateDef): string;
|
|
|
93
111
|
/** Map of `templateName -> insertable MDX snippet`, built from the live registry. */
|
|
94
112
|
declare const templateSnippets: Record<string, string>;
|
|
95
113
|
|
|
96
|
-
export { type CompileDeckResult, DeckEditor, type DeckEditorProps, compileDeck, stripImports, synthExample, templateSnippets, toInsertSnippet };
|
|
114
|
+
export { type CompileDeckResult, DeckEditor, type DeckEditorProps, DeckSurface, type DeckSurfaceMode, type DeckSurfaceProps, compileDeck, stripImports, synthExample, templateSnippets, toInsertSnippet };
|
package/dist/editor/index.js
CHANGED
|
@@ -2,9 +2,9 @@ import { listTemplates, slideMdxComponents, SlidesPlayer } from '../chunk-AD3ZOV
|
|
|
2
2
|
import { evaluate } from '@mdx-js/mdx';
|
|
3
3
|
import yaml from 'js-yaml';
|
|
4
4
|
import * as runtime from 'react/jsx-runtime';
|
|
5
|
-
import {
|
|
5
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
6
6
|
import remarkGfm from 'remark-gfm';
|
|
7
|
-
import { useState,
|
|
7
|
+
import { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
|
8
8
|
|
|
9
9
|
function parseFrontmatter(source) {
|
|
10
10
|
const normalized = source.replace(/^/, "");
|
|
@@ -130,50 +130,29 @@ ${synthExample(t)}
|
|
|
130
130
|
var templateSnippets = Object.fromEntries(
|
|
131
131
|
listTemplates().map((t) => [t.name, toInsertSnippet(t)])
|
|
132
132
|
);
|
|
133
|
-
function
|
|
134
|
-
const isControlled = onChange !== void 0;
|
|
135
|
-
const [internalSource, setInternalSource] = useState(sourceProp);
|
|
136
|
-
const source = isControlled ? sourceProp : internalSource;
|
|
137
|
-
const [preview, setPreview] = useState(null);
|
|
138
|
-
const [error, setError] = useState(null);
|
|
133
|
+
function EditPane({ source, onChange, onSave, deck, error, onPresent }) {
|
|
139
134
|
const [theme, setTheme] = useState("light");
|
|
140
135
|
const textareaRef = useRef(null);
|
|
141
136
|
const templates = useMemo(() => listTemplates(), []);
|
|
142
|
-
const
|
|
143
|
-
(
|
|
144
|
-
|
|
145
|
-
else setInternalSource(next);
|
|
146
|
-
},
|
|
147
|
-
[isControlled, onChange]
|
|
137
|
+
const handleChange = useCallback(
|
|
138
|
+
(e) => onChange(e.target.value),
|
|
139
|
+
[onChange]
|
|
148
140
|
);
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (result.ok) {
|
|
155
|
-
setPreview({ Content: result.Content, meta: result.meta });
|
|
156
|
-
setError(null);
|
|
157
|
-
} else {
|
|
158
|
-
setError(result.error);
|
|
141
|
+
const handleKeyDown = useCallback(
|
|
142
|
+
(e) => {
|
|
143
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "s") {
|
|
144
|
+
e.preventDefault();
|
|
145
|
+
onSave?.(source);
|
|
159
146
|
}
|
|
160
|
-
},
|
|
161
|
-
|
|
162
|
-
cancelled = true;
|
|
163
|
-
clearTimeout(handle);
|
|
164
|
-
};
|
|
165
|
-
}, [source, debounceMs]);
|
|
166
|
-
const handleChange = useCallback(
|
|
167
|
-
(e) => setSource(e.target.value),
|
|
168
|
-
[setSource]
|
|
147
|
+
},
|
|
148
|
+
[onSave, source]
|
|
169
149
|
);
|
|
170
|
-
const
|
|
150
|
+
const insert = useCallback(
|
|
171
151
|
(name) => {
|
|
172
152
|
const snippet = templateSnippets[name];
|
|
173
153
|
if (!snippet) return;
|
|
174
|
-
|
|
175
|
-
${snippet}
|
|
176
|
-
setSource(next);
|
|
154
|
+
onChange(`${source.replace(/\s*$/, "")}
|
|
155
|
+
${snippet}`);
|
|
177
156
|
requestAnimationFrame(() => {
|
|
178
157
|
const el = textareaRef.current;
|
|
179
158
|
if (!el) return;
|
|
@@ -182,37 +161,34 @@ ${snippet}`;
|
|
|
182
161
|
el.scrollTop = el.scrollHeight;
|
|
183
162
|
});
|
|
184
163
|
},
|
|
185
|
-
[source,
|
|
164
|
+
[source, onChange]
|
|
186
165
|
);
|
|
187
|
-
const deck = useMemo(() => {
|
|
188
|
-
if (!preview) return null;
|
|
189
|
-
return { slug: "draft", meta: preview.meta, content: preview.Content };
|
|
190
|
-
}, [preview]);
|
|
191
166
|
return /* @__PURE__ */ jsxs("div", { className: "a63-editor", children: [
|
|
192
167
|
/* @__PURE__ */ jsxs("section", { className: "a63-editor__preview", "data-theme": theme, children: [
|
|
193
168
|
error ? /* @__PURE__ */ jsxs("div", { className: "a63-editor__error", role: "alert", children: [
|
|
194
169
|
/* @__PURE__ */ jsx("span", { className: "a63-editor__error-tag", children: "MDX error" }),
|
|
195
170
|
/* @__PURE__ */ jsx("span", { className: "a63-editor__error-msg", children: error })
|
|
196
171
|
] }) : null,
|
|
197
|
-
deck ? (
|
|
198
|
-
|
|
199
|
-
/* @__PURE__ */ jsx("div", { className: "a63-editor__preview-stage", children: /* @__PURE__ */ jsx(SlidesPlayer, { deck, onBack: () => {
|
|
200
|
-
} }) }, `${theme}`)
|
|
201
|
-
) : /* @__PURE__ */ jsx("div", { className: "a63-editor__preview-empty", children: error ? "Fix the MDX error to render a preview." : "Compiling preview\u2026" })
|
|
172
|
+
deck ? /* @__PURE__ */ jsx("div", { className: "a63-editor__preview-stage", children: /* @__PURE__ */ jsx(SlidesPlayer, { deck, onBack: () => {
|
|
173
|
+
} }) }, theme) : /* @__PURE__ */ jsx("div", { className: "a63-editor__preview-empty", children: error ? "Fix the MDX error to render a preview." : "Compiling preview\u2026" })
|
|
202
174
|
] }),
|
|
203
175
|
/* @__PURE__ */ jsxs("section", { className: "a63-editor__source", children: [
|
|
204
176
|
/* @__PURE__ */ jsxs("div", { className: "a63-editor__toolbar", children: [
|
|
205
177
|
/* @__PURE__ */ jsx("span", { className: "a63-editor__title", children: "Deck source \xB7 MDX" }),
|
|
206
|
-
/* @__PURE__ */
|
|
207
|
-
"button",
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
178
|
+
/* @__PURE__ */ jsxs("div", { className: "a63-editor__toolbar-actions", children: [
|
|
179
|
+
onSave ? /* @__PURE__ */ jsx("button", { type: "button", className: "a63-editor__save", onClick: () => onSave(source), children: "Save" }) : null,
|
|
180
|
+
/* @__PURE__ */ jsx(
|
|
181
|
+
"button",
|
|
182
|
+
{
|
|
183
|
+
type: "button",
|
|
184
|
+
className: "a63-editor__theme-toggle",
|
|
185
|
+
onClick: () => setTheme((t) => t === "light" ? "dark" : "light"),
|
|
186
|
+
"aria-pressed": theme === "dark",
|
|
187
|
+
children: theme === "light" ? "\u2600\uFE0E Light" : "\u263E Dark"
|
|
188
|
+
}
|
|
189
|
+
),
|
|
190
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "a63-editor__present", onClick: onPresent, children: "Present" })
|
|
191
|
+
] })
|
|
216
192
|
] }),
|
|
217
193
|
/* @__PURE__ */ jsx(
|
|
218
194
|
"textarea",
|
|
@@ -221,6 +197,7 @@ ${snippet}`;
|
|
|
221
197
|
className: "a63-editor__textarea",
|
|
222
198
|
value: source,
|
|
223
199
|
onChange: handleChange,
|
|
200
|
+
onKeyDown: handleKeyDown,
|
|
224
201
|
spellCheck: false,
|
|
225
202
|
"aria-label": "Deck MDX source"
|
|
226
203
|
}
|
|
@@ -232,7 +209,7 @@ ${snippet}`;
|
|
|
232
209
|
{
|
|
233
210
|
type: "button",
|
|
234
211
|
className: "a63-editor__chip",
|
|
235
|
-
onClick: () =>
|
|
212
|
+
onClick: () => insert(t.name),
|
|
236
213
|
title: `Insert ${t.name}`,
|
|
237
214
|
children: [
|
|
238
215
|
/* @__PURE__ */ jsx("span", { className: "a63-editor__chip-name", children: t.name }),
|
|
@@ -240,13 +217,91 @@ ${snippet}`;
|
|
|
240
217
|
]
|
|
241
218
|
},
|
|
242
219
|
t.name
|
|
243
|
-
)) })
|
|
244
|
-
/* @__PURE__ */ jsx("p", { className: "a63-editor__footnote", children: "v0.1 \xB7 plain-textarea editing (CodeMirror is a future upgrade). Per-slide form editing & template-switch are deferred to v2." })
|
|
220
|
+
)) })
|
|
245
221
|
] })
|
|
246
222
|
] })
|
|
247
223
|
] });
|
|
248
224
|
}
|
|
225
|
+
function DeckSurface({
|
|
226
|
+
source: sourceProp,
|
|
227
|
+
onChange,
|
|
228
|
+
onSave,
|
|
229
|
+
initialMode = "present",
|
|
230
|
+
debounceMs = 300
|
|
231
|
+
}) {
|
|
232
|
+
const editable = onChange !== void 0;
|
|
233
|
+
const [internalSource, setInternalSource] = useState(sourceProp);
|
|
234
|
+
const source = editable ? sourceProp : internalSource;
|
|
235
|
+
const setSource = (next) => {
|
|
236
|
+
if (editable) onChange(next);
|
|
237
|
+
else setInternalSource(next);
|
|
238
|
+
};
|
|
239
|
+
const [mode, setMode] = useState(editable ? initialMode : "present");
|
|
240
|
+
const [preview, setPreview] = useState(null);
|
|
241
|
+
const [error, setError] = useState(null);
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
let cancelled = false;
|
|
244
|
+
const h = setTimeout(async () => {
|
|
245
|
+
const r = await compileDeck(source);
|
|
246
|
+
if (cancelled) return;
|
|
247
|
+
if (r.ok) {
|
|
248
|
+
setPreview({ Content: r.Content, meta: r.meta });
|
|
249
|
+
setError(null);
|
|
250
|
+
} else {
|
|
251
|
+
setError(r.error);
|
|
252
|
+
}
|
|
253
|
+
}, debounceMs);
|
|
254
|
+
return () => {
|
|
255
|
+
cancelled = true;
|
|
256
|
+
clearTimeout(h);
|
|
257
|
+
};
|
|
258
|
+
}, [source, debounceMs]);
|
|
259
|
+
const deck = useMemo(
|
|
260
|
+
() => preview ? { slug: "draft", meta: preview.meta, content: preview.Content } : null,
|
|
261
|
+
[preview]
|
|
262
|
+
);
|
|
263
|
+
useEffect(() => {
|
|
264
|
+
if (!editable) return;
|
|
265
|
+
const onKey = (e) => {
|
|
266
|
+
const typing = e.target?.tagName === "TEXTAREA";
|
|
267
|
+
if (e.key === "e" && !typing && mode === "present") setMode("edit");
|
|
268
|
+
if (e.key === "Escape" && mode === "edit") setMode("present");
|
|
269
|
+
};
|
|
270
|
+
window.addEventListener("keydown", onKey);
|
|
271
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
272
|
+
}, [editable, mode]);
|
|
273
|
+
if (editable && mode === "edit") {
|
|
274
|
+
return /* @__PURE__ */ jsx(
|
|
275
|
+
EditPane,
|
|
276
|
+
{
|
|
277
|
+
source,
|
|
278
|
+
onChange: setSource,
|
|
279
|
+
onSave,
|
|
280
|
+
deck,
|
|
281
|
+
error,
|
|
282
|
+
onPresent: () => setMode("present")
|
|
283
|
+
}
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
return /* @__PURE__ */ jsxs("div", { className: "a63-surface", children: [
|
|
287
|
+
editable && /* @__PURE__ */ jsx("button", { type: "button", className: "a63-surface__edit", onClick: () => setMode("edit"), children: "Edit" }),
|
|
288
|
+
deck ? /* @__PURE__ */ jsx(SlidesPlayer, { deck, onBack: () => {
|
|
289
|
+
} }) : /* @__PURE__ */ jsx("div", { className: "a63-editor__preview-empty", children: error ? "Fix the MDX error to render a preview." : "Compiling preview\u2026" })
|
|
290
|
+
] });
|
|
291
|
+
}
|
|
292
|
+
function DeckEditor({ source, onChange, debounceMs }) {
|
|
293
|
+
return /* @__PURE__ */ jsx(
|
|
294
|
+
DeckSurface,
|
|
295
|
+
{
|
|
296
|
+
source,
|
|
297
|
+
onChange: onChange ?? (() => {
|
|
298
|
+
}),
|
|
299
|
+
initialMode: "edit",
|
|
300
|
+
debounceMs
|
|
301
|
+
}
|
|
302
|
+
);
|
|
303
|
+
}
|
|
249
304
|
|
|
250
|
-
export { DeckEditor, compileDeck, stripImports, synthExample, templateSnippets, toInsertSnippet };
|
|
305
|
+
export { DeckEditor, DeckSurface, compileDeck, stripImports, synthExample, templateSnippets, toInsertSnippet };
|
|
251
306
|
//# sourceMappingURL=index.js.map
|
|
252
307
|
//# sourceMappingURL=index.js.map
|
package/dist/editor/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/editor/compile-deck.ts","../../src/editor/template-snippets.ts","../../src/editor/deck-editor.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { evaluate } from '@mdx-js/mdx'\nimport yaml from 'js-yaml'\nimport type { ComponentType } from 'react'\nimport * as runtime from 'react/jsx-runtime'\nimport remarkGfm from 'remark-gfm'\nimport { slideMdxComponents } from '../content/mdx-components'\nimport type { SlideDeckMeta } from '../types'\n\n/**\n * Browser-safe YAML frontmatter split. We deliberately avoid the common\n * \"gray matter\" style parser because it depends on Node's `Buffer`, which\n * doesn't exist in the browser — a browser library must not force consumers to\n * polyfill Node globals.\n *\n * If `source` begins with a `---` fence line, the block up to the next `---`\n * fence is parsed as YAML into `meta` and the remainder is returned as `body`.\n * Otherwise `meta` is empty and the whole source is the `body`.\n */\nexport function parseFrontmatter(source: string): { meta: Record<string, unknown>; body: string } {\n // Frontmatter must start at the very top of the file (allowing a UTF-8 BOM).\n const normalized = source.replace(/^/, '')\n const lines = normalized.split('\\n')\n if (lines[0]?.trim() !== '---') {\n return { meta: {}, body: source }\n }\n\n // Find the closing fence line.\n let end = -1\n for (let i = 1; i < lines.length; i++) {\n if (lines[i].trim() === '---') {\n end = i\n break\n }\n }\n if (end === -1) {\n // No closing fence — treat the whole thing as body, no frontmatter.\n return { meta: {}, body: source }\n }\n\n const yamlBlock = lines.slice(1, end).join('\\n')\n const body = lines.slice(end + 1).join('\\n')\n const loaded = yaml.load(yamlBlock)\n const meta = loaded && typeof loaded === 'object' ? (loaded as Record<string, unknown>) : {}\n return { meta, body }\n}\n\n/**\n * Result of a runtime deck compile.\n *\n * On success: the parsed frontmatter `meta` plus a `Content` component ready to\n * hand to `SlidesPlayer` as `deck.content`. On failure: a human-readable\n * `error` string (compile + frontmatter parsing both throw on bad input).\n */\nexport type CompileDeckResult =\n | { ok: true; meta: SlideDeckMeta; Content: ComponentType }\n | { ok: false; error: string }\n\n/**\n * Matches bare ES `import` statements at the start of a line. A deck's MDX body\n * carries lines like `import { CoverSlide } from \"@atom63/slides\"`. The runtime\n * `evaluate` below has no module resolver, so these must be stripped — the\n * components are injected via `useMDXComponents` instead (see compileDeck).\n *\n * Covers single- and multi-line import specifier blocks:\n * import Foo from \"x\"\n * import { A, B } from \"x\"\n * import {\n * A,\n * B,\n * } from \"x\"\n */\nconst IMPORT_RE = /^[ \\t]*import\\b[\\s\\S]*?(?:from\\s*['\"][^'\"]*['\"]|['\"][^'\"]*['\"])[ \\t]*;?[ \\t]*$/gm\n\n/** Remove bare `import ... from \"...\"` lines so runtime evaluate doesn't choke. */\nexport function stripImports(body: string): string {\n return body.replace(IMPORT_RE, '')\n}\n\nconst DEFAULT_META: SlideDeckMeta = {\n title: 'Untitled deck',\n date: new Date().toISOString().slice(0, 10),\n}\n\n/**\n * Compile a deck's raw MDX source into a renderable Content component using a\n * runtime MDX pipeline (no bundler step).\n *\n * Pipeline:\n * 1. `parseFrontmatter` splits the YAML frontmatter (the deck `meta`) from the\n * body using a browser-safe parser (no Node `Buffer`).\n * 2. `stripImports` removes bare `import ... from \"@atom63/slides\"` lines that\n * the runtime evaluator cannot resolve.\n * 3. `@mdx-js/mdx`'s `evaluate` compiles + runs the body. The slide components\n * (templates + primitives + markdown mappings) are provided through MDX\n * context via `useMDXComponents: () => slideMdxComponents`, so bare JSX such\n * as `<CoverSlide/>` resolves with no import. `---` thematic breaks are\n * preserved as the engine's slide separators.\n *\n * Async and can throw on malformed MDX/frontmatter — callers should debounce\n * and keep the last good render on failure (DeckEditor does this).\n */\nexport async function compileDeck(source: string): Promise<CompileDeckResult> {\n try {\n const { meta: data, body: content } = parseFrontmatter(source)\n const meta: SlideDeckMeta = { ...DEFAULT_META, ...(data as Partial<SlideDeckMeta>) }\n const body = stripImports(content)\n\n const { default: Content } = await evaluate(body, {\n ...runtime,\n useMDXComponents: () => slideMdxComponents,\n remarkPlugins: [remarkGfm],\n })\n\n return { ok: true, meta, Content: Content as ComponentType }\n } catch (err) {\n const error = err instanceof Error ? err.message : String(err)\n return { ok: false, error }\n }\n}\n","import {\n listTemplates,\n type SlotDef,\n type SlotGroupDef,\n type TemplateDef,\n} from '../content/template-registry'\n\n/**\n * Generates a minimal, paste-ready MDX snippet per template from the engine's\n * registry (`listTemplates()`), mirroring the synthesis logic in\n * `skill/scripts/gen-templates.mjs`. The palette in\n * <DeckEditor> appends `templateSnippets[name]` when a template is clicked.\n *\n * Registry is the single source of truth; snippets cannot drift from the\n * published template props/slots.\n */\n\nconst PLACEHOLDER_IMG = '/images/placeholder-1920x1080.webp'\n\nfunction exampleValue(prop: SlotDef): string {\n switch (prop.kind) {\n case 'media':\n return PLACEHOLDER_IMG\n default:\n // text / richtext / list-as-scalar\n return `${prop.label}…`\n }\n}\n\n/** Render the direct-prop attributes for a template's opening tag. */\nfunction renderPropAttrs(props: SlotDef[], indent = ' '): string[] {\n const lines: string[] = []\n for (const prop of props) {\n if (prop.key === 'children') continue\n if (prop.array) {\n if (prop.kind === 'media') {\n lines.push(\n `${indent}${prop.key}={[\"${PLACEHOLDER_IMG}\", \"${PLACEHOLDER_IMG}\", \"${PLACEHOLDER_IMG}\"]}`\n )\n } else {\n lines.push(`${indent}${prop.key}={[{ label: \"Label\", value: \"Value\", href: \"https://…\" }]}`)\n }\n continue\n }\n lines.push(`${indent}${prop.key}=\"${exampleValue(prop)}\"`)\n }\n return lines\n}\n\n/** Render one compound-slot child element, e.g. `<HeroBento.Card title=\"…\" />`. */\nfunction renderSlotChild(templateName: string, slot: SlotGroupDef): string {\n const attrs: string[] = []\n let childrenText: string | null = null\n for (const prop of slot.props) {\n if (prop.key === 'children') {\n childrenText = `${prop.label}…`\n continue\n }\n if (prop.array) {\n attrs.push(`${prop.key}={[…]}`)\n continue\n }\n const value = prop.kind === 'media' ? PLACEHOLDER_IMG : `${prop.label}…`\n attrs.push(`${prop.key}=\"${value}\"`)\n }\n const tag = `${templateName}.${slot.name}`\n const attrStr = attrs.length ? ` ${attrs.join(' ')}` : ''\n if (childrenText !== null) {\n return ` <${tag}${attrStr}>${childrenText}</${tag}>`\n }\n return ` <${tag}${attrStr} />`\n}\n\n/**\n * Synthesize a minimal, valid MDX usage example from a template's schema.\n * - Simple templates → self-closing tag with its props.\n * - Compound templates → open tag + one instance of each slot at `min` (or 1).\n */\nexport function synthExample(t: TemplateDef): string {\n const hasSlots = t.slots.length > 0\n const propLines = renderPropAttrs(t.props)\n\n if (!hasSlots) {\n if (propLines.length === 0) return `<${t.name} />`\n return [`<${t.name}`, ...propLines, '/>'].join('\\n')\n }\n\n const open = propLines.length ? [`<${t.name}`, ...propLines, '>'] : [`<${t.name}>`]\n const childLines: string[] = []\n for (const slot of t.slots) {\n const count = Math.min(Math.max(slot.min, 1), 3)\n for (let i = 0; i < count; i++) {\n childLines.push(renderSlotChild(t.name, slot))\n }\n }\n return [...open, ...childLines, `</${t.name}>`].join('\\n')\n}\n\n/**\n * Wrap a synthesized example as an insertable slide: a leading `---` separator\n * (the engine's slide break) followed by the example, so appending to a deck\n * body always starts a fresh slide.\n */\nexport function toInsertSnippet(t: TemplateDef): string {\n return `\\n---\\n\\n${synthExample(t)}\\n`\n}\n\n/** Map of `templateName -> insertable MDX snippet`, built from the live registry. */\nexport const templateSnippets: Record<string, string> = Object.fromEntries(\n listTemplates().map(t => [t.name, toInsertSnippet(t)])\n)\n","import {\n type ChangeEvent,\n type ComponentType,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { listTemplates } from '../content/template-registry'\nimport { SlidesPlayer } from '../player/slides-player'\nimport type { SlideDeckItem } from '../types'\nimport { compileDeck } from './compile-deck'\nimport { templateSnippets } from './template-snippets'\n\nexport interface DeckEditorProps {\n /** Raw deck MDX source (frontmatter + body with `---` slide separators). */\n source: string\n /**\n * Called with the next source on every edit / template insert. When omitted,\n * the editor manages source internally (uncontrolled).\n */\n onChange?: (next: string) => void\n /** Debounce window (ms) before recompiling the preview. Default 300. */\n debounceMs?: number\n}\n\ntype PreviewState = {\n Content: ComponentType\n meta: SlideDeckItem['meta']\n} | null\n\ntype EditorTheme = 'light' | 'dark'\n\n/**\n * v0.1 GUI deck editor for @atom63/slides.\n *\n * Two panes:\n * - left: a LIVE preview rendered by `SlidesPlayer`, compiled from the source\n * at runtime via {@link compileDeck} (browser-safe frontmatter split +\n * strip-imports + `@mdx-js/mdx` evaluate, with slide components injected\n * through MDX context).\n * - right: a `<textarea>` editing surface, a registry-driven template palette\n * (click inserts the template's MDX snippet at the end of the source), and a\n * light/dark theme toggle for the preview container.\n *\n * Compilation is debounced and resilient: the LAST good preview is retained on\n * a failed compile and the error is surfaced in a non-blocking banner, so a bad\n * keystroke never blanks or crashes the editor.\n *\n * The editor's own chrome is styled by self-contained plain CSS shipped as a\n * side-effect import — consumers must `import '@atom63/slides/editor/styles'`\n * (the CSS is intentionally NOT bundled into the JS so it isn't orphaned by the\n * build, mirroring how `@atom63/slides` ships `./styles`).\n *\n * A `<textarea>` is intentional\n * for v0.1; CodeMirror (syntax highlight, structured editing) is a future\n * upgrade. Structured per-slide form editing and template-switch-by-form are\n * explicitly v2 — the source textarea is the only editing surface here.\n */\nexport function DeckEditor({ source: sourceProp, onChange, debounceMs = 300 }: DeckEditorProps) {\n const isControlled = onChange !== undefined\n const [internalSource, setInternalSource] = useState(sourceProp)\n const source = isControlled ? sourceProp : internalSource\n\n const [preview, setPreview] = useState<PreviewState>(null)\n const [error, setError] = useState<string | null>(null)\n const [theme, setTheme] = useState<EditorTheme>('light')\n const textareaRef = useRef<HTMLTextAreaElement>(null)\n\n const templates = useMemo(() => listTemplates(), [])\n\n const setSource = useCallback(\n (next: string) => {\n if (isControlled) onChange(next)\n else setInternalSource(next)\n },\n [isControlled, onChange]\n )\n\n // Debounced runtime compile. Keeps the last good preview on error.\n useEffect(() => {\n let cancelled = false\n const handle = setTimeout(async () => {\n const result = await compileDeck(source)\n if (cancelled) return\n if (result.ok) {\n setPreview({ Content: result.Content, meta: result.meta })\n setError(null)\n } else {\n setError(result.error)\n }\n }, debounceMs)\n return () => {\n cancelled = true\n clearTimeout(handle)\n }\n }, [source, debounceMs])\n\n const handleChange = useCallback(\n (e: ChangeEvent<HTMLTextAreaElement>) => setSource(e.target.value),\n [setSource]\n )\n\n const handleInsertTemplate = useCallback(\n (name: string) => {\n const snippet = templateSnippets[name]\n if (!snippet) return\n const next = `${source.replace(/\\s*$/, '')}\\n${snippet}`\n setSource(next)\n // Move caret to the end so the inserted slide is visible.\n requestAnimationFrame(() => {\n const el = textareaRef.current\n if (!el) return\n el.focus()\n el.selectionStart = el.selectionEnd = el.value.length\n el.scrollTop = el.scrollHeight\n })\n },\n [source, setSource]\n )\n\n const deck: SlideDeckItem | null = useMemo(() => {\n if (!preview) return null\n return { slug: 'draft', meta: preview.meta, content: preview.Content }\n }, [preview])\n\n return (\n <div className=\"a63-editor\">\n <section className=\"a63-editor__preview\" data-theme={theme}>\n {error ? (\n <div className=\"a63-editor__error\" role=\"alert\">\n <span className=\"a63-editor__error-tag\">MDX error</span>\n <span className=\"a63-editor__error-msg\">{error}</span>\n </div>\n ) : null}\n {deck ? (\n // Remount on theme change so the player picks up the canvas theme.\n <div className=\"a63-editor__preview-stage\" key={`${theme}`}>\n <SlidesPlayer deck={deck} onBack={() => {}} />\n </div>\n ) : (\n <div className=\"a63-editor__preview-empty\">\n {error ? 'Fix the MDX error to render a preview.' : 'Compiling preview…'}\n </div>\n )}\n </section>\n\n <section className=\"a63-editor__source\">\n <div className=\"a63-editor__toolbar\">\n <span className=\"a63-editor__title\">Deck source · MDX</span>\n <button\n type=\"button\"\n className=\"a63-editor__theme-toggle\"\n onClick={() => setTheme(t => (t === 'light' ? 'dark' : 'light'))}\n aria-pressed={theme === 'dark'}\n >\n {theme === 'light' ? '☀︎ Light' : '☾ Dark'}\n </button>\n </div>\n\n <textarea\n ref={textareaRef}\n className=\"a63-editor__textarea\"\n value={source}\n onChange={handleChange}\n spellCheck={false}\n aria-label=\"Deck MDX source\"\n />\n\n <div className=\"a63-editor__palette\">\n <div className=\"a63-editor__palette-label\">Templates · click to append</div>\n <div className=\"a63-editor__palette-grid\">\n {templates.map(t => (\n <button\n key={t.name}\n type=\"button\"\n className=\"a63-editor__chip\"\n onClick={() => handleInsertTemplate(t.name)}\n title={`Insert ${t.name}`}\n >\n <span className=\"a63-editor__chip-name\">{t.name}</span>\n <span className=\"a63-editor__chip-meta\">{t.label}</span>\n </button>\n ))}\n </div>\n <p className=\"a63-editor__footnote\">\n v0.1 · plain-textarea editing (CodeMirror is a future upgrade). Per-slide form editing\n & template-switch are deferred to v2.\n </p>\n </div>\n </section>\n </div>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/editor/compile-deck.ts","../../src/editor/template-snippets.ts","../../src/editor/edit-pane.tsx","../../src/editor/deck-surface.tsx","../../src/editor/deck-editor.tsx"],"names":["useState","useMemo","jsx","jsxs"],"mappings":"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","file":"index.js","sourcesContent":["import { evaluate } from '@mdx-js/mdx'\nimport yaml from 'js-yaml'\nimport type { ComponentType } from 'react'\nimport * as runtime from 'react/jsx-runtime'\nimport remarkGfm from 'remark-gfm'\nimport { slideMdxComponents } from '../content/mdx-components'\nimport type { SlideDeckMeta } from '../types'\n\n/**\n * Browser-safe YAML frontmatter split. We deliberately avoid the common\n * \"gray matter\" style parser because it depends on Node's `Buffer`, which\n * doesn't exist in the browser — a browser library must not force consumers to\n * polyfill Node globals.\n *\n * If `source` begins with a `---` fence line, the block up to the next `---`\n * fence is parsed as YAML into `meta` and the remainder is returned as `body`.\n * Otherwise `meta` is empty and the whole source is the `body`.\n */\nexport function parseFrontmatter(source: string): { meta: Record<string, unknown>; body: string } {\n // Frontmatter must start at the very top of the file (allowing a UTF-8 BOM).\n const normalized = source.replace(/^/, '')\n const lines = normalized.split('\\n')\n if (lines[0]?.trim() !== '---') {\n return { meta: {}, body: source }\n }\n\n // Find the closing fence line.\n let end = -1\n for (let i = 1; i < lines.length; i++) {\n if (lines[i].trim() === '---') {\n end = i\n break\n }\n }\n if (end === -1) {\n // No closing fence — treat the whole thing as body, no frontmatter.\n return { meta: {}, body: source }\n }\n\n const yamlBlock = lines.slice(1, end).join('\\n')\n const body = lines.slice(end + 1).join('\\n')\n const loaded = yaml.load(yamlBlock)\n const meta = loaded && typeof loaded === 'object' ? (loaded as Record<string, unknown>) : {}\n return { meta, body }\n}\n\n/**\n * Result of a runtime deck compile.\n *\n * On success: the parsed frontmatter `meta` plus a `Content` component ready to\n * hand to `SlidesPlayer` as `deck.content`. On failure: a human-readable\n * `error` string (compile + frontmatter parsing both throw on bad input).\n */\nexport type CompileDeckResult =\n | { ok: true; meta: SlideDeckMeta; Content: ComponentType }\n | { ok: false; error: string }\n\n/**\n * Matches bare ES `import` statements at the start of a line. A deck's MDX body\n * carries lines like `import { CoverSlide } from \"@atom63/slides\"`. The runtime\n * `evaluate` below has no module resolver, so these must be stripped — the\n * components are injected via `useMDXComponents` instead (see compileDeck).\n *\n * Covers single- and multi-line import specifier blocks:\n * import Foo from \"x\"\n * import { A, B } from \"x\"\n * import {\n * A,\n * B,\n * } from \"x\"\n */\nconst IMPORT_RE = /^[ \\t]*import\\b[\\s\\S]*?(?:from\\s*['\"][^'\"]*['\"]|['\"][^'\"]*['\"])[ \\t]*;?[ \\t]*$/gm\n\n/** Remove bare `import ... from \"...\"` lines so runtime evaluate doesn't choke. */\nexport function stripImports(body: string): string {\n return body.replace(IMPORT_RE, '')\n}\n\nconst DEFAULT_META: SlideDeckMeta = {\n title: 'Untitled deck',\n date: new Date().toISOString().slice(0, 10),\n}\n\n/**\n * Compile a deck's raw MDX source into a renderable Content component using a\n * runtime MDX pipeline (no bundler step).\n *\n * Pipeline:\n * 1. `parseFrontmatter` splits the YAML frontmatter (the deck `meta`) from the\n * body using a browser-safe parser (no Node `Buffer`).\n * 2. `stripImports` removes bare `import ... from \"@atom63/slides\"` lines that\n * the runtime evaluator cannot resolve.\n * 3. `@mdx-js/mdx`'s `evaluate` compiles + runs the body. The slide components\n * (templates + primitives + markdown mappings) are provided through MDX\n * context via `useMDXComponents: () => slideMdxComponents`, so bare JSX such\n * as `<CoverSlide/>` resolves with no import. `---` thematic breaks are\n * preserved as the engine's slide separators.\n *\n * Async and can throw on malformed MDX/frontmatter — callers should debounce\n * and keep the last good render on failure (DeckEditor does this).\n */\nexport async function compileDeck(source: string): Promise<CompileDeckResult> {\n try {\n const { meta: data, body: content } = parseFrontmatter(source)\n const meta: SlideDeckMeta = { ...DEFAULT_META, ...(data as Partial<SlideDeckMeta>) }\n const body = stripImports(content)\n\n const { default: Content } = await evaluate(body, {\n ...runtime,\n useMDXComponents: () => slideMdxComponents,\n remarkPlugins: [remarkGfm],\n })\n\n return { ok: true, meta, Content: Content as ComponentType }\n } catch (err) {\n const error = err instanceof Error ? err.message : String(err)\n return { ok: false, error }\n }\n}\n","import {\n listTemplates,\n type SlotDef,\n type SlotGroupDef,\n type TemplateDef,\n} from '../content/template-registry'\n\n/**\n * Generates a minimal, paste-ready MDX snippet per template from the engine's\n * registry (`listTemplates()`), mirroring the synthesis logic in\n * `skill/scripts/gen-templates.mjs`. The palette in\n * <DeckEditor> appends `templateSnippets[name]` when a template is clicked.\n *\n * Registry is the single source of truth; snippets cannot drift from the\n * published template props/slots.\n */\n\nconst PLACEHOLDER_IMG = '/images/placeholder-1920x1080.webp'\n\nfunction exampleValue(prop: SlotDef): string {\n switch (prop.kind) {\n case 'media':\n return PLACEHOLDER_IMG\n default:\n // text / richtext / list-as-scalar\n return `${prop.label}…`\n }\n}\n\n/** Render the direct-prop attributes for a template's opening tag. */\nfunction renderPropAttrs(props: SlotDef[], indent = ' '): string[] {\n const lines: string[] = []\n for (const prop of props) {\n if (prop.key === 'children') continue\n if (prop.array) {\n if (prop.kind === 'media') {\n lines.push(\n `${indent}${prop.key}={[\"${PLACEHOLDER_IMG}\", \"${PLACEHOLDER_IMG}\", \"${PLACEHOLDER_IMG}\"]}`\n )\n } else {\n lines.push(`${indent}${prop.key}={[{ label: \"Label\", value: \"Value\", href: \"https://…\" }]}`)\n }\n continue\n }\n lines.push(`${indent}${prop.key}=\"${exampleValue(prop)}\"`)\n }\n return lines\n}\n\n/** Render one compound-slot child element, e.g. `<HeroBento.Card title=\"…\" />`. */\nfunction renderSlotChild(templateName: string, slot: SlotGroupDef): string {\n const attrs: string[] = []\n let childrenText: string | null = null\n for (const prop of slot.props) {\n if (prop.key === 'children') {\n childrenText = `${prop.label}…`\n continue\n }\n if (prop.array) {\n attrs.push(`${prop.key}={[…]}`)\n continue\n }\n const value = prop.kind === 'media' ? PLACEHOLDER_IMG : `${prop.label}…`\n attrs.push(`${prop.key}=\"${value}\"`)\n }\n const tag = `${templateName}.${slot.name}`\n const attrStr = attrs.length ? ` ${attrs.join(' ')}` : ''\n if (childrenText !== null) {\n return ` <${tag}${attrStr}>${childrenText}</${tag}>`\n }\n return ` <${tag}${attrStr} />`\n}\n\n/**\n * Synthesize a minimal, valid MDX usage example from a template's schema.\n * - Simple templates → self-closing tag with its props.\n * - Compound templates → open tag + one instance of each slot at `min` (or 1).\n */\nexport function synthExample(t: TemplateDef): string {\n const hasSlots = t.slots.length > 0\n const propLines = renderPropAttrs(t.props)\n\n if (!hasSlots) {\n if (propLines.length === 0) return `<${t.name} />`\n return [`<${t.name}`, ...propLines, '/>'].join('\\n')\n }\n\n const open = propLines.length ? [`<${t.name}`, ...propLines, '>'] : [`<${t.name}>`]\n const childLines: string[] = []\n for (const slot of t.slots) {\n const count = Math.min(Math.max(slot.min, 1), 3)\n for (let i = 0; i < count; i++) {\n childLines.push(renderSlotChild(t.name, slot))\n }\n }\n return [...open, ...childLines, `</${t.name}>`].join('\\n')\n}\n\n/**\n * Wrap a synthesized example as an insertable slide: a leading `---` separator\n * (the engine's slide break) followed by the example, so appending to a deck\n * body always starts a fresh slide.\n */\nexport function toInsertSnippet(t: TemplateDef): string {\n return `\\n---\\n\\n${synthExample(t)}\\n`\n}\n\n/** Map of `templateName -> insertable MDX snippet`, built from the live registry. */\nexport const templateSnippets: Record<string, string> = Object.fromEntries(\n listTemplates().map(t => [t.name, toInsertSnippet(t)])\n)\n","import { type ChangeEvent, type KeyboardEvent, useCallback, useMemo, useRef, useState } from 'react'\nimport { listTemplates } from '../content/template-registry'\nimport { SlidesPlayer } from '../player/slides-player'\nimport type { SlideDeckItem } from '../types'\nimport { templateSnippets } from './template-snippets'\n\nexport interface EditPaneProps {\n source: string\n onChange: (next: string) => void\n onSave?: (source: string) => void | Promise<void>\n deck: SlideDeckItem | null\n error: string | null\n onPresent: () => void\n}\n\nexport function EditPane({ source, onChange, onSave, deck, error, onPresent }: EditPaneProps) {\n const [theme, setTheme] = useState<'light' | 'dark'>('light')\n const textareaRef = useRef<HTMLTextAreaElement>(null)\n const templates = useMemo(() => listTemplates(), [])\n\n const handleChange = useCallback(\n (e: ChangeEvent<HTMLTextAreaElement>) => onChange(e.target.value),\n [onChange]\n )\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLTextAreaElement>) => {\n if ((e.metaKey || e.ctrlKey) && e.key === 's') {\n e.preventDefault()\n onSave?.(source)\n }\n },\n [onSave, source]\n )\n const insert = useCallback(\n (name: string) => {\n const snippet = templateSnippets[name]\n if (!snippet) return\n onChange(`${source.replace(/\\s*$/, '')}\\n${snippet}`)\n requestAnimationFrame(() => {\n const el = textareaRef.current\n if (!el) return\n el.focus()\n el.selectionStart = el.selectionEnd = el.value.length\n el.scrollTop = el.scrollHeight\n })\n },\n [source, onChange]\n )\n\n return (\n <div className=\"a63-editor\">\n <section className=\"a63-editor__preview\" data-theme={theme}>\n {error ? (\n <div className=\"a63-editor__error\" role=\"alert\">\n <span className=\"a63-editor__error-tag\">MDX error</span>\n <span className=\"a63-editor__error-msg\">{error}</span>\n </div>\n ) : null}\n {deck ? (\n <div className=\"a63-editor__preview-stage\" key={theme}>\n <SlidesPlayer deck={deck} onBack={() => {}} />\n </div>\n ) : (\n <div className=\"a63-editor__preview-empty\">\n {error ? 'Fix the MDX error to render a preview.' : 'Compiling preview…'}\n </div>\n )}\n </section>\n <section className=\"a63-editor__source\">\n <div className=\"a63-editor__toolbar\">\n <span className=\"a63-editor__title\">Deck source · MDX</span>\n <div className=\"a63-editor__toolbar-actions\">\n {onSave ? (\n <button type=\"button\" className=\"a63-editor__save\" onClick={() => onSave(source)}>\n Save\n </button>\n ) : null}\n <button\n type=\"button\"\n className=\"a63-editor__theme-toggle\"\n onClick={() => setTheme(t => (t === 'light' ? 'dark' : 'light'))}\n aria-pressed={theme === 'dark'}\n >\n {theme === 'light' ? '☀︎ Light' : '☾ Dark'}\n </button>\n <button type=\"button\" className=\"a63-editor__present\" onClick={onPresent}>\n Present\n </button>\n </div>\n </div>\n <textarea\n ref={textareaRef}\n className=\"a63-editor__textarea\"\n value={source}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n spellCheck={false}\n aria-label=\"Deck MDX source\"\n />\n <div className=\"a63-editor__palette\">\n <div className=\"a63-editor__palette-label\">Templates · click to append</div>\n <div className=\"a63-editor__palette-grid\">\n {templates.map(t => (\n <button\n key={t.name}\n type=\"button\"\n className=\"a63-editor__chip\"\n onClick={() => insert(t.name)}\n title={`Insert ${t.name}`}\n >\n <span className=\"a63-editor__chip-name\">{t.name}</span>\n <span className=\"a63-editor__chip-meta\">{t.label}</span>\n </button>\n ))}\n </div>\n </div>\n </section>\n </div>\n )\n}\n","import { type ComponentType, useEffect, useMemo, useState } from 'react'\nimport { SlidesPlayer } from '../player/slides-player'\nimport type { SlideDeckItem } from '../types'\nimport { compileDeck } from './compile-deck'\nimport { EditPane } from './edit-pane'\n\nexport type DeckSurfaceMode = 'present' | 'edit'\n\nexport interface DeckSurfaceProps {\n /**\n * Raw deck MDX source (frontmatter + `---`-separated slide body). The single\n * canonical input for both Present and Edit modes.\n *\n * **Controlled vs. uncontrolled:**\n * - When `onChange` is provided the surface is **controlled** — the parent\n * owns source state and must feed back the updated string on each change.\n * - When `onChange` is omitted the surface is **uncontrolled** and\n * **present-only**: `source` is read once on mount and later prop changes\n * are ignored. This is the production / read-only contract.\n */\n source: string\n /**\n * Called with the next source string on every edit or template insert\n * (in-memory, per-keystroke). Presence of this prop **enables Edit mode**\n * and makes the surface controlled — the parent holds source state.\n * When omitted the surface is present-only (no edit chrome, no keyboard\n * shortcut to enter Edit).\n */\n onChange?: (next: string) => void\n /**\n * Called on an **explicit** save action (Save button or Cmd/Ctrl-S in Edit\n * mode). This is the persistence hook, distinct from the per-keystroke\n * `onChange`. Wire it to the dev write-back plugin (or any async persistence\n * layer) to flush the source to disk on demand. No-op / absent in a\n * production build.\n */\n onSave?: (source: string) => void | Promise<void>\n /**\n * The mode the surface starts in. Defaults to `'present'`. Forced to\n * `'present'` when `onChange` is absent (present-only surfaces cannot enter\n * Edit mode).\n */\n initialMode?: DeckSurfaceMode\n /**\n * Debounce window in milliseconds before recompiling the preview after a\n * source change. Defaults to `300`. Pass `0` in tests to compile\n * synchronously on the next microtask tick.\n */\n debounceMs?: number\n}\n\ntype Preview = { Content: ComponentType; meta: SlideDeckItem['meta'] } | null\n\nexport function DeckSurface({\n source: sourceProp,\n onChange,\n onSave,\n initialMode = 'present',\n debounceMs = 300,\n}: DeckSurfaceProps) {\n const editable = onChange !== undefined\n\n // Controlled when onChange provided; uncontrolled otherwise.\n const [internalSource, setInternalSource] = useState(sourceProp)\n const source = editable ? sourceProp : internalSource\n\n const setSource = (next: string) => {\n if (editable) onChange(next)\n else setInternalSource(next)\n }\n\n const [mode, setMode] = useState<DeckSurfaceMode>(editable ? initialMode : 'present')\n\n const [preview, setPreview] = useState<Preview>(null)\n const [error, setError] = useState<string | null>(null)\n\n // Debounced runtime compile. Keeps the last good preview on error.\n useEffect(() => {\n let cancelled = false\n const h = setTimeout(async () => {\n const r = await compileDeck(source)\n if (cancelled) return\n if (r.ok) {\n setPreview({ Content: r.Content, meta: r.meta })\n setError(null)\n } else {\n setError(r.error)\n }\n }, debounceMs)\n return () => {\n cancelled = true\n clearTimeout(h)\n }\n }, [source, debounceMs])\n\n const deck: SlideDeckItem | null = useMemo(\n () => (preview ? { slug: 'draft', meta: preview.meta, content: preview.Content } : null),\n [preview]\n )\n\n // Keyboard shortcuts (only when editable).\n useEffect(() => {\n if (!editable) return\n const onKey = (e: KeyboardEvent) => {\n const typing = (e.target as HTMLElement)?.tagName === 'TEXTAREA'\n if (e.key === 'e' && !typing && mode === 'present') setMode('edit')\n if (e.key === 'Escape' && mode === 'edit') setMode('present')\n }\n window.addEventListener('keydown', onKey)\n return () => window.removeEventListener('keydown', onKey)\n }, [editable, mode])\n\n if (editable && mode === 'edit') {\n return (\n <EditPane\n source={source}\n onChange={setSource}\n onSave={onSave}\n deck={deck}\n error={error}\n onPresent={() => setMode('present')}\n />\n )\n }\n\n return (\n <div className=\"a63-surface\">\n {editable && (\n <button type=\"button\" className=\"a63-surface__edit\" onClick={() => setMode('edit')}>\n Edit\n </button>\n )}\n {deck ? (\n <SlidesPlayer deck={deck} onBack={() => {}} />\n ) : (\n <div className=\"a63-editor__preview-empty\">\n {error ? 'Fix the MDX error to render a preview.' : 'Compiling preview…'}\n </div>\n )}\n </div>\n )\n}\n","import { DeckSurface } from './deck-surface'\n\nexport interface DeckEditorProps {\n source: string\n onChange?: (next: string) => void\n debounceMs?: number\n}\n\n/**\n * Backward-compatible editor: DeckSurface locked to Edit mode. The unified\n * surface ({@link DeckSurface}) is the preferred API; this remains for existing\n * consumers.\n */\nexport function DeckEditor({ source, onChange, debounceMs }: DeckEditorProps) {\n return (\n <DeckSurface\n source={source}\n onChange={onChange ?? (() => {})}\n initialMode=\"edit\"\n debounceMs={debounceMs}\n />\n )\n}\n"]}
|
package/dist/vite/index.d.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
2
|
|
|
3
|
+
interface DeckWriteBackOptions {
|
|
4
|
+
/** Path (absolute or relative to Vite root) of the deck file to persist to. */
|
|
5
|
+
deckPath?: string;
|
|
6
|
+
/** POST endpoint. Default '/__write-deck'. */
|
|
7
|
+
endpoint?: string;
|
|
8
|
+
}
|
|
9
|
+
/** DEV-ONLY plugin: POST <endpoint> writes the request body to the deck file. */
|
|
10
|
+
declare function deckWriteBackPlugin(options?: DeckWriteBackOptions): Plugin;
|
|
11
|
+
|
|
3
12
|
interface ManifestConfig {
|
|
4
13
|
/** Content directory relative to project root */
|
|
5
14
|
contentDir: string;
|
|
@@ -27,4 +36,4 @@ declare function mdxManifestPlugin(configs?: ManifestConfig[]): Plugin;
|
|
|
27
36
|
*/
|
|
28
37
|
declare function mdxRawPlugin(): Plugin;
|
|
29
38
|
|
|
30
|
-
export { type ManifestConfig, mdxManifestPlugin, mdxRawPlugin };
|
|
39
|
+
export { type DeckWriteBackOptions, type ManifestConfig, deckWriteBackPlugin, mdxManifestPlugin, mdxRawPlugin };
|
package/dist/vite/index.js
CHANGED
|
@@ -1,9 +1,45 @@
|
|
|
1
|
+
import fs2, { writeFile } from 'fs/promises';
|
|
2
|
+
import path, { isAbsolute, resolve, relative } from 'path';
|
|
1
3
|
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
4
|
import matter from 'gray-matter';
|
|
4
|
-
import fs2 from 'fs/promises';
|
|
5
5
|
|
|
6
|
-
// src/vite/
|
|
6
|
+
// src/vite/deck-write-back-plugin.ts
|
|
7
|
+
async function handleWriteBack(cfg, body) {
|
|
8
|
+
const abs = isAbsolute(cfg.deckPath) ? cfg.deckPath : resolve(cfg.root, cfg.deckPath);
|
|
9
|
+
const rel = relative(cfg.root, abs);
|
|
10
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
11
|
+
return { ok: false, error: "deck path escapes project root" };
|
|
12
|
+
}
|
|
13
|
+
await writeFile(abs, body, "utf8");
|
|
14
|
+
return { ok: true };
|
|
15
|
+
}
|
|
16
|
+
var MAX_BODY = 5e6;
|
|
17
|
+
function deckWriteBackPlugin(options = {}) {
|
|
18
|
+
const endpoint = options.endpoint ?? "/__write-deck";
|
|
19
|
+
const deckPath = options.deckPath ?? "src/deck.mdx";
|
|
20
|
+
return {
|
|
21
|
+
name: "@atom63/slides:deck-write-back",
|
|
22
|
+
apply: "serve",
|
|
23
|
+
configureServer(server) {
|
|
24
|
+
server.middlewares.use(async (req, res, next) => {
|
|
25
|
+
if (req.method !== "POST" || req.url?.split("?")[0] !== endpoint) return next();
|
|
26
|
+
let body = "";
|
|
27
|
+
for await (const chunk of req) {
|
|
28
|
+
body += chunk;
|
|
29
|
+
if (body.length > MAX_BODY) {
|
|
30
|
+
res.statusCode = 413;
|
|
31
|
+
res.end();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const result = await handleWriteBack({ deckPath, root: server.config.root }, body);
|
|
36
|
+
res.statusCode = result.ok ? 200 : 400;
|
|
37
|
+
res.setHeader("content-type", "application/json");
|
|
38
|
+
res.end(JSON.stringify(result));
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
7
43
|
var DEFAULT_CONFIGS = [
|
|
8
44
|
{
|
|
9
45
|
contentDir: "src/content/slides",
|
|
@@ -116,6 +152,6 @@ function mdxRawPlugin() {
|
|
|
116
152
|
};
|
|
117
153
|
}
|
|
118
154
|
|
|
119
|
-
export { mdxManifestPlugin, mdxRawPlugin };
|
|
155
|
+
export { deckWriteBackPlugin, mdxManifestPlugin, mdxRawPlugin };
|
|
120
156
|
//# sourceMappingURL=index.js.map
|
|
121
157
|
//# sourceMappingURL=index.js.map
|
package/dist/vite/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/vite/mdx-manifest-plugin.ts","../../src/vite/mdx-raw-plugin.ts"],"names":["fs"],"mappings":";;;;;;AAYA,IAAM,eAAA,GAAoC;AAAA,EACxC;AAAA,IACE,UAAA,EAAY,oBAAA;AAAA,IACZ,UAAA,EAAY;AAAA;AAEhB,CAAA;AAEA,SAAS,mBAAmB,QAAA,EAAkD;AAC5E,EAAA,IAAI;AACF,IAAA,MAAM,OAAA,GAAU,EAAA,CAAG,YAAA,CAAa,QAAA,EAAU,OAAO,CAAA;AACjD,IAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAA,CAAO,OAAO,CAAA;AAC/B,IAAA,OAAO,IAAA;AAAA,EACT,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAEA,SAAS,gBAAA,CAAiB,MAAc,MAAA,EAAgC;AACtE,EAAA,MAAM,UAAA,GAAa,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM,OAAO,UAAU,CAAA;AAEvD,EAAA,IAAI,CAAC,EAAA,CAAG,UAAA,CAAW,UAAU,CAAA,EAAG;AAC9B,IAAA,OAAO,gGAAA;AAAA,EACT;AAEA,EAAA,MAAM,UAAkE,EAAC;AAEzE,EAAA,MAAM,QAAA,GAAW,EAAA,CAAG,WAAA,CAAY,UAAU,CAAA,CAAE,OAAO,CAAA,CAAA,KAAK,CAAA,CAAE,QAAA,CAAS,MAAM,CAAC,CAAA;AAC1E,EAAA,KAAA,MAAW,QAAQ,QAAA,EAAU;AAC3B,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,IAAA,CAAK,UAAA,EAAY,IAAI,CAAA;AAC3C,IAAA,IAAI,EAAA,CAAG,QAAA,CAAS,QAAQ,CAAA,CAAE,aAAY,EAAG;AACzC,IAAA,MAAM,IAAA,GAAO,mBAAmB,QAAQ,CAAA;AACxC,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,OAAA,CAAQ,IAAA,CAAK,EAAE,IAAA,EAAM,IAAA,CAAK,QAAQ,MAAA,EAAQ,EAAE,CAAA,EAAG,IAAA,EAAM,CAAA;AAAA,IACvD;AAAA,EACF;AAGA,EAAA,MAAM,UAAU,EAAA,CAAG,WAAA,CAAY,UAAU,CAAA,CAAE,OAAO,CAAA,CAAA,KAAK;AACrD,IAAA,MAAM,EAAA,GAAK,IAAA,CAAK,IAAA,CAAK,UAAA,EAAY,CAAC,CAAA;AAClC,IAAA,OAAO,EAAA,CAAG,QAAA,CAAS,EAAE,CAAA,CAAE,WAAA,EAAY;AAAA,EACrC,CAAC,CAAA;AACD,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,IAAA,CAAK,UAAA,EAAY,MAAM,CAAA;AAC/C,IAAA,MAAM,QAAA,GAAW,EAAA,CAAG,WAAA,CAAY,UAAU,CAAA,CAAE,OAAO,CAAA,CAAA,KAAK,CAAA,CAAE,QAAA,CAAS,MAAM,CAAC,CAAA;AAC1E,IAAA,KAAA,MAAW,QAAQ,QAAA,EAAU;AAC3B,MAAA,MAAM,OAAO,kBAAA,CAAmB,IAAA,CAAK,IAAA,CAAK,UAAA,EAAY,IAAI,CAAC,CAAA;AAC3D,MAAA,IAAI,IAAA,EAAM;AACR,QAAA,OAAA,CAAQ,IAAA,CAAK,EAAE,IAAA,EAAM,IAAA,CAAK,QAAQ,MAAA,EAAQ,EAAE,CAAA,EAAG,IAAA,EAAM,CAAA;AAAA,MACvD;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO;AAAA,IACL,sEAAA;AAAA,IACA,2BAA2B,IAAA,CAAK,SAAA;AAAA,MAC9B,MAAA,CAAO,WAAA,CAAY,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAA,KAAK,CAAC,CAAA,CAAE,IAAA,EAAM,CAAA,CAAE,IAAI,CAAC,CAAC,CAAA;AAAA,MACrD,IAAA;AAAA,MACA;AAAA,KACD,CAAA,SAAA,CAAA;AAAA,IACD;AAAA,GACF,CAAE,KAAK,IAAI,CAAA;AACb;AAUO,SAAS,iBAAA,CAAkB,UAA4B,eAAA,EAAyB;AACrF,EAAA,IAAI,IAAA,GAAO,EAAA;AAEX,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,qBAAA;AAAA,IAEN,eAAe,MAAA,EAAQ;AACrB,MAAA,IAAA,GAAO,MAAA,CAAO,IAAA;AAAA,IAChB,CAAA;AAAA,IAEA,UAAA,GAAa;AACX,MAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,QAAA,MAAM,UAAA,GAAa,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM,OAAO,UAAU,CAAA;AACvD,QAAA,MAAM,OAAA,GAAU,gBAAA,CAAiB,IAAA,EAAM,MAAM,CAAA;AAC7C,QAAA,MAAM,SAAA,GAAY,IAAA,CAAK,OAAA,CAAQ,UAAU,CAAA;AACzC,QAAA,IAAI,CAAC,EAAA,CAAG,UAAA,CAAW,SAAS,CAAA,EAAG;AAC7B,UAAA,EAAA,CAAG,SAAA,CAAU,SAAA,EAAW,EAAE,SAAA,EAAW,MAAM,CAAA;AAAA,QAC7C;AACA,QAAA,EAAA,CAAG,aAAA,CAAc,UAAA,EAAY,OAAA,EAAS,OAAO,CAAA;AAAA,MAC/C;AAAA,IACF,CAAA;AAAA;AAAA,IAGA,eAAA,CAAgB,EAAE,IAAA,EAAK,EAAG;AACxB,MAAA,IAAI,CAAC,IAAA,CAAK,QAAA,CAAS,MAAM,CAAA,EAAG;AAC5B,MAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,QAAA,MAAM,UAAA,GAAa,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM,OAAO,UAAU,CAAA;AACvD,QAAA,IAAI,IAAA,CAAK,UAAA,CAAW,UAAU,CAAA,EAAG;AAC/B,UAAA,MAAM,OAAA,GAAU,gBAAA,CAAiB,IAAA,EAAM,MAAM,CAAA;AAC7C,UAAA,EAAA,CAAG,aAAA,CAAc,KAAK,OAAA,CAAQ,IAAA,EAAM,OAAO,UAAU,CAAA,EAAG,SAAS,OAAO,CAAA;AAAA,QAC1E;AAAA,MACF;AAAA,IACF;AAAA,GACF;AACF;AClHA,IAAM,OAAA,GAAU,aAAA;AAChB,IAAM,cAAA,GAAiB,YAAA;AACvB,IAAM,cAAA,GAAiB,MAAA;AAWhB,SAAS,YAAA,GAAuB;AACrC,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,SAAA;AAAA,IACN,OAAA,EAAS,KAAA;AAAA,IACT,MAAM,SAAA,CAAU,EAAA,EAAI,QAAA,EAAU;AAC5B,MAAA,IAAI,CAAC,OAAA,CAAQ,IAAA,CAAK,EAAE,CAAA,EAAG;AACrB,QAAA,OAAO,IAAA;AAAA,MACT;AACA,MAAA,MAAM,KAAA,GAAQ,EAAA,CAAG,OAAA,CAAQ,QAAA,EAAU,EAAE,CAAA;AACrC,MAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,QAAA,EAAU,EAAE,QAAA,EAAU,IAAA,EAAM,CAAA;AACvE,MAAA,MAAM,QAAA,GAAW,UAAU,EAAA,IAAM,KAAA;AACjC,MAAA,OAAO,CAAA,EAAG,cAAc,CAAA,EAAG,QAAQ,GAAG,cAAc,CAAA,CAAA;AAAA,IACtD,CAAA;AAAA,IACA,MAAM,KAAK,EAAA,EAAI;AACb,MAAA,IAAI,CAAC,EAAA,CAAG,UAAA,CAAW,cAAc,CAAA,EAAG;AAClC,QAAA,OAAO,IAAA;AAAA,MACT;AACA,MAAA,MAAM,QAAA,GAAW,GAAG,KAAA,CAAM,cAAA,CAAe,QAAQ,EAAA,CAAG,MAAA,GAAS,eAAe,MAAM,CAAA;AAClF,MAAA,MAAM,IAAA,GAAO,MAAMA,GAAAA,CAAG,QAAA,CAAS,UAAU,OAAO,CAAA;AAChD,MAAA,OAAO,EAAE,MAAM,CAAA,eAAA,EAAkB,IAAA,CAAK,UAAU,IAAI,CAAC,CAAA,CAAA,EAAI,GAAA,EAAK,IAAA,EAAK;AAAA,IACrE;AAAA,GACF;AACF","file":"index.js","sourcesContent":["import fs from 'node:fs'\nimport path from 'node:path'\nimport matter from 'gray-matter'\nimport type { Plugin } from 'vite'\n\nexport interface ManifestConfig {\n /** Content directory relative to project root */\n contentDir: string\n /** Output manifest file path relative to project root */\n outputPath: string\n}\n\nconst DEFAULT_CONFIGS: ManifestConfig[] = [\n {\n contentDir: 'src/content/slides',\n outputPath: 'src/content/slides/manifest.gen.ts',\n },\n]\n\nfunction extractFrontmatter(filePath: string): Record<string, unknown> | null {\n try {\n const content = fs.readFileSync(filePath, 'utf-8')\n const { data } = matter(content)\n return data\n } catch {\n return null\n }\n}\n\nfunction generateManifest(root: string, config: ManifestConfig): string {\n const contentDir = path.resolve(root, config.contentDir)\n\n if (!fs.existsSync(contentDir)) {\n return '// Auto-generated — no content found\\nexport const manifest: Record<string, never> = {}\\n'\n }\n\n const entries: Array<{ slug: string; data: Record<string, unknown> }> = []\n\n const topLevel = fs.readdirSync(contentDir).filter(f => f.endsWith('.mdx'))\n for (const file of topLevel) {\n const filePath = path.join(contentDir, file)\n if (fs.statSync(filePath).isDirectory()) continue\n const data = extractFrontmatter(filePath)\n if (data) {\n entries.push({ slug: file.replace('.mdx', ''), data })\n }\n }\n\n // Also scan one level of subdirectories for nested content groups.\n const subdirs = fs.readdirSync(contentDir).filter(f => {\n const fp = path.join(contentDir, f)\n return fs.statSync(fp).isDirectory()\n })\n for (const subdir of subdirs) {\n const subdirPath = path.join(contentDir, subdir)\n const subFiles = fs.readdirSync(subdirPath).filter(f => f.endsWith('.mdx'))\n for (const file of subFiles) {\n const data = extractFrontmatter(path.join(subdirPath, file))\n if (data) {\n entries.push({ slug: file.replace('.mdx', ''), data })\n }\n }\n }\n\n return [\n '// Auto-generated by mdx-manifest-plugin — do not edit manually',\n `export const manifest = ${JSON.stringify(\n Object.fromEntries(entries.map(e => [e.slug, e.data])),\n null,\n 2\n )} as const`,\n '',\n ].join('\\n')\n}\n\n/**\n * Vite plugin that extracts slide-deck MDX frontmatter into a static manifest.\n *\n * Mirrors the atom63.io plugin: it decouples metadata from MDX module loading\n * (so Rollup can code-split decks into lazy chunks) and — critically for os63 —\n * regenerates `manifest.gen.ts` in dev when a deck is added/edited, so new decks\n * actually surface in the picker. The committed manifest is the build input.\n */\nexport function mdxManifestPlugin(configs: ManifestConfig[] = DEFAULT_CONFIGS): Plugin {\n let root = ''\n\n return {\n name: 'mdx-manifest-plugin',\n\n configResolved(config) {\n root = config.root\n },\n\n buildStart() {\n for (const config of configs) {\n const outputPath = path.resolve(root, config.outputPath)\n const content = generateManifest(root, config)\n const outputDir = path.dirname(outputPath)\n if (!fs.existsSync(outputDir)) {\n fs.mkdirSync(outputDir, { recursive: true })\n }\n fs.writeFileSync(outputPath, content, 'utf-8')\n }\n },\n\n // In dev, regenerate the manifest when a deck MDX file changes.\n handleHotUpdate({ file }) {\n if (!file.endsWith('.mdx')) return\n for (const config of configs) {\n const contentDir = path.resolve(root, config.contentDir)\n if (file.startsWith(contentDir)) {\n const content = generateManifest(root, config)\n fs.writeFileSync(path.resolve(root, config.outputPath), content, 'utf-8')\n }\n }\n },\n }\n}\n","import fs from 'node:fs/promises'\nimport type { Plugin } from 'vite'\n\nconst RAW_MDX = /\\.mdx\\?raw$/\nconst VIRTUAL_PREFIX = '\\0mdx-raw:'\nconst VIRTUAL_SUFFIX = '.raw'\n\n/**\n * Serve raw `.mdx?raw` imports as plain strings.\n *\n * Vite's built-in `?raw` works for most files, but `@mdx-js/rollup` strips the\n * query and compiles every `.mdx` to a component — so `?raw` would otherwise\n * yield the compiled module, not the source. This `pre` plugin resolves\n * `*.mdx?raw` to a virtual id ending in `.raw` (not `.mdx`), which MDX's\n * extension check skips, then loads the file text for it.\n */\nexport function mdxRawPlugin(): Plugin {\n return {\n name: 'mdx-raw',\n enforce: 'pre',\n async resolveId(id, importer) {\n if (!RAW_MDX.test(id)) {\n return null\n }\n const clean = id.replace(/\\?raw$/, '')\n const resolved = await this.resolve(clean, importer, { skipSelf: true })\n const filePath = resolved?.id ?? clean\n return `${VIRTUAL_PREFIX}${filePath}${VIRTUAL_SUFFIX}`\n },\n async load(id) {\n if (!id.startsWith(VIRTUAL_PREFIX)) {\n return null\n }\n const filePath = id.slice(VIRTUAL_PREFIX.length, id.length - VIRTUAL_SUFFIX.length)\n const code = await fs.readFile(filePath, 'utf-8')\n return { code: `export default ${JSON.stringify(code)}`, map: null }\n },\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/vite/deck-write-back-plugin.ts","../../src/vite/mdx-manifest-plugin.ts","../../src/vite/mdx-raw-plugin.ts"],"names":["fs"],"mappings":"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","file":"index.js","sourcesContent":["import { writeFile } from 'node:fs/promises'\nimport { isAbsolute, relative, resolve } from 'node:path'\nimport type { Plugin } from 'vite'\n\nexport interface DeckWriteBackOptions {\n /** Path (absolute or relative to Vite root) of the deck file to persist to. */\n deckPath?: string\n /** POST endpoint. Default '/__write-deck'. */\n endpoint?: string\n}\n\n/** @internal Not part of the public API — use `deckWriteBackPlugin` instead. */\nexport async function handleWriteBack(\n cfg: { deckPath: string; root: string },\n body: string\n): Promise<{ ok: boolean; error?: string }> {\n const abs = isAbsolute(cfg.deckPath) ? cfg.deckPath : resolve(cfg.root, cfg.deckPath)\n const rel = relative(cfg.root, abs)\n if (rel.startsWith('..') || isAbsolute(rel)) {\n return { ok: false, error: 'deck path escapes project root' }\n }\n await writeFile(abs, body, 'utf8')\n return { ok: true }\n}\n\nconst MAX_BODY = 5_000_000\n\n/** DEV-ONLY plugin: POST <endpoint> writes the request body to the deck file. */\nexport function deckWriteBackPlugin(options: DeckWriteBackOptions = {}): Plugin {\n const endpoint = options.endpoint ?? '/__write-deck'\n const deckPath = options.deckPath ?? 'src/deck.mdx'\n return {\n name: '@atom63/slides:deck-write-back',\n apply: 'serve',\n configureServer(server) {\n server.middlewares.use(async (req, res, next) => {\n if (req.method !== 'POST' || req.url?.split('?')[0] !== endpoint) return next()\n let body = ''\n for await (const chunk of req) {\n body += chunk\n if (body.length > MAX_BODY) {\n res.statusCode = 413\n res.end()\n return\n }\n }\n const result = await handleWriteBack({ deckPath, root: server.config.root }, body)\n res.statusCode = result.ok ? 200 : 400\n res.setHeader('content-type', 'application/json')\n res.end(JSON.stringify(result))\n })\n },\n }\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport matter from 'gray-matter'\nimport type { Plugin } from 'vite'\n\nexport interface ManifestConfig {\n /** Content directory relative to project root */\n contentDir: string\n /** Output manifest file path relative to project root */\n outputPath: string\n}\n\nconst DEFAULT_CONFIGS: ManifestConfig[] = [\n {\n contentDir: 'src/content/slides',\n outputPath: 'src/content/slides/manifest.gen.ts',\n },\n]\n\nfunction extractFrontmatter(filePath: string): Record<string, unknown> | null {\n try {\n const content = fs.readFileSync(filePath, 'utf-8')\n const { data } = matter(content)\n return data\n } catch {\n return null\n }\n}\n\nfunction generateManifest(root: string, config: ManifestConfig): string {\n const contentDir = path.resolve(root, config.contentDir)\n\n if (!fs.existsSync(contentDir)) {\n return '// Auto-generated — no content found\\nexport const manifest: Record<string, never> = {}\\n'\n }\n\n const entries: Array<{ slug: string; data: Record<string, unknown> }> = []\n\n const topLevel = fs.readdirSync(contentDir).filter(f => f.endsWith('.mdx'))\n for (const file of topLevel) {\n const filePath = path.join(contentDir, file)\n if (fs.statSync(filePath).isDirectory()) continue\n const data = extractFrontmatter(filePath)\n if (data) {\n entries.push({ slug: file.replace('.mdx', ''), data })\n }\n }\n\n // Also scan one level of subdirectories for nested content groups.\n const subdirs = fs.readdirSync(contentDir).filter(f => {\n const fp = path.join(contentDir, f)\n return fs.statSync(fp).isDirectory()\n })\n for (const subdir of subdirs) {\n const subdirPath = path.join(contentDir, subdir)\n const subFiles = fs.readdirSync(subdirPath).filter(f => f.endsWith('.mdx'))\n for (const file of subFiles) {\n const data = extractFrontmatter(path.join(subdirPath, file))\n if (data) {\n entries.push({ slug: file.replace('.mdx', ''), data })\n }\n }\n }\n\n return [\n '// Auto-generated by mdx-manifest-plugin — do not edit manually',\n `export const manifest = ${JSON.stringify(\n Object.fromEntries(entries.map(e => [e.slug, e.data])),\n null,\n 2\n )} as const`,\n '',\n ].join('\\n')\n}\n\n/**\n * Vite plugin that extracts slide-deck MDX frontmatter into a static manifest.\n *\n * Mirrors the atom63.io plugin: it decouples metadata from MDX module loading\n * (so Rollup can code-split decks into lazy chunks) and — critically for os63 —\n * regenerates `manifest.gen.ts` in dev when a deck is added/edited, so new decks\n * actually surface in the picker. The committed manifest is the build input.\n */\nexport function mdxManifestPlugin(configs: ManifestConfig[] = DEFAULT_CONFIGS): Plugin {\n let root = ''\n\n return {\n name: 'mdx-manifest-plugin',\n\n configResolved(config) {\n root = config.root\n },\n\n buildStart() {\n for (const config of configs) {\n const outputPath = path.resolve(root, config.outputPath)\n const content = generateManifest(root, config)\n const outputDir = path.dirname(outputPath)\n if (!fs.existsSync(outputDir)) {\n fs.mkdirSync(outputDir, { recursive: true })\n }\n fs.writeFileSync(outputPath, content, 'utf-8')\n }\n },\n\n // In dev, regenerate the manifest when a deck MDX file changes.\n handleHotUpdate({ file }) {\n if (!file.endsWith('.mdx')) return\n for (const config of configs) {\n const contentDir = path.resolve(root, config.contentDir)\n if (file.startsWith(contentDir)) {\n const content = generateManifest(root, config)\n fs.writeFileSync(path.resolve(root, config.outputPath), content, 'utf-8')\n }\n }\n },\n }\n}\n","import fs from 'node:fs/promises'\nimport type { Plugin } from 'vite'\n\nconst RAW_MDX = /\\.mdx\\?raw$/\nconst VIRTUAL_PREFIX = '\\0mdx-raw:'\nconst VIRTUAL_SUFFIX = '.raw'\n\n/**\n * Serve raw `.mdx?raw` imports as plain strings.\n *\n * Vite's built-in `?raw` works for most files, but `@mdx-js/rollup` strips the\n * query and compiles every `.mdx` to a component — so `?raw` would otherwise\n * yield the compiled module, not the source. This `pre` plugin resolves\n * `*.mdx?raw` to a virtual id ending in `.raw` (not `.mdx`), which MDX's\n * extension check skips, then loads the file text for it.\n */\nexport function mdxRawPlugin(): Plugin {\n return {\n name: 'mdx-raw',\n enforce: 'pre',\n async resolveId(id, importer) {\n if (!RAW_MDX.test(id)) {\n return null\n }\n const clean = id.replace(/\\?raw$/, '')\n const resolved = await this.resolve(clean, importer, { skipSelf: true })\n const filePath = resolved?.id ?? clean\n return `${VIRTUAL_PREFIX}${filePath}${VIRTUAL_SUFFIX}`\n },\n async load(id) {\n if (!id.startsWith(VIRTUAL_PREFIX)) {\n return null\n }\n const filePath = id.slice(VIRTUAL_PREFIX.length, id.length - VIRTUAL_SUFFIX.length)\n const code = await fs.readFile(filePath, 'utf-8')\n return { code: `export default ${JSON.stringify(code)}`, map: null }\n },\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atom63/slides",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
|
-
"url": "https://github.com/
|
|
7
|
+
"url": "https://github.com/atom63/slides.git",
|
|
8
8
|
"directory": "packages/slides"
|
|
9
9
|
},
|
|
10
|
-
"
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/atom63/slides/issues"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/atom63/slides/tree/main/packages/slides#readme",
|
|
11
14
|
"type": "module",
|
|
12
15
|
"description": "Host-agnostic MDX slide presentation engine — an opinionated template grammar + token theming for building decks in MDX.",
|
|
13
16
|
"keywords": [
|
|
@@ -89,10 +92,10 @@
|
|
|
89
92
|
"dependencies": {
|
|
90
93
|
"@base-ui/react": "^1.3.0",
|
|
91
94
|
"@iconify/react": "^6.0.2",
|
|
92
|
-
"class-variance-authority": "^0.7.1",
|
|
93
95
|
"@mdx-js/mdx": "^3.1.1",
|
|
94
96
|
"@mdx-js/react": "^3.1.1",
|
|
95
97
|
"@tanstack/react-virtual": "^3.14.2",
|
|
98
|
+
"class-variance-authority": "^0.7.1",
|
|
96
99
|
"clsx": "^2.1.1",
|
|
97
100
|
"gray-matter": "^4.0.3",
|
|
98
101
|
"js-yaml": "^4.1.0",
|
|
@@ -116,18 +119,23 @@
|
|
|
116
119
|
},
|
|
117
120
|
"devDependencies": {
|
|
118
121
|
"@biomejs/biome": "2.4.6",
|
|
122
|
+
"@testing-library/dom": "10.4.1",
|
|
123
|
+
"@testing-library/jest-dom": "6.9.1",
|
|
124
|
+
"@testing-library/react": "16.3.2",
|
|
119
125
|
"@types/js-yaml": "^4.0.9",
|
|
120
126
|
"@types/node": "^22.10.0",
|
|
121
127
|
"@types/react": "^19.1.13",
|
|
122
128
|
"@types/react-dom": "^19.1.9",
|
|
129
|
+
"@vitejs/plugin-react": "5.1.4",
|
|
130
|
+
"jsdom": "29.1.1",
|
|
123
131
|
"react": "^19.1.1",
|
|
124
132
|
"react-dom": "^19.1.1",
|
|
125
133
|
"tsup": "^8.3.5",
|
|
126
134
|
"typescript": "~5.8.3",
|
|
127
135
|
"vite": "^7.1.5",
|
|
128
136
|
"vitest": "^4.1.8",
|
|
129
|
-
"@atom63/
|
|
130
|
-
"@atom63/
|
|
137
|
+
"@atom63/tsconfig": "0.1.0",
|
|
138
|
+
"@atom63/biome-config": "0.1.0"
|
|
131
139
|
},
|
|
132
140
|
"scripts": {
|
|
133
141
|
"build": "tsup",
|
package/src/editor/styles.css
CHANGED
|
@@ -204,3 +204,62 @@
|
|
|
204
204
|
opacity: 0.5;
|
|
205
205
|
line-height: 1.4;
|
|
206
206
|
}
|
|
207
|
+
|
|
208
|
+
/* DeckSurface present-mode container. */
|
|
209
|
+
.a63-surface {
|
|
210
|
+
position: relative;
|
|
211
|
+
width: 100%;
|
|
212
|
+
height: 100%;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* Floating Edit button — top-right corner of present view. */
|
|
216
|
+
.a63-surface__edit {
|
|
217
|
+
position: absolute;
|
|
218
|
+
top: 0.6rem;
|
|
219
|
+
right: 0.6rem;
|
|
220
|
+
z-index: 10;
|
|
221
|
+
display: inline-flex;
|
|
222
|
+
align-items: center;
|
|
223
|
+
padding: 0.25rem 0.6rem;
|
|
224
|
+
border: 1px solid rgba(127, 127, 127, 0.4);
|
|
225
|
+
border-radius: 999px;
|
|
226
|
+
background: rgba(255, 255, 255, 0.85);
|
|
227
|
+
color: #1a1a1a;
|
|
228
|
+
font-size: 0.72rem;
|
|
229
|
+
font-weight: 600;
|
|
230
|
+
cursor: pointer;
|
|
231
|
+
backdrop-filter: blur(4px);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.a63-surface__edit:hover {
|
|
235
|
+
background: rgba(255, 255, 255, 0.95);
|
|
236
|
+
border-color: rgba(59, 130, 246, 0.6);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/* Toolbar actions group — flex row with consistent gap. */
|
|
240
|
+
.a63-editor__toolbar-actions {
|
|
241
|
+
display: flex;
|
|
242
|
+
align-items: center;
|
|
243
|
+
gap: 0.4rem;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/* Save and Present buttons — same pill look as the theme toggle. */
|
|
247
|
+
.a63-editor__save,
|
|
248
|
+
.a63-editor__present {
|
|
249
|
+
display: inline-flex;
|
|
250
|
+
align-items: center;
|
|
251
|
+
gap: 0.4rem;
|
|
252
|
+
padding: 0.25rem 0.6rem;
|
|
253
|
+
border: 1px solid rgba(127, 127, 127, 0.4);
|
|
254
|
+
border-radius: 999px;
|
|
255
|
+
background: transparent;
|
|
256
|
+
color: inherit;
|
|
257
|
+
font-size: 0.72rem;
|
|
258
|
+
font-weight: 600;
|
|
259
|
+
cursor: pointer;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.a63-editor__save:hover,
|
|
263
|
+
.a63-editor__present:hover {
|
|
264
|
+
background: rgba(127, 127, 127, 0.12);
|
|
265
|
+
}
|