From 888b8439aa1bfba64f8908f46cb63b83c6f48deb Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Tue, 28 Jul 2026 18:22:56 -0500 Subject: [PATCH] add shortcuts --- src/webpage/localuser.ts | 33 +++++++ src/webpage/shortcuts/shortcut.ts | 117 +++++++++++++++++++++++++ src/webpage/style.css | 5 ++ src/webpage/utils/storage/shortcuts.ts | 22 +++++ translations/en.json | 7 ++ 5 files changed, 184 insertions(+) create mode 100644 src/webpage/shortcuts/shortcut.ts create mode 100644 src/webpage/utils/storage/shortcuts.ts diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 3799688..0a4b835 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -51,6 +51,8 @@ import {CDNParams} from "./utils/cdnParams.js"; import {SnowFlake} from "./snowflake.js"; import {trimTrailingSlashes} from "./utils/netUtils.js"; import {Versions} from "./versions.js"; +import {Shortcut} from "./shortcuts/shortcut.js"; +import {getShortcuts, setShortcuts} from "./utils/storage/shortcuts.js"; type traceObj = { micros: number; calls?: (string | traceObj)[]; @@ -93,6 +95,7 @@ class Localuser { readonly messages = new Map(); readonly idToPrev = new Map(); readonly idToNext = new Map(); + static readonly globalShortcuts = new Shortcut(); get status() { return this.user.status; } @@ -2570,6 +2573,15 @@ class Localuser { }); d.show(); } + static initShortcuts() { + const cur = getShortcuts(); + for (const [name, combo] of cur) { + this.globalShortcuts.registerKeycombo(combo, name); + } + this.globalShortcuts.registerShortcut("gifSearch", () => { + document.getElementById("gifTB")?.click(); + }); + } async showusersettings() { const prefs = await getPreferences(); const localSettings = getLocalSettings(); @@ -3167,6 +3179,25 @@ class Localuser { } })(); } + { + const shortcuts = settings.addButton(I18n.keyboard.shortcuts(), {contained: true}); + const cur = getShortcuts(); + console.log(Localuser.globalShortcuts); + for (const [name] of cur) { + shortcuts.addText(I18n.keyboard.descs[name]()); + shortcuts.addHTMLArea( + Localuser.globalShortcuts.shortCutbutton( + name, + (short) => { + cur[name] = short; + setShortcuts(cur); + console.log(cur); + }, + true, + ), + ); + } + } const installP = installPGet(); if (installP) { @@ -5528,4 +5559,6 @@ class Localuser { return userinfos.preferences.notisound; } } +Localuser.globalShortcuts.listen(document.body); +Localuser.initShortcuts(); export {Localuser}; diff --git a/src/webpage/shortcuts/shortcut.ts b/src/webpage/shortcuts/shortcut.ts new file mode 100644 index 0000000..c9388ff --- /dev/null +++ b/src/webpage/shortcuts/shortcut.ts @@ -0,0 +1,117 @@ +import {I18n} from "../i18n"; + +export class Shortcut { + private shortcuts = new Map void>(); + private keycombos = new Map(); + private nameToCombo = new Map>(); + registerShortcut(name: string, callback: () => void) { + this.shortcuts.set(name, callback); + } + unregisterKeycombo(combo: string) { + const name = this.keycombos.get(combo); + this.keycombos.delete(Shortcut.normalize(combo)); + if (!name) return; + let nset = this.nameToCombo.get(name); + if (nset) nset.delete(combo); + } + registerKeycombo(combo: string, name: string) { + combo = Shortcut.normalize(combo); + this.keycombos.set(combo, name); + let nset = this.nameToCombo.get(name); + if (!nset) { + nset = new Set(); + this.nameToCombo.set(name, nset); + } + nset.add(combo); + } + clear() { + this.keycombos.clear(); + this.shortcuts.clear(); + this.nameToCombo.clear(); + } + private static normalize(e: string): string { + const code = new Set(e.toLowerCase().split("-")); + const build = [] as string[]; + if (code.has("meta")) build.push("meta"); + if (code.has("ctrl")) build.push("ctrl"); + if (code.has("alt")) build.push("alt"); + if (code.has("shift")) build.push("shift"); + build.push(...[...code].filter((_) => _.length === 1).map((_) => _.toLowerCase())); + return build.join("-"); + } + private static eventToCombo(e: KeyboardEvent, down = true): string { + const build = [] as string[]; + if (e.metaKey || (e.key === "Meta" && down)) { + if (e.key !== "Meta" || down) build.push("meta"); + } + if (e.ctrlKey) build.push("ctrl"); + if (e.altKey) build.push("alt"); + if (e.shiftKey) build.push("shift"); + if (e.key.length === 1) build.push(e.key.toLowerCase()); + return build.join("-"); + } + shortCutbutton(name: string | null, callback: (combo: string) => void, updateCombo: boolean) { + let cur = (name && [...(this.nameToCombo.get(name) ?? [])].at(0)) ?? ""; + const button = document.createElement("button"); + function updateButton(combo?: string) { + button.textContent = combo || cur || I18n.keyboard.empty(); + } + const register = () => { + button.onkeyup = (event) => { + const combo = Shortcut.eventToCombo(event, false); + updateButton(combo); + }; + button.onkeydown = (event) => { + const combo = Shortcut.eventToCombo(event); + updateButton(combo); + if (event.key.length === 1) { + callback(combo); + button.onkeyup = () => {}; + button.onkeydown = () => {}; + if (updateCombo && name) { + this.unregisterKeycombo(cur); + this.registerKeycombo(combo, name); + } + cur = combo; + updateButton(); + } + event.preventDefault(); + }; + button.onblur = () => { + button.onkeyup = () => {}; + button.onkeydown = () => {}; + updateButton(); + }; + }; + button.onclick = register; + updateButton(); + const div = document.createElement("div"); + div.classList.add("flexltr", "shortCutSetting"); + + const clear = document.createElement("button"); + clear.textContent = I18n.settings.clear(); + div.append(button, clear); + clear.onclick = () => { + callback(""); + if (updateCombo && name) { + this.unregisterKeycombo(cur); + } + cur = ""; + updateButton(); + }; + return div; + } + listen(elm: HTMLElement) { + elm.addEventListener("keydown", (event) => { + if (event.key.length !== 1) return; + const combo = Shortcut.eventToCombo(event); + const name = this.keycombos.get(combo); + if (!name) return; + const func = this.shortcuts.get(name); + if (!func) return; + event.stopImmediatePropagation(); + event.preventDefault(); + func(); + }); + } +} diff --git a/src/webpage/style.css b/src/webpage/style.css index e933c6d..c446e6c 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -187,6 +187,11 @@ body { min-height: 0; display: flex; } +.shortCutSetting { + :last-child { + margin-left: 10px; + } +} .pollRes { padding-left: 50px; } diff --git a/src/webpage/utils/storage/shortcuts.ts b/src/webpage/utils/storage/shortcuts.ts new file mode 100644 index 0000000..d0d94b7 --- /dev/null +++ b/src/webpage/utils/storage/shortcuts.ts @@ -0,0 +1,22 @@ +const props = ["gifSearch"] as const; +export class Shortcuts { + gifSearch = "ctrl+g"; + + constructor(data: Partial = {}) { + Object.assign(this, data); + } + *[Symbol.iterator]() { + for (const thing of props) { + yield [thing, this[thing]] as const; + } + return; + } +} + +export function getShortcuts(): Shortcuts { + return new Shortcuts(JSON.parse(localStorage.getItem("shortCuts") ?? "{}")); +} + +export function setShortcuts(settings: Shortcuts): void { + localStorage.setItem("shortCuts", JSON.stringify(settings)); +} diff --git a/translations/en.json b/translations/en.json index cf85e91..3316790 100644 --- a/translations/en.json +++ b/translations/en.json @@ -991,6 +991,13 @@ "editMode":{ "editMsg":"Escape to $1cancel$1" }, + "keyboard":{ + "shortcuts":"Shortcuts", + "empty":"", + "descs":{ + "gifSearch":"Search GIFs:" + } + }, "domain":{ "title":"Verify domain", "domain":"Domain:",