feat: global search and keyboard shortcuts
Ctrl/⌘ K (or /) opens a search over the projects and tickets of all my projects, driven by the keyboard. g plus a letter moves between project pages, c creates a ticket on the board, e edits the open ticket and ? lists every shortcut. My work can now be searched by text through the API. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,3 +9,4 @@ export * from './lib/models/views';
|
||||
export * from './lib/models/estimation';
|
||||
export * from './lib/models/swimlanes';
|
||||
export * from './lib/models/roadmap';
|
||||
export * from './lib/models/shortcuts';
|
||||
|
||||
@@ -73,6 +73,8 @@ export interface MyIssuesFilter {
|
||||
/** true: assigned to me. false: any ticket of my projects. */
|
||||
assignedToMe: boolean;
|
||||
categories?: StatusCategory[];
|
||||
/** Matches key, title or description. */
|
||||
search?: string;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
|
||||
22
libs/web/tracker/domain/src/lib/models/shortcuts.spec.ts
Normal file
22
libs/web/tracker/domain/src/lib/models/shortcuts.spec.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { GO_TO, isTyping, projectKeyIn } from './shortcuts';
|
||||
|
||||
describe('shortcuts', () => {
|
||||
it('knows the project of a URL', () => {
|
||||
expect(projectKeyIn('/p/WEB/issues/WEB-3')).toBe('WEB');
|
||||
expect(projectKeyIn('/explorar')).toBeNull();
|
||||
expect(GO_TO['k']).toEqual({ path: 'backlog', inProject: true });
|
||||
});
|
||||
|
||||
it('leaves keys alone while typing', () => {
|
||||
expect(isTyping({ tagName: 'TEXTAREA' } as unknown as EventTarget)).toBe(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
isTyping({
|
||||
tagName: 'DIV',
|
||||
isContentEditable: false,
|
||||
} as unknown as EventTarget),
|
||||
).toBe(false);
|
||||
expect(isTyping(null)).toBe(false);
|
||||
});
|
||||
});
|
||||
43
libs/web/tracker/domain/src/lib/models/shortcuts.ts
Normal file
43
libs/web/tracker/domain/src/lib/models/shortcuts.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/** Keys that do something, as the help lists them. */
|
||||
export const SHORTCUTS: { keys: string; what: string; where: string }[] = [
|
||||
{
|
||||
keys: 'Ctrl K · ⌘ K · /',
|
||||
what: 'Buscar tickets y proyectos',
|
||||
where: 'En todas partes',
|
||||
},
|
||||
{ keys: '?', what: 'Ver estos atajos', where: 'En todas partes' },
|
||||
{ keys: 'g p', what: 'Ir a mis proyectos', where: 'En todas partes' },
|
||||
{ keys: 'g e', what: 'Ir a explorar', where: 'En todas partes' },
|
||||
{ keys: 'g b', what: 'Ir al tablero', where: 'Dentro de un proyecto' },
|
||||
{ keys: 'g k', what: 'Ir al backlog', where: 'Dentro de un proyecto' },
|
||||
{ keys: 'g s', what: 'Ir a los sprints', where: 'Dentro de un proyecto' },
|
||||
{ keys: 'g r', what: 'Ir a la hoja de ruta', where: 'Dentro de un proyecto' },
|
||||
{ keys: 'c', what: 'Crear un ticket', where: 'Tablero' },
|
||||
{ keys: 'e', what: 'Editar el ticket', where: 'Ficha de un ticket' },
|
||||
{ keys: 'Esc', what: 'Cerrar', where: 'Búsqueda y ayuda' },
|
||||
];
|
||||
|
||||
/** Where `g` + key goes; project pages are relative to `/p/KEY`. */
|
||||
export const GO_TO: Record<string, { path: string; inProject: boolean }> = {
|
||||
p: { path: '/', inProject: false },
|
||||
e: { path: '/explorar', inProject: false },
|
||||
b: { path: 'board', inProject: true },
|
||||
k: { path: 'backlog', inProject: true },
|
||||
s: { path: 'sprints', inProject: true },
|
||||
r: { path: 'roadmap', inProject: true },
|
||||
};
|
||||
|
||||
/** Typing in a field is typing, not a shortcut. */
|
||||
export function isTyping(target: EventTarget | null): boolean {
|
||||
const element = target as HTMLElement | null;
|
||||
if (!element?.tagName) return false;
|
||||
return (
|
||||
['INPUT', 'TEXTAREA', 'SELECT'].includes(element.tagName) ||
|
||||
element.isContentEditable
|
||||
);
|
||||
}
|
||||
|
||||
/** The project key in a URL like /p/WEB/board, if any. */
|
||||
export function projectKeyIn(url: string): string | null {
|
||||
return /^\/p\/([^/?#]+)/.exec(url)?.[1] ?? null;
|
||||
}
|
||||
Reference in New Issue
Block a user