Files
donetick/src/search/searchProviders.js

165 lines
4.9 KiB
JavaScript

import { SETTINGS_SECTIONS } from '../constants/settingsSections'
const stripHtml = value => {
if (!value) return ''
if (typeof globalThis.document === 'undefined')
return String(value).replace(/<[^>]*>/g, ' ')
const element = globalThis.document.createElement('div')
element.innerHTML = String(value)
return element.textContent || element.innerText || ''
}
const HISTORY_STATUS = {
0: 'in progress',
1: 'completed',
2: 'skipped',
3: 'pending approval',
4: 'rejected',
5: 'missed',
6: 'rescheduled',
}
const providers = []
export const registerSearchProvider = provider => {
if (!provider?.id || typeof provider.getDocuments !== 'function') {
throw new Error('A search provider needs an id and getDocuments function')
}
const existing = providers.findIndex(item => item.id === provider.id)
if (existing >= 0) providers.splice(existing, 1, provider)
else providers.push(provider)
return () => {
const index = providers.indexOf(provider)
if (index >= 0) providers.splice(index, 1)
}
}
export const getSearchProviders = () => [...providers]
const document = (provider, item) => ({ provider, ...item })
registerSearchProvider({
id: 'tasks',
getDocuments: ({ chores, membersById, projectsById }) =>
chores.map(chore => {
const labels =
chore.labelsV2?.map(label => label.name).filter(Boolean) || []
const project = projectsById.get(String(chore.projectId))
const assignees = (chore.assignees || [])
.map(assignee => membersById.get(String(assignee.userId))?.displayName)
.filter(Boolean)
const description = stripHtml(chore.description)
return document('tasks', {
id: `task:${chore.id}`,
entityId: chore.id,
title: chore.name || 'Untitled task',
subtitle:
[project?.name, ...labels].filter(Boolean).join(' · ') || 'Task',
body: description,
keywords: [...labels, project?.name, ...assignees]
.filter(Boolean)
.join(' '),
route: `/chores/${chore.id}`,
updatedAt: chore.updatedAt || chore.createdAt,
})
}),
})
registerSearchProvider({
id: 'history',
getDocuments: ({ choresById, history, membersById }) =>
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))
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,
}),
]
}),
})
registerSearchProvider({
id: 'projects',
getDocuments: ({ projects }) =>
projects.map(project =>
document('projects', {
id: `project:${project.id}`,
entityId: project.id,
title: project.name || 'Untitled project',
subtitle: 'Project',
body: stripHtml(project.description),
keywords: 'folder project',
route: `/chores?project=${encodeURIComponent(project.id)}`,
updatedAt: project.updatedAt,
}),
),
})
registerSearchProvider({
id: 'labels',
getDocuments: ({ labels }) =>
labels.map(label =>
document('labels', {
id: `label:${label.id}`,
entityId: label.id,
title: label.name || 'Untitled label',
subtitle: 'Label',
keywords: 'tag label',
route: '/labels',
color: label.color,
}),
),
})
registerSearchProvider({
id: 'people',
getDocuments: ({ members }) =>
members.map(member =>
document('people', {
id: `person:${member.userId}`,
entityId: member.userId,
title: member.displayName || member.username || 'Circle member',
subtitle: 'Circle member',
keywords: `${member.username || ''} person member assignee`,
route: '/chores',
}),
),
})
registerSearchProvider({
id: 'settings',
getDocuments: ({ isParent, t }) =>
SETTINGS_SECTIONS.filter(({ parentOnly }) => !parentOnly || isParent).map(
({ id }) =>
document('settings', {
id: `setting:${id}`,
entityId: id,
title: t(`overview.sections.${id}.title`),
subtitle: 'Settings',
body: t(`overview.sections.${id}.description`),
keywords: `preferences configuration ${id}`,
route: `/settings/${id}`,
}),
),
})