mirror of
https://github.com/elder-plinius/P4RS3LT0NGV3.git
synced 2026-07-11 23:16:34 +02:00
added routing for loading into specific pages. updated translations to appear near the top again.
This commit is contained in:
@@ -423,6 +423,7 @@
|
||||
<script src="js/utils/history.js"></script>
|
||||
<script src="js/utils/clipboard.js"></script>
|
||||
<script src="js/utils/theme.js"></script>
|
||||
<script src="js/utils/routing.js"></script>
|
||||
<script src="js/utils/emoji.js"></script>
|
||||
|
||||
<!-- Core modules (feature libraries) -->
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
+108
-109
@@ -190,126 +190,125 @@
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-for="(category, categoryIndex) in categories" :key="category">
|
||||
<!-- AI Translation Section — renders above "symbol" (scripts & alphabets) -->
|
||||
<div v-if="category === 'symbol'" id="category-translate" class="transform-category-section translate-inline-section">
|
||||
<h4 class="category-title transform-category-translate">
|
||||
<i class="fas fa-language"></i> Translation (AI)
|
||||
<small class="translate-powered-by">TranslateGemma</small>
|
||||
</h4>
|
||||
|
||||
<div v-if="translateError" class="pc-error">
|
||||
<i class="fas fa-exclamation-triangle"></i> {{ translateError }}
|
||||
</div>
|
||||
<!-- AI Translation — pinned after Favorites / Last Used, before transform categories -->
|
||||
<div id="category-translate" class="transform-category-section translate-inline-section">
|
||||
<h4 class="category-title transform-category-translate">
|
||||
<i class="fas fa-language"></i> Translation (AI)
|
||||
<small class="translate-powered-by">TranslateGemma</small>
|
||||
</h4>
|
||||
|
||||
<div v-if="translateLoading" class="translate-loading">
|
||||
<i class="fas fa-spinner fa-spin"></i> Translating to {{ translateActiveLang }}...
|
||||
</div>
|
||||
<div v-if="translateError" class="pc-error">
|
||||
<i class="fas fa-exclamation-triangle"></i> {{ translateError }}
|
||||
</div>
|
||||
|
||||
<div class="translate-model-picker">
|
||||
<select v-model="translateModel" class="translate-model-select">
|
||||
<option v-for="m in translateModels" :key="m.id" :value="m.id">{{ m.name }} — {{ m.note }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div v-if="translateLoading" class="translate-loading">
|
||||
<i class="fas fa-spinner fa-spin"></i> Translating to {{ translateActiveLang }}...
|
||||
</div>
|
||||
|
||||
<div class="translate-subsection">
|
||||
<div class="translate-subsection-label"><i class="fas fa-globe"></i> Major</div>
|
||||
<div class="translate-lang-grid translate-lang-grid-inline">
|
||||
<button
|
||||
v-for="lang in translateMainLangs"
|
||||
:key="lang.code"
|
||||
class="translate-lang-btn translate-lang-main"
|
||||
@click="translateTo(lang.name)"
|
||||
:disabled="translateLoading"
|
||||
:title="'Translate to ' + lang.name"
|
||||
>
|
||||
<span class="translate-flag">{{ translateGetFlag(lang.flag) }}</span>
|
||||
<span class="translate-name">{{ lang.name }}</span>
|
||||
<i
|
||||
@click.stop="toggleTranslateFavorite(lang.name, false, $event)"
|
||||
class="fas fa-star favorite-icon translate-lang-favorite"
|
||||
:class="{ favorited: isTranslateFavorite(lang.name, false) }"
|
||||
:title="isTranslateFavorite(lang.name, false) ? 'Remove from favorites' : 'Add to favorites'"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="translate-model-picker">
|
||||
<select v-model="translateModel" class="translate-model-select">
|
||||
<option v-for="m in translateModels" :key="m.id" :value="m.id">{{ m.name }} — {{ m.note }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="translate-subsection">
|
||||
<div class="translate-subsection-label"><i class="fas fa-scroll"></i> Dead & Exotic</div>
|
||||
<div class="translate-lang-grid translate-lang-grid-inline">
|
||||
<button
|
||||
v-for="lang in translateExoticLangs"
|
||||
:key="lang.code"
|
||||
class="translate-lang-btn translate-lang-exotic"
|
||||
@click="translateTo(lang.name)"
|
||||
:disabled="translateLoading"
|
||||
:title="'Translate to ' + lang.name"
|
||||
>
|
||||
<span class="translate-flag">{{ translateGetFlag(lang.flag) }}</span>
|
||||
<span class="translate-name">{{ lang.name }}</span>
|
||||
<i
|
||||
@click.stop="toggleTranslateFavorite(lang.name, false, $event)"
|
||||
class="fas fa-star favorite-icon translate-lang-favorite"
|
||||
:class="{ favorited: isTranslateFavorite(lang.name, false) }"
|
||||
:title="isTranslateFavorite(lang.name, false) ? 'Remove from favorites' : 'Add to favorites'"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="translate-subsection">
|
||||
<div class="translate-subsection">
|
||||
<div class="translate-subsection-label"><i class="fas fa-globe"></i> Major</div>
|
||||
<div class="translate-lang-grid translate-lang-grid-inline">
|
||||
<button
|
||||
type="button"
|
||||
class="translate-subsection-label translate-custom-label-btn"
|
||||
@click="translateAddingLang = !translateAddingLang"
|
||||
title="Add a language"
|
||||
v-for="lang in translateMainLangs"
|
||||
:key="lang.code"
|
||||
class="translate-lang-btn translate-lang-main"
|
||||
@click="translateTo(lang.name)"
|
||||
:disabled="translateLoading"
|
||||
:title="'Translate to ' + lang.name"
|
||||
>
|
||||
<i class="fas fa-plus-circle"></i>
|
||||
<span>Custom</span>
|
||||
<span class="translate-custom-toggle-end" aria-hidden="true">
|
||||
<i :class="translateAddingLang ? 'fas fa-minus' : 'fas fa-plus'"></i>
|
||||
</span>
|
||||
<span class="translate-flag">{{ translateGetFlag(lang.flag) }}</span>
|
||||
<span class="translate-name">{{ lang.name }}</span>
|
||||
<i
|
||||
@click.stop="toggleTranslateFavorite(lang.name, false, $event)"
|
||||
class="fas fa-star favorite-icon translate-lang-favorite"
|
||||
:class="{ favorited: isTranslateFavorite(lang.name, false) }"
|
||||
:title="isTranslateFavorite(lang.name, false) ? 'Remove from favorites' : 'Add to favorites'"
|
||||
></i>
|
||||
</button>
|
||||
<div v-if="translateAddingLang" class="translate-add-form">
|
||||
<input
|
||||
type="text"
|
||||
v-model="translateNewLangName"
|
||||
placeholder="e.g., Tagalog, Esperanto, Nahuatl..."
|
||||
@keyup.enter="translateAddCustomLang"
|
||||
/>
|
||||
<button class="translate-add-btn" @click="translateAddCustomLang" :disabled="!translateNewLangName.trim()">
|
||||
<i class="fas fa-plus"></i> Add
|
||||
</button>
|
||||
</div>
|
||||
<div class="translate-lang-grid translate-lang-grid-inline" v-if="translateCustomLangs.length">
|
||||
<button
|
||||
v-for="(lang, idx) in translateCustomLangs"
|
||||
:key="'custom-'+idx"
|
||||
class="translate-lang-btn translate-lang-custom"
|
||||
@click="translateTo(lang.name)"
|
||||
:disabled="translateLoading"
|
||||
:title="'Translate to ' + lang.name"
|
||||
>
|
||||
<span class="translate-flag translate-custom-flag" aria-hidden="true"><i class="fas fa-globe"></i></span>
|
||||
<span class="translate-name">{{ lang.name }}</span>
|
||||
<span class="translate-remove" @click.stop="translateRemoveCustomLang(idx)" title="Remove">×</span>
|
||||
<i
|
||||
@click.stop="toggleTranslateFavorite(lang.name, true, $event)"
|
||||
class="fas fa-star favorite-icon translate-lang-favorite"
|
||||
:class="{ favorited: isTranslateFavorite(lang.name, true) }"
|
||||
:title="isTranslateFavorite(lang.name, true) ? 'Remove from favorites' : 'Add to favorites'"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="!translateCustomLangs.length && !translateAddingLang" class="translate-empty-custom">
|
||||
<small>Click <strong>Custom</strong> above to add any language.</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Special handling for randomizer category -->
|
||||
<div class="translate-subsection">
|
||||
<div class="translate-subsection-label"><i class="fas fa-scroll"></i> Dead & Exotic</div>
|
||||
<div class="translate-lang-grid translate-lang-grid-inline">
|
||||
<button
|
||||
v-for="lang in translateExoticLangs"
|
||||
:key="lang.code"
|
||||
class="translate-lang-btn translate-lang-exotic"
|
||||
@click="translateTo(lang.name)"
|
||||
:disabled="translateLoading"
|
||||
:title="'Translate to ' + lang.name"
|
||||
>
|
||||
<span class="translate-flag">{{ translateGetFlag(lang.flag) }}</span>
|
||||
<span class="translate-name">{{ lang.name }}</span>
|
||||
<i
|
||||
@click.stop="toggleTranslateFavorite(lang.name, false, $event)"
|
||||
class="fas fa-star favorite-icon translate-lang-favorite"
|
||||
:class="{ favorited: isTranslateFavorite(lang.name, false) }"
|
||||
:title="isTranslateFavorite(lang.name, false) ? 'Remove from favorites' : 'Add to favorites'"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="translate-subsection">
|
||||
<button
|
||||
type="button"
|
||||
class="translate-subsection-label translate-custom-label-btn"
|
||||
@click="translateAddingLang = !translateAddingLang"
|
||||
title="Add a language"
|
||||
>
|
||||
<i class="fas fa-plus-circle"></i>
|
||||
<span>Custom</span>
|
||||
<span class="translate-custom-toggle-end" aria-hidden="true">
|
||||
<i :class="translateAddingLang ? 'fas fa-minus' : 'fas fa-plus'"></i>
|
||||
</span>
|
||||
</button>
|
||||
<div v-if="translateAddingLang" class="translate-add-form">
|
||||
<input
|
||||
type="text"
|
||||
v-model="translateNewLangName"
|
||||
placeholder="e.g., Tagalog, Esperanto, Nahuatl..."
|
||||
@keyup.enter="translateAddCustomLang"
|
||||
/>
|
||||
<button class="translate-add-btn" @click="translateAddCustomLang" :disabled="!translateNewLangName.trim()">
|
||||
<i class="fas fa-plus"></i> Add
|
||||
</button>
|
||||
</div>
|
||||
<div class="translate-lang-grid translate-lang-grid-inline" v-if="translateCustomLangs.length">
|
||||
<button
|
||||
v-for="(lang, idx) in translateCustomLangs"
|
||||
:key="'custom-'+idx"
|
||||
class="translate-lang-btn translate-lang-custom"
|
||||
@click="translateTo(lang.name)"
|
||||
:disabled="translateLoading"
|
||||
:title="'Translate to ' + lang.name"
|
||||
>
|
||||
<span class="translate-flag translate-custom-flag" aria-hidden="true"><i class="fas fa-globe"></i></span>
|
||||
<span class="translate-name">{{ lang.name }}</span>
|
||||
<span class="translate-remove" @click.stop="translateRemoveCustomLang(idx)" title="Remove">×</span>
|
||||
<i
|
||||
@click.stop="toggleTranslateFavorite(lang.name, true, $event)"
|
||||
class="fas fa-star favorite-icon translate-lang-favorite"
|
||||
:class="{ favorited: isTranslateFavorite(lang.name, true) }"
|
||||
:title="isTranslateFavorite(lang.name, true) ? 'Remove from favorites' : 'Add to favorites'"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="!translateCustomLangs.length && !translateAddingLang" class="translate-empty-custom">
|
||||
<small>Click <strong>Custom</strong> above to add any language.</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-for="(category, categoryIndex) in categories" :key="category">
|
||||
<div
|
||||
v-if="isSpecialCategory(category)"
|
||||
:id="'category-' + category"
|
||||
|
||||
Reference in New Issue
Block a user