このコミットが含まれているのは:
@@ -9,10 +9,9 @@ import {
|
||||
} from 'react'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
|
||||
import { toast } from '@/components/ui/use-toast'
|
||||
import ShortcutHelpDialog from '@/components/users/ShortcutHelpDialog'
|
||||
import {
|
||||
DEFAULT_KEY_BINDINGS,
|
||||
formatKeyBinding,
|
||||
getConflictingShortcutActionIds,
|
||||
isEditableEventTarget,
|
||||
keyBindingFromKeyboardEvent,
|
||||
@@ -46,6 +45,7 @@ type KeyboardShortcutsContextValue = {
|
||||
keyboardSettings: ClientKeyboardSettings
|
||||
effectiveBindings: Record<ShortcutActionId, KeyBinding | null>
|
||||
conflictActionIds: Set<ShortcutActionId>
|
||||
saveKeyboardSettings: (settings: ClientKeyboardSettings) => void
|
||||
setEnabled: (enabled: boolean) => void
|
||||
setBinding: (actionId: ShortcutActionId, binding: KeyBinding | null) => void
|
||||
resetBinding: (actionId: ShortcutActionId) => void
|
||||
@@ -57,17 +57,47 @@ const KeyboardShortcutsContext =
|
||||
createContext<KeyboardShortcutsContextValue | null> (null)
|
||||
|
||||
|
||||
const buildShortcutHelpText = (
|
||||
activeScope: ShortcutScope,
|
||||
effectiveBindings: Record<ShortcutActionId, KeyBinding | null>,
|
||||
): string =>
|
||||
SHORTCUT_DEFINITIONS
|
||||
.filter (definition =>
|
||||
definition.scope === 'global' || definition.scope === activeScope)
|
||||
.filter (definition => effectiveBindings[definition.id] != null)
|
||||
.map (definition =>
|
||||
`${ definition.label }: ${ formatKeyBinding (effectiveBindings[definition.id]) }`)
|
||||
.join (' / ')
|
||||
const isFocusableShortcutTarget = (element: HTMLElement): boolean => {
|
||||
if (element.closest ('[hidden], [aria-hidden="true"]') != null)
|
||||
return false
|
||||
|
||||
if (
|
||||
(element instanceof HTMLInputElement
|
||||
|| element instanceof HTMLButtonElement
|
||||
|| element instanceof HTMLSelectElement
|
||||
|| element instanceof HTMLTextAreaElement)
|
||||
&& element.disabled
|
||||
)
|
||||
return false
|
||||
|
||||
const style = window.getComputedStyle (element)
|
||||
if (
|
||||
style.display === 'none'
|
||||
|| style.visibility === 'hidden'
|
||||
|| style.pointerEvents === 'none'
|
||||
)
|
||||
return false
|
||||
|
||||
return element.getClientRects ().length > 0
|
||||
}
|
||||
|
||||
|
||||
const focusSearchTarget = (): void => {
|
||||
const targets = [
|
||||
...document.querySelectorAll<HTMLElement> (
|
||||
'[data-shortcut-focus-search="true"]',
|
||||
),
|
||||
]
|
||||
const target = targets.find (isFocusableShortcutTarget)
|
||||
|
||||
if (target == null)
|
||||
return
|
||||
|
||||
target.focus ()
|
||||
|
||||
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement)
|
||||
target.select ()
|
||||
}
|
||||
|
||||
|
||||
export const KeyboardShortcutsProvider = ({ children }: PropsWithChildren) => {
|
||||
@@ -78,6 +108,7 @@ export const KeyboardShortcutsProvider = ({ children }: PropsWithChildren) => {
|
||||
useState<ClientKeyboardSettings> (() => getClientKeyboardSettings ())
|
||||
const [registeredHandlers, setRegisteredHandlers] =
|
||||
useState<Record<number, ShortcutHandlers>> ({ })
|
||||
const [shortcutHelpOpen, setShortcutHelpOpen] = useState (false)
|
||||
const registrationIdRef = useRef (0)
|
||||
|
||||
const activeScope = useMemo<ShortcutScope> (
|
||||
@@ -108,6 +139,11 @@ export const KeyboardShortcutsProvider = ({ children }: PropsWithChildren) => {
|
||||
setKeyboardSettingsState (next)
|
||||
}, [keyboardSettings])
|
||||
|
||||
const saveKeyboardSettings = useCallback ((settings: ClientKeyboardSettings) => {
|
||||
const next = setClientKeyboardSettings (settings)
|
||||
setKeyboardSettingsState (next)
|
||||
}, [])
|
||||
|
||||
const setBinding = useCallback ((
|
||||
actionId: ShortcutActionId,
|
||||
binding: KeyBinding | null,
|
||||
@@ -157,6 +193,9 @@ export const KeyboardShortcutsProvider = ({ children }: PropsWithChildren) => {
|
||||
if (isEditableEventTarget (event.target))
|
||||
return
|
||||
|
||||
if (shortcutHelpOpen)
|
||||
return
|
||||
|
||||
const binding = keyBindingFromKeyboardEvent (event)
|
||||
if (binding == null)
|
||||
return
|
||||
@@ -183,21 +222,10 @@ export const KeyboardShortcutsProvider = ({ children }: PropsWithChildren) => {
|
||||
const builtinHandler = (
|
||||
{
|
||||
'global.openShortcutHelp': () => {
|
||||
const description =
|
||||
buildShortcutHelpText (activeScope, effectiveBindings)
|
||||
|| 'この画面で使えるショートカットはありません.'
|
||||
toast ({
|
||||
title: 'キーボード・ショートカット',
|
||||
description,
|
||||
})
|
||||
setShortcutHelpOpen (true)
|
||||
},
|
||||
'global.focusSearch': () => {
|
||||
const target = document.querySelector<HTMLElement> (
|
||||
'[data-shortcut-focus-search="true"]',
|
||||
)
|
||||
target?.focus ()
|
||||
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement)
|
||||
target.select ()
|
||||
focusSearchTarget ()
|
||||
},
|
||||
'navigation.posts': () => navigate ('/posts'),
|
||||
'navigation.tags': () => navigate ('/tags'),
|
||||
@@ -226,6 +254,7 @@ export const KeyboardShortcutsProvider = ({ children }: PropsWithChildren) => {
|
||||
keyboardSettings.enabled,
|
||||
mergedRegisteredHandlers,
|
||||
navigate,
|
||||
shortcutHelpOpen,
|
||||
])
|
||||
|
||||
const value = useMemo<KeyboardShortcutsContextValue> (() => ({
|
||||
@@ -234,6 +263,7 @@ export const KeyboardShortcutsProvider = ({ children }: PropsWithChildren) => {
|
||||
effectiveBindings,
|
||||
conflictActionIds,
|
||||
setEnabled,
|
||||
saveKeyboardSettings,
|
||||
setBinding,
|
||||
resetBinding,
|
||||
resetAllBindings,
|
||||
@@ -246,6 +276,7 @@ export const KeyboardShortcutsProvider = ({ children }: PropsWithChildren) => {
|
||||
registerHandlers,
|
||||
resetAllBindings,
|
||||
resetBinding,
|
||||
saveKeyboardSettings,
|
||||
setBinding,
|
||||
setEnabled,
|
||||
])
|
||||
@@ -253,6 +284,12 @@ export const KeyboardShortcutsProvider = ({ children }: PropsWithChildren) => {
|
||||
return (
|
||||
<KeyboardShortcutsContext.Provider value={value}>
|
||||
{children}
|
||||
<ShortcutHelpDialog
|
||||
open={shortcutHelpOpen}
|
||||
onOpenChange={setShortcutHelpOpen}
|
||||
activeScope={activeScope}
|
||||
effectiveBindings={effectiveBindings}
|
||||
conflictActionIds={conflictActionIds}/>
|
||||
</KeyboardShortcutsContext.Provider>)
|
||||
}
|
||||
|
||||
|
||||
新しい課題から参照
ユーザをブロックする