From 384876a83b0ad371a43de20fca261062f90bf220 Mon Sep 17 00:00:00 2001 From: ph1r3754r73r <6174900+ph1r3574r73r@users.noreply.github.com> Date: Sat, 13 Jun 2026 14:02:07 -0700 Subject: [PATCH] added routing for loading into specific pages. updated translations to appear near the top again. --- index.template.html | 1 + js/app.js | 82 +++++++++++++- js/tools/CodesTool.js | 5 +- js/utils/routing.js | 67 ++++++++++++ templates/transforms.html | 217 +++++++++++++++++++------------------- 5 files changed, 261 insertions(+), 111 deletions(-) create mode 100644 js/utils/routing.js diff --git a/index.template.html b/index.template.html index 1992695..7336514 100644 --- a/index.template.html +++ b/index.template.html @@ -423,6 +423,7 @@ + diff --git a/js/app.js b/js/app.js index 32659f6..22736f5 100644 --- a/js/app.js +++ b/js/app.js @@ -137,7 +137,59 @@ window.app = new Vue({ setTimeout(()=>section && section.classList.remove('shake-once','randomizer-glow'), 600); } catch(_) {} }, - switchToTab(tabName) { + getValidToolIds() { + if (window.toolRegistry && typeof window.toolRegistry.getAll === 'function') { + return window.toolRegistry.getAll().map(function(tool) { return tool.id; }); + } + return []; + }, + + getRouteSubState(tabName) { + if (tabName === 'codes' && this.codesMode === 'decode') { + return 'decode'; + } + return ''; + }, + + applyRouteSubState(route) { + if (!route || !route.sub) { + return; + } + + if (route.tab === 'codes' && (route.sub === 'generate' || route.sub === 'decode')) { + this.codesMode = route.sub; + } + }, + + applyRouteFromHash() { + if (!window.TabRouting) { + return; + } + + var route = window.TabRouting.parse(); + var validIds = this.getValidToolIds(); + var defaultTab = 'transforms'; + + if (!route || !route.tab) { + return; + } + + if (!validIds.includes(route.tab)) { + this.switchToTab(defaultTab, { fromRoute: true, updateUrl: true, replaceUrl: true }); + return; + } + + if (route.tab !== this.activeTab) { + this.switchToTab(route.tab, { fromRoute: true, updateUrl: false, route: route }); + return; + } + + this.applyRouteSubState(route); + }, + + switchToTab(tabName, options) { + options = options || {}; + if (this.activeTab && window.toolRegistry) { window.toolRegistry.deactivateTool(this.activeTab, this); } @@ -149,6 +201,14 @@ window.app = new Vue({ if (window.toolRegistry) { window.toolRegistry.activateTool(tabName, this); } + + if (options.fromRoute && options.route) { + this.applyRouteSubState(options.route); + } + + if (options.updateUrl !== false && !options.fromRoute && window.TabRouting) { + window.TabRouting.setHash(tabName, this.getRouteSubState(tabName), !!options.replaceUrl); + } }, toggleTheme() { @@ -392,6 +452,21 @@ window.app = new Vue({ if (window.toolRegistry && typeof window.toolRegistry.getAll === 'function') { this.registeredTools = window.toolRegistry.getAll(); } + + var initialRoute = window.TabRouting && window.TabRouting.parse(); + if (initialRoute && initialRoute.tab && this.getValidToolIds().includes(initialRoute.tab)) { + this.applyRouteFromHash(); + } else if (window.toolRegistry) { + window.toolRegistry.activateTool(this.activeTab, this); + } + + this._onHashChange = () => { + if (window.TabRouting && window.TabRouting.shouldIgnoreHashChange()) { + return; + } + this.applyRouteFromHash(); + }; + window.addEventListener('hashchange', this._onHashChange); this.$nextTick(() => { const closeButton = document.querySelector('#unicode-options-panel .close-button'); @@ -508,6 +583,11 @@ window.app = new Vue({ }, beforeDestroy() { + if (this._onHashChange) { + window.removeEventListener('hashchange', this._onHashChange); + this._onHashChange = null; + } + if (this._emojiGridInitializer) { clearInterval(this._emojiGridInitializer); this._emojiGridInitializer = null; diff --git a/js/tools/CodesTool.js b/js/tools/CodesTool.js index bee1dcb..0084fe0 100644 --- a/js/tools/CodesTool.js +++ b/js/tools/CodesTool.js @@ -241,8 +241,11 @@ class CodesTool extends Tool { getVueWatchers() { return { - codesMode: function() { + codesMode: function(mode) { this.codesError = ''; + if (this.activeTab === 'codes' && window.TabRouting) { + window.TabRouting.setHash('codes', mode === 'decode' ? 'decode' : ''); + } }, codesFormat: function() { this.codesClearOutput(); diff --git a/js/utils/routing.js b/js/utils/routing.js new file mode 100644 index 0000000..436cdfe --- /dev/null +++ b/js/utils/routing.js @@ -0,0 +1,67 @@ +/** + * Hash-based tab routing for static hosting (GitHub Pages friendly). + * Examples: #decoder, #/transforms, #codes/decode + */ +(function(global) { + var suppressHashChange = false; + + function normalizeSegment(value) { + return String(value || '').trim().toLowerCase(); + } + + function parseHash() { + var raw = (global.location.hash || '').replace(/^#\/?/, ''); + if (!raw) { + return null; + } + + var slashIndex = raw.indexOf('/'); + if (slashIndex === -1) { + return { + tab: normalizeSegment(decodeURIComponent(raw)), + sub: '' + }; + } + + return { + tab: normalizeSegment(decodeURIComponent(raw.slice(0, slashIndex))), + sub: normalizeSegment(decodeURIComponent(raw.slice(slashIndex + 1))) + }; + } + + function buildHash(tab, sub) { + var hash = '#' + encodeURIComponent(String(tab || '').trim()); + if (sub) { + hash += '/' + encodeURIComponent(String(sub).trim()); + } + return hash; + } + + function setHash(tab, sub, replace) { + var next = buildHash(tab, sub); + if (global.location.hash === next) { + return; + } + + suppressHashChange = true; + if (replace && global.history && typeof global.history.replaceState === 'function') { + global.history.replaceState(null, '', next); + suppressHashChange = false; + return; + } + + global.location.hash = next; + global.setTimeout(function() { + suppressHashChange = false; + }, 0); + } + + global.TabRouting = { + parse: parseHash, + buildHash: buildHash, + setHash: setHash, + shouldIgnoreHashChange: function() { + return suppressHashChange; + } + }; +})(typeof window !== 'undefined' ? window : globalThis); diff --git a/templates/transforms.html b/templates/transforms.html index 3cf72fc..4f7b67d 100644 --- a/templates/transforms.html +++ b/templates/transforms.html @@ -190,126 +190,125 @@ - - - - - - Translation (AI) - TranslateGemma - - - {{ translateError }} - + + + + Translation (AI) + TranslateGemma + - - Translating to {{ translateActiveLang }}... - + + {{ translateError }} + - - - {{ m.name }} — {{ m.note }} - - + + Translating to {{ translateActiveLang }}... + - - Major - - - {{ translateGetFlag(lang.flag) }} - {{ lang.name }} - - - - + + + {{ m.name }} — {{ m.note }} + + - - Dead & Exotic - - - {{ translateGetFlag(lang.flag) }} - {{ lang.name }} - - - - - - + + Major + - - Custom - - - + {{ translateGetFlag(lang.flag) }} + {{ lang.name }} + - - - - Add - - - - - - {{ lang.name }} - × - - - - - Click Custom above to add any language. - - + + Dead & Exotic + + + {{ translateGetFlag(lang.flag) }} + {{ lang.name }} + + + + + + + + + Custom + + + + + + + + Add + + + + + + {{ lang.name }} + × + + + + + Click Custom above to add any language. + + + + +