diff --git a/src/app.js b/src/app.js index f0106da..01cb97c 100644 --- a/src/app.js +++ b/src/app.js @@ -2,6 +2,7 @@ import { $ } from "./dom.js"; import { initEvents, updateOptionVisibility } from "./events.js"; import { applyLanguage, getSavedLang, loadI18n } from "./i18n.js"; import { loadHistory, loadMode, loadSession, state } from "./state.js"; +import { parseUrlParams, applyUrlParamsToUI, applyUrlConfigToUI } from "./url-params.js"; async function initializeApp() { await loadI18n(); @@ -10,15 +11,24 @@ async function initializeApp() { loadHistory(); loadMode(); - const session = loadSession(); - $("input-text").value = session.input; - $("output-text").value = session.output; + const urlParams = parseUrlParams(); await new Promise(r => setTimeout(r, 100)); applyLanguage(); initEvents(); updateOptionVisibility(); + + if (urlParams.text) { + await applyUrlParamsToUI(urlParams); + } else { + applyUrlConfigToUI(urlParams); + const session = loadSession(); + if (session.input) { + $("input-text").value = session.input; + $("output-text").value = session.output; + } + } const topbar = $("app-title")?.closest(".topbar"); if (topbar) { diff --git a/src/events.js b/src/events.js index de1207d..20109dd 100644 --- a/src/events.js +++ b/src/events.js @@ -2,6 +2,7 @@ import { $, escapeHtml, hideModal, showModal, showToast } from "./dom.js"; import { setLang, t } from "./i18n.js"; import { processText } from "./processor.js"; import { clearHistoryStorage, loadCharLib, saveHistory, saveMode, saveSession, state } from "./state.js"; +import { syncUrlParams } from "./url-params.js"; function renderLibraryModal() { loadCharLib().then(lib => { @@ -40,6 +41,7 @@ export function initEvents() { saveMode(radio.value); } }); + syncUrlParams(); }; for (const radio of modeRadios) radio.addEventListener('change', updateMode); modeRadios.forEach(radio => { @@ -78,15 +80,34 @@ export function initEvents() { upper?.addEventListener('change', () => { if (upper.selected) lower.selected = false; updateOptionVisibility(); + syncUrlParams(); }); lower?.addEventListener('change', () => { if (lower.selected) upper.selected = false; updateOptionVisibility(); + syncUrlParams(); }); - $("suffix-select")?.addEventListener("change", updateOptionVisibility); - $("dbvowel")?.addEventListener("change", updateOptionVisibility); - $("addHash")?.addEventListener("change", updateOptionVisibility); + $("suffix-select")?.addEventListener("change", () => { + updateOptionVisibility(); + syncUrlParams(); + }); + $("dbvowel")?.addEventListener("change", () => { + updateOptionVisibility(); + syncUrlParams(); + }); + $("addHash")?.addEventListener("change", () => { + updateOptionVisibility(); + syncUrlParams(); + }); + + // Sync URL when any More Settings input changes + ["custom-prefix", "custom-suffix", "custom-repeat", "custom-repeat-count", + "dbvowel-count", "hash-length", "numcir", "preserveEsc"].forEach(id => { + const el = $(id); + const eventType = el?.tagName === "MD-SWITCH" ? "change" : "input"; + el?.addEventListener(eventType, syncUrlParams); + }); $("process-btn")?.addEventListener("click", async () => { const text = $("input-text")?.value || ""; @@ -110,6 +131,7 @@ export function initEvents() { const result = await processText(text, options); $("output-text").value = result; saveSession(text, result); + syncUrlParams(); if (text.trim()) { state.processingHistory.unshift({ timestamp: Date.now(), @@ -126,8 +148,11 @@ export function initEvents() { $("clear-btn")?.addEventListener("click", () => { $("input-text").value = ""; $("output-text").value = ""; + syncUrlParams(); }); + $("input-text")?.addEventListener("input", syncUrlParams); + $("copy-btn")?.addEventListener("click", async () => { const val = $("output-text")?.value; if (val) { diff --git a/src/url-params.js b/src/url-params.js new file mode 100644 index 0000000..5e366f2 --- /dev/null +++ b/src/url-params.js @@ -0,0 +1,200 @@ +import { $ } from "./dom.js"; +import { updateOptionVisibility } from "./events.js"; +import { processText } from "./processor.js"; +import { saveSession, saveHistory, state } from "./state.js"; + +const DEFAULTS = { + mode: "XA", + suffix: "0", + customPrefix: "[", + customSuffix: "]", + customRepeatCount: 7, + dbvowelCount: 1, + hashLength: 5, + upper: false, + lower: false, + dbvowel: false, + numcir: false, + addHash: false, + preserveEsc: false, +}; + +function parseBool(value) { + return value?.toLowerCase() === "true"; +} + +function parseNumber(value, fallback) { + const num = Number(value); + return Number.isFinite(num) ? num : fallback; +} + +export function parseUrlParams() { + const params = new URLSearchParams(window.location.search); + + const letterRaw = params.get("letter"); + const letter = ["upper", "lower"].includes(letterRaw) ? letterRaw : "normal"; + + return { + text: params.get("text") || undefined, + mode: params.get("mode") || DEFAULTS.mode, + upper: letter === "upper", + lower: letter === "lower", + suffix: params.get("suffix") || DEFAULTS.suffix, + customPrefix: params.get("customPrefix") ?? DEFAULTS.customPrefix, + customSuffix: params.get("customSuffix") ?? DEFAULTS.customSuffix, + customRepeat: params.get("customRepeat") || undefined, + customRepeatCount: parseNumber(params.get("customRepeatCount"), DEFAULTS.customRepeatCount), + dbvowel: parseBool(params.get("dbvowel")), + dbvowelCount: parseNumber(params.get("dbvowelCount"), DEFAULTS.dbvowelCount), + numcir: parseBool(params.get("numcir")), + addHash: parseBool(params.get("addHash")), + hashLength: parseNumber(params.get("hashLength"), DEFAULTS.hashLength), + preserveEsc: parseBool(params.get("preserveEsc")), + }; +} + +export function applyUrlConfigToUI(params) { + const modeRadio = $(params.mode === "XB" ? "mode-xb" : "mode-xa"); + if (modeRadio) { + modeRadio.checked = true; + } + + const switches = ["upper", "lower", "dbvowel", "numcir", "addHash", "preserveEsc"]; + for (const id of switches) { + const el = $(id); + if (el) { + el.selected = params[id] ?? DEFAULTS[id]; + } + } + + if ($("suffix-select")) { + $("suffix-select").value = params.suffix; + } + + const textFields = { + "custom-prefix": params.customPrefix, + "custom-suffix": params.customSuffix, + "custom-repeat": params.customRepeat ?? "", + "custom-repeat-count": String(params.customRepeatCount), + "dbvowel-count": String(params.dbvowelCount), + "hash-length": String(params.hashLength), + }; + + for (const [id, value] of Object.entries(textFields)) { + const el = $(id); + if (el && value !== undefined) { + el.value = value; + } + } + + updateOptionVisibility(); +} + +export async function applyUrlParamsToUI(params) { + applyUrlConfigToUI(params); + + if (!params.text) return; + + $("input-text").value = params.text; + + const options = { + mode: params.mode, + upper: params.upper, + lower: params.lower, + suffix: params.suffix, + customPrefix: params.customPrefix, + customSuffix: params.customSuffix, + customRepeat: params.customRepeat || "", + customRepeatCount: params.customRepeatCount, + dbvowel: params.dbvowel, + dbvowelCount: params.dbvowelCount, + numcir: params.numcir, + addHash: params.addHash, + hashLength: params.hashLength, + preserveEsc: params.preserveEsc, + }; + + try { + const result = await processText(params.text, options); + $("output-text").value = result; + saveSession(params.text, result); + state.processingHistory.unshift({ + timestamp: Date.now(), + input: params.text, + output: result + }); + saveHistory(state.processingHistory); + } catch (e) { + $("output-text").value = `Error: ${e.message}`; + } +} + +export function syncUrlParams() { + const params = new URLSearchParams(); + + const text = $("input-text")?.value || ""; + if (text) params.set("text", text); + + const mode = document.querySelector('md-radio[name="mode"]:checked')?.value || DEFAULTS.mode; + if (mode !== DEFAULTS.mode) { + params.set("mode", mode); + } + + const upper = $("upper")?.selected ?? false; + const lower = $("lower")?.selected ?? false; + const letter = upper ? "upper" : lower ? "lower" : "normal"; + if (letter !== "normal") params.set("letter", letter); + + const suffix = $("suffix-select")?.value || DEFAULTS.suffix; + if (suffix !== DEFAULTS.suffix) { + params.set("suffix", suffix); + } + + const customPrefix = $("custom-prefix")?.value ?? ""; + if (customPrefix !== DEFAULTS.customPrefix) { + params.set("customPrefix", customPrefix); + } + + const customSuffix = $("custom-suffix")?.value ?? ""; + if (customSuffix !== DEFAULTS.customSuffix) { + params.set("customSuffix", customSuffix); + } + + const customRepeat = $("custom-repeat")?.value || ""; + if (customRepeat) { + params.set("customRepeat", customRepeat); + } + + const customRepeatCount = parseInt($("custom-repeat-count")?.value, 10) || DEFAULTS.customRepeatCount; + if (customRepeatCount !== DEFAULTS.customRepeatCount) { + params.set("customRepeatCount", String(customRepeatCount)); + } + + const dbvowel = $("dbvowel")?.selected ?? false; + if (dbvowel) params.set("dbvowel", "true"); + + const dbvowelCount = parseInt($("dbvowel-count")?.value, 10) || DEFAULTS.dbvowelCount; + if (dbvowelCount !== DEFAULTS.dbvowelCount) { + params.set("dbvowelCount", String(dbvowelCount)); + } + + const numcir = $("numcir")?.selected ?? false; + if (numcir) params.set("numcir", "true"); + + const addHash = $("addHash")?.selected ?? false; + if (addHash) params.set("addHash", "true"); + + const hashLength = parseInt($("hash-length")?.value, 10) || DEFAULTS.hashLength; + if (hashLength !== DEFAULTS.hashLength) { + params.set("hashLength", String(hashLength)); + } + + const preserveEsc = $("preserveEsc")?.selected ?? false; + if (preserveEsc) params.set("preserveEsc", "true"); + + const url = params.toString() + ? `${window.location.pathname}?${params.toString()}` + : window.location.pathname; + + history.replaceState(null, "", url); +}