hono-decks 1.3.0 → 1.4.1
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 +1 -1
- package/README.md +1 -1
- package/dist/adapters/hono-markdown.js +33 -1
- package/dist/advanced.js +140 -22
- package/dist/bin.js +42 -6
- package/dist/cli.js +42 -6
- package/dist/client.js +14 -2
- package/dist/node.js +182 -28
- package/dist/vite.js +42 -6
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -294,7 +294,7 @@ export default defineConfig({
|
|
|
294
294
|
});
|
|
295
295
|
```
|
|
296
296
|
|
|
297
|
-
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を安全に識別できる場合は表示ページを再読み込みせずに内容を更新します。対応するprops・Markdownコンテキストの変更、別々のislandの並べ替え・追加・削除でも状態を維持し、現在のスライド・ステップを保ちます。islandの部品やその他のクライアントコードを編集すると、表示用iframeを再読み込みしてislandの状態をリセットします。viewerの枠は維持し、スライドとステップを復元します。同一islandの区別がつかない場合、アダプターが更新時の初期化に対応していない場合、取得に失敗した場合なども同じ動作になります。どちらの構成でも通常の`bun run dev`だけでauthoringできます。
|
|
298
298
|
|
|
299
299
|
## Runtime configuration
|
|
300
300
|
|
package/README.md
CHANGED
|
@@ -295,7 +295,7 @@ export default defineConfig({
|
|
|
295
295
|
});
|
|
296
296
|
~~~
|
|
297
297
|
|
|
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,
|
|
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, presentation pages update without reloading when existing islands can be identified safely. Their state survives supported prop and Markdown context changes, reordering, and the addition or removal of distinct islands. The current slide and step are retained. Editing an island component or other client code reloads the presentation iframe and resets island state; the viewer remains open and the slide and step are restored. Unsafe updates, such as ambiguous duplicate islands, unsupported adapter initialization, or a failed fetch, use the same fallback. In both setups, the application's normal <code>dev</code> command is enough for authoring.
|
|
299
299
|
|
|
300
300
|
## Runtime configuration
|
|
301
301
|
|
|
@@ -31,8 +31,37 @@ function createMarkdownAdapter({ cwd, options }) {
|
|
|
31
31
|
function emitMarkdownClient(islands, runtime) {
|
|
32
32
|
if (!islands.length) return "";
|
|
33
33
|
const literal = (value) => JSON.stringify(value).replace(/</g, "\\u003c");
|
|
34
|
+
const isVoid = runtime.startsWith("void-plugin-hono-jsx/");
|
|
35
|
+
const contextModule = isVoid ? "void-plugin-hono-jsx/markdown" : "@ts-76/hono-markdown";
|
|
36
|
+
const scheduler = isVoid ? "void/pages-client" : "@ts-76/hono-markdown/client";
|
|
37
|
+
const schedulerName = isVoid ? "scheduleHydration" : "scheduleMarkdownIsland";
|
|
34
38
|
return `import { hydrateMarkdownIslands } from ${literal(runtime)};
|
|
39
|
+
import { ${schedulerName} as scheduleIsland } from ${literal(scheduler)};
|
|
40
|
+
import { MarkdownContext } from ${literal(contextModule)};
|
|
41
|
+
import { jsx as liveJsx } from "hono/jsx/dom/jsx-runtime";
|
|
42
|
+
import { flushSync as flushLiveSync, useState as useLiveState } from "hono/jsx/dom";
|
|
43
|
+
function hydrateDeckMarkdownIslands() {
|
|
35
44
|
hydrateMarkdownIslands({ ${islands.map((island) => `${literal(island.id)}: () => import(${literal(island.modulePath)})`).join(",")} }, {
|
|
45
|
+
schedule(element, loader, props, strategy, mount) {
|
|
46
|
+
return scheduleIsland(element, loader, props, strategy, (target, module, initialProps) => {
|
|
47
|
+
const live = window.__honoDecksLive;
|
|
48
|
+
if (!live || !module || typeof module.default !== 'function') return mount(target, module, initialProps);
|
|
49
|
+
const Component = module.default;
|
|
50
|
+
function LiveMarkdownIsland(componentProps) {
|
|
51
|
+
const [current, setCurrent] = useLiveState({
|
|
52
|
+
props: componentProps,
|
|
53
|
+
context: JSON.parse(element.closest('[data-hono-md-context]')?.getAttribute('data-hono-md-context') || '{}'),
|
|
54
|
+
});
|
|
55
|
+
const [active, setActive] = useLiveState(true);
|
|
56
|
+
live.register(element, setCurrent, () => flushLiveSync(() => setActive(false)));
|
|
57
|
+
return active ? liveJsx(MarkdownContext.Provider, {
|
|
58
|
+
value: current.context,
|
|
59
|
+
children: liveJsx(Component, current.props),
|
|
60
|
+
}) : null;
|
|
61
|
+
}
|
|
62
|
+
return mount(target, { ...module, default: LiveMarkdownIsland }, initialProps);
|
|
63
|
+
});
|
|
64
|
+
},
|
|
36
65
|
ready(element, start) {
|
|
37
66
|
const slide = element.closest('section[data-slide-state]');
|
|
38
67
|
if (!slide || slide.dataset.slideState === 'active') { start(); return; }
|
|
@@ -41,7 +70,10 @@ hydrateMarkdownIslands({ ${islands.map((island) => `${literal(island.id)}: () =>
|
|
|
41
70
|
});
|
|
42
71
|
observer.observe(slide, { attributes: true, attributeFilter: ['data-slide-state'] });
|
|
43
72
|
}
|
|
44
|
-
})
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
hydrateDeckMarkdownIslands();
|
|
76
|
+
if (window.__honoDecksLive) window.__honoDecksLive.registerHydrator(hydrateDeckMarkdownIslands);`;
|
|
45
77
|
}
|
|
46
78
|
//#endregion
|
|
47
79
|
export { createHonoMarkdownAdapter, createMarkdownAdapter };
|
package/dist/advanced.js
CHANGED
|
@@ -805,9 +805,16 @@ function renderLiveReloadScript(eventsPath, nonce) {
|
|
|
805
805
|
return `<script${documentNonceAttribute(nonce)}>
|
|
806
806
|
(() => {
|
|
807
807
|
const events = new EventSource(${JSON.stringify(eventsPath)});
|
|
808
|
+
const scriptNonce = document.currentScript?.nonce;
|
|
808
809
|
let running = false;
|
|
809
810
|
let pending = false;
|
|
810
811
|
const selector = "[data-hono-decks-island],[data-hono-md-island]";
|
|
812
|
+
const islandUpdaters = new WeakMap();
|
|
813
|
+
const hydrators = new Set();
|
|
814
|
+
window.__honoDecksLive = {
|
|
815
|
+
register: (element, update, dispose) => islandUpdaters.set(element, { update, dispose }),
|
|
816
|
+
registerHydrator: (hydrate) => hydrators.add(hydrate),
|
|
817
|
+
};
|
|
811
818
|
const clientSelector = "script[data-hono-decks-client-entry]";
|
|
812
819
|
const clientText = async (url) => {
|
|
813
820
|
if (!url) return "";
|
|
@@ -820,12 +827,12 @@ function renderLiveReloadScript(eventsPath, nonce) {
|
|
|
820
827
|
: Promise.resolve();
|
|
821
828
|
let clientSnapshot = domReady.then(() => clientText(document.querySelector(clientSelector)?.src)).catch(() => null);
|
|
822
829
|
|
|
823
|
-
function signature(element) {
|
|
830
|
+
function signature(element, includeContext = true, includeProps = true) {
|
|
824
831
|
const attributes = element.hasAttribute("data-hono-md-island")
|
|
825
832
|
? ["data-hono-md-island", "data-hono-md-hydrate", "data-hono-md-props"]
|
|
826
833
|
: ["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]);
|
|
834
|
+
return JSON.stringify([attributes.map((name) => !includeProps && name.endsWith("-props") ? null : element.getAttribute(name)),
|
|
835
|
+
includeContext ? element.closest("[data-hono-md-context]")?.getAttribute("data-hono-md-context") ?? null : null]);
|
|
829
836
|
}
|
|
830
837
|
|
|
831
838
|
async function update() {
|
|
@@ -843,34 +850,142 @@ function renderLiveReloadScript(eventsPath, nonce) {
|
|
|
843
850
|
if (!newSlides.length) throw new Error("Empty deck");
|
|
844
851
|
// Scripts parsed from fetched HTML are inert; reload so their behavior is not lost.
|
|
845
852
|
if (currentDeck.querySelector("script") || nextDeck.querySelector("script")) throw new Error("Deck contains scripts");
|
|
846
|
-
const
|
|
853
|
+
const collect = (slides) => slides.flatMap((slide, slideIndex) =>
|
|
854
|
+
Array.from(slide.querySelectorAll(selector)).map((element, position) => ({ element, slideIndex, position })));
|
|
855
|
+
const oldItems = collect(oldSlides);
|
|
856
|
+
const newItems = collect(newSlides);
|
|
847
857
|
const nextScript = next.querySelector(clientSelector);
|
|
848
|
-
const
|
|
858
|
+
const fetchedClient = await clientText(nextScript?.getAttribute("src"));
|
|
849
859
|
const previousClient = await clientSnapshot;
|
|
850
860
|
if (previousClient === null) throw new Error("Initial client entry fetch failed");
|
|
851
|
-
//
|
|
852
|
-
|
|
861
|
+
// Keep the loaded registry when the last island disappears; it can dispose
|
|
862
|
+
// that island and initialize it again if it is reintroduced later.
|
|
863
|
+
const nextClient = !nextScript && !newItems.length ? previousClient : fetchedClient;
|
|
864
|
+
// A changed client bundle could contain new implementations for running islands.
|
|
865
|
+
if (oldItems.length && previousClient !== nextClient) throw new Error("Client code changed");
|
|
866
|
+
const needsEntry = !document.querySelector(clientSelector) && Boolean(nextScript);
|
|
867
|
+
if (newItems.length && !nextScript) throw new Error("New islands require a client entry");
|
|
868
|
+
if (!needsEntry && previousClient !== nextClient) throw new Error("Client code changed");
|
|
869
|
+
|
|
870
|
+
const duplicateKeys = new Set([...oldItems, ...newItems].map((item) => signature(item.element, false)));
|
|
871
|
+
for (const key of duplicateKeys) {
|
|
872
|
+
const before = oldItems.filter((item) => signature(item.element, false) === key);
|
|
873
|
+
const after = newItems.filter((item) => signature(item.element, false) === key);
|
|
874
|
+
if (before.length <= 1 && after.length <= 1) continue;
|
|
875
|
+
if (before.length !== after.length || before.some((item, index) =>
|
|
876
|
+
item.slideIndex !== after[index].slideIndex || item.position !== after[index].position))
|
|
877
|
+
throw new Error("Ambiguous duplicate islands");
|
|
878
|
+
}
|
|
853
879
|
const pairs = [];
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
880
|
+
const remainingOld = new Set(oldItems);
|
|
881
|
+
const remainingNew = new Set(newItems);
|
|
882
|
+
function pair(oldItem, newItem) {
|
|
883
|
+
remainingOld.delete(oldItem);
|
|
884
|
+
remainingNew.delete(newItem);
|
|
885
|
+
pairs.push([oldItem, newItem]);
|
|
886
|
+
}
|
|
887
|
+
// Exact matches can move between slides. Match only unique instances so
|
|
888
|
+
// duplicate islands cannot accidentally inherit each other's state.
|
|
889
|
+
for (const oldItem of oldItems) {
|
|
890
|
+
const key = signature(oldItem.element, false);
|
|
891
|
+
const oldMatches = Array.from(remainingOld).filter((item) => signature(item.element, false) === key);
|
|
892
|
+
const newMatches = Array.from(remainingNew).filter((item) => signature(item.element, false) === key);
|
|
893
|
+
if (oldMatches.length === 1 && newMatches.length === 1) pair(oldItem, newMatches[0]);
|
|
894
|
+
}
|
|
895
|
+
// Changed props can be reconciled if there is only one island of that type.
|
|
896
|
+
for (const oldItem of Array.from(remainingOld)) {
|
|
897
|
+
const key = signature(oldItem.element, false, false);
|
|
898
|
+
const oldMatches = Array.from(remainingOld).filter((item) => signature(item.element, false, false) === key);
|
|
899
|
+
const newMatches = Array.from(remainingNew).filter((item) => signature(item.element, false, false) === key);
|
|
900
|
+
if (oldMatches.length === 1 && newMatches.length === 1 &&
|
|
901
|
+
oldItem.slideIndex === newMatches[0].slideIndex && oldItem.position === newMatches[0].position)
|
|
902
|
+
pair(oldItem, newMatches[0]);
|
|
903
|
+
}
|
|
904
|
+
// Existing duplicate instances remain safe only when their positions and
|
|
905
|
+
// identity attributes are unchanged; ambiguous structural edits reload.
|
|
906
|
+
for (const oldItem of Array.from(remainingOld)) {
|
|
907
|
+
const newItem = Array.from(remainingNew).find((item) => item.slideIndex === oldItem.slideIndex && item.position === oldItem.position);
|
|
908
|
+
if (newItem && signature(oldItem.element, false) === signature(newItem.element, false)) pair(oldItem, newItem);
|
|
909
|
+
}
|
|
910
|
+
for (const oldItem of remainingOld) {
|
|
911
|
+
const key = signature(oldItem.element, false, false);
|
|
912
|
+
if (Array.from(remainingNew).some((item) => signature(item.element, false, false) === key))
|
|
913
|
+
throw new Error("Ambiguous island identity");
|
|
914
|
+
}
|
|
915
|
+
const removals = [];
|
|
916
|
+
for (const oldItem of remainingOld) {
|
|
917
|
+
const entry = islandUpdaters.get(oldItem.element);
|
|
918
|
+
if (!entry?.dispose) throw new Error("Cannot safely dispose island");
|
|
919
|
+
removals.push(entry.dispose);
|
|
920
|
+
}
|
|
921
|
+
const updates = [];
|
|
922
|
+
let preserveSlides = false;
|
|
923
|
+
for (const [oldItem, newItem] of pairs) {
|
|
924
|
+
const before = oldItem.element;
|
|
925
|
+
const after = newItem.element;
|
|
926
|
+
const pendingMarkdown = before.hasAttribute("data-hono-md-island") && !before.hasAttribute("data-hono-md-mounted");
|
|
927
|
+
if (pendingMarkdown) {
|
|
928
|
+
if (oldSlides.length !== newSlides.length || oldItem.slideIndex !== newItem.slideIndex)
|
|
929
|
+
throw new Error("Pending island moved between slides");
|
|
930
|
+
preserveSlides = true;
|
|
931
|
+
}
|
|
932
|
+
if (signature(before, !pendingMarkdown) !== signature(after, !pendingMarkdown)) {
|
|
933
|
+
const entry = islandUpdaters.get(before);
|
|
934
|
+
if (!entry?.update || signature(before, false, false) !== signature(after, false, false))
|
|
935
|
+
throw new Error("Island properties changed");
|
|
936
|
+
const markdown = before.hasAttribute("data-hono-md-island");
|
|
937
|
+
const propsAttribute = markdown ? "data-hono-md-props" : "data-hono-decks-props";
|
|
938
|
+
const rawProps = after.getAttribute(propsAttribute) || "{}";
|
|
939
|
+
const props = JSON.parse(rawProps);
|
|
940
|
+
const context = markdown ? JSON.parse(after.closest("[data-hono-md-context]")?.getAttribute("data-hono-md-context") || "{}") : undefined;
|
|
941
|
+
updates.push([before, entry.update, propsAttribute, rawProps, markdown ? { props, context } : props]);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
for (const dispose of removals) dispose();
|
|
945
|
+
for (const [oldItem, newItem] of pairs) newItem.element.replaceWith(oldItem.element);
|
|
946
|
+
if (preserveSlides) {
|
|
947
|
+
for (let i = 0; i < oldSlides.length; i++) {
|
|
948
|
+
const oldSlide = oldSlides[i];
|
|
949
|
+
const newSlide = newSlides[i];
|
|
950
|
+
for (const attribute of Array.from(oldSlide.attributes)) oldSlide.removeAttribute(attribute.name);
|
|
951
|
+
for (const attribute of Array.from(newSlide.attributes)) oldSlide.setAttribute(attribute.name, attribute.value);
|
|
952
|
+
oldSlide.replaceChildren(...newSlide.childNodes);
|
|
953
|
+
newSlide.replaceWith(oldSlide);
|
|
865
954
|
}
|
|
866
955
|
}
|
|
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
956
|
currentDeck.replaceChildren(...nextDeck.childNodes);
|
|
957
|
+
for (const [island, update, propsAttribute, rawProps, value] of updates) {
|
|
958
|
+
island.setAttribute(propsAttribute, rawProps);
|
|
959
|
+
update(value);
|
|
960
|
+
}
|
|
870
961
|
style.textContent = nextStyle.textContent;
|
|
871
962
|
document.title = next.title;
|
|
872
963
|
clientSnapshot = Promise.resolve(nextClient);
|
|
873
964
|
runtime.refresh();
|
|
965
|
+
if (remainingNew.size) {
|
|
966
|
+
if (needsEntry) {
|
|
967
|
+
const url = new URL(nextScript.getAttribute("src"), location.href);
|
|
968
|
+
if (url.origin !== location.origin) throw new Error("Cross-origin client entry");
|
|
969
|
+
url.searchParams.set("hono-decks-update", String(Date.now()));
|
|
970
|
+
const script = document.createElement("script");
|
|
971
|
+
script.type = "module";
|
|
972
|
+
script.setAttribute("data-hono-decks-client-entry", "");
|
|
973
|
+
if (scriptNonce) script.nonce = scriptNonce;
|
|
974
|
+
script.src = url.href;
|
|
975
|
+
await new Promise((resolve, reject) => {
|
|
976
|
+
script.onload = resolve;
|
|
977
|
+
script.onerror = () => reject(new Error("Client entry failed"));
|
|
978
|
+
document.body.appendChild(script);
|
|
979
|
+
});
|
|
980
|
+
} else {
|
|
981
|
+
for (const hydrate of hydrators) hydrate();
|
|
982
|
+
}
|
|
983
|
+
for (const item of remainingNew) {
|
|
984
|
+
const element = item.element;
|
|
985
|
+
if (!element.hasAttribute("data-hono-decks-live-mounted") && !element.hasAttribute("data-hono-md-scheduled"))
|
|
986
|
+
throw new Error("New island did not initialize");
|
|
987
|
+
}
|
|
988
|
+
}
|
|
874
989
|
}
|
|
875
990
|
|
|
876
991
|
async function onUpdate() {
|
|
@@ -1272,12 +1387,15 @@ function renderPresentationScript(nonce) {
|
|
|
1272
1387
|
await document.documentElement.requestFullscreen?.();
|
|
1273
1388
|
}
|
|
1274
1389
|
|
|
1390
|
+
const boundSlides = new WeakSet();
|
|
1275
1391
|
function bindSlideClicks() {
|
|
1276
|
-
slides.forEach((slide
|
|
1392
|
+
slides.forEach((slide) => {
|
|
1393
|
+
if (boundSlides.has(slide)) return;
|
|
1394
|
+
boundSlides.add(slide);
|
|
1277
1395
|
slide.addEventListener("click", () => {
|
|
1278
1396
|
if (!document.body.hasAttribute("data-overview-mode")) return;
|
|
1279
1397
|
document.body.removeAttribute("data-overview-mode");
|
|
1280
|
-
show(
|
|
1398
|
+
show(slides.indexOf(slide));
|
|
1281
1399
|
});
|
|
1282
1400
|
});
|
|
1283
1401
|
}
|
package/dist/bin.js
CHANGED
|
@@ -1660,8 +1660,37 @@ async function createHonoMarkdownAdapter(cwd, legacy = false, options) {
|
|
|
1660
1660
|
function emitMarkdownClient(islands, runtime) {
|
|
1661
1661
|
if (!islands.length) return "";
|
|
1662
1662
|
const literal = (value) => JSON.stringify(value).replace(/</g, "\\u003c");
|
|
1663
|
+
const isVoid = runtime.startsWith("void-plugin-hono-jsx/");
|
|
1664
|
+
const contextModule = isVoid ? "void-plugin-hono-jsx/markdown" : "@ts-76/hono-markdown";
|
|
1665
|
+
const scheduler = isVoid ? "void/pages-client" : "@ts-76/hono-markdown/client";
|
|
1666
|
+
const schedulerName = isVoid ? "scheduleHydration" : "scheduleMarkdownIsland";
|
|
1663
1667
|
return `import { hydrateMarkdownIslands } from ${literal(runtime)};
|
|
1668
|
+
import { ${schedulerName} as scheduleIsland } from ${literal(scheduler)};
|
|
1669
|
+
import { MarkdownContext } from ${literal(contextModule)};
|
|
1670
|
+
import { jsx as liveJsx } from "hono/jsx/dom/jsx-runtime";
|
|
1671
|
+
import { flushSync as flushLiveSync, useState as useLiveState } from "hono/jsx/dom";
|
|
1672
|
+
function hydrateDeckMarkdownIslands() {
|
|
1664
1673
|
hydrateMarkdownIslands({ ${islands.map((island) => `${literal(island.id)}: () => import(${literal(island.modulePath)})`).join(",")} }, {
|
|
1674
|
+
schedule(element, loader, props, strategy, mount) {
|
|
1675
|
+
return scheduleIsland(element, loader, props, strategy, (target, module, initialProps) => {
|
|
1676
|
+
const live = window.__honoDecksLive;
|
|
1677
|
+
if (!live || !module || typeof module.default !== 'function') return mount(target, module, initialProps);
|
|
1678
|
+
const Component = module.default;
|
|
1679
|
+
function LiveMarkdownIsland(componentProps) {
|
|
1680
|
+
const [current, setCurrent] = useLiveState({
|
|
1681
|
+
props: componentProps,
|
|
1682
|
+
context: JSON.parse(element.closest('[data-hono-md-context]')?.getAttribute('data-hono-md-context') || '{}'),
|
|
1683
|
+
});
|
|
1684
|
+
const [active, setActive] = useLiveState(true);
|
|
1685
|
+
live.register(element, setCurrent, () => flushLiveSync(() => setActive(false)));
|
|
1686
|
+
return active ? liveJsx(MarkdownContext.Provider, {
|
|
1687
|
+
value: current.context,
|
|
1688
|
+
children: liveJsx(Component, current.props),
|
|
1689
|
+
}) : null;
|
|
1690
|
+
}
|
|
1691
|
+
return mount(target, { ...module, default: LiveMarkdownIsland }, initialProps);
|
|
1692
|
+
});
|
|
1693
|
+
},
|
|
1665
1694
|
ready(element, start) {
|
|
1666
1695
|
const slide = element.closest('section[data-slide-state]');
|
|
1667
1696
|
if (!slide || slide.dataset.slideState === 'active') { start(); return; }
|
|
@@ -1670,7 +1699,10 @@ hydrateMarkdownIslands({ ${islands.map((island) => `${literal(island.id)}: () =>
|
|
|
1670
1699
|
});
|
|
1671
1700
|
observer.observe(slide, { attributes: true, attributeFilter: ['data-slide-state'] });
|
|
1672
1701
|
}
|
|
1673
|
-
})
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
hydrateDeckMarkdownIslands();
|
|
1705
|
+
if (window.__honoDecksLive) window.__honoDecksLive.registerHydrator(hydrateDeckMarkdownIslands);`;
|
|
1674
1706
|
}
|
|
1675
1707
|
//#endregion
|
|
1676
1708
|
//#region src/node/markdown-adapter.ts
|
|
@@ -1719,11 +1751,15 @@ async function emitClientEntryModule(input) {
|
|
|
1719
1751
|
import { hydrateSlideIslands } from "hono-decks/client";
|
|
1720
1752
|
${imports.join("\n")}
|
|
1721
1753
|
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
}
|
|
1754
|
+
function hydrateDeckIslands() {
|
|
1755
|
+
hydrateSlideIslands({
|
|
1756
|
+
components: {
|
|
1757
|
+
${registrations.join(",\n ")}
|
|
1758
|
+
}
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1761
|
+
hydrateDeckIslands();
|
|
1762
|
+
if (window.__honoDecksLive) window.__honoDecksLive.registerHydrator(hydrateDeckIslands);
|
|
1727
1763
|
`,
|
|
1728
1764
|
resolveDir: input.cwd,
|
|
1729
1765
|
sourcefile: "hono-decks-client-entry.tsx",
|
package/dist/cli.js
CHANGED
|
@@ -1659,8 +1659,37 @@ async function createHonoMarkdownAdapter(cwd, legacy = false, options) {
|
|
|
1659
1659
|
function emitMarkdownClient(islands, runtime) {
|
|
1660
1660
|
if (!islands.length) return "";
|
|
1661
1661
|
const literal = (value) => JSON.stringify(value).replace(/</g, "\\u003c");
|
|
1662
|
+
const isVoid = runtime.startsWith("void-plugin-hono-jsx/");
|
|
1663
|
+
const contextModule = isVoid ? "void-plugin-hono-jsx/markdown" : "@ts-76/hono-markdown";
|
|
1664
|
+
const scheduler = isVoid ? "void/pages-client" : "@ts-76/hono-markdown/client";
|
|
1665
|
+
const schedulerName = isVoid ? "scheduleHydration" : "scheduleMarkdownIsland";
|
|
1662
1666
|
return `import { hydrateMarkdownIslands } from ${literal(runtime)};
|
|
1667
|
+
import { ${schedulerName} as scheduleIsland } from ${literal(scheduler)};
|
|
1668
|
+
import { MarkdownContext } from ${literal(contextModule)};
|
|
1669
|
+
import { jsx as liveJsx } from "hono/jsx/dom/jsx-runtime";
|
|
1670
|
+
import { flushSync as flushLiveSync, useState as useLiveState } from "hono/jsx/dom";
|
|
1671
|
+
function hydrateDeckMarkdownIslands() {
|
|
1663
1672
|
hydrateMarkdownIslands({ ${islands.map((island) => `${literal(island.id)}: () => import(${literal(island.modulePath)})`).join(",")} }, {
|
|
1673
|
+
schedule(element, loader, props, strategy, mount) {
|
|
1674
|
+
return scheduleIsland(element, loader, props, strategy, (target, module, initialProps) => {
|
|
1675
|
+
const live = window.__honoDecksLive;
|
|
1676
|
+
if (!live || !module || typeof module.default !== 'function') return mount(target, module, initialProps);
|
|
1677
|
+
const Component = module.default;
|
|
1678
|
+
function LiveMarkdownIsland(componentProps) {
|
|
1679
|
+
const [current, setCurrent] = useLiveState({
|
|
1680
|
+
props: componentProps,
|
|
1681
|
+
context: JSON.parse(element.closest('[data-hono-md-context]')?.getAttribute('data-hono-md-context') || '{}'),
|
|
1682
|
+
});
|
|
1683
|
+
const [active, setActive] = useLiveState(true);
|
|
1684
|
+
live.register(element, setCurrent, () => flushLiveSync(() => setActive(false)));
|
|
1685
|
+
return active ? liveJsx(MarkdownContext.Provider, {
|
|
1686
|
+
value: current.context,
|
|
1687
|
+
children: liveJsx(Component, current.props),
|
|
1688
|
+
}) : null;
|
|
1689
|
+
}
|
|
1690
|
+
return mount(target, { ...module, default: LiveMarkdownIsland }, initialProps);
|
|
1691
|
+
});
|
|
1692
|
+
},
|
|
1664
1693
|
ready(element, start) {
|
|
1665
1694
|
const slide = element.closest('section[data-slide-state]');
|
|
1666
1695
|
if (!slide || slide.dataset.slideState === 'active') { start(); return; }
|
|
@@ -1669,7 +1698,10 @@ hydrateMarkdownIslands({ ${islands.map((island) => `${literal(island.id)}: () =>
|
|
|
1669
1698
|
});
|
|
1670
1699
|
observer.observe(slide, { attributes: true, attributeFilter: ['data-slide-state'] });
|
|
1671
1700
|
}
|
|
1672
|
-
})
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
hydrateDeckMarkdownIslands();
|
|
1704
|
+
if (window.__honoDecksLive) window.__honoDecksLive.registerHydrator(hydrateDeckMarkdownIslands);`;
|
|
1673
1705
|
}
|
|
1674
1706
|
//#endregion
|
|
1675
1707
|
//#region src/node/markdown-adapter.ts
|
|
@@ -1718,11 +1750,15 @@ async function emitClientEntryModule(input) {
|
|
|
1718
1750
|
import { hydrateSlideIslands } from "hono-decks/client";
|
|
1719
1751
|
${imports.join("\n")}
|
|
1720
1752
|
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
}
|
|
1753
|
+
function hydrateDeckIslands() {
|
|
1754
|
+
hydrateSlideIslands({
|
|
1755
|
+
components: {
|
|
1756
|
+
${registrations.join(",\n ")}
|
|
1757
|
+
}
|
|
1758
|
+
});
|
|
1759
|
+
}
|
|
1760
|
+
hydrateDeckIslands();
|
|
1761
|
+
if (window.__honoDecksLive) window.__honoDecksLive.registerHydrator(hydrateDeckIslands);
|
|
1726
1762
|
`,
|
|
1727
1763
|
resolveDir: input.cwd,
|
|
1728
1764
|
sourcefile: "hono-decks-client-entry.tsx",
|
package/dist/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { render } from "hono/jsx/dom";
|
|
1
|
+
import { flushSync, render, useState } from "hono/jsx/dom";
|
|
2
2
|
import { jsx } from "hono/jsx/dom/jsx-runtime";
|
|
3
3
|
//#region src/client/islands.ts
|
|
4
4
|
function hydrateSlideIslands(input) {
|
|
@@ -8,7 +8,19 @@ function hydrateSlideIslands(input) {
|
|
|
8
8
|
if (!name) continue;
|
|
9
9
|
const component = input.components[name];
|
|
10
10
|
if (!component) continue;
|
|
11
|
-
|
|
11
|
+
const props = parseIslandProps(island.dataset.honoDecksProps);
|
|
12
|
+
const live = window.__honoDecksLive;
|
|
13
|
+
if (live) {
|
|
14
|
+
if (island.dataset.honoDecksLiveMounted) continue;
|
|
15
|
+
function LiveIsland() {
|
|
16
|
+
const [currentProps, setCurrentProps] = useState(props);
|
|
17
|
+
const [active, setActive] = useState(true);
|
|
18
|
+
live.register(island, setCurrentProps, () => flushSync(() => setActive(false)));
|
|
19
|
+
return active ? jsx(component, currentProps) : null;
|
|
20
|
+
}
|
|
21
|
+
render(jsx(LiveIsland, {}), island);
|
|
22
|
+
island.dataset.honoDecksLiveMounted = "true";
|
|
23
|
+
} else render(jsx(component, props), island);
|
|
12
24
|
}
|
|
13
25
|
}
|
|
14
26
|
function parseIslandProps(value) {
|
package/dist/node.js
CHANGED
|
@@ -1852,8 +1852,37 @@ async function createHonoMarkdownAdapter(cwd, legacy = false, options) {
|
|
|
1852
1852
|
function emitMarkdownClient(islands, runtime) {
|
|
1853
1853
|
if (!islands.length) return "";
|
|
1854
1854
|
const literal = (value) => JSON.stringify(value).replace(/</g, "\\u003c");
|
|
1855
|
+
const isVoid = runtime.startsWith("void-plugin-hono-jsx/");
|
|
1856
|
+
const contextModule = isVoid ? "void-plugin-hono-jsx/markdown" : "@ts-76/hono-markdown";
|
|
1857
|
+
const scheduler = isVoid ? "void/pages-client" : "@ts-76/hono-markdown/client";
|
|
1858
|
+
const schedulerName = isVoid ? "scheduleHydration" : "scheduleMarkdownIsland";
|
|
1855
1859
|
return `import { hydrateMarkdownIslands } from ${literal(runtime)};
|
|
1860
|
+
import { ${schedulerName} as scheduleIsland } from ${literal(scheduler)};
|
|
1861
|
+
import { MarkdownContext } from ${literal(contextModule)};
|
|
1862
|
+
import { jsx as liveJsx } from "hono/jsx/dom/jsx-runtime";
|
|
1863
|
+
import { flushSync as flushLiveSync, useState as useLiveState } from "hono/jsx/dom";
|
|
1864
|
+
function hydrateDeckMarkdownIslands() {
|
|
1856
1865
|
hydrateMarkdownIslands({ ${islands.map((island) => `${literal(island.id)}: () => import(${literal(island.modulePath)})`).join(",")} }, {
|
|
1866
|
+
schedule(element, loader, props, strategy, mount) {
|
|
1867
|
+
return scheduleIsland(element, loader, props, strategy, (target, module, initialProps) => {
|
|
1868
|
+
const live = window.__honoDecksLive;
|
|
1869
|
+
if (!live || !module || typeof module.default !== 'function') return mount(target, module, initialProps);
|
|
1870
|
+
const Component = module.default;
|
|
1871
|
+
function LiveMarkdownIsland(componentProps) {
|
|
1872
|
+
const [current, setCurrent] = useLiveState({
|
|
1873
|
+
props: componentProps,
|
|
1874
|
+
context: JSON.parse(element.closest('[data-hono-md-context]')?.getAttribute('data-hono-md-context') || '{}'),
|
|
1875
|
+
});
|
|
1876
|
+
const [active, setActive] = useLiveState(true);
|
|
1877
|
+
live.register(element, setCurrent, () => flushLiveSync(() => setActive(false)));
|
|
1878
|
+
return active ? liveJsx(MarkdownContext.Provider, {
|
|
1879
|
+
value: current.context,
|
|
1880
|
+
children: liveJsx(Component, current.props),
|
|
1881
|
+
}) : null;
|
|
1882
|
+
}
|
|
1883
|
+
return mount(target, { ...module, default: LiveMarkdownIsland }, initialProps);
|
|
1884
|
+
});
|
|
1885
|
+
},
|
|
1857
1886
|
ready(element, start) {
|
|
1858
1887
|
const slide = element.closest('section[data-slide-state]');
|
|
1859
1888
|
if (!slide || slide.dataset.slideState === 'active') { start(); return; }
|
|
@@ -1862,7 +1891,10 @@ hydrateMarkdownIslands({ ${islands.map((island) => `${literal(island.id)}: () =>
|
|
|
1862
1891
|
});
|
|
1863
1892
|
observer.observe(slide, { attributes: true, attributeFilter: ['data-slide-state'] });
|
|
1864
1893
|
}
|
|
1865
|
-
})
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
hydrateDeckMarkdownIslands();
|
|
1897
|
+
if (window.__honoDecksLive) window.__honoDecksLive.registerHydrator(hydrateDeckMarkdownIslands);`;
|
|
1866
1898
|
}
|
|
1867
1899
|
//#endregion
|
|
1868
1900
|
//#region src/node/markdown-adapter.ts
|
|
@@ -2001,11 +2033,15 @@ async function emitClientEntryModule(input) {
|
|
|
2001
2033
|
import { hydrateSlideIslands } from "hono-decks/client";
|
|
2002
2034
|
${imports.join("\n")}
|
|
2003
2035
|
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
}
|
|
2036
|
+
function hydrateDeckIslands() {
|
|
2037
|
+
hydrateSlideIslands({
|
|
2038
|
+
components: {
|
|
2039
|
+
${registrations.join(",\n ")}
|
|
2040
|
+
}
|
|
2041
|
+
});
|
|
2042
|
+
}
|
|
2043
|
+
hydrateDeckIslands();
|
|
2044
|
+
if (window.__honoDecksLive) window.__honoDecksLive.registerHydrator(hydrateDeckIslands);
|
|
2009
2045
|
`,
|
|
2010
2046
|
resolveDir: input.cwd,
|
|
2011
2047
|
sourcefile: "hono-decks-client-entry.tsx",
|
|
@@ -3524,9 +3560,16 @@ function renderLiveReloadScript(eventsPath, nonce) {
|
|
|
3524
3560
|
return `<script${documentNonceAttribute(nonce)}>
|
|
3525
3561
|
(() => {
|
|
3526
3562
|
const events = new EventSource(${JSON.stringify(eventsPath)});
|
|
3563
|
+
const scriptNonce = document.currentScript?.nonce;
|
|
3527
3564
|
let running = false;
|
|
3528
3565
|
let pending = false;
|
|
3529
3566
|
const selector = "[data-hono-decks-island],[data-hono-md-island]";
|
|
3567
|
+
const islandUpdaters = new WeakMap();
|
|
3568
|
+
const hydrators = new Set();
|
|
3569
|
+
window.__honoDecksLive = {
|
|
3570
|
+
register: (element, update, dispose) => islandUpdaters.set(element, { update, dispose }),
|
|
3571
|
+
registerHydrator: (hydrate) => hydrators.add(hydrate),
|
|
3572
|
+
};
|
|
3530
3573
|
const clientSelector = "script[data-hono-decks-client-entry]";
|
|
3531
3574
|
const clientText = async (url) => {
|
|
3532
3575
|
if (!url) return "";
|
|
@@ -3539,12 +3582,12 @@ function renderLiveReloadScript(eventsPath, nonce) {
|
|
|
3539
3582
|
: Promise.resolve();
|
|
3540
3583
|
let clientSnapshot = domReady.then(() => clientText(document.querySelector(clientSelector)?.src)).catch(() => null);
|
|
3541
3584
|
|
|
3542
|
-
function signature(element) {
|
|
3585
|
+
function signature(element, includeContext = true, includeProps = true) {
|
|
3543
3586
|
const attributes = element.hasAttribute("data-hono-md-island")
|
|
3544
3587
|
? ["data-hono-md-island", "data-hono-md-hydrate", "data-hono-md-props"]
|
|
3545
3588
|
: ["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]);
|
|
3589
|
+
return JSON.stringify([attributes.map((name) => !includeProps && name.endsWith("-props") ? null : element.getAttribute(name)),
|
|
3590
|
+
includeContext ? element.closest("[data-hono-md-context]")?.getAttribute("data-hono-md-context") ?? null : null]);
|
|
3548
3591
|
}
|
|
3549
3592
|
|
|
3550
3593
|
async function update() {
|
|
@@ -3562,34 +3605,142 @@ function renderLiveReloadScript(eventsPath, nonce) {
|
|
|
3562
3605
|
if (!newSlides.length) throw new Error("Empty deck");
|
|
3563
3606
|
// Scripts parsed from fetched HTML are inert; reload so their behavior is not lost.
|
|
3564
3607
|
if (currentDeck.querySelector("script") || nextDeck.querySelector("script")) throw new Error("Deck contains scripts");
|
|
3565
|
-
const
|
|
3608
|
+
const collect = (slides) => slides.flatMap((slide, slideIndex) =>
|
|
3609
|
+
Array.from(slide.querySelectorAll(selector)).map((element, position) => ({ element, slideIndex, position })));
|
|
3610
|
+
const oldItems = collect(oldSlides);
|
|
3611
|
+
const newItems = collect(newSlides);
|
|
3566
3612
|
const nextScript = next.querySelector(clientSelector);
|
|
3567
|
-
const
|
|
3613
|
+
const fetchedClient = await clientText(nextScript?.getAttribute("src"));
|
|
3568
3614
|
const previousClient = await clientSnapshot;
|
|
3569
3615
|
if (previousClient === null) throw new Error("Initial client entry fetch failed");
|
|
3570
|
-
//
|
|
3571
|
-
|
|
3616
|
+
// Keep the loaded registry when the last island disappears; it can dispose
|
|
3617
|
+
// that island and initialize it again if it is reintroduced later.
|
|
3618
|
+
const nextClient = !nextScript && !newItems.length ? previousClient : fetchedClient;
|
|
3619
|
+
// A changed client bundle could contain new implementations for running islands.
|
|
3620
|
+
if (oldItems.length && previousClient !== nextClient) throw new Error("Client code changed");
|
|
3621
|
+
const needsEntry = !document.querySelector(clientSelector) && Boolean(nextScript);
|
|
3622
|
+
if (newItems.length && !nextScript) throw new Error("New islands require a client entry");
|
|
3623
|
+
if (!needsEntry && previousClient !== nextClient) throw new Error("Client code changed");
|
|
3624
|
+
|
|
3625
|
+
const duplicateKeys = new Set([...oldItems, ...newItems].map((item) => signature(item.element, false)));
|
|
3626
|
+
for (const key of duplicateKeys) {
|
|
3627
|
+
const before = oldItems.filter((item) => signature(item.element, false) === key);
|
|
3628
|
+
const after = newItems.filter((item) => signature(item.element, false) === key);
|
|
3629
|
+
if (before.length <= 1 && after.length <= 1) continue;
|
|
3630
|
+
if (before.length !== after.length || before.some((item, index) =>
|
|
3631
|
+
item.slideIndex !== after[index].slideIndex || item.position !== after[index].position))
|
|
3632
|
+
throw new Error("Ambiguous duplicate islands");
|
|
3633
|
+
}
|
|
3572
3634
|
const pairs = [];
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3635
|
+
const remainingOld = new Set(oldItems);
|
|
3636
|
+
const remainingNew = new Set(newItems);
|
|
3637
|
+
function pair(oldItem, newItem) {
|
|
3638
|
+
remainingOld.delete(oldItem);
|
|
3639
|
+
remainingNew.delete(newItem);
|
|
3640
|
+
pairs.push([oldItem, newItem]);
|
|
3641
|
+
}
|
|
3642
|
+
// Exact matches can move between slides. Match only unique instances so
|
|
3643
|
+
// duplicate islands cannot accidentally inherit each other's state.
|
|
3644
|
+
for (const oldItem of oldItems) {
|
|
3645
|
+
const key = signature(oldItem.element, false);
|
|
3646
|
+
const oldMatches = Array.from(remainingOld).filter((item) => signature(item.element, false) === key);
|
|
3647
|
+
const newMatches = Array.from(remainingNew).filter((item) => signature(item.element, false) === key);
|
|
3648
|
+
if (oldMatches.length === 1 && newMatches.length === 1) pair(oldItem, newMatches[0]);
|
|
3649
|
+
}
|
|
3650
|
+
// Changed props can be reconciled if there is only one island of that type.
|
|
3651
|
+
for (const oldItem of Array.from(remainingOld)) {
|
|
3652
|
+
const key = signature(oldItem.element, false, false);
|
|
3653
|
+
const oldMatches = Array.from(remainingOld).filter((item) => signature(item.element, false, false) === key);
|
|
3654
|
+
const newMatches = Array.from(remainingNew).filter((item) => signature(item.element, false, false) === key);
|
|
3655
|
+
if (oldMatches.length === 1 && newMatches.length === 1 &&
|
|
3656
|
+
oldItem.slideIndex === newMatches[0].slideIndex && oldItem.position === newMatches[0].position)
|
|
3657
|
+
pair(oldItem, newMatches[0]);
|
|
3658
|
+
}
|
|
3659
|
+
// Existing duplicate instances remain safe only when their positions and
|
|
3660
|
+
// identity attributes are unchanged; ambiguous structural edits reload.
|
|
3661
|
+
for (const oldItem of Array.from(remainingOld)) {
|
|
3662
|
+
const newItem = Array.from(remainingNew).find((item) => item.slideIndex === oldItem.slideIndex && item.position === oldItem.position);
|
|
3663
|
+
if (newItem && signature(oldItem.element, false) === signature(newItem.element, false)) pair(oldItem, newItem);
|
|
3664
|
+
}
|
|
3665
|
+
for (const oldItem of remainingOld) {
|
|
3666
|
+
const key = signature(oldItem.element, false, false);
|
|
3667
|
+
if (Array.from(remainingNew).some((item) => signature(item.element, false, false) === key))
|
|
3668
|
+
throw new Error("Ambiguous island identity");
|
|
3669
|
+
}
|
|
3670
|
+
const removals = [];
|
|
3671
|
+
for (const oldItem of remainingOld) {
|
|
3672
|
+
const entry = islandUpdaters.get(oldItem.element);
|
|
3673
|
+
if (!entry?.dispose) throw new Error("Cannot safely dispose island");
|
|
3674
|
+
removals.push(entry.dispose);
|
|
3675
|
+
}
|
|
3676
|
+
const updates = [];
|
|
3677
|
+
let preserveSlides = false;
|
|
3678
|
+
for (const [oldItem, newItem] of pairs) {
|
|
3679
|
+
const before = oldItem.element;
|
|
3680
|
+
const after = newItem.element;
|
|
3681
|
+
const pendingMarkdown = before.hasAttribute("data-hono-md-island") && !before.hasAttribute("data-hono-md-mounted");
|
|
3682
|
+
if (pendingMarkdown) {
|
|
3683
|
+
if (oldSlides.length !== newSlides.length || oldItem.slideIndex !== newItem.slideIndex)
|
|
3684
|
+
throw new Error("Pending island moved between slides");
|
|
3685
|
+
preserveSlides = true;
|
|
3686
|
+
}
|
|
3687
|
+
if (signature(before, !pendingMarkdown) !== signature(after, !pendingMarkdown)) {
|
|
3688
|
+
const entry = islandUpdaters.get(before);
|
|
3689
|
+
if (!entry?.update || signature(before, false, false) !== signature(after, false, false))
|
|
3690
|
+
throw new Error("Island properties changed");
|
|
3691
|
+
const markdown = before.hasAttribute("data-hono-md-island");
|
|
3692
|
+
const propsAttribute = markdown ? "data-hono-md-props" : "data-hono-decks-props";
|
|
3693
|
+
const rawProps = after.getAttribute(propsAttribute) || "{}";
|
|
3694
|
+
const props = JSON.parse(rawProps);
|
|
3695
|
+
const context = markdown ? JSON.parse(after.closest("[data-hono-md-context]")?.getAttribute("data-hono-md-context") || "{}") : undefined;
|
|
3696
|
+
updates.push([before, entry.update, propsAttribute, rawProps, markdown ? { props, context } : props]);
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
for (const dispose of removals) dispose();
|
|
3700
|
+
for (const [oldItem, newItem] of pairs) newItem.element.replaceWith(oldItem.element);
|
|
3701
|
+
if (preserveSlides) {
|
|
3702
|
+
for (let i = 0; i < oldSlides.length; i++) {
|
|
3703
|
+
const oldSlide = oldSlides[i];
|
|
3704
|
+
const newSlide = newSlides[i];
|
|
3705
|
+
for (const attribute of Array.from(oldSlide.attributes)) oldSlide.removeAttribute(attribute.name);
|
|
3706
|
+
for (const attribute of Array.from(newSlide.attributes)) oldSlide.setAttribute(attribute.name, attribute.value);
|
|
3707
|
+
oldSlide.replaceChildren(...newSlide.childNodes);
|
|
3708
|
+
newSlide.replaceWith(oldSlide);
|
|
3584
3709
|
}
|
|
3585
3710
|
}
|
|
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
3711
|
currentDeck.replaceChildren(...nextDeck.childNodes);
|
|
3712
|
+
for (const [island, update, propsAttribute, rawProps, value] of updates) {
|
|
3713
|
+
island.setAttribute(propsAttribute, rawProps);
|
|
3714
|
+
update(value);
|
|
3715
|
+
}
|
|
3589
3716
|
style.textContent = nextStyle.textContent;
|
|
3590
3717
|
document.title = next.title;
|
|
3591
3718
|
clientSnapshot = Promise.resolve(nextClient);
|
|
3592
3719
|
runtime.refresh();
|
|
3720
|
+
if (remainingNew.size) {
|
|
3721
|
+
if (needsEntry) {
|
|
3722
|
+
const url = new URL(nextScript.getAttribute("src"), location.href);
|
|
3723
|
+
if (url.origin !== location.origin) throw new Error("Cross-origin client entry");
|
|
3724
|
+
url.searchParams.set("hono-decks-update", String(Date.now()));
|
|
3725
|
+
const script = document.createElement("script");
|
|
3726
|
+
script.type = "module";
|
|
3727
|
+
script.setAttribute("data-hono-decks-client-entry", "");
|
|
3728
|
+
if (scriptNonce) script.nonce = scriptNonce;
|
|
3729
|
+
script.src = url.href;
|
|
3730
|
+
await new Promise((resolve, reject) => {
|
|
3731
|
+
script.onload = resolve;
|
|
3732
|
+
script.onerror = () => reject(new Error("Client entry failed"));
|
|
3733
|
+
document.body.appendChild(script);
|
|
3734
|
+
});
|
|
3735
|
+
} else {
|
|
3736
|
+
for (const hydrate of hydrators) hydrate();
|
|
3737
|
+
}
|
|
3738
|
+
for (const item of remainingNew) {
|
|
3739
|
+
const element = item.element;
|
|
3740
|
+
if (!element.hasAttribute("data-hono-decks-live-mounted") && !element.hasAttribute("data-hono-md-scheduled"))
|
|
3741
|
+
throw new Error("New island did not initialize");
|
|
3742
|
+
}
|
|
3743
|
+
}
|
|
3593
3744
|
}
|
|
3594
3745
|
|
|
3595
3746
|
async function onUpdate() {
|
|
@@ -3991,12 +4142,15 @@ function renderPresentationScript(nonce) {
|
|
|
3991
4142
|
await document.documentElement.requestFullscreen?.();
|
|
3992
4143
|
}
|
|
3993
4144
|
|
|
4145
|
+
const boundSlides = new WeakSet();
|
|
3994
4146
|
function bindSlideClicks() {
|
|
3995
|
-
slides.forEach((slide
|
|
4147
|
+
slides.forEach((slide) => {
|
|
4148
|
+
if (boundSlides.has(slide)) return;
|
|
4149
|
+
boundSlides.add(slide);
|
|
3996
4150
|
slide.addEventListener("click", () => {
|
|
3997
4151
|
if (!document.body.hasAttribute("data-overview-mode")) return;
|
|
3998
4152
|
document.body.removeAttribute("data-overview-mode");
|
|
3999
|
-
show(
|
|
4153
|
+
show(slides.indexOf(slide));
|
|
4000
4154
|
});
|
|
4001
4155
|
});
|
|
4002
4156
|
}
|
package/dist/vite.js
CHANGED
|
@@ -1659,8 +1659,37 @@ async function createHonoMarkdownAdapter(cwd, legacy = false, options) {
|
|
|
1659
1659
|
function emitMarkdownClient(islands, runtime) {
|
|
1660
1660
|
if (!islands.length) return "";
|
|
1661
1661
|
const literal = (value) => JSON.stringify(value).replace(/</g, "\\u003c");
|
|
1662
|
+
const isVoid = runtime.startsWith("void-plugin-hono-jsx/");
|
|
1663
|
+
const contextModule = isVoid ? "void-plugin-hono-jsx/markdown" : "@ts-76/hono-markdown";
|
|
1664
|
+
const scheduler = isVoid ? "void/pages-client" : "@ts-76/hono-markdown/client";
|
|
1665
|
+
const schedulerName = isVoid ? "scheduleHydration" : "scheduleMarkdownIsland";
|
|
1662
1666
|
return `import { hydrateMarkdownIslands } from ${literal(runtime)};
|
|
1667
|
+
import { ${schedulerName} as scheduleIsland } from ${literal(scheduler)};
|
|
1668
|
+
import { MarkdownContext } from ${literal(contextModule)};
|
|
1669
|
+
import { jsx as liveJsx } from "hono/jsx/dom/jsx-runtime";
|
|
1670
|
+
import { flushSync as flushLiveSync, useState as useLiveState } from "hono/jsx/dom";
|
|
1671
|
+
function hydrateDeckMarkdownIslands() {
|
|
1663
1672
|
hydrateMarkdownIslands({ ${islands.map((island) => `${literal(island.id)}: () => import(${literal(island.modulePath)})`).join(",")} }, {
|
|
1673
|
+
schedule(element, loader, props, strategy, mount) {
|
|
1674
|
+
return scheduleIsland(element, loader, props, strategy, (target, module, initialProps) => {
|
|
1675
|
+
const live = window.__honoDecksLive;
|
|
1676
|
+
if (!live || !module || typeof module.default !== 'function') return mount(target, module, initialProps);
|
|
1677
|
+
const Component = module.default;
|
|
1678
|
+
function LiveMarkdownIsland(componentProps) {
|
|
1679
|
+
const [current, setCurrent] = useLiveState({
|
|
1680
|
+
props: componentProps,
|
|
1681
|
+
context: JSON.parse(element.closest('[data-hono-md-context]')?.getAttribute('data-hono-md-context') || '{}'),
|
|
1682
|
+
});
|
|
1683
|
+
const [active, setActive] = useLiveState(true);
|
|
1684
|
+
live.register(element, setCurrent, () => flushLiveSync(() => setActive(false)));
|
|
1685
|
+
return active ? liveJsx(MarkdownContext.Provider, {
|
|
1686
|
+
value: current.context,
|
|
1687
|
+
children: liveJsx(Component, current.props),
|
|
1688
|
+
}) : null;
|
|
1689
|
+
}
|
|
1690
|
+
return mount(target, { ...module, default: LiveMarkdownIsland }, initialProps);
|
|
1691
|
+
});
|
|
1692
|
+
},
|
|
1664
1693
|
ready(element, start) {
|
|
1665
1694
|
const slide = element.closest('section[data-slide-state]');
|
|
1666
1695
|
if (!slide || slide.dataset.slideState === 'active') { start(); return; }
|
|
@@ -1669,7 +1698,10 @@ hydrateMarkdownIslands({ ${islands.map((island) => `${literal(island.id)}: () =>
|
|
|
1669
1698
|
});
|
|
1670
1699
|
observer.observe(slide, { attributes: true, attributeFilter: ['data-slide-state'] });
|
|
1671
1700
|
}
|
|
1672
|
-
})
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
hydrateDeckMarkdownIslands();
|
|
1704
|
+
if (window.__honoDecksLive) window.__honoDecksLive.registerHydrator(hydrateDeckMarkdownIslands);`;
|
|
1673
1705
|
}
|
|
1674
1706
|
//#endregion
|
|
1675
1707
|
//#region src/node/markdown-adapter.ts
|
|
@@ -1718,11 +1750,15 @@ async function emitClientEntryModule(input) {
|
|
|
1718
1750
|
import { hydrateSlideIslands } from "hono-decks/client";
|
|
1719
1751
|
${imports.join("\n")}
|
|
1720
1752
|
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
}
|
|
1753
|
+
function hydrateDeckIslands() {
|
|
1754
|
+
hydrateSlideIslands({
|
|
1755
|
+
components: {
|
|
1756
|
+
${registrations.join(",\n ")}
|
|
1757
|
+
}
|
|
1758
|
+
});
|
|
1759
|
+
}
|
|
1760
|
+
hydrateDeckIslands();
|
|
1761
|
+
if (window.__honoDecksLive) window.__honoDecksLive.registerHydrator(hydrateDeckIslands);
|
|
1726
1762
|
`,
|
|
1727
1763
|
resolveDir: input.cwd,
|
|
1728
1764
|
sourcefile: "hono-decks-client-entry.tsx",
|