Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions playwright/e2e/smart-picker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
87 changes: 54 additions & 33 deletions src/components/Suggestion/LinkPicker/suggestions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)

/**
*
Expand All @@ -34,35 +37,51 @@ 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'),
action: (command) => command.insertContent(':'),
},
]
.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) => {
let label = p.title
if (p.id === 'files') {
// Rename "Files" to "Link a file", less ambiguous
label = t('text', 'Link a file')
}
return {
label,
icon: p.icon_url,
providerId: p.id,
order: p.order,
}
})
.filter((e) => e?.label?.toLowerCase?.()?.includes(query.toLowerCase()))
}

export default () => createSuggestions({
Expand Down Expand Up @@ -101,23 +120,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 })),
]
},
})
Loading