diff --git a/source/js/main.js b/source/js/main.js index 3503d922..47270160 100755 --- a/source/js/main.js +++ b/source/js/main.js @@ -54,6 +54,17 @@ let globalsInitialized = false; let didInitRefreshEvent = false; let appController = null; let pageController = null; +let typedModule = null; + +const loadTypedModule = () => { + const pending = import("./plugins/typed.js"); + pending + .then((module) => { + typedModule = module; + }) + .catch(() => {}); + return pending; +}; const getAppSignal = () => { if (!appController) { @@ -253,7 +264,7 @@ const initPage = () => { lazyRun( "typed", pageSignal, - () => import("./plugins/typed.js"), + loadTypedModule, ({ default: initTyped }) => { initTyped("subtitle"); }, @@ -343,7 +354,10 @@ const swup = window.swup; if (swup?.hooks) { swup.hooks.on("page:view", initPage); - swup.hooks.before("content:replace", abortPageScope); + swup.hooks.before("content:replace", () => { + typedModule?.destroyTyped("subtitle"); + abortPageScope(); + }); swup.hooks.on("visit:start", () => { navbarShrink.setNavigating(true); }); diff --git a/source/js/plugins/typed.js b/source/js/plugins/typed.js index de617b5c..a7d23e89 100644 --- a/source/js/plugins/typed.js +++ b/source/js/plugins/typed.js @@ -17,11 +17,30 @@ const normalizeSubtitleText = (subtitleText) => { return []; }; +const removeCursorSiblings = (element) => { + if (!element?.parentNode) { + return; + } + + let sibling = element.nextSibling; + while (sibling?.classList?.contains("typed-cursor")) { + const next = sibling.nextSibling; + sibling.remove(); + sibling = next; + } +}; + const destroyInstance = (id) => { const instance = instances.get(id); if (instance && typeof instance.destroy === "function") { try { + if (instance.timeout) { + clearTimeout(instance.timeout); + } instance.destroy(); + if (instance.timeout) { + clearTimeout(instance.timeout); + } } catch (error) { console.error("Failed to destroy Typed instance:", error); } @@ -32,20 +51,28 @@ const destroyInstance = (id) => { const element = document.getElementById(id); if (element) { element.innerHTML = ""; + removeCursorSiblings(element); } }; +// Bump the token first so a hitokoto response still in flight cannot +// start another Typed after the page has gone away. +export function destroyTyped(id) { + initTokens.set(id, (initTokens.get(id) || 0) + 1); + destroyInstance(id); +} + const createTyped = (id, strings, options, callbacks = {}) => { if (typeof window.Typed === "undefined") { return; } + destroyInstance(id); + if (!document.getElementById(id)) { return; } - destroyInstance(id); - const instance = new window.Typed(`#${id}`, { strings, typeSpeed: options.typeSpeed, @@ -109,6 +136,7 @@ export const config = { export default function initTyped(id) { const currentToken = (initTokens.get(id) || 0) + 1; initTokens.set(id, currentToken); + destroyInstance(id); const { usrTypeSpeed,