Skip to content

Commit 678be14

Browse files
authored
improve(frontend): hotkeyの改修 (misskey-dev#234)
1 parent a1bd8a1 commit 678be14

13 files changed

+253
-189
lines changed

packages/frontend/src/boot/main-boot.ts

+14-13
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ import { useStream } from '@/stream.js';
1212
import * as sound from '@/scripts/sound.js';
1313
import { $i, signout, updateAccount } from '@/account.js';
1414
import { ColdDeviceStorage, defaultStore } from '@/store.js';
15-
import { makeHotkey } from '@/scripts/hotkey.js';
1615
import { reactionPicker } from '@/scripts/reaction-picker.js';
1716
import { miLocalStorage } from '@/local-storage.js';
1817
import { claimAchievement, claimedAchievements } from '@/scripts/achievements.js';
1918
import { initializeSw } from '@/scripts/initialize-sw.js';
2019
import { deckStore } from '@/ui/deck/deck-store.js';
2120
import { emojiPicker } from '@/scripts/emoji-picker.js';
2221
import { mainRouter } from '@/router/main.js';
22+
import { type Keymap, makeHotkey } from '@/scripts/tms/hotkey.js';
2323

2424
export async function mainBoot() {
2525
const { isClientUpdated } = await common(() => createApp(
@@ -66,14 +66,6 @@ export async function mainBoot() {
6666
});
6767
}
6868

69-
const hotkeys = {
70-
'd': (): void => {
71-
defaultStore.set('darkMode', !defaultStore.state.darkMode);
72-
},
73-
's': (): void => {
74-
mainRouter.push('/search');
75-
},
76-
};
7769
try {
7870
if (defaultStore.state.enableSeasonalScreenEffect) {
7971
const month = new Date().getMonth() + 1;
@@ -102,9 +94,6 @@ export async function mainBoot() {
10294
}
10395

10496
if ($i) {
105-
// only add post shortcuts if logged in
106-
hotkeys['p|n'] = post;
107-
10897
defaultStore.loaded.then(() => {
10998
if (defaultStore.state.accountSetupWizard !== -1) {
11099
popup(defineAsyncComponent(() => import('@/components/MkUserSetupDialog.vue')), {}, {}, 'closed');
@@ -308,7 +297,19 @@ export async function mainBoot() {
308297
}
309298

310299
// shortcut
311-
document.addEventListener('keydown', makeHotkey(hotkeys), { passive: false });
300+
const keymap = {
301+
'p|n': () => {
302+
if ($i == null) return;
303+
post();
304+
},
305+
'd': () => {
306+
defaultStore.set('darkMode', !defaultStore.state.darkMode);
307+
},
308+
's': () => {
309+
mainRouter.push('/search');
310+
},
311+
} as const satisfies Keymap;
312+
document.addEventListener('keydown', makeHotkey(keymap), { passive: false });
312313

313314
initializeSw();
314315
}

packages/frontend/src/components/MkMediaAudio.vue

+30-17
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ import { i18n } from '@/i18n.js';
112112
import { popupMenu } from '@/os.js';
113113
import { defaultStore } from '@/store.js';
114114
import { getMediaMenu } from '@/scripts/tms/get-media-menu.js';
115+
import { type Keymap } from '@/scripts/tms/hotkey.js';
115116
import { useReactiveDriveFile } from '@/scripts/tms/use-reactive-drive-file.js';
116117
import bytes from '@/filters/bytes.js';
117118
import { hms } from '@/filters/hms.js';
@@ -184,32 +185,44 @@ const showAudioMenu = (ev: MouseEvent) => {
184185
};
185186

186187
const keymap = {
187-
'up': () => {
188-
if (hasFocus() && audioEl.value) {
189-
volume.value = Math.min(volume.value + 0.1, 1);
190-
}
188+
'up': {
189+
allowRepeat: true,
190+
callback: () => {
191+
if (hasFocus() && audioEl.value) {
192+
volume.value = Math.min(volume.value + 0.1, 1);
193+
}
194+
},
191195
},
192-
'down': () => {
193-
if (hasFocus() && audioEl.value) {
194-
volume.value = Math.max(volume.value - 0.1, 0);
195-
}
196+
'down': {
197+
allowRepeat: true,
198+
callback: () => {
199+
if (hasFocus() && audioEl.value) {
200+
volume.value = Math.max(volume.value - 0.1, 0);
201+
}
202+
},
196203
},
197-
'left': () => {
198-
if (hasFocus() && audioEl.value) {
199-
audioEl.value.currentTime = Math.max(audioEl.value.currentTime - 5, 0);
200-
}
204+
'left': {
205+
allowRepeat: true,
206+
callback: () => {
207+
if (hasFocus() && audioEl.value) {
208+
audioEl.value.currentTime = Math.max(audioEl.value.currentTime - 5, 0);
209+
}
210+
},
201211
},
202-
'right': () => {
203-
if (hasFocus() && audioEl.value) {
204-
audioEl.value.currentTime = Math.min(audioEl.value.currentTime + 5, audioEl.value.duration);
205-
}
212+
'right': {
213+
allowRepeat: true,
214+
callback: () => {
215+
if (hasFocus() && audioEl.value) {
216+
audioEl.value.currentTime = Math.min(audioEl.value.currentTime + 5, audioEl.value.duration);
217+
}
218+
},
206219
},
207220
'space': () => {
208221
if (hasFocus()) {
209222
togglePlayPause();
210223
}
211224
},
212-
};
225+
} as const satisfies Keymap;
213226

214227
// PlayerElもしくはその子要素にフォーカスがあるかどうか
215228
function hasFocus() {

packages/frontend/src/components/MkMediaVideo.vue

+30-17
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ import { defaultStore } from '@/store.js';
160160
import { isFullscreenNotSupported } from '@/scripts/device-kind.js';
161161
import hasAudio from '@/scripts/media-has-audio.js';
162162
import { getMediaMenu } from '@/scripts/tms/get-media-menu.js';
163+
import { type Keymap } from '@/scripts/tms/hotkey.js';
163164
import { useReactiveDriveFile } from '@/scripts/tms/use-reactive-drive-file.js';
164165
import bytes from '@/filters/bytes.js';
165166
import { hms } from '@/filters/hms.js';
@@ -247,32 +248,44 @@ const showVideoMenu = (ev: MouseEvent) => {
247248
};
248249

249250
const keymap = {
250-
'up': () => {
251-
if (hasFocus() && videoEl.value) {
252-
volume.value = Math.min(volume.value + 0.1, 1);
253-
}
251+
'up': {
252+
allowRepeat: true,
253+
callback: () => {
254+
if (hasFocus() && videoEl.value) {
255+
volume.value = Math.min(volume.value + 0.1, 1);
256+
}
257+
},
254258
},
255-
'down': () => {
256-
if (hasFocus() && videoEl.value) {
257-
volume.value = Math.max(volume.value - 0.1, 0);
258-
}
259+
'down': {
260+
allowRepeat: true,
261+
callback: () => {
262+
if (hasFocus() && videoEl.value) {
263+
volume.value = Math.max(volume.value - 0.1, 0);
264+
}
265+
},
259266
},
260-
'left': () => {
261-
if (hasFocus() && videoEl.value) {
262-
videoEl.value.currentTime = Math.max(videoEl.value.currentTime - 5, 0);
263-
}
267+
'left': {
268+
allowRepeat: true,
269+
callback: () => {
270+
if (hasFocus() && videoEl.value) {
271+
videoEl.value.currentTime = Math.max(videoEl.value.currentTime - 5, 0);
272+
}
273+
},
264274
},
265-
'right': () => {
266-
if (hasFocus() && videoEl.value) {
267-
videoEl.value.currentTime = Math.min(videoEl.value.currentTime + 5, videoEl.value.duration);
268-
}
275+
'right': {
276+
allowRepeat: true,
277+
callback: () => {
278+
if (hasFocus() && videoEl.value) {
279+
videoEl.value.currentTime = Math.min(videoEl.value.currentTime + 5, videoEl.value.duration);
280+
}
281+
},
269282
},
270283
'space': () => {
271284
if (hasFocus()) {
272285
togglePlayPause();
273286
}
274287
},
275-
};
288+
} as const satisfies Keymap;
276289

277290
// PlayerElもしくはその子要素にフォーカスがあるかどうか
278291
function hasFocus() {

packages/frontend/src/components/MkMenu.vue

+14-4
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ import { i18n } from '@/i18n.js';
109109
import { isTouchUsing } from '@/scripts/touch.js';
110110
import { focusParent, isFocusable } from '@/scripts/tms/focus.js';
111111
import { getNodeOrNull } from '@/scripts/tms/get-or-null.js';
112+
import { type Keymap } from '@/scripts/tms/hotkey.js';
112113

113114
const childrenCache = new WeakMap<MenuParent, MenuItem[]>();
114115
</script>
@@ -139,10 +140,19 @@ const items2 = ref<InnerMenuItem[]>();
139140
const child = shallowRef<InstanceType<typeof XChild>>();
140141

141142
const keymap = {
142-
'up|k|shift+tab': () => focusUp(),
143-
'down|j|tab': () => focusDown(),
144-
'esc': () => close(false),
145-
};
143+
'up|k|shift+tab': {
144+
allowRepeat: true,
145+
callback: () => focusUp(),
146+
},
147+
'down|j|tab': {
148+
allowRepeat: true,
149+
callback: () => focusDown(),
150+
},
151+
'esc': {
152+
allowRepeat: true,
153+
callback: () => close(false),
154+
},
155+
} as const satisfies Keymap;
146156

147157
const childShowingItem = ref<MenuItem | null>();
148158

packages/frontend/src/components/MkModal.vue

+6-2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import * as os from '@/os.js';
4747
import { isTouchUsing } from '@/scripts/touch.js';
4848
import { defaultStore } from '@/store.js';
4949
import { deviceKind } from '@/scripts/device-kind.js';
50+
import { type Keymap } from '@/scripts/tms/hotkey.js';
5051

5152
function getFixedContainer(el: Element | null): Element | null {
5253
if (el == null || el.tagName === 'BODY') return null;
@@ -154,8 +155,11 @@ if (type.value === 'drawer') {
154155
}
155156

156157
const keymap = {
157-
'esc': () => emit('esc'),
158-
};
158+
'esc': {
159+
allowRepeat: true,
160+
callback: () => emit('esc'),
161+
},
162+
} as const satisfies Keymap;
159163

160164
const MARGIN = 16;
161165
const SCROLLBAR_THICKNESS = 16;

packages/frontend/src/components/MkNote.vue

+14-4
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ import { shouldCollapsed } from '@/scripts/collapsed.js';
199199
import { isEnabledUrlPreview } from '@/instance.js';
200200
import { focusPrev, focusNext } from '@/scripts/tms/focus.js';
201201
import { getAppearNote } from '@/scripts/tms/get-appear-note.js';
202+
import { type Keymap } from '@/scripts/tms/hotkey.js';
202203
import { isQuote, isRenote } from '@/scripts/tms/is-renote.js';
203204
import { tmsStore } from '@/tms/store.js';
204205

@@ -326,10 +327,19 @@ const keymap = {
326327
collapsed.value = !collapsed.value;
327328
}
328329
},
329-
'esc': () => blur(),
330-
'up|k|shift+tab': () => focusBefore(),
331-
'down|j|tab': () => focusAfter(),
332-
};
330+
'esc': {
331+
allowRepeat: true,
332+
callback: () => blur(),
333+
},
334+
'up|k|shift+tab': {
335+
allowRepeat: true,
336+
callback: () => focusBefore(),
337+
},
338+
'down|j|tab': {
339+
allowRepeat: true,
340+
callback: () => focusAfter(),
341+
},
342+
} as const satisfies Keymap;
333343

334344
provide('react', (reaction: string) => {
335345
misskeyApi('notes/reactions/create', {

packages/frontend/src/components/MkNoteDetailed.vue

+6-2
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ import MkReactionIcon from '@/components/MkReactionIcon.vue';
236236
import MkButton from '@/components/MkButton.vue';
237237
import { isEnabledUrlPreview } from '@/instance.js';
238238
import { getAppearNote } from '@/scripts/tms/get-appear-note.js';
239+
import { type Keymap } from '@/scripts/tms/hotkey.js';
239240
import { isQuote, isRenote } from '@/scripts/tms/is-renote.js';
240241
import { tmsStore } from '@/tms/store.js';
241242

@@ -308,8 +309,11 @@ const keymap = {
308309
showContent.value = !showContent.value;
309310
}
310311
},
311-
'esc': () => blur(),
312-
};
312+
'esc': {
313+
allowRepeat: true,
314+
callback: () => blur(),
315+
},
316+
} as const satisfies Keymap;
313317

314318
provide('react', (reaction: string) => {
315319
misskeyApi('notes/reactions/create', {

packages/frontend/src/directives/hotkey.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
import { Directive } from 'vue';
7-
import { makeHotkey } from '../scripts/hotkey.js';
7+
import { makeHotkey } from '@/scripts/tms/hotkey.js';
88

99
export default {
1010
mounted(el, binding) {

packages/frontend/src/pages/antenna-timeline.vue

+1-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
77
<MkStickyContainer>
88
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
99
<MkSpacer :contentMax="800">
10-
<div ref="rootEl" v-hotkey.global="keymap">
10+
<div ref="rootEl">
1111
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
1212
<div :class="$style.tl">
1313
<MkTimeline
@@ -44,9 +44,6 @@ const antenna = ref<Misskey.entities.Antenna | null>(null);
4444
const queue = ref(0);
4545
const rootEl = shallowRef<HTMLElement>();
4646
const tlEl = shallowRef<InstanceType<typeof MkTimeline>>();
47-
const keymap = computed(() => ({
48-
't': focus,
49-
}));
5047

5148
function queueUpdated(q) {
5249
queue.value = q;

packages/frontend/src/pages/timeline.vue

+1-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
88
<template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :displayMyAvatar="true"/></template>
99
<MkSpacer :contentMax="800">
1010
<MkHorizontalSwipe v-model:tab="src" :tabs="$i ? headerTabs : headerTabsWhenNotLogin">
11-
<div :key="src" ref="rootEl" v-hotkey.global="keymap">
11+
<div :key="src" ref="rootEl">
1212
<MkInfo v-if="['home', 'local', 'social', 'global'].includes(src) && !defaultStore.reactiveState.timelineTutorials.value[src]" style="margin-bottom: var(--margin);" closable @close="closeTutorial()">
1313
{{ i18n.ts._timelineDescription[src] }}
1414
</MkInfo>
@@ -58,9 +58,6 @@ provide('shouldOmitHeaderTitle', true);
5858

5959
const isLocalTimelineAvailable = ($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable);
6060
const isGlobalTimelineAvailable = ($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable);
61-
const keymap = {
62-
't': focus,
63-
};
6461

6562
const tlComponent = shallowRef<InstanceType<typeof MkTimeline>>();
6663
const rootEl = shallowRef<HTMLElement>();

0 commit comments

Comments
 (0)