Quick Actions
Last modified by Vincent Massol on 2024/11/19 16:14
Description
The goal of this design page is to gather specifications for the Quick Actions feature and to document implementation solutions. Quick Actions is a feature of the WYSIWYG editor that allows users to perform editing operations (styling, inserting content elements or rendering macros, etc.) using only the keyboard and without the need to remember lots of complex shortcut keys.
Existing Implementations
Notion
- Discoverability: The focused empty line shows a placeholder message "Press / for commands..."
- Trigger: Typing '/' opens the quick actions drop down even when there is text right before it.
- Grouping:
- The actions (commands) are grouped into Basic blocks, Media, Database, AI blocks, Advanced blocks, Inline, Embeds, Synced Databases, Turn into, Actions, Color, Background.
- Group order seems to be dynamic, probably depending on the score of the results from each group.
- Scalability: Infinite scrolling
- Model: An action can have icon, name, description, shortcut key, state (checked / selected).
- Keyboard interaction:
- One space continues suggestions; two spaces exits suggestions
- Typing 3 consecutive keys without results exits the suggestions
- Escape key closes the quick actions drop down and typing afterwards doesn't re-activate the drop down unless you type '/' again.
- Delete key does not re-activate suggestions
- Home, End, Page Up, Page Down are not used for navigating the suggestions (they move the caret in the editing area).
- Preview: Hovering the suggested actions shows an image preview (thumbnail) on the right of the actions dropdown, with the action description as caption.
- When the image preview is missing the action description is shown on the right (as a caption without an image).
- Highlighting: Typed text is not highlighted
- Search:
- Only the action name appears to be matched (doesn't search in action description).
- It's not a simple string find, as it finds "Delete" and "Bulleted list" when searching for "deleted".
- It's case insensitive, but it doesn't remove diacritics.
- Shows "No results" when no match.
Confluence
- Discoverability: No placeholder text shown on empty line (only when the page is completely empty).
- Trigger: Typing '/' opens the quick actions drop down only if there is no text right before it (you need to type '/' at the start of the line or after a space, dot, question mark or exclamation mark).
- Grouping: No grouping
- Scalability: Infinite scrolling
- Model: An action can have icon, name, description and shortcut key
- Keyboard interaction:
- One space continues suggestions; two spaces exits suggestions
- Typing a single key without results exits the suggestions.
- Escape key closes the quick actions drop down and typing afterwards doesn't re-activate the drop down unless you type '/' again.
- Delete key re-activates suggestions.
- Home, End, Page Up, Page Down highlight the first suggestion.
- Preview: No preview besides the action icon
- Highlighting: Typed text is highlighted with blue color in the editing area.
- Search:
- Search both in action name and description
- It's not a simple string find, as it finds "Page Tree" when searching for "pagi"
- It's case insensitive and it removes diacritics (I think, the results scores / order is unexpected).
- Hides the suggestion dropdown when no match.
GitHub
- Discoverability: No placeholder text shown on empty line.
- Trigger: Typing '/' opens the quick actions drop down only if the caret is at the start of the line.
- Grouping: There seems to be a single group for now, "Markdown" (at least when commenting on a PR).
- Scalability: No infinite scrolling, they have only a few actions
- Model: An action can have name and description.
- Keyboard interaction:
- One space exits suggestions.
- Typing a single key without results exits the suggestions.
- Escape key closes the quick actions drop down but typing afterwards re-activates it.
- Delete key re-activates suggestions.
- Home, End, Page Up, Page Down are not used for navigating the suggestions (they move the caret in the editing area).
- Preview: No preview (and no action icon either)
- Highlighting: Typed text is not highlighted.
- Search:
- Matches only the action name.
- It's not a simple string find, as it finds "Code block" when searching for "blck".
- It's case insensitive, but it doesn't remove diacritics.
- Hides the suggestion dropdown when no match.
Affine
- Discoverability: The focused empty line shows a placeholder message "type '/' for commands".
- Trigger: Typing '/' opens the quick actions drop down even when there is text right before it.
- Grouping:
- The actions (commands) are grouped into Text, Style, List, Image & File, Date & Time, Actions.
- The group names are shown only initially, on the left; they are not shown when searching
- Group order is static.
- Scalability: No infinite scrolling (not that many commands so it wasn't needed)
- Model: An action can have icon and name.
- Keyboard interaction:
- One space exits suggestions.
- Typing a single key without results exits the suggestions.
- Escape key closes the quick actions drop down and typing afterwards doesn't re-activate the drop down unless you type '/' again.
- Delete key does not re-activate suggestions.
- Home, End, Page Up, Page Down are not used for navigating the suggestions (they move the caret in the editing area).
- Preview: No preview and no hint on hover either
- Highlighting: Typed text is not highlighted.
- Search:
- Only the action name is matched (there's no action description anyway).
- It's not a simple string find, as it finds "Code block" when searching for "blk".
- It's case insensitive, but it doesn't remove diacritics.
- Hides the suggestion dropdown when no match.
Microsoft Loop
- Discoverability: Empty lines have a balloon toolbar showing "/ to insert".
- Trigger: Typing '/' opens the quick actions drop down only if there is no text right before it (you need to type '/' at the start of the line or after a space).
- Grouping:
- The actions (commands) are grouped into General, Templates, Communication, Media.
- Group order is static.
- Scalability: No infinite scrolling (not that many commands so it wasn't needed)
- Model: An action can have icon, name and description (the later displayed only on hover).
- Keyboard interaction:
- One space continues suggestions; typing after 2 or more spaces exits suggestions.
- Suggestion dropdown remains open when typing without results; you need to use the Escape key to close it
- Escape key closes the quick actions drop down but typing afterwards re-activates it.
- Delete key does not re-activate suggestions.
- Home / End select the first / last suggestion.
- Page Up, Page Down are not used for navigating the suggestions (they move the caret in the editing area).
- Preview: No preview but a tool tip / hint is displayed on the right side of the suggestion on hover.
- Highlighting: Typed text is not highlighted.
- Search:
- Only the action name is matched (the action hint is not matched).
- It's not a simple string find: seems to split action names into tokens (whitespace) and match only prefixes
- It's case insensitive, but it doesn't remove diacritics.
- Shows "No results" when no match.
Specifications
- Discoverability: It should be easy for the user to discover this feature while editing a wiki page
- Minimum: It's trivial to improve the placeholder text we show already on empty content to advertise the '/' quick actions, but this means the users will discover the feature only when creating a new page.
- Optimal: Show a placeholder text when the caret is inside an empty paragraph. This is not supported by CKEditor and it's not easy to implement because empty paragraphs are not actually empty in CKEditor: they have a <br/> tag in order to reserve space for the paragraph, otherwise the paragraph is not visible and thus not editable.
- See XWIKI-20903. This is a good opportunity to add placeholders for various blocks in order to indicate the type of block the caret is in.
- Trigger: The quick actions dropdown should be triggered by the '/' key, showing by default all actions available for the current context (caret position).
- Note that the quick actions act only on the collapsed selection (caret), because once you type slash the selected text is replaced by it and there's no way to know whether the user intention is to open the quick actions panel or to insert a slash. All the applications we reviewed behave the same. Some show a floating toolbar when text is selected, but all replace the selected text when slash is typed and there's no way to act on the text replaced by the typed slash.
- Grouping: The quick actions should be organized in groups based on functionality. Each group will have a name displayed in the dropdown.
- Question What groups should we use? There's no standard / common practice in this area, each tool we analysed uses its own grouping.
- Question Should the groups be displayed when searching? We think so.
- Question Should the group order be dynamic (based on some search score)? We think we can start with static order. If we go for dynamic order then we probably need to compute the average score within each group
- Question Should we have a single group for rendering macros or put them in separate groups? (since rendering macros are already grouped in categories)
- CKEditor's Autocomplete and Mentions plugins that we plan to use don't support out of the box grouping the suggestions so we may have to do some hacks. Displaying a special suggestion as a group header should be easy but we need to see how to skip the group headings when navigating with the keyboard. For this we may have to overwrite something from the Autocomplete model.)
- Scalability: The quick actions dropdown opened when typing '/' should load very fast (as it's going to be used very often).
- Some entries will be dynamic (e.g. we'll have to fetch the list of rendering macros from the server) so we need to make sure we apply caching when possible.
- Dynamic groups (e.g. rendering macros) could be loaded async after the dropdown is shown, so that the users don't have to wait for them in case they're looking for some static action (e.g. insert a table).
- We could have tens of entries (e.g. if we show rendering macros) so we'll probably have to look into infinite scrolling (even if the entries are already on the client side, building the dropdown list with tens of entries will not be fast and it's useless most of the time because the users will check only the first results). The alternative is to show on search (when typing) only the top N results.
- Model: An action can have icon, name, description and shortcut key.
- Question: Should we display the action description in the dropdown or only on hover? The downside of displaying the hint directly in the dropdown is that some actions (e.g. some rendering macros) may have long descriptions making it harder to overview the available actions (you're forced to scroll).
- We think it's good to display the shortcut key (when available) on the right of the action name.
- Keyboard interaction:
- Typing one space should not end the search. The search should continue until there are no results or when a limit of typed characters is reached (e.g. max 30 characters).
- CKEditor's Mentions plugin doesn't support this so we'll have to use directly the Autocomplete plugin and the Text Match plugin.
- Suggestion dropdown should be hidden when there are no results (default behaviour of Autocomplete plugin).
- Escape key should close the quick actions drop down.
- Delete key re-activate suggestions (default behaviour of Autocomplete plugin).
- Home, End, Page Up, Page Down are not used for navigating the suggestions (they move the caret in the editing area). This is the default behaviour of Autocomplete plugin.
- Typing one space should not end the search. The search should continue until there are no results or when a limit of typed characters is reached (e.g. max 30 characters).
- Preview: No preview but we should display the action hint on hover, in case we decide to not display it directly in the dropdown.
- Highlighting: Typed text doesn't have to be highlighted (definitely not needed for the first version).
- Search:
- We can integrate https://fusejs.io/ for fuzzy searching
- Question Should we match also the action description? I think we should, but then we probably need to use a lower score than for the action name.
- Question Should we match anywhere inside the action name / description or just word prefixes?
- Question Should we display a "No results" message or simply hide the dropdown? The Autocomplete plugin doesn't support the "No results" message so if we go this way we'll have to do some hacks. It's relatively easy to return a fake "No results" suggestion but we need to make sure the user can't select it.
Search
For searching quick actions we propose integrating https://fusejs.io/ in order to support fuzzy search. We propose the following search configuration:
// Allow one character mismatch in 4 (i.e. match at least 3 characters when the input text is 4 characters).
// With the default distance parameter being 100 and location 0 this also means we're matching only the first 25
// characters which is fine considering that we split the indexed information into tokens (by white-space).
//
// Using a larger threshold allows for more fuzzy search but it also gives too many poor results.
// We found the default 0.6 threshold to be too much (too many surprising results), and using a threshold below 0.2
// leads to exact matches most of the time because users don't type many characters when searching for quick actions.
threshold: 0.25,
keys: [
// We obviously give more weight to the action id and name, but we also match the action description and group.
// By matching the action id we can allow power users (e.g. that know HTML and wiki syntax) to find a quick action by
// the corresponding HTML tag (e.g. img) or macro id (e.g. toc).
{name: 'id', weight: 5},
// We found that splitting the action name, description and group name into tokens (by white-space) leads to better
// results because it gives more weight to word prefix matching.
{name: 'nameTokens', weight: 4},
{name: 'descriptionTokens', weight: 2},
{name: 'group.id', weight: 1},
{name: 'group.nameTokens', weight: 1}
]
// With the default distance parameter being 100 and location 0 this also means we're matching only the first 25
// characters which is fine considering that we split the indexed information into tokens (by white-space).
//
// Using a larger threshold allows for more fuzzy search but it also gives too many poor results.
// We found the default 0.6 threshold to be too much (too many surprising results), and using a threshold below 0.2
// leads to exact matches most of the time because users don't type many characters when searching for quick actions.
threshold: 0.25,
keys: [
// We obviously give more weight to the action id and name, but we also match the action description and group.
// By matching the action id we can allow power users (e.g. that know HTML and wiki syntax) to find a quick action by
// the corresponding HTML tag (e.g. img) or macro id (e.g. toc).
{name: 'id', weight: 5},
// We found that splitting the action name, description and group name into tokens (by white-space) leads to better
// results because it gives more weight to word prefix matching.
{name: 'nameTokens', weight: 4},
{name: 'descriptionTokens', weight: 2},
{name: 'group.id', weight: 1},
{name: 'group.nameTokens', weight: 1}
]
Quick Actions
In order to decide which WYSIWYG editor features to expose on the quick actions panel let's see what others are doing:
WYSIWYG Editor Feature | Notion | Confluence | GitHub | Affine | Microsoft Loop | |
---|---|---|---|---|---|---|
Format | Paragraph | ![]() | ![]() | ![]() | ![]() | ![]() |
Heading 1 | ![]() | ![]() | ![]() | ![]() | ![]() | |
Heading 2 | ![]() | ![]() | ![]() | ![]() | ![]() | |
Heading 3 | ![]() | ![]() | ![]() | ![]() | ![]() | |
Heading 4 | ![]() | ![]() | ![]() | ![]() | ![]() | |
Heading 5 | ![]() | ![]() | ![]() | ![]() | ![]() | |
Heading 6 | ![]() | ![]() | ![]() | ![]() | ![]() | |
Blockquote | ![]() | ![]() | ![]() | ![]() | ![]() | |
Formatted (PRE) | ![]() | ![]() | ![]() | ![]() | ![]() | |
DIV | ![]() | ![]() | ![]() | ![]() | ![]() | |
Style | Bold | ![]() | ![]() | ![]() | ![]() | ![]() |
Italic | ![]() | ![]() | ![]() | ![]() | ![]() | |
Underline | ![]() | ![]() | ![]() | ![]() | ![]() | |
Strikethrough | ![]() | ![]() | ![]() | ![]() | ![]() | |
Superscript | ![]() | ![]() | ![]() | ![]() | ![]() | |
Subscript | ![]() | ![]() | ![]() | ![]() | ![]() | |
Remove format | ![]() | ![]() | ![]() | ![]() | ![]() | |
Lists | Bulleted | ![]() | ![]() | ![]() | ![]() | ![]() |
Numbered | ![]() | ![]() | ![]() | ![]() | ![]() | |
Indent | ![]() | ![]() | ![]() | ![]() | ![]() | |
Outdent | ![]() | ![]() | ![]() | ![]() | ![]() | |
Insert | Link | ![]() | ![]() | ![]() | ![]() | ![]() |
Image | ![]() | ![]() | ![]() | ![]() | ![]() | |
Table | ![]() | ![]() | ![]() | ![]() | ![]() | |
Emoji | ![]() | ![]() | ![]() | ![]() | ![]() | |
Horizontal line | ![]() | ![]() | ![]() | ![]() | ![]() | |
Special character | ![]() | ![]() | ![]() | ![]() | ![]() | |
Macros | Info | ![]() | ![]() | ![]() | ![]() | ![]() |
Success | ![]() | ![]() | ![]() | ![]() | ![]() | |
Warning | ![]() | ![]() | ![]() | ![]() | ![]() | |
Error | ![]() | ![]() | ![]() | ![]() | ![]() | |
ToC | ![]() | ![]() | ![]() | ![]() | ![]() | |
Include | ![]() | ![]() | ![]() | ![]() | ![]() | |
Code | ![]() | ![]() | ![]() | ![]() | ![]() | |
Actions | Find & Replace | ![]() | ![]() | ![]() | ![]() | ![]() |
Undo | ![]() | ![]() | ![]() | ![]() | ![]() | |
Redo | ![]() | ![]() | ![]() | ![]() | ![]() | |
Switch to Source | ![]() | ![]() | ![]() | ![]() | ![]() | |
Maximize / Minimize | ![]() | ![]() | ![]() | ![]() | ![]() |
What can be noticed is that:
- Format:
- Paragraph is not exposed by everyone, probably because it's the default format and you can get it without the slash shortcut (e.g. by pressing Enter)
- Formatted (pre / verbatim) and DIV formats are not exposed, probably because they are too technical
- Not all application expose H4-6, probably because they are less often used; still, it doesn't cost us much to expose them
- Style: inline styles (bold, italic, etc.) are not exposed; Affine makes an exception but the behaviour it has is incomplete: e.g. you can switch to bold style (for new text) but you can't switch back to normal text (the quick action doesn't act like a toggle); moreover, you can't make existing text bold using slash, of course.
- Lists: indent and outdent actions are not exposed, probably because the Tab and Shift+Tab shortcuts are very well known and way faster to use than typing "/ind..."
- Insert: special character is not exposed, probably because it's used very little
- Macros: We should expose as many macros as we can
- Actions:
- It doesn't make sense to expose Undo & Redo (by typing you introduce entries in the history)
- Source and Maximize are a bit specific to XWiki so we don't see them in other applications; moreover, these actions don't affect the edited content so I'm not convinced they should be exposed.
- Find & Replace is not available in other applications, still I find it interesting to expose
Based on this I propose we start with the following quick actions:
- Structure
- Heading 1-6 (execute the corresponding editor command, converting the current block into a heading)
- Paragraph (execute the corresponding editor command, converting the current block into a paragraph)
- Bulleted List (execute the corresponding editor command, converting the current block into a bulleted list)
- Numbered List (execute the corresponding editor command, converting the current block into a numbered list)
- Table (we can start by opening the table editor but ideally we should insert directly a standard 3x3 table first and then let the user add / remove lines and columns, but we need to be careful to place the caret in the first table cell)
- Quote (Blockquote, execute the corresponding editor command)
- Info Box (insert the info macro with predefined content because the content is mandatory)
- Warning Box (insert the warning macro with predefined content because the content is mandatory)
- Error Box (insert the error macro with predefined content because the content is mandatory)
- Success Box (insert the success macro with predefined content because the content is mandatory)
- Divider (Horizontal line, execute the corresponding editor command)
- Content
- Link (trigger the link shortcut [). Note that it would be interesting to extend the link suggest to support:
- Uploading an attachment and then creating a link to it
- Creating a link to a wanted (missing) page
- Image (ideally it should trigger a shortcut for image search, similar to the page search for links, allowing both to find an existing image and to upload a new image)
- Mention (trigger the mention shortcut @)
- Emoji (trigger the emoji shortcut with some prefilled emoji search text, e.g. :sm, because by default the emoji shortcut is triggered when typing at least 2 characters)
- Special Character (open the corresponding dialog)
- Link (trigger the link shortcut [). Note that it would be interesting to extend the link suggest to support:
- Macros
- Code Snippet (simply open the macro editor)
- Table Of Contents (simply open the macro editor)
- Include (simply open the macro editor)
- ...
- Actions
- Find & Replace (open the find & replace dialog)