improve keyboard shortcuts handling

This commit is contained in:
Mo Tarbin
2026-08-10 00:19:57 -04:00
parent b7bdfa7885
commit af926b033e
3 changed files with 81 additions and 76 deletions

View File

@@ -40,7 +40,7 @@ const GROUPS = [
]
const GROUP_LABELS = {
tasks: 'Tasks',
history: 'Notes & activity',
history: 'Notes',
projects: 'Projects',
labels: 'Labels',
people: 'People',
@@ -164,6 +164,7 @@ const SearchContainer = ({ children, onClose, presentation }) => {
<AppModal
open
onClose={onClose}
disableRestoreFocus
title='Search'
size='lg'
maxHeight='min(720px, calc(100dvh - 48px))'
@@ -196,20 +197,31 @@ const GlobalSearchPalette = ({
const [recents] = useState(readRecents)
const selectedResultRef = useRef(null)
const fuse = useMemo(
const searchIndexes = useMemo(
() =>
new Fuse(documents, {
threshold: 0.38,
distance: 120,
ignoreLocation: true,
includeScore: true,
keys: [
{ name: 'title', weight: 0.5 },
{ name: 'keywords', weight: 0.25 },
{ name: 'body', weight: 0.17 },
{ name: 'subtitle', weight: 0.08 },
],
}),
new Map(
GROUPS.filter(group => group !== 'actions').map(group => [
group,
new Fuse(
documents.filter(item => item.provider === group),
{
threshold: 0.38,
distance: 120,
ignoreLocation: true,
includeScore: true,
keys:
group === 'history'
? [{ name: 'body', weight: 1 }]
: [
{ name: 'title', weight: 0.5 },
{ name: 'keywords', weight: 0.25 },
{ name: 'body', weight: 0.17 },
{ name: 'subtitle', weight: 0.08 },
],
},
),
]),
),
[documents],
)
@@ -217,39 +229,32 @@ const GlobalSearchPalette = ({
const normalized = query.trim().toLocaleLowerCase()
if (!normalized) {
const currentById = new Map(documents.map(item => [item.id, item]))
const recentResults = recents.map(
item => currentById.get(item.id) || item,
)
const recentResults = recents
.map(item => currentById.get(item.id) || item)
.filter(item => item.provider !== 'history' || currentById.has(item.id))
return [...recentResults, ...QUICK_ACTIONS]
}
const matches = fuse
.search(normalized, { limit: 60 })
.map(match => {
const title = match.item.title?.toLocaleLowerCase() ?? ''
let score = match.score ?? 1
const grouped = GROUPS.filter(group => group !== 'actions').flatMap(group =>
(searchIndexes.get(group)?.search(normalized, { limit: 7 }) || [])
.map(match => {
const title = match.item.title?.toLocaleLowerCase() ?? ''
let score = match.score ?? 1
if (title === normalized) {
score -= 1
} else if (title.startsWith(normalized)) {
score -= 0.15
} else if (title.includes(normalized)) {
score -= 0.08
}
if (group !== 'history') {
if (title === normalized) {
score -= 1
} else if (title.startsWith(normalized)) {
score -= 0.15
} else if (title.includes(normalized)) {
score -= 0.08
}
}
return {
...match.item,
score,
}
})
.sort((a, b) => a.score - b.score)
const byGroup = new Map(GROUPS.map(group => [group, []]))
matches.forEach(item => {
const group = byGroup.get(item.provider)
if (group && group.length < 7) group.push(item)
})
const grouped = GROUPS.flatMap(group => byGroup.get(group))
return { ...match.item, score }
})
.sort((a, b) => a.score - b.score),
)
grouped.push({
id: 'action:filter-tasks',
provider: 'actions',
@@ -258,7 +263,7 @@ const GlobalSearchPalette = ({
route: `/chores?search=${encodeURIComponent(query.trim())}`,
})
return grouped
}, [documents, fuse, query, recents])
}, [documents, query, recents, searchIndexes])
useEffect(() => {
selectedResultRef.current?.scrollIntoView({
@@ -383,7 +388,7 @@ const GlobalSearchPalette = ({
<ListItemButton
ref={index === selectedIndex ? selectedResultRef : null}
selected={index === selectedIndex}
onMouseEnter={() => setSelectedIndex(index)}
onMouseMove={() => setSelectedIndex(index)}
onClick={() => selectResult(result)}
sx={{
borderRadius: 'md',

View File

@@ -86,29 +86,33 @@ registerSearchProvider({
registerSearchProvider({
id: 'history',
getDocuments: ({ choresById, history, membersById }) =>
history.map(entry => {
history.flatMap(entry => {
const note = stripHtml(entry.notes).trim()
if (!note) return []
const chore = choresById.get(String(entry.choreId))
const member = membersById.get(String(entry.completedBy))
const note = stripHtml(entry.notes)
return document('history', {
id: `history:${entry.id}`,
entityId: entry.id,
title: chore?.name || entry.choreName || 'Task activity',
subtitle: [
member?.displayName,
entry.performedAt
? new Date(entry.performedAt).toLocaleDateString()
: null,
]
.filter(Boolean)
.join(' · '),
body: note,
keywords: `${HISTORY_STATUS[entry.status] || 'activity'} ${member?.displayName || ''}`,
route: entry.choreId
? `/chores/${entry.choreId}/history`
: '/activities',
updatedAt: entry.performedAt || entry.updatedAt,
})
return [
document('history', {
id: `history:${entry.id}`,
entityId: entry.id,
title: chore?.name || entry.choreName || 'Task note',
subtitle: [
member?.displayName,
entry.performedAt
? new Date(entry.performedAt).toLocaleDateString()
: null,
]
.filter(Boolean)
.join(' · '),
body: note,
keywords: `${HISTORY_STATUS[entry.status] || 'activity'} ${member?.displayName || ''}`,
route: entry.choreId
? `/chores/${entry.choreId}/history`
: '/activities',
updatedAt: entry.performedAt || entry.updatedAt,
}),
]
}),
})

View File

@@ -1,15 +1,15 @@
import { useState, useEffect } from 'react'
import { useEffect, useState } from 'react'
export const useKeyboardShortcuts = ({
isMultiSelectMode,
selectedChores,
addTaskModalOpen,
searchTerm,
searchFilter,
filteredChores,
choreSections,
openChoreSections,
filteredChores,
handlers,
isMultiSelectMode,
openChoreSections,
searchFilter,
searchTerm,
selectedChores,
}) => {
const [showKeyboardShortcuts, setShowKeyboardShortcuts] = useState(false)
@@ -35,10 +35,6 @@ export const useKeyboardShortcuts = ({
event.preventDefault()
handlers.onNavigateToCreate()
return
} else if (isHoldingCmdOrCtrl && event.key === 'f') {
event.preventDefault()
handlers.onFocusSearch()
return
} else if (isHoldingCmdOrCtrl && event.key === 'x') {
event.preventDefault()
if (searchTerm?.length > 0) {