mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
fix(client): show the platform's own shortcut modifier
Lift the bulk rename empty state out of the vertical centre of the tools pane so it sits near the top instead of floating in the void. Replace the hardcoded Cmd in the settings search hint with a shared useModifierKey composable, so Windows and Linux see Ctrl+K. The translated hint now takes the combo as a placeholder.
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { useModifierKey } from '../useModifierKey'
|
||||
|
||||
function withUserAgent(userAgent: string) {
|
||||
vi.spyOn(navigator, 'userAgent', 'get').mockReturnValue(userAgent)
|
||||
return useModifierKey()
|
||||
}
|
||||
|
||||
describe('useModifierKey', () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('uses the command glyph on Apple platforms', () => {
|
||||
const { isMac, modifierKey } = withUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36')
|
||||
expect(isMac.value).toBe(true)
|
||||
expect(modifierKey.value).toBe('⌘')
|
||||
})
|
||||
|
||||
it.each([
|
||||
['Windows', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'],
|
||||
['Linux', 'Mozilla/5.0 (X11; Linux x86_64; rv:130.0) Gecko/20100101 Firefox/130.0'],
|
||||
])('uses Ctrl on %s, which has no command key', (_platform, userAgent) => {
|
||||
const { isMac, modifierKey } = withUserAgent(userAgent)
|
||||
expect(isMac.value).toBe(false)
|
||||
expect(modifierKey.value).toBe('Ctrl')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,11 @@
|
||||
import { computed, type ComputedRef } from 'vue'
|
||||
|
||||
/**
|
||||
* Apple keyboards label the shortcut modifier differently from every other platform, so a hint
|
||||
* that hardcodes one of them is wrong for half the users. Keep the detection here so the label
|
||||
* cannot drift between the places that display it.
|
||||
*/
|
||||
export function useModifierKey(): { isMac: ComputedRef<boolean>; modifierKey: ComputedRef<string> } {
|
||||
const isMac = computed(() => typeof navigator !== 'undefined' && /Mac/i.test(navigator.userAgent))
|
||||
return { isMac, modifierKey: computed(() => (isMac.value ? '⌘' : 'Ctrl')) }
|
||||
}
|
||||
@@ -2,14 +2,14 @@
|
||||
import { computed } from 'vue'
|
||||
import { X } from '@lucide/vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useModifierKey } from '@/composables/useModifierKey'
|
||||
|
||||
defineProps<{ open: boolean }>()
|
||||
const emit = defineEmits<{ 'update:open': [value: boolean] }>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const isMac = computed(() => typeof navigator !== 'undefined' && /Mac/i.test(navigator.userAgent))
|
||||
const modKey = computed(() => (isMac.value ? '⌘' : 'Ctrl'))
|
||||
const { modifierKey: modKey } = useModifierKey()
|
||||
|
||||
function handleClose() {
|
||||
emit('update:open', false)
|
||||
|
||||
@@ -66,10 +66,22 @@ async function search(wrapper: Wrapper, term: string): Promise<string[]> {
|
||||
|
||||
describe('SettingsNav', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.restoreAllMocks()
|
||||
push.mockClear()
|
||||
})
|
||||
|
||||
describe('shortcut hint', () => {
|
||||
it('names the command key on Apple platforms', () => {
|
||||
vi.spyOn(navigator, 'userAgent', 'get').mockReturnValue('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36')
|
||||
expect(mountNav().text()).toContain('Press \u2318K to jump to any setting')
|
||||
})
|
||||
|
||||
it('names Ctrl everywhere else, since those keyboards have no command key', () => {
|
||||
vi.spyOn(navigator, 'userAgent', 'get').mockReturnValue('Mozilla/5.0 (X11; Linux x86_64; rv:130.0) Gecko/20100101 Firefox/130.0')
|
||||
expect(mountNav().text()).toContain('Press Ctrl+K to jump to any setting')
|
||||
})
|
||||
})
|
||||
|
||||
it('places an icon-only back action beside search', () => {
|
||||
const wrapper = mountNav()
|
||||
const back = wrapper.get('[data-testid="settings-nav-back"]')
|
||||
|
||||
@@ -4,6 +4,7 @@ import { computed, ref, watch, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ArrowLeft, ChevronRight, Search, X } from '@lucide/vue'
|
||||
import { useModifierKey } from '@/composables/useModifierKey'
|
||||
import { usePermissions } from '@/features/auth/composables/usePermissions'
|
||||
import { useSettingsNavStatus } from '../composables/useSettingsNavStatus'
|
||||
import { visibleSettingsNav, type SettingsNavGroup, type SettingsNavItem } from '../lib/settings-nav'
|
||||
@@ -17,6 +18,10 @@ const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { isSuperuser, userPermissions, isDemoRestrictedAccount } = usePermissions()
|
||||
const { isMac, modifierKey } = useModifierKey()
|
||||
|
||||
/** Apple writes the combo as one glyph pair, everyone else separates the two keys. */
|
||||
const shortcutLabel = computed(() => (isMac.value ? `${modifierKey.value}K` : `${modifierKey.value}+K`))
|
||||
|
||||
const query = ref('')
|
||||
const searchInput = ref<HTMLInputElement | null>(null)
|
||||
@@ -296,7 +301,7 @@ defineExpose({ focusSearch })
|
||||
</div>
|
||||
|
||||
<div class="hidden shrink-0 border-t border-border/70 px-4 py-2 text-[11px] text-muted-foreground md:block">
|
||||
{{ t('settings.nav.shortcutHint') }}
|
||||
{{ t('settings.nav.shortcutHint', { shortcut: shortcutLabel }) }}
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
@@ -246,7 +246,7 @@ function handleRetryPreview(): void {
|
||||
<span>{{ t('tools.bulkRename.executionFailed') }} - {{ executionErrorMessage }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="bulk.selectedLibraryId.value === null" class="flex flex-1 flex-col items-center justify-center gap-4 p-10 text-center">
|
||||
<div v-if="bulk.selectedLibraryId.value === null" class="flex flex-1 flex-col items-center justify-start gap-4 px-10 pt-12 pb-10 text-center">
|
||||
<span class="grid size-14 place-items-center rounded-full bg-muted text-muted-foreground">
|
||||
<SlidersHorizontal class="size-6" aria-hidden="true" />
|
||||
</span>
|
||||
|
||||
@@ -3089,7 +3089,6 @@
|
||||
"searchPlaceholder": "Sucheinstellungen",
|
||||
"clearSearch": "Suche zurücksetzen",
|
||||
"noResults": "Keine Einstellungen entsprechen {query}.",
|
||||
"shortcutHint": "Drücke Cmd K um zu einer beliebigen Einstellung zu springen",
|
||||
"groups": {
|
||||
"you": "Du",
|
||||
"library": "Bibliothek",
|
||||
|
||||
@@ -2291,7 +2291,6 @@
|
||||
"searchPlaceholder": "Ρυθμίσεις αναζήτησης",
|
||||
"clearSearch": "Εκκαθάριση αναζήτησης",
|
||||
"noResults": "Καμία ρύθμιση δεν ταιριάζει με το {query}.",
|
||||
"shortcutHint": "Πατήστε Cmd K για να μεταβείτε σε οποιαδήποτε ρύθμιση",
|
||||
"groups": {
|
||||
"you": "Εσείς",
|
||||
"library": "Βιβλιοθήκη",
|
||||
|
||||
@@ -3368,7 +3368,7 @@
|
||||
"searchPlaceholder": "Search settings",
|
||||
"clearSearch": "Clear search",
|
||||
"noResults": "No settings match {query}.",
|
||||
"shortcutHint": "Press Cmd K to jump to any setting",
|
||||
"shortcutHint": "Press {shortcut} to jump to any setting",
|
||||
"status": {
|
||||
"scanning": "Scanning"
|
||||
},
|
||||
|
||||
@@ -3365,7 +3365,6 @@
|
||||
"searchPlaceholder": "Ajustes de búsqueda",
|
||||
"clearSearch": "Borrar búsqueda",
|
||||
"noResults": "No hay ajustes que coincidan con {query}.",
|
||||
"shortcutHint": "Presiona Cmd K para saltar a cualquier ajuste",
|
||||
"status": {
|
||||
"scanning": "Escaneando"
|
||||
},
|
||||
|
||||
@@ -3306,7 +3306,6 @@
|
||||
"searchPlaceholder": "Rechercher des paramètres",
|
||||
"clearSearch": "Effacer la recherche",
|
||||
"noResults": "Aucun paramètre ne correspond à « {query} ».",
|
||||
"shortcutHint": "Appuyez sur Cmd K pour accéder directement à un paramètre",
|
||||
"status": {
|
||||
"scanning": "Analyse en cours"
|
||||
},
|
||||
|
||||
@@ -3238,7 +3238,6 @@
|
||||
"searchPlaceholder": "Keresési beállítások",
|
||||
"clearSearch": "Keresés törlése",
|
||||
"noResults": "Nincsenek a következőnek megfelelő beállítások: {query}.",
|
||||
"shortcutHint": "Nyomd meg a Cmd K billentyűkombinációt bármelyik beállításhoz ugráshoz",
|
||||
"status": {
|
||||
"scanning": "Beolvasás"
|
||||
},
|
||||
|
||||
@@ -2262,7 +2262,6 @@
|
||||
"searchPlaceholder": "検索設定",
|
||||
"clearSearch": "検索をクリア",
|
||||
"noResults": "{query} に一致する設定がありません。",
|
||||
"shortcutHint": "Cmd K を押して任意の設定にジャンプします",
|
||||
"groups": {
|
||||
"you": "あなたです",
|
||||
"library": "ライブラリ",
|
||||
|
||||
@@ -2946,7 +2946,6 @@
|
||||
"searchPlaceholder": "설정 검색",
|
||||
"clearSearch": "검색 초기화",
|
||||
"noResults": "{query}와 일치하는 설정 항목 없음",
|
||||
"shortcutHint": "설정으로 이동하려면 Cmd K 단축키를 눌러주세요",
|
||||
"groups": {
|
||||
"you": "개인",
|
||||
"library": "라이브러리",
|
||||
|
||||
@@ -3241,7 +3241,6 @@
|
||||
"searchPlaceholder": "Procurar nas configurações",
|
||||
"clearSearch": "Limpar busca",
|
||||
"noResults": "Nenhuma configuração corresponde a {query}.",
|
||||
"shortcutHint": "Pressione Cmd K para pular para qualquer configuração",
|
||||
"status": {
|
||||
"scanning": "Analisando"
|
||||
},
|
||||
|
||||
@@ -2359,7 +2359,6 @@
|
||||
"searchPlaceholder": "Setări căutare",
|
||||
"clearSearch": "Ștergeți căutarea",
|
||||
"noResults": "Nicio setare nu corespunde cu {query}.",
|
||||
"shortcutHint": "Apăsați Cmd K pentru a sări la orice setare",
|
||||
"groups": {
|
||||
"you": "Tu",
|
||||
"library": "Bibliotecă",
|
||||
|
||||
@@ -2509,7 +2509,6 @@
|
||||
"searchPlaceholder": "Nastavitve iskanja",
|
||||
"clearSearch": "Počisti iskanje",
|
||||
"noResults": "Nobena nastavitev ne ustreza {query}.",
|
||||
"shortcutHint": "Pritisnite Cmd K za skok na katero koli nastavitev",
|
||||
"groups": {
|
||||
"you": "Vi",
|
||||
"library": "Knjižnica",
|
||||
|
||||
@@ -3015,7 +3015,6 @@
|
||||
"searchPlaceholder": "搜索设置",
|
||||
"clearSearch": "清空搜索内容",
|
||||
"noResults": "没有设置匹配{query}",
|
||||
"shortcutHint": "按 Cmd K 跳转到任何设置",
|
||||
"status": {
|
||||
"scanning": "扫描中"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user