fix(editor): match slash menu against id + keywords (BUG-1419) (#551)

Typing `/h2` (or `/ul`, `/hr`, `/todo`, etc.) in the tiptap editor
auto-closed the slash menu because the filter only matched on `label`
and `description`. "Heading 2".includes("h2") is false — the space
between "Heading" and "2" breaks the substring match — and zero
matches triggers closeSlash(), so the picker vanished as soon as the
user typed the second character.

Add optional `keywords?: string[]` to BlockType and populate common
abbreviations per block (h1/h2/h3, ul/ol, todo/checkbox, hr/rule,
quote/bq, code, html, tbl, etc.). Extend getFilteredSlash() to join
label + description + id + keywords into a single lowercased haystack
and substring-match the query against it.

Pure UI filter change — Y.Doc / ProseMirror shape unchanged, no
SCHEMA_VERSION bump. Turn-into menu unaffected (no filter there).
This commit is contained in:
xarmian
2026-05-14 22:22:35 -04:00
committed by GitHub
parent 38aa872864
commit b1fcedd5b5
2 changed files with 24 additions and 12 deletions
+6 -1
View File
@@ -425,7 +425,12 @@
function getFilteredSlash() {
if (!slashQuery) return SLASH_ITEMS;
const q = slashQuery.toLowerCase();
return SLASH_ITEMS.filter(i => i.label.toLowerCase().includes(q) || i.description.toLowerCase().includes(q));
return SLASH_ITEMS.filter((i) => {
const hay = [i.label, i.description, i.id, ...(i.keywords ?? [])]
.join(' ')
.toLowerCase();
return hay.includes(q);
});
}
function execSlash(id: string) {
+18 -11
View File
@@ -8,6 +8,13 @@ export interface BlockType {
icon: string;
label: string;
description: string;
/**
* Search aliases for the slash-command menu. The filter matches the query
* against the label, description, id, and each keyword. Use this for
* common abbreviations the label/description wouldn't otherwise cover
* (e.g. `h2` for "Heading 2", `hr` for "Divider", `todo` for "Checklist").
*/
keywords?: string[];
/** Only available via slash command (insert), not turn-into (convert) */
insertOnly?: boolean;
/** Only available via turn-into (convert), not slash command (insert) */
@@ -16,17 +23,17 @@ export interface BlockType {
export const BLOCK_TYPES: BlockType[] = [
{ id: 'paragraph', icon: 'Aa', label: 'Text', description: 'Plain text', convertOnly: true },
{ id: 'heading1', icon: 'H1', label: 'Heading 1', description: 'Large heading' },
{ id: 'heading2', icon: 'H2', label: 'Heading 2', description: 'Medium heading' },
{ id: 'heading3', icon: 'H3', label: 'Heading 3', description: 'Small heading' },
{ id: 'bulletList', icon: '•', label: 'Bullet List', description: 'Unordered list' },
{ id: 'orderedList', icon: '1.', label: 'Numbered List', description: 'Ordered list' },
{ id: 'taskList', icon: '☐', label: 'Checklist', description: 'Task list' },
{ id: 'codeBlock', icon: '<>', label: 'Code Block', description: 'Fenced code block' },
{ id: 'htmlBlock', icon: 'HTML', label: 'HTML Block', description: 'Sanitized HTML embed (live preview)', insertOnly: true },
{ id: 'blockquote', icon: '❝', label: 'Blockquote', description: 'Quote block' },
{ id: 'horizontalRule', icon: '——', label: 'Divider', description: 'Horizontal rule', insertOnly: true },
{ id: 'table', icon: '⊞', label: 'Table', description: '3×3 table', insertOnly: true },
{ id: 'heading1', icon: 'H1', label: 'Heading 1', description: 'Large heading', keywords: ['h1'] },
{ id: 'heading2', icon: 'H2', label: 'Heading 2', description: 'Medium heading', keywords: ['h2'] },
{ id: 'heading3', icon: 'H3', label: 'Heading 3', description: 'Small heading', keywords: ['h3'] },
{ id: 'bulletList', icon: '•', label: 'Bullet List', description: 'Unordered list', keywords: ['ul', 'bullets', 'unordered'] },
{ id: 'orderedList', icon: '1.', label: 'Numbered List', description: 'Ordered list', keywords: ['ol', 'numbered', 'ordered'] },
{ id: 'taskList', icon: '☐', label: 'Checklist', description: 'Task list', keywords: ['todo', 'task', 'checkbox', 'check'] },
{ id: 'codeBlock', icon: '<>', label: 'Code Block', description: 'Fenced code block', keywords: ['code', 'pre'] },
{ id: 'htmlBlock', icon: 'HTML', label: 'HTML Block', description: 'Sanitized HTML embed (live preview)', insertOnly: true, keywords: ['html', 'embed'] },
{ id: 'blockquote', icon: '❝', label: 'Blockquote', description: 'Quote block', keywords: ['quote', 'bq'] },
{ id: 'horizontalRule', icon: '——', label: 'Divider', description: 'Horizontal rule', insertOnly: true, keywords: ['hr', 'rule', 'separator'] },
{ id: 'table', icon: '⊞', label: 'Table', description: '3×3 table', insertOnly: true, keywords: ['tbl', 'grid'] },
];
/** Block types available in the slash command menu (excludes convert-only types like "Text") */