From 7d34f7a073a61fca437a707cc51247ea997d02aa Mon Sep 17 00:00:00 2001 From: taiy <53635909+taiyme@users.noreply.github.com> Date: Fri, 21 Jun 2024 17:09:54 +0900 Subject: [PATCH 1/7] =?UTF-8?q?improve(frontend):=20hotkey=E3=81=AE?= =?UTF-8?q?=E6=94=B9=E4=BF=AE=20(#234)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 678be147f4db709dadf25d007cc2e679e98a370e) --- packages/frontend/src/boot/main-boot.ts | 27 ++-- .../frontend/src/components/MkMediaAudio.vue | 47 +++--- .../frontend/src/components/MkMediaVideo.vue | 47 +++--- packages/frontend/src/components/MkMenu.vue | 21 ++- packages/frontend/src/components/MkModal.vue | 8 +- packages/frontend/src/components/MkNote.vue | 57 ++++++-- .../src/components/MkNoteDetailed.vue | 26 +++- packages/frontend/src/directives/hotkey.ts | 2 +- .../frontend/src/pages/antenna-timeline.vue | 5 +- packages/frontend/src/pages/timeline.vue | 5 +- packages/frontend/src/scripts/hotkey.ts | 95 ------------ packages/frontend/src/scripts/keycode.ts | 24 ---- packages/frontend/src/scripts/tms/hotkey.ts | 136 ++++++++++++++++++ 13 files changed, 302 insertions(+), 198 deletions(-) delete mode 100644 packages/frontend/src/scripts/hotkey.ts delete mode 100644 packages/frontend/src/scripts/keycode.ts create mode 100644 packages/frontend/src/scripts/tms/hotkey.ts diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index faf230a1a293..3559ec9632de 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -13,7 +13,6 @@ import * as sound from '@/scripts/sound.js'; import { $i, signout, updateAccount } from '@/account.js'; import { instance } from '@/instance.js'; import { ColdDeviceStorage, defaultStore } from '@/store.js'; -import { makeHotkey } from '@/scripts/hotkey.js'; import { reactionPicker } from '@/scripts/reaction-picker.js'; import { miLocalStorage } from '@/local-storage.js'; import { claimAchievement, claimedAchievements } from '@/scripts/achievements.js'; @@ -21,6 +20,7 @@ import { initializeSw } from '@/scripts/initialize-sw.js'; import { deckStore } from '@/ui/deck/deck-store.js'; import { emojiPicker } from '@/scripts/emoji-picker.js'; import { mainRouter } from '@/router/main.js'; +import { type Keymap, makeHotkey } from '@/scripts/tms/hotkey.js'; export async function mainBoot() { const { isClientUpdated } = await common(() => createApp( @@ -69,14 +69,6 @@ export async function mainBoot() { }); } - const hotkeys = { - 'd': (): void => { - defaultStore.set('darkMode', !defaultStore.state.darkMode); - }, - 's': (): void => { - mainRouter.push('/search'); - }, - }; try { if (defaultStore.state.enableSeasonalScreenEffect) { const month = new Date().getMonth() + 1; @@ -105,9 +97,6 @@ export async function mainBoot() { } if ($i) { - // only add post shortcuts if logged in - hotkeys['p|n'] = post; - defaultStore.loaded.then(() => { if (defaultStore.state.accountSetupWizard !== -1) { const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkUserSetupDialog.vue')), {}, { @@ -334,7 +323,19 @@ export async function mainBoot() { } // shortcut - document.addEventListener('keydown', makeHotkey(hotkeys)); + const keymap = { + 'p|n': () => { + if ($i == null) return; + post(); + }, + 'd': () => { + defaultStore.set('darkMode', !defaultStore.state.darkMode); + }, + 's': () => { + mainRouter.push('/search'); + }, + } as const satisfies Keymap; + document.addEventListener('keydown', makeHotkey(keymap), { passive: false }); initializeSw(); } diff --git a/packages/frontend/src/components/MkMediaAudio.vue b/packages/frontend/src/components/MkMediaAudio.vue index ebd4fc9ca418..d73395690cf4 100644 --- a/packages/frontend/src/components/MkMediaAudio.vue +++ b/packages/frontend/src/components/MkMediaAudio.vue @@ -80,6 +80,7 @@ import type { MenuItem } from '@/types/menu.js'; import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; +import { type Keymap } from '@/scripts/tms/hotkey.js'; import bytes from '@/filters/bytes.js'; import { hms } from '@/filters/hms.js'; import MkMediaRange from '@/components/MkMediaRange.vue'; @@ -90,32 +91,44 @@ const props = defineProps<{ }>(); const keymap = { - 'up': () => { - if (hasFocus() && audioEl.value) { - volume.value = Math.min(volume.value + 0.1, 1); - } + 'up': { + allowRepeat: true, + callback: () => { + if (hasFocus() && audioEl.value) { + volume.value = Math.min(volume.value + 0.1, 1); + } + }, }, - 'down': () => { - if (hasFocus() && audioEl.value) { - volume.value = Math.max(volume.value - 0.1, 0); - } + 'down': { + allowRepeat: true, + callback: () => { + if (hasFocus() && audioEl.value) { + volume.value = Math.max(volume.value - 0.1, 0); + } + }, }, - 'left': () => { - if (hasFocus() && audioEl.value) { - audioEl.value.currentTime = Math.max(audioEl.value.currentTime - 5, 0); - } + 'left': { + allowRepeat: true, + callback: () => { + if (hasFocus() && audioEl.value) { + audioEl.value.currentTime = Math.max(audioEl.value.currentTime - 5, 0); + } + }, }, - 'right': () => { - if (hasFocus() && audioEl.value) { - audioEl.value.currentTime = Math.min(audioEl.value.currentTime + 5, audioEl.value.duration); - } + 'right': { + allowRepeat: true, + callback: () => { + if (hasFocus() && audioEl.value) { + audioEl.value.currentTime = Math.min(audioEl.value.currentTime + 5, audioEl.value.duration); + } + }, }, 'space': () => { if (hasFocus()) { togglePlayPause(); } }, -}; +} as const satisfies Keymap; // PlayerElもしくはその子要素にフォーカスがあるかどうか function hasFocus() { diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue index 707d7c150111..51647c014f16 100644 --- a/packages/frontend/src/components/MkMediaVideo.vue +++ b/packages/frontend/src/components/MkMediaVideo.vue @@ -112,6 +112,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { ref, shallowRef, computed, watch, onDeactivated, onActivated, onMounted } from 'vue'; import * as Misskey from 'misskey-js'; import type { MenuItem } from '@/types/menu.js'; +import { type Keymap } from '@/scripts/tms/hotkey.js'; import bytes from '@/filters/bytes.js'; import { hms } from '@/filters/hms.js'; import { defaultStore } from '@/store.js'; @@ -127,32 +128,44 @@ const props = defineProps<{ }>(); const keymap = { - 'up': () => { - if (hasFocus() && videoEl.value) { - volume.value = Math.min(volume.value + 0.1, 1); - } + 'up': { + allowRepeat: true, + callback: () => { + if (hasFocus() && videoEl.value) { + volume.value = Math.min(volume.value + 0.1, 1); + } + }, }, - 'down': () => { - if (hasFocus() && videoEl.value) { - volume.value = Math.max(volume.value - 0.1, 0); - } + 'down': { + allowRepeat: true, + callback: () => { + if (hasFocus() && videoEl.value) { + volume.value = Math.max(volume.value - 0.1, 0); + } + }, }, - 'left': () => { - if (hasFocus() && videoEl.value) { - videoEl.value.currentTime = Math.max(videoEl.value.currentTime - 5, 0); - } + 'left': { + allowRepeat: true, + callback: () => { + if (hasFocus() && videoEl.value) { + videoEl.value.currentTime = Math.max(videoEl.value.currentTime - 5, 0); + } + }, }, - 'right': () => { - if (hasFocus() && videoEl.value) { - videoEl.value.currentTime = Math.min(videoEl.value.currentTime + 5, videoEl.value.duration); - } + 'right': { + allowRepeat: true, + callback: () => { + if (hasFocus() && videoEl.value) { + videoEl.value.currentTime = Math.min(videoEl.value.currentTime + 5, videoEl.value.duration); + } + }, }, 'space': () => { if (hasFocus()) { togglePlayPause(); } }, -}; +} as const satisfies Keymap; // PlayerElもしくはその子要素にフォーカスがあるかどうか function hasFocus() { diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue index d91239b9e221..f32188102d94 100644 --- a/packages/frontend/src/components/MkMenu.vue +++ b/packages/frontend/src/components/MkMenu.vue @@ -98,11 +98,13 @@ import { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuRadio import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; import { isTouchUsing } from '@/scripts/touch.js'; +import { type Keymap } from '@/scripts/tms/hotkey.js'; const childrenCache = new WeakMap(); diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue index 9e50713bba06..264d8b6c9cc8 100644 --- a/packages/frontend/src/components/MkModal.vue +++ b/packages/frontend/src/components/MkModal.vue @@ -47,7 +47,7 @@ import * as os from '@/os.js'; import { isTouchUsing } from '@/scripts/touch.js'; import { defaultStore } from '@/store.js'; import { deviceKind } from '@/scripts/device-kind.js'; -import { type Keymap } from '@/scripts/tms/hotkey.js'; +import { type Keymap } from '@/scripts/hotkey.js'; function getFixedContainer(el: Element | null): Element | null { if (el == null || el.tagName === 'BODY') return null; diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 742a8331bb06..5f1820a37935 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -198,7 +198,7 @@ import MkRippleEffect from '@/components/MkRippleEffect.vue'; import { showMovedDialog } from '@/scripts/show-moved-dialog.js'; import { shouldCollapsed } from '@/scripts/collapsed.js'; import { isEnabledUrlPreview } from '@/instance.js'; -import { type Keymap } from '@/scripts/tms/hotkey.js'; +import { type Keymap } from '@/scripts/hotkey.js'; const props = withDefaults(defineProps<{ note: Misskey.entities.Note; diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index fd5b053f9627..8f65e3b60acb 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -233,7 +233,7 @@ import MkPagination, { type Paging } from '@/components/MkPagination.vue'; import MkReactionIcon from '@/components/MkReactionIcon.vue'; import MkButton from '@/components/MkButton.vue'; import { isEnabledUrlPreview } from '@/instance.js'; -import { type Keymap } from '@/scripts/tms/hotkey.js'; +import { type Keymap } from '@/scripts/hotkey.js'; const props = withDefaults(defineProps<{ note: Misskey.entities.Note; diff --git a/packages/frontend/src/directives/hotkey.ts b/packages/frontend/src/directives/hotkey.ts index 53ce481daf08..0a7d136f182c 100644 --- a/packages/frontend/src/directives/hotkey.ts +++ b/packages/frontend/src/directives/hotkey.ts @@ -4,7 +4,7 @@ */ import { Directive } from 'vue'; -import { makeHotkey } from '@/scripts/tms/hotkey.js'; +import { makeHotkey } from '@/scripts/hotkey.js'; export default { mounted(el, binding) { diff --git a/packages/frontend/src/scripts/get-or-null.ts b/packages/frontend/src/scripts/get-or-null.ts new file mode 100644 index 000000000000..fbf54675fda2 --- /dev/null +++ b/packages/frontend/src/scripts/get-or-null.ts @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export const getNodeOrNull = (input: unknown): Node | null => { + if (input instanceof Node) return input; + return null; +}; + +export const getElementOrNull = (input: unknown): Element | null => { + if (input instanceof Element) return input; + return null; +}; + +export const getHTMLElementOrNull = (input: unknown): HTMLElement | null => { + if (input instanceof HTMLElement) return input; + return null; +}; diff --git a/packages/frontend/src/scripts/tms/hotkey.ts b/packages/frontend/src/scripts/hotkey.ts similarity index 98% rename from packages/frontend/src/scripts/tms/hotkey.ts rename to packages/frontend/src/scripts/hotkey.ts index 339d4a4d84c5..578ce2946d85 100644 --- a/packages/frontend/src/scripts/tms/hotkey.ts +++ b/packages/frontend/src/scripts/hotkey.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { getHTMLElementOrNull } from '@/scripts/tms/get-or-null.js'; +import { getHTMLElementOrNull } from '@/scripts/get-or-null.js'; //#region types export type Keymap = Record; From f9453598439f1c81ef509d83d248ab859f4e0356 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Tue, 9 Jul 2024 17:49:44 +0900 Subject: [PATCH 3/7] fix --- packages/frontend/src/scripts/get-or-null.ts | 19 ------------------- packages/frontend/src/scripts/hotkey.ts | 2 +- 2 files changed, 1 insertion(+), 20 deletions(-) delete mode 100644 packages/frontend/src/scripts/get-or-null.ts diff --git a/packages/frontend/src/scripts/get-or-null.ts b/packages/frontend/src/scripts/get-or-null.ts deleted file mode 100644 index fbf54675fda2..000000000000 --- a/packages/frontend/src/scripts/get-or-null.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export const getNodeOrNull = (input: unknown): Node | null => { - if (input instanceof Node) return input; - return null; -}; - -export const getElementOrNull = (input: unknown): Element | null => { - if (input instanceof Element) return input; - return null; -}; - -export const getHTMLElementOrNull = (input: unknown): HTMLElement | null => { - if (input instanceof HTMLElement) return input; - return null; -}; diff --git a/packages/frontend/src/scripts/hotkey.ts b/packages/frontend/src/scripts/hotkey.ts index 578ce2946d85..169c440f0ff2 100644 --- a/packages/frontend/src/scripts/hotkey.ts +++ b/packages/frontend/src/scripts/hotkey.ts @@ -53,7 +53,7 @@ export const makeHotkey = (keymap: Keymap) => { if ('pswp' in window && window.pswp != null) return; if (document.activeElement != null) { if (IGNORE_ELEMENTS.includes(document.activeElement.tagName.toLowerCase())) return; - if (getHTMLElementOrNull(document.activeElement)?.isContentEditable) return; + if ((document.activeElement as HTMLElement).isContentEditable) return; } for (const { patterns, callback, options } of actions) { if (matchPatterns(ev, patterns, options)) { From 07259cbe8bbb49fd9d22e063d9ff31107e5ba959 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Tue, 9 Jul 2024 17:49:53 +0900 Subject: [PATCH 4/7] fix --- packages/frontend/src/scripts/hotkey.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/frontend/src/scripts/hotkey.ts b/packages/frontend/src/scripts/hotkey.ts index 169c440f0ff2..015407ce103e 100644 --- a/packages/frontend/src/scripts/hotkey.ts +++ b/packages/frontend/src/scripts/hotkey.ts @@ -3,8 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { getHTMLElementOrNull } from '@/scripts/get-or-null.js'; - //#region types export type Keymap = Record; From e2bc7b16da088a64e22becafbfeb9bb6edc53e80 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Tue, 9 Jul 2024 17:51:21 +0900 Subject: [PATCH 5/7] add missing keycodes --- packages/frontend/src/scripts/hotkey.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/scripts/hotkey.ts b/packages/frontend/src/scripts/hotkey.ts index 015407ce103e..fd79baa60484 100644 --- a/packages/frontend/src/scripts/hotkey.ts +++ b/packages/frontend/src/scripts/hotkey.ts @@ -30,8 +30,8 @@ type Action = { //#region consts const KEY_ALIASES = { 'esc': 'Escape', - 'enter': 'Enter', - 'space': ' ', + 'enter': ['Enter', 'NumpadEnter'], + 'space': [' ', 'Spacebar'], 'up': 'ArrowUp', 'down': 'ArrowDown', 'left': 'ArrowLeft', From b010e1bb0ab1b012d90a326275fdc189e0c59d26 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Tue, 9 Jul 2024 17:52:54 +0900 Subject: [PATCH 6/7] fix --- packages/frontend/src/components/MkMenu.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue index bc5b46fc0738..119504f7448f 100644 --- a/packages/frontend/src/components/MkMenu.vue +++ b/packages/frontend/src/components/MkMenu.vue @@ -104,7 +104,6 @@ const childrenCache = new WeakMap();