hono-decks 1.2.3 → 1.3.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/README.ja.md +6 -4
- package/README.md +7 -5
- package/dist/advanced.js +115 -14
- package/dist/bin.js +1 -1
- package/dist/cli.js +1 -1
- package/dist/{init-vite-abCCwOcl.js → init-vite-BPRPonCD.js} +6 -5
- package/dist/node.js +114 -13
- package/dist/vite.js +19 -2
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -72,11 +72,13 @@ npm run build
|
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
この連携には standalone compiler/client export を持つアダプターが必要です。
|
|
75
|
-
新規インストールの既定値は `void-plugin-hono-jsx@^0.
|
|
75
|
+
新規インストールの既定値は `void-plugin-hono-jsx@^0.9.1` です。
|
|
76
76
|
`--install` は、インストール済みの互換アダプターを維持します。
|
|
77
77
|
`--install` を省略する場合は、Markdown 用の任意 peer も必要です:
|
|
78
|
-
`
|
|
79
|
-
|
|
78
|
+
`yaml@^2.9.1 shiki@^4.5.0 @shikijs/transformers@^4.5.0`。
|
|
79
|
+
|
|
80
|
+
新しい導入では `@ts-76/hono-markdown` 0.2.1以降がパーサー2パッケージを自動導入します。利用者側で `markdown-exit`・`markdown-it-container` を追加する必要はありません。既存Void 1.1用の0.6アダプターでは従来のpeer依存をinitが補完します。
|
|
81
|
+
動作確認した組み合わせは Node >=24.22.1 <25、Void 1.2.1、Vite 8.3.4、Hono 4.13.13 です。既存のVoid 1.1.11アプリでは互換性のある0.6アダプターを維持し、未導入の場合も0.6を選択します。0.9.1では[Void/Cloudflare依存の既知リスク](https://github.com/ts-76/void-plugin-hono-jsx/blob/v0.9.1/docs/dependency-security.md)が残ります。
|
|
80
82
|
|
|
81
83
|
`--install` は npm/pnpm/Yarn/Bun を packageManager または lockfile から判定し、
|
|
82
84
|
不足する依存だけを追加します。互換性のない既存バージョンは自動更新せずエラーにします。
|
|
@@ -292,7 +294,7 @@ export default defineConfig({
|
|
|
292
294
|
});
|
|
293
295
|
```
|
|
294
296
|
|
|
295
|
-
pluginはVite起動前にcompileし、deck rootと`hono-decks.config.ts`をVite watcherへ登録します。再compile
|
|
297
|
+
pluginはVite起動前にcompileし、deck rootと`hono-decks.config.ts`をVite watcherへ登録します。再compileに成功すると、安全にislandを維持できる場合はスライドの内容だけを更新し、現在のスライド・ステップとislandの状態を保ちます。islandのコード、props、Markdownのコンテキスト、構造などが変わり、安全に引き継げない場合は表示ページを再読み込みし、スライドとステップを復元します。どちらの構成でも通常の`bun run dev`だけでauthoringできます。
|
|
296
298
|
|
|
297
299
|
## Runtime configuration
|
|
298
300
|
|
package/README.md
CHANGED
|
@@ -71,10 +71,12 @@ npm run build
|
|
|
71
71
|
~~~
|
|
72
72
|
|
|
73
73
|
This integration requires an adapter with the `/markdown/compiler` and
|
|
74
|
-
`/markdown/client` exports. New installations default to `void-plugin-hono-jsx@^0.
|
|
74
|
+
`/markdown/client` exports. New installations default to `void-plugin-hono-jsx@^0.9.1`;
|
|
75
75
|
`--install` preserves an already installed compatible adapter. Without `--install`, also install the optional Markdown peers:
|
|
76
|
-
`
|
|
77
|
-
|
|
76
|
+
`yaml@^2.9.1 shiki@^4.5.0 @shikijs/transformers@^4.5.0`.
|
|
77
|
+
|
|
78
|
+
With `@ts-76/hono-markdown` 0.2.1 or newer, parser dependencies are installed automatically. Do not add `markdown-exit` or `markdown-it-container` to new apps. Existing Void 1.1 apps using adapter 0.6 retain automatic installation of its legacy parser peers.
|
|
79
|
+
The verified host versions are Void 1.2.1, Vite 8.3.4, and Hono 4.13.13 on Node >=24.22.1 <25. Existing Void 1.1.11 apps keep their compatible 0.6 adapter; if it is missing, init selects 0.6 rather than forcing a host upgrade. Adapter 0.9.1 retains the documented [Void/Cloudflare dependency risks](https://github.com/ts-76/void-plugin-hono-jsx/blob/v0.9.1/docs/dependency-security.md).
|
|
78
80
|
|
|
79
81
|
`--install` selects npm/pnpm/Yarn/Bun from packageManager or lockfiles and installs
|
|
80
82
|
missing dependencies. Incompatible installed versions require an explicit update;
|
|
@@ -293,7 +295,7 @@ export default defineConfig({
|
|
|
293
295
|
});
|
|
294
296
|
~~~
|
|
295
297
|
|
|
296
|
-
The plugin compiles before Vite starts and adds the deck root and <code>hono-decks.config.ts</code> to the Vite watcher. After a successful recompile, it
|
|
298
|
+
The plugin compiles before Vite starts and adds the deck root and <code>hono-decks.config.ts</code> to the Vite watcher. After a successful recompile, open presentation pages update their deck content without reloading when it is safe to retain the existing islands. The current slide, step, and island state are preserved. If the update cannot safely retain islands (for example, their code, props, Markdown context, or structure changed), the presentation page reloads instead and restores the slide and step. In both setups, the application's normal <code>dev</code> command is enough for authoring.
|
|
297
299
|
|
|
298
300
|
## Runtime configuration
|
|
299
301
|
|
|
@@ -467,7 +469,7 @@ At this level, the application owns the mount path, source policy, and option me
|
|
|
467
469
|
## Void Hono Markdown
|
|
468
470
|
|
|
469
471
|
Set `build.markdown: "void"` in `hono-decks.config.ts` to opt into `.md` decks
|
|
470
|
-
alongside MDX. This requires `void-plugin-hono-jsx@^0.
|
|
472
|
+
alongside MDX. This requires `void-plugin-hono-jsx@^0.9.1` with its standalone compiler and client exports.
|
|
471
473
|
See the [Void consumer fixture](https://github.com/ts-76/hono-decks/tree/main/fixtures/void-markdown)
|
|
472
474
|
for package verification, Void middleware hosting, supported syntax and limitations.
|
|
473
475
|
|
package/dist/advanced.js
CHANGED
|
@@ -804,19 +804,94 @@ function escapeHtml$7(value) {
|
|
|
804
804
|
function renderLiveReloadScript(eventsPath, nonce) {
|
|
805
805
|
return `<script${documentNonceAttribute(nonce)}>
|
|
806
806
|
(() => {
|
|
807
|
-
const
|
|
808
|
-
|
|
809
|
-
|
|
807
|
+
const events = new EventSource(${JSON.stringify(eventsPath)});
|
|
808
|
+
let running = false;
|
|
809
|
+
let pending = false;
|
|
810
|
+
const selector = "[data-hono-decks-island],[data-hono-md-island]";
|
|
811
|
+
const clientSelector = "script[data-hono-decks-client-entry]";
|
|
812
|
+
const clientText = async (url) => {
|
|
813
|
+
if (!url) return "";
|
|
814
|
+
const response = await fetch(url, { cache: "no-store" });
|
|
815
|
+
if (!response.ok) throw new Error("Client entry fetch failed");
|
|
816
|
+
return response.text();
|
|
817
|
+
};
|
|
818
|
+
const domReady = document.readyState === "loading"
|
|
819
|
+
? new Promise((resolve) => document.addEventListener("DOMContentLoaded", resolve, { once: true }))
|
|
820
|
+
: Promise.resolve();
|
|
821
|
+
let clientSnapshot = domReady.then(() => clientText(document.querySelector(clientSelector)?.src)).catch(() => null);
|
|
822
|
+
|
|
823
|
+
function signature(element) {
|
|
824
|
+
const attributes = element.hasAttribute("data-hono-md-island")
|
|
825
|
+
? ["data-hono-md-island", "data-hono-md-hydrate", "data-hono-md-props"]
|
|
826
|
+
: ["data-hono-decks-island", "data-hono-decks-props"];
|
|
827
|
+
return JSON.stringify([attributes.map((name) => element.getAttribute(name)),
|
|
828
|
+
element.closest("[data-hono-md-context]")?.getAttribute("data-hono-md-context") ?? null]);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
async function update() {
|
|
832
|
+
const response = await fetch(location.href, { cache: "no-store" });
|
|
833
|
+
if (!response.ok || !response.headers.get("content-type")?.includes("text/html")) throw new Error("Deck fetch failed");
|
|
834
|
+
const next = new DOMParser().parseFromString(await response.text(), "text/html");
|
|
835
|
+
const currentDeck = document.querySelector("[data-hono-decks-deck]");
|
|
836
|
+
const nextDeck = next.querySelector("[data-hono-decks-deck]");
|
|
837
|
+
const style = document.querySelector("[data-hono-decks-presentation-style]");
|
|
838
|
+
const nextStyle = next.querySelector("[data-hono-decks-presentation-style]");
|
|
839
|
+
const runtime = window.__honoDecksPresentationRuntime;
|
|
840
|
+
if (!currentDeck || !nextDeck || !style || !nextStyle || !runtime?.refresh) throw new Error("Invalid deck document");
|
|
841
|
+
const oldSlides = Array.from(currentDeck.querySelectorAll(".slide"));
|
|
842
|
+
const newSlides = Array.from(nextDeck.querySelectorAll(".slide"));
|
|
843
|
+
if (!newSlides.length) throw new Error("Empty deck");
|
|
844
|
+
// Scripts parsed from fetched HTML are inert; reload so their behavior is not lost.
|
|
845
|
+
if (currentDeck.querySelector("script") || nextDeck.querySelector("script")) throw new Error("Deck contains scripts");
|
|
846
|
+
const oldIslands = oldSlides.flatMap((slide) => Array.from(slide.querySelectorAll(selector)));
|
|
847
|
+
const nextScript = next.querySelector(clientSelector);
|
|
848
|
+
const nextClient = await clientText(nextScript?.getAttribute("src"));
|
|
849
|
+
const previousClient = await clientSnapshot;
|
|
850
|
+
if (previousClient === null) throw new Error("Initial client entry fetch failed");
|
|
851
|
+
// Changed client code cannot safely replace a running component while retaining its state.
|
|
852
|
+
if (oldIslands.length && previousClient !== nextClient) throw new Error("Client code changed");
|
|
853
|
+
const pairs = [];
|
|
854
|
+
if (oldIslands.length && oldSlides.length !== newSlides.length) throw new Error("Slide structure changed");
|
|
855
|
+
for (let i = 0; i < oldSlides.length && i < newSlides.length; i++) {
|
|
856
|
+
const before = Array.from(oldSlides[i].querySelectorAll(selector));
|
|
857
|
+
const after = Array.from(newSlides[i].querySelectorAll(selector));
|
|
858
|
+
if (before.length !== after.length) throw new Error("Island structure changed");
|
|
859
|
+
for (let j = 0; j < before.length; j++) {
|
|
860
|
+
if (signature(before[j]) !== signature(after[j])) throw new Error("Island properties changed");
|
|
861
|
+
// Deferred Markdown islands observe their original slide. Reload rather than strand the observer.
|
|
862
|
+
if (before[j].hasAttribute("data-hono-md-island") && !before[j].hasAttribute("data-hono-md-mounted"))
|
|
863
|
+
throw new Error("Island is pending hydration");
|
|
864
|
+
pairs.push([before[j], after[j]]);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
if (!oldIslands.length && nextDeck.querySelector(selector)) throw new Error("New islands require initialization");
|
|
868
|
+
for (const [oldIsland, newIsland] of pairs) newIsland.replaceWith(oldIsland);
|
|
869
|
+
currentDeck.replaceChildren(...nextDeck.childNodes);
|
|
870
|
+
style.textContent = nextStyle.textContent;
|
|
871
|
+
document.title = next.title;
|
|
872
|
+
clientSnapshot = Promise.resolve(nextClient);
|
|
873
|
+
runtime.refresh();
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
async function onUpdate() {
|
|
877
|
+
if (running) { pending = true; return; }
|
|
878
|
+
running = true;
|
|
879
|
+
try {
|
|
880
|
+
do { pending = false; await update(); } while (pending);
|
|
881
|
+
} catch (error) { console.warn("[hono-decks] Live update failed; reloading", error); location.reload(); }
|
|
882
|
+
finally { running = false; }
|
|
883
|
+
}
|
|
884
|
+
events.addEventListener("deck:updated", onUpdate);
|
|
810
885
|
})();
|
|
811
886
|
<\/script>`;
|
|
812
887
|
}
|
|
813
888
|
function renderClientEntryScript(clientEntry, nonce) {
|
|
814
|
-
return `<script type="module" src="${escapeHtml$6(clientEntry)}"${documentNonceAttribute(nonce)}><\/script>`;
|
|
889
|
+
return `<script data-hono-decks-client-entry type="module" src="${escapeHtml$6(clientEntry)}"${documentNonceAttribute(nonce)}><\/script>`;
|
|
815
890
|
}
|
|
816
891
|
function renderPresentationScript(nonce) {
|
|
817
892
|
return `<script${documentNonceAttribute(nonce)}>
|
|
818
893
|
(() => {
|
|
819
|
-
|
|
894
|
+
let slides = Array.from(document.querySelectorAll(".slide"));
|
|
820
895
|
const stage = document.querySelector("[data-hono-decks-stage]");
|
|
821
896
|
const deck = document.querySelector("[data-hono-decks-deck]");
|
|
822
897
|
const DESIGN_WIDTH = ${PRESENTATION_DESIGN_WIDTH};
|
|
@@ -1090,6 +1165,7 @@ function renderPresentationScript(nonce) {
|
|
|
1090
1165
|
updateFires(nextStepIndex);
|
|
1091
1166
|
publishState();
|
|
1092
1167
|
requestAnimationFrame(() => {
|
|
1168
|
+
if (token !== transitionToken) return;
|
|
1093
1169
|
setSlideState(outgoing, "leaving", direction, transition, timing);
|
|
1094
1170
|
setSlideState(incoming, "active", direction, transition, timing);
|
|
1095
1171
|
waitForSlideTransition(outgoing, incoming, direction, token);
|
|
@@ -1196,13 +1272,38 @@ function renderPresentationScript(nonce) {
|
|
|
1196
1272
|
await document.documentElement.requestFullscreen?.();
|
|
1197
1273
|
}
|
|
1198
1274
|
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1275
|
+
function bindSlideClicks() {
|
|
1276
|
+
slides.forEach((slide, slideIndex) => {
|
|
1277
|
+
slide.addEventListener("click", () => {
|
|
1278
|
+
if (!document.body.hasAttribute("data-overview-mode")) return;
|
|
1279
|
+
document.body.removeAttribute("data-overview-mode");
|
|
1280
|
+
show(slideIndex);
|
|
1281
|
+
});
|
|
1204
1282
|
});
|
|
1205
|
-
}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
function refresh() {
|
|
1286
|
+
transitionToken += 1;
|
|
1287
|
+
isTransitioning = false;
|
|
1288
|
+
pendingNavigation = null;
|
|
1289
|
+
slides = Array.from(deck?.querySelectorAll(".slide") ?? []);
|
|
1290
|
+
bindSlideClicks();
|
|
1291
|
+
if (document.body.hasAttribute("data-overview-mode")) {
|
|
1292
|
+
slides.forEach((slide) => { slide.hidden = false; slide.setAttribute("data-slide-state", "active"); });
|
|
1293
|
+
setFiresVisible(Array.from(deck?.querySelectorAll("[data-hono-decks-fire]") ?? []), true);
|
|
1294
|
+
index = Math.max(0, Math.min(slides.length - 1, index));
|
|
1295
|
+
stepCount = fireCountForSlide(index);
|
|
1296
|
+
stepIndex = Math.max(0, Math.min(stepCount, stepIndex));
|
|
1297
|
+
publishState();
|
|
1298
|
+
} else {
|
|
1299
|
+
applyInstantSlideChange(index, stepIndex);
|
|
1300
|
+
}
|
|
1301
|
+
document.querySelectorAll(".speaker-notes").forEach((note) => {
|
|
1302
|
+
note.hidden = !document.body.hasAttribute("data-presenter-mode");
|
|
1303
|
+
});
|
|
1304
|
+
fitDeck();
|
|
1305
|
+
}
|
|
1306
|
+
bindSlideClicks();
|
|
1206
1307
|
|
|
1207
1308
|
document.addEventListener("keydown", (event) => {
|
|
1208
1309
|
if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.altKey) return;
|
|
@@ -1225,7 +1326,7 @@ function renderPresentationScript(nonce) {
|
|
|
1225
1326
|
handleCommand(message.action, message.index, message.stepIndex);
|
|
1226
1327
|
});
|
|
1227
1328
|
|
|
1228
|
-
window.__honoDecksPresentationRuntime = { command: handleCommand };
|
|
1329
|
+
window.__honoDecksPresentationRuntime = { command: handleCommand, refresh };
|
|
1229
1330
|
|
|
1230
1331
|
window.addEventListener("resize", fitDeck);
|
|
1231
1332
|
if (typeof ResizeObserver !== "undefined" && stage instanceof HTMLElement) {
|
|
@@ -1261,7 +1362,7 @@ function renderCompiledDeckPage(input) {
|
|
|
1261
1362
|
<meta charset="utf-8" />
|
|
1262
1363
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1263
1364
|
<title>${escapeHtml$5(presentationPageTitle(deck))}</title>
|
|
1264
|
-
<style${nonceAttribute}>${basePresentationStyle()}${deck.themeStyle ?? ""}${input.style ?? ""}</style>
|
|
1365
|
+
<style data-hono-decks-presentation-style${nonceAttribute}>${basePresentationStyle()}${deck.themeStyle ?? ""}${input.style ?? ""}</style>
|
|
1265
1366
|
${document.head}
|
|
1266
1367
|
</head>
|
|
1267
1368
|
<body${bodyAttrs.length ? ` ${bodyAttrs.join(" ")}` : ""}>
|
|
@@ -1292,7 +1393,7 @@ async function renderCompiledDeckPageAsync(input) {
|
|
|
1292
1393
|
<meta charset="utf-8" />
|
|
1293
1394
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1294
1395
|
<title>${escapeHtml$5(presentationPageTitle(deck))}</title>
|
|
1295
|
-
<style${nonceAttribute}>${basePresentationStyle()}${deck.themeStyle ?? ""}${input.style ?? ""}</style>
|
|
1396
|
+
<style data-hono-decks-presentation-style${nonceAttribute}>${basePresentationStyle()}${deck.themeStyle ?? ""}${input.style ?? ""}</style>
|
|
1296
1397
|
${document.head}
|
|
1297
1398
|
</head>
|
|
1298
1399
|
<body${bodyAttrs.length ? ` ${bodyAttrs.join(" ")}` : ""}>
|
package/dist/bin.js
CHANGED
|
@@ -2350,7 +2350,7 @@ async function runInitCommand(input, args, stdout, stderr) {
|
|
|
2350
2350
|
return { exitCode: 1 };
|
|
2351
2351
|
}
|
|
2352
2352
|
if (parsed.options.framework) try {
|
|
2353
|
-
const { initializeVite } = await import("./init-vite-
|
|
2353
|
+
const { initializeVite } = await import("./init-vite-BPRPonCD.js");
|
|
2354
2354
|
const sampleSlug = await initializeVite(input.cwd, parsed.options.framework, parsed.options.configFile, parsed.options.entry, parsed.options.install ? { adapter: parsed.options.adapter } : void 0);
|
|
2355
2355
|
stdout(`Initialized ${parsed.options.framework} decks at /slides/${sampleSlug}. Run your usual dev or build command; no manual compile is needed.`);
|
|
2356
2356
|
return { exitCode: 0 };
|
package/dist/cli.js
CHANGED
|
@@ -2349,7 +2349,7 @@ async function runInitCommand(input, args, stdout, stderr) {
|
|
|
2349
2349
|
return { exitCode: 1 };
|
|
2350
2350
|
}
|
|
2351
2351
|
if (parsed.options.framework) try {
|
|
2352
|
-
const { initializeVite } = await import("./init-vite-
|
|
2352
|
+
const { initializeVite } = await import("./init-vite-BPRPonCD.js");
|
|
2353
2353
|
const sampleSlug = await initializeVite(input.cwd, parsed.options.framework, parsed.options.configFile, parsed.options.entry, parsed.options.install ? { adapter: parsed.options.adapter } : void 0);
|
|
2354
2354
|
stdout(`Initialized ${parsed.options.framework} decks at /slides/${sampleSlug}. Run your usual dev or build command; no manual compile is needed.`);
|
|
2355
2355
|
return { exitCode: 0 };
|
|
@@ -12,12 +12,12 @@ const voidDependencies = [
|
|
|
12
12
|
["void", "void@>=1.2.1 <1.3.0"],
|
|
13
13
|
["vite", "vite@^8.0.16"],
|
|
14
14
|
["hono", "hono@^4.13.13"],
|
|
15
|
-
["markdown-exit", "markdown-exit@^1.3.0"],
|
|
16
15
|
["yaml", "yaml@^2.9.1"],
|
|
17
|
-
["markdown-it-container", "markdown-it-container@^4.0.0"],
|
|
18
16
|
["shiki", "shiki@^4.5.0"],
|
|
19
17
|
["@shikijs/transformers", "@shikijs/transformers@^4.5.0"]
|
|
20
18
|
];
|
|
19
|
+
/** Pre-0.9 adapters on existing Void 1.1 hosts still use parser peers. */
|
|
20
|
+
const legacyMarkdownDependencies = [["markdown-exit", "markdown-exit@^1.3.0"], ["markdown-it-container", "markdown-it-container@^4.0.0"]];
|
|
21
21
|
//#endregion
|
|
22
22
|
//#region src/node/install-void.ts
|
|
23
23
|
async function installVoidDependencies(cwd, options = {}) {
|
|
@@ -46,8 +46,9 @@ async function installVoidDependencies(cwd, options = {}) {
|
|
|
46
46
|
return true;
|
|
47
47
|
}
|
|
48
48
|
};
|
|
49
|
-
const
|
|
50
|
-
let legacyVoid =
|
|
49
|
+
const hostVersion = missing("void") ? void 0 : await installedVersion(require.resolve("void"), "void");
|
|
50
|
+
let legacyVoid = /^1\.1\./.test(hostVersion ?? "");
|
|
51
|
+
const required = [...voidDependencies, ...legacyVoid ? legacyMarkdownDependencies : []];
|
|
51
52
|
for (const [id, spec] of required) {
|
|
52
53
|
if (missing(id)) continue;
|
|
53
54
|
const version = await installedVersion(require.resolve(id), id);
|
|
@@ -83,7 +84,7 @@ async function installVoidDependencies(cwd, options = {}) {
|
|
|
83
84
|
specs.push(`hono-decks@${requested}`);
|
|
84
85
|
}
|
|
85
86
|
if (options.adapter || missing("void-plugin-hono-jsx/markdown/compiler")) {
|
|
86
|
-
let adapter = options.adapter ?? `void-plugin-hono-jsx@${legacyVoid ? "^0.6.0" : "^0.
|
|
87
|
+
let adapter = options.adapter ?? `void-plugin-hono-jsx@${legacyVoid ? "^0.6.0" : "^0.9.1"}`;
|
|
87
88
|
if (/^\d+\./.test(adapter)) adapter = "void-plugin-hono-jsx@" + adapter;
|
|
88
89
|
else if (modernYarn && (adapter.endsWith(".tgz") || adapter.startsWith("file:"))) adapter = "void-plugin-hono-jsx@file:" + resolve(cwd, adapter.replace(/^file:/, ""));
|
|
89
90
|
specs.push(adapter);
|
package/dist/node.js
CHANGED
|
@@ -3523,19 +3523,94 @@ function escapeHtml$8(value) {
|
|
|
3523
3523
|
function renderLiveReloadScript(eventsPath, nonce) {
|
|
3524
3524
|
return `<script${documentNonceAttribute(nonce)}>
|
|
3525
3525
|
(() => {
|
|
3526
|
-
const
|
|
3527
|
-
|
|
3528
|
-
|
|
3526
|
+
const events = new EventSource(${JSON.stringify(eventsPath)});
|
|
3527
|
+
let running = false;
|
|
3528
|
+
let pending = false;
|
|
3529
|
+
const selector = "[data-hono-decks-island],[data-hono-md-island]";
|
|
3530
|
+
const clientSelector = "script[data-hono-decks-client-entry]";
|
|
3531
|
+
const clientText = async (url) => {
|
|
3532
|
+
if (!url) return "";
|
|
3533
|
+
const response = await fetch(url, { cache: "no-store" });
|
|
3534
|
+
if (!response.ok) throw new Error("Client entry fetch failed");
|
|
3535
|
+
return response.text();
|
|
3536
|
+
};
|
|
3537
|
+
const domReady = document.readyState === "loading"
|
|
3538
|
+
? new Promise((resolve) => document.addEventListener("DOMContentLoaded", resolve, { once: true }))
|
|
3539
|
+
: Promise.resolve();
|
|
3540
|
+
let clientSnapshot = domReady.then(() => clientText(document.querySelector(clientSelector)?.src)).catch(() => null);
|
|
3541
|
+
|
|
3542
|
+
function signature(element) {
|
|
3543
|
+
const attributes = element.hasAttribute("data-hono-md-island")
|
|
3544
|
+
? ["data-hono-md-island", "data-hono-md-hydrate", "data-hono-md-props"]
|
|
3545
|
+
: ["data-hono-decks-island", "data-hono-decks-props"];
|
|
3546
|
+
return JSON.stringify([attributes.map((name) => element.getAttribute(name)),
|
|
3547
|
+
element.closest("[data-hono-md-context]")?.getAttribute("data-hono-md-context") ?? null]);
|
|
3548
|
+
}
|
|
3549
|
+
|
|
3550
|
+
async function update() {
|
|
3551
|
+
const response = await fetch(location.href, { cache: "no-store" });
|
|
3552
|
+
if (!response.ok || !response.headers.get("content-type")?.includes("text/html")) throw new Error("Deck fetch failed");
|
|
3553
|
+
const next = new DOMParser().parseFromString(await response.text(), "text/html");
|
|
3554
|
+
const currentDeck = document.querySelector("[data-hono-decks-deck]");
|
|
3555
|
+
const nextDeck = next.querySelector("[data-hono-decks-deck]");
|
|
3556
|
+
const style = document.querySelector("[data-hono-decks-presentation-style]");
|
|
3557
|
+
const nextStyle = next.querySelector("[data-hono-decks-presentation-style]");
|
|
3558
|
+
const runtime = window.__honoDecksPresentationRuntime;
|
|
3559
|
+
if (!currentDeck || !nextDeck || !style || !nextStyle || !runtime?.refresh) throw new Error("Invalid deck document");
|
|
3560
|
+
const oldSlides = Array.from(currentDeck.querySelectorAll(".slide"));
|
|
3561
|
+
const newSlides = Array.from(nextDeck.querySelectorAll(".slide"));
|
|
3562
|
+
if (!newSlides.length) throw new Error("Empty deck");
|
|
3563
|
+
// Scripts parsed from fetched HTML are inert; reload so their behavior is not lost.
|
|
3564
|
+
if (currentDeck.querySelector("script") || nextDeck.querySelector("script")) throw new Error("Deck contains scripts");
|
|
3565
|
+
const oldIslands = oldSlides.flatMap((slide) => Array.from(slide.querySelectorAll(selector)));
|
|
3566
|
+
const nextScript = next.querySelector(clientSelector);
|
|
3567
|
+
const nextClient = await clientText(nextScript?.getAttribute("src"));
|
|
3568
|
+
const previousClient = await clientSnapshot;
|
|
3569
|
+
if (previousClient === null) throw new Error("Initial client entry fetch failed");
|
|
3570
|
+
// Changed client code cannot safely replace a running component while retaining its state.
|
|
3571
|
+
if (oldIslands.length && previousClient !== nextClient) throw new Error("Client code changed");
|
|
3572
|
+
const pairs = [];
|
|
3573
|
+
if (oldIslands.length && oldSlides.length !== newSlides.length) throw new Error("Slide structure changed");
|
|
3574
|
+
for (let i = 0; i < oldSlides.length && i < newSlides.length; i++) {
|
|
3575
|
+
const before = Array.from(oldSlides[i].querySelectorAll(selector));
|
|
3576
|
+
const after = Array.from(newSlides[i].querySelectorAll(selector));
|
|
3577
|
+
if (before.length !== after.length) throw new Error("Island structure changed");
|
|
3578
|
+
for (let j = 0; j < before.length; j++) {
|
|
3579
|
+
if (signature(before[j]) !== signature(after[j])) throw new Error("Island properties changed");
|
|
3580
|
+
// Deferred Markdown islands observe their original slide. Reload rather than strand the observer.
|
|
3581
|
+
if (before[j].hasAttribute("data-hono-md-island") && !before[j].hasAttribute("data-hono-md-mounted"))
|
|
3582
|
+
throw new Error("Island is pending hydration");
|
|
3583
|
+
pairs.push([before[j], after[j]]);
|
|
3584
|
+
}
|
|
3585
|
+
}
|
|
3586
|
+
if (!oldIslands.length && nextDeck.querySelector(selector)) throw new Error("New islands require initialization");
|
|
3587
|
+
for (const [oldIsland, newIsland] of pairs) newIsland.replaceWith(oldIsland);
|
|
3588
|
+
currentDeck.replaceChildren(...nextDeck.childNodes);
|
|
3589
|
+
style.textContent = nextStyle.textContent;
|
|
3590
|
+
document.title = next.title;
|
|
3591
|
+
clientSnapshot = Promise.resolve(nextClient);
|
|
3592
|
+
runtime.refresh();
|
|
3593
|
+
}
|
|
3594
|
+
|
|
3595
|
+
async function onUpdate() {
|
|
3596
|
+
if (running) { pending = true; return; }
|
|
3597
|
+
running = true;
|
|
3598
|
+
try {
|
|
3599
|
+
do { pending = false; await update(); } while (pending);
|
|
3600
|
+
} catch (error) { console.warn("[hono-decks] Live update failed; reloading", error); location.reload(); }
|
|
3601
|
+
finally { running = false; }
|
|
3602
|
+
}
|
|
3603
|
+
events.addEventListener("deck:updated", onUpdate);
|
|
3529
3604
|
})();
|
|
3530
3605
|
<\/script>`;
|
|
3531
3606
|
}
|
|
3532
3607
|
function renderClientEntryScript(clientEntry, nonce) {
|
|
3533
|
-
return `<script type="module" src="${escapeHtml$7(clientEntry)}"${documentNonceAttribute(nonce)}><\/script>`;
|
|
3608
|
+
return `<script data-hono-decks-client-entry type="module" src="${escapeHtml$7(clientEntry)}"${documentNonceAttribute(nonce)}><\/script>`;
|
|
3534
3609
|
}
|
|
3535
3610
|
function renderPresentationScript(nonce) {
|
|
3536
3611
|
return `<script${documentNonceAttribute(nonce)}>
|
|
3537
3612
|
(() => {
|
|
3538
|
-
|
|
3613
|
+
let slides = Array.from(document.querySelectorAll(".slide"));
|
|
3539
3614
|
const stage = document.querySelector("[data-hono-decks-stage]");
|
|
3540
3615
|
const deck = document.querySelector("[data-hono-decks-deck]");
|
|
3541
3616
|
const DESIGN_WIDTH = ${PRESENTATION_DESIGN_WIDTH};
|
|
@@ -3809,6 +3884,7 @@ function renderPresentationScript(nonce) {
|
|
|
3809
3884
|
updateFires(nextStepIndex);
|
|
3810
3885
|
publishState();
|
|
3811
3886
|
requestAnimationFrame(() => {
|
|
3887
|
+
if (token !== transitionToken) return;
|
|
3812
3888
|
setSlideState(outgoing, "leaving", direction, transition, timing);
|
|
3813
3889
|
setSlideState(incoming, "active", direction, transition, timing);
|
|
3814
3890
|
waitForSlideTransition(outgoing, incoming, direction, token);
|
|
@@ -3915,13 +3991,38 @@ function renderPresentationScript(nonce) {
|
|
|
3915
3991
|
await document.documentElement.requestFullscreen?.();
|
|
3916
3992
|
}
|
|
3917
3993
|
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3994
|
+
function bindSlideClicks() {
|
|
3995
|
+
slides.forEach((slide, slideIndex) => {
|
|
3996
|
+
slide.addEventListener("click", () => {
|
|
3997
|
+
if (!document.body.hasAttribute("data-overview-mode")) return;
|
|
3998
|
+
document.body.removeAttribute("data-overview-mode");
|
|
3999
|
+
show(slideIndex);
|
|
4000
|
+
});
|
|
3923
4001
|
});
|
|
3924
|
-
}
|
|
4002
|
+
}
|
|
4003
|
+
|
|
4004
|
+
function refresh() {
|
|
4005
|
+
transitionToken += 1;
|
|
4006
|
+
isTransitioning = false;
|
|
4007
|
+
pendingNavigation = null;
|
|
4008
|
+
slides = Array.from(deck?.querySelectorAll(".slide") ?? []);
|
|
4009
|
+
bindSlideClicks();
|
|
4010
|
+
if (document.body.hasAttribute("data-overview-mode")) {
|
|
4011
|
+
slides.forEach((slide) => { slide.hidden = false; slide.setAttribute("data-slide-state", "active"); });
|
|
4012
|
+
setFiresVisible(Array.from(deck?.querySelectorAll("[data-hono-decks-fire]") ?? []), true);
|
|
4013
|
+
index = Math.max(0, Math.min(slides.length - 1, index));
|
|
4014
|
+
stepCount = fireCountForSlide(index);
|
|
4015
|
+
stepIndex = Math.max(0, Math.min(stepCount, stepIndex));
|
|
4016
|
+
publishState();
|
|
4017
|
+
} else {
|
|
4018
|
+
applyInstantSlideChange(index, stepIndex);
|
|
4019
|
+
}
|
|
4020
|
+
document.querySelectorAll(".speaker-notes").forEach((note) => {
|
|
4021
|
+
note.hidden = !document.body.hasAttribute("data-presenter-mode");
|
|
4022
|
+
});
|
|
4023
|
+
fitDeck();
|
|
4024
|
+
}
|
|
4025
|
+
bindSlideClicks();
|
|
3925
4026
|
|
|
3926
4027
|
document.addEventListener("keydown", (event) => {
|
|
3927
4028
|
if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.altKey) return;
|
|
@@ -3944,7 +4045,7 @@ function renderPresentationScript(nonce) {
|
|
|
3944
4045
|
handleCommand(message.action, message.index, message.stepIndex);
|
|
3945
4046
|
});
|
|
3946
4047
|
|
|
3947
|
-
window.__honoDecksPresentationRuntime = { command: handleCommand };
|
|
4048
|
+
window.__honoDecksPresentationRuntime = { command: handleCommand, refresh };
|
|
3948
4049
|
|
|
3949
4050
|
window.addEventListener("resize", fitDeck);
|
|
3950
4051
|
if (typeof ResizeObserver !== "undefined" && stage instanceof HTMLElement) {
|
|
@@ -3980,7 +4081,7 @@ async function renderCompiledDeckPageAsync(input) {
|
|
|
3980
4081
|
<meta charset="utf-8" />
|
|
3981
4082
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
3982
4083
|
<title>${escapeHtml$6(presentationPageTitle(deck))}</title>
|
|
3983
|
-
<style${nonceAttribute}>${basePresentationStyle()}${deck.themeStyle ?? ""}${input.style ?? ""}</style>
|
|
4084
|
+
<style data-hono-decks-presentation-style${nonceAttribute}>${basePresentationStyle()}${deck.themeStyle ?? ""}${input.style ?? ""}</style>
|
|
3984
4085
|
${document.head}
|
|
3985
4086
|
</head>
|
|
3986
4087
|
<body${bodyAttrs.length ? ` ${bodyAttrs.join(" ")}` : ""}>
|
package/dist/vite.js
CHANGED
|
@@ -2307,6 +2307,8 @@ function honoDecks(options = {}) {
|
|
|
2307
2307
|
let mountChanged = false;
|
|
2308
2308
|
let modules = /* @__PURE__ */ new Map();
|
|
2309
2309
|
let routerModulePath = "";
|
|
2310
|
+
const eventClients = /* @__PURE__ */ new Set();
|
|
2311
|
+
let eventsPath = "/@hono-decks/events";
|
|
2310
2312
|
const syncWatchedPaths = async () => {
|
|
2311
2313
|
if (!server) return;
|
|
2312
2314
|
const next = /* @__PURE__ */ new Set([
|
|
@@ -2374,7 +2376,7 @@ function honoDecks(options = {}) {
|
|
|
2374
2376
|
environment.moduleGraph.invalidateAll();
|
|
2375
2377
|
if (environment.name !== "client") environment.hot.send({ type: "full-reload" });
|
|
2376
2378
|
}
|
|
2377
|
-
|
|
2379
|
+
for (const client of eventClients) client.write("event: deck:updated\ndata: {}\n\n");
|
|
2378
2380
|
}
|
|
2379
2381
|
} finally {
|
|
2380
2382
|
compiling = false;
|
|
@@ -2422,8 +2424,9 @@ export { createDecks };
|
|
|
2422
2424
|
export const decks = createDecks(config);`;
|
|
2423
2425
|
if (!id.startsWith(MODULE_PREFIX)) return null;
|
|
2424
2426
|
const path = id.slice(MODULE_PREFIX.length);
|
|
2425
|
-
|
|
2427
|
+
let code = modules.get(path);
|
|
2426
2428
|
if (code === void 0) return null;
|
|
2429
|
+
if (path === routerModulePath && resolvedConfig?.command === "serve") code = code.replace("const generatedDecks = defineDecks({", `const generatedDecks = defineDecks({\n liveReloadPath: () => ${JSON.stringify(eventsPath)},`);
|
|
2427
2430
|
return (await transform(code, {
|
|
2428
2431
|
loader: "ts",
|
|
2429
2432
|
format: "esm",
|
|
@@ -2436,16 +2439,30 @@ export const decks = createDecks(config);`;
|
|
|
2436
2439
|
},
|
|
2437
2440
|
async configResolved(config) {
|
|
2438
2441
|
resolvedConfig = config;
|
|
2442
|
+
eventsPath = `${config.base?.startsWith("/") ? config.base.replace(/\/$/, "") : ""}/@hono-decks/events`;
|
|
2439
2443
|
projectRoot = config.resolve?.preserveSymlinks ? config.root : await realpath(config.root);
|
|
2440
2444
|
configPath = resolve(projectRoot, options.configFile ?? "hono-decks.config.ts");
|
|
2441
2445
|
await compile(true);
|
|
2442
2446
|
},
|
|
2443
2447
|
configureServer(devServer) {
|
|
2444
2448
|
server = devServer;
|
|
2449
|
+
devServer.middlewares.use((req, res, next) => {
|
|
2450
|
+
if (req.url?.split("?")[0] !== eventsPath || req.method !== "GET") return next();
|
|
2451
|
+
res.writeHead(200, {
|
|
2452
|
+
"Content-Type": "text/event-stream",
|
|
2453
|
+
"Cache-Control": "no-cache, no-transform",
|
|
2454
|
+
Connection: "keep-alive"
|
|
2455
|
+
});
|
|
2456
|
+
res.write(": connected\n\n");
|
|
2457
|
+
eventClients.add(res);
|
|
2458
|
+
res.on("close", () => eventClients.delete(res));
|
|
2459
|
+
});
|
|
2445
2460
|
syncWatchedPaths();
|
|
2446
2461
|
devServer.watcher.on("all", onWatcherEvent);
|
|
2447
2462
|
const cleanup = () => {
|
|
2448
2463
|
if (timer) clearTimeout(timer);
|
|
2464
|
+
for (const client of eventClients) client.end();
|
|
2465
|
+
eventClients.clear();
|
|
2449
2466
|
devServer.watcher.off("all", onWatcherEvent);
|
|
2450
2467
|
server = void 0;
|
|
2451
2468
|
watchedPaths.clear();
|