From bcdf0b6dc25e1184e71eef840db9bf99714a8c2b Mon Sep 17 00:00:00 2001 From: Jonas Date: Sun, 9 Aug 2026 18:04:21 +0200 Subject: [PATCH 1/3] fix(LinkPicker): improve ordering of suggestions * List smart picker items with `order === -1` first (e.g. "Link to page in collective" inside Collectives) * List some items declared as "important" second * List remaining smart picker items third * List remaining formatting items last Signed-off-by: Jonas Assisted-by: OpenCode:claude-fable-5 --- .../Suggestion/LinkPicker/suggestions.js | 82 +++++++++++-------- 1 file changed, 49 insertions(+), 33 deletions(-) diff --git a/src/components/Suggestion/LinkPicker/suggestions.js b/src/components/Suggestion/LinkPicker/suggestions.js index ecf89fcf19d..23eed9f9235 100644 --- a/src/components/Suggestion/LinkPicker/suggestions.js +++ b/src/components/Suggestion/LinkPicker/suggestions.js @@ -16,11 +16,14 @@ import { getIsActive } from '../../Menu/utils.js' import createSuggestions from '../suggestions.js' import { getMenuEntries } from './../../Menu/entries.ts' +const suggestGroupImportant = t('text', 'Suggestions') const suggestGroupFormat = t('text', 'Formatting') const suggestGroupPicker = t('text', 'Smart picker') -const important = ['task-list', 'table'] -const excluded = ['undo', 'redo', 'outline', 'emoji-picker'] +const important = ['task-list', 'table', 'callout-info'] +const excludedFormatting = ['undo', 'redo', 'outline', 'emoji-picker'] + +const isImportant = (item) => important.includes(item.key) || important.includes(item.providerId) /** * @@ -34,27 +37,17 @@ function isValidUrl(url) { } } -/** - * - * @param {Array} list of menu entries - */ -function sortImportantFirst(list) { - return [ - ...list.filter((e) => important.indexOf(e.key) > -1), - ...list.filter((e) => important.indexOf(e.key) === -1), - ] -} - /** * * @param {string} query to filter by + * @param {object} editor the editor instance */ -function formattingSuggestions(query) { +function formattingItems(query, editor) { const menuEntries = getMenuEntries(false, false) - return sortImportantFirst([ + return [ ...menuEntries.find((e) => e.key === 'headings').children, ...menuEntries.find((e) => e.key === 'lists').children, - ...menuEntries.filter((e) => e.action && !excluded.includes(e.key)), + ...menuEntries.filter((e) => e.action && !excludedFormatting.includes(e.key)), ...menuEntries.find((e) => e.key === 'blocks').children, { ...menuEntries.find((e) => e.key === 'emoji-picker'), @@ -62,7 +55,28 @@ function formattingSuggestions(query) { }, ] .filter((e) => e?.label?.toLowerCase?.()?.includes(query.toLowerCase())) - .map((e) => ({ ...e, suggestGroup: suggestGroupFormat }))) + .filter(({ action, isActive }) => { + const canRunState = action(editor?.can()) + const isActiveState + = isActive && getIsActive({ isActive }, editor) + return canRunState && !isActiveState + }) +} + +/** + * @param {string} query to filter by + */ +function pickerItems(query) { + return searchProvider(query) + .map((p) => { + return { + label: p.title, + icon: p.icon_url, + providerId: p.id, + order: p.order, + } + }) + .filter((e) => e?.label?.toLowerCase?.()?.includes(query.toLowerCase())) } export default () => createSuggestions({ @@ -101,23 +115,25 @@ export default () => createSuggestions({ }) }, items: ({ editor, query }) => { + const pickers = pickerItems(query) + const formatting = formattingItems(query, editor) return [ - ...searchProvider(query) - .map((p) => { - return { - suggestGroup: suggestGroupPicker, - label: p.title, - icon: p.icon_url, - providerId: p.id, - } - }) - .filter((e) => e?.label?.toLowerCase?.()?.includes(query.toLowerCase())), - ...formattingSuggestions(query).filter(({ action, isActive }) => { - const canRunState = action(editor?.can()) - const isActiveState - = isActive && getIsActive({ isActive }, editor) - return canRunState && !isActiveState - }), + // pickers with order -1, then important pickers, then important formatting + ...[ + ...pickers.filter((e) => e.order === -1), + ...pickers.filter((e) => e.order !== -1 && isImportant(e)), + ...formatting.filter(isImportant), + ].map((e) => ({ ...e, suggestGroup: suggestGroupImportant })), + + // Smart picker: remaining (non-important) pickers + ...pickers + .filter((e) => e.order !== -1 && !isImportant(e)) + .map((e) => ({ ...e, suggestGroup: suggestGroupPicker })), + + // Formatting: remaining (non-important) formatting entries + ...formatting + .filter((e) => !isImportant(e)) + .map((e) => ({ ...e, suggestGroup: suggestGroupFormat })), ] }, }) From 32849d16d8d333f577511266358ce5a12b349842 Mon Sep 17 00:00:00 2001 From: Jonas Date: Sun, 9 Aug 2026 18:19:06 +0200 Subject: [PATCH 2/3] fix(LinkPicker): rename 'Files' to 'Link a file' The generic 'Files' is too ambiguous in document editing context. Signed-off-by: Jonas --- src/components/Suggestion/LinkPicker/suggestions.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/Suggestion/LinkPicker/suggestions.js b/src/components/Suggestion/LinkPicker/suggestions.js index 23eed9f9235..0cbb1a207d5 100644 --- a/src/components/Suggestion/LinkPicker/suggestions.js +++ b/src/components/Suggestion/LinkPicker/suggestions.js @@ -69,8 +69,13 @@ function formattingItems(query, editor) { function pickerItems(query) { return searchProvider(query) .map((p) => { + let label = p.title + if (p.id === 'files') { + // Rename "Files" to "Link a file", less ambiguous + label = t('text', 'Link a file') + } return { - label: p.title, + label, icon: p.icon_url, providerId: p.id, order: p.order, From e4fae5cfb3149afb7174a9f60fae768adb430727 Mon Sep 17 00:00:00 2001 From: Jonas Date: Sun, 9 Aug 2026 18:32:40 +0200 Subject: [PATCH 3/3] test(playwright): test custom suggestions order Signed-off-by: Jonas --- playwright/e2e/smart-picker.spec.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/playwright/e2e/smart-picker.spec.ts b/playwright/e2e/smart-picker.spec.ts index 06708d0dbed..e5ce2d7c6c3 100644 --- a/playwright/e2e/smart-picker.spec.ts +++ b/playwright/e2e/smart-picker.spec.ts @@ -34,3 +34,27 @@ test('Insert Link', async ({ editor }) => { await editor.referencePicker.press('Enter') await expect(editor.content.getByRole('link')).toContainText('github.com') }) + +test('Files provider is renamed to "Link a file"', async ({ editor }) => { + await editor.type('/') + await expect(editor.getSuggestion('Link a file')).toBeVisible() +}) + +test('Important items appear before remaining items', async ({ editor }) => { + await editor.type('/') + await expect(editor.getSuggestion('To-Do list')).toBeVisible() + const allTexts = await editor.suggestions + .locator('.suggestion-list__item') + .allTextContents() + + const todoIdx = allTexts.findIndex((t) => t.includes('To-Do list')) + const tableIdx = allTexts.findIndex((t) => t.includes('Table')) + const filesIdx = allTexts.findIndex((t) => t.includes('Link a file')) + const heading1Idx = allTexts.findIndex((t) => t.includes('Heading 1')) + + expect(todoIdx).toBeLessThan(filesIdx) + expect(todoIdx).toBeLessThan(heading1Idx) + expect(tableIdx).toBeLessThan(filesIdx) + expect(tableIdx).toBeLessThan(heading1Idx) + expect(filesIdx).toBeLessThan(heading1Idx) +})