diff --git a/cypress/e2e/workspace.spec.js b/cypress/e2e/workspace.spec.js index bfb4c793c5d..d8a378ecc4c 100644 --- a/cypress/e2e/workspace.spec.js +++ b/cypress/e2e/workspace.spec.js @@ -194,7 +194,7 @@ describe('Workspace', function() { const actionName = `callout-${type}` // enable callout - cy.getSubmenuEntry('callouts', actionName).click() + cy.getSubmenuEntry('blocks', actionName).click() // check content cy.getContent() @@ -202,7 +202,7 @@ describe('Workspace', function() { .should('contain', 'Callout') // disable - cy.getSubmenuEntry('callouts', actionName) + cy.getSubmenuEntry('blocks', actionName) .should('have.class', 'is-active') .click() }) @@ -212,19 +212,19 @@ describe('Workspace', function() { const [first, ...rest] = types // enable callout - cy.getSubmenuEntry('callouts', `callout-${first}`) + cy.getSubmenuEntry('blocks', `callout-${first}`) .click() cy.wrap(rest).each(type => { const actionName = `callout-${type}` - cy.getSubmenuEntry('callouts', actionName).click() + cy.getSubmenuEntry('blocks', actionName).click() cy.getContent().find(`.callout.callout--${type}`) .should('contain', 'Callout') }) - cy.getSubmenuEntry('callouts', `callout-${rest.at(-1)}`) + cy.getSubmenuEntry('blocks', `callout-${rest.at(-1)}`) .click() - cy.getMenuEntry('callouts') + cy.getMenuEntry('blocks') .should('not.have.class', 'is-active') }) }) diff --git a/src/components/Menu/ActionList.vue b/src/components/Menu/ActionList.vue index 936ec31b927..e961b02a171 100644 --- a/src/components/Menu/ActionList.vue +++ b/src/components/Menu/ActionList.vue @@ -103,7 +103,7 @@ export default { }, labelWithSelected() { if (this.currentChild) { - // TRANSLATORS: examples - Headings, "Heading 1" is selected - Callouts, "Info" is selected + // TRANSLATORS: examples - Headings, "Heading 1" is selected - Blocks, "Info callout" is selected return t('text', '{menuItemName}, "{selectedSubMenuItemName}" is selected', { menuItemName: this.actionEntry.label, selectedSubMenuItemName: this.currentChild.label, diff --git a/src/components/Menu/entries.js b/src/components/Menu/entries.js index c8b0cde831f..e6896af587d 100644 --- a/src/components/Menu/entries.js +++ b/src/components/Menu/entries.js @@ -6,12 +6,14 @@ import { Undo, Redo, + CodeBrackets, CodeTags, Danger, Emoticon, FormatBold, FormatItalic, FormatUnderline, + FormatSize, FormatStrikethrough, FormatHeader1, FormatHeader2, @@ -70,60 +72,12 @@ export default [ action: (command) => command.redo(), priority: 10, }, - { - key: 'bold', - label: t('text', 'Bold'), - keyChar: 'b', - keyModifiers: [MODIFIERS.Mod], - icon: FormatBold, - isActive: 'strong', - action: (command) => { - return command.toggleBold() - }, - priority: 8, - }, - { - key: 'italic', - label: t('text', 'Italic'), - keyChar: 'i', - keyModifiers: [MODIFIERS.Mod], - icon: FormatItalic, - isActive: 'em', - action: (command) => { - return command.toggleItalic() - }, - priority: 9, - }, - { - key: 'underline', - label: t('text', 'Underline'), - keyChar: 'u', - keyModifiers: [MODIFIERS.Mod], - icon: FormatUnderline, - isActive: 'underline', - action: (command) => { - return command.toggleUnderline() - }, - priority: 13, - }, - { - key: 'strikethrough', - label: t('text', 'Strikethrough'), - keyChar: 's', - keyModifiers: [MODIFIERS.Mod, MODIFIERS.Shift], - icon: FormatStrikethrough, - isActive: 'strike', - action: (command) => { - return command.toggleStrike() - }, - priority: 14, - }, { key: 'headings', label: t('text', 'Headings'), keyChar: '1…6', keyModifiers: [MODIFIERS.Mod, MODIFIERS.Shift], - icon: FormatHeader1, + icon: FormatSize, isActive: 'heading', children: [ { @@ -132,7 +86,7 @@ export default [ keyChar: '1', keyModifiers: [MODIFIERS.Mod, MODIFIERS.Shift], icon: FormatHeader1, - isActive: ['heading', { level: 1 }], + isActive: { name: 'heading', attributes: { level: 1 } }, action: (command) => { return command.toggleHeading({ level: 1 }) }, @@ -143,7 +97,7 @@ export default [ keyChar: '2', keyModifiers: [MODIFIERS.Mod, MODIFIERS.Shift], icon: FormatHeader2, - isActive: ['heading', { level: 2 }], + isActive: { name: 'heading', attributes: { level: 2 } }, action: (command) => { return command.toggleHeading({ level: 2 }) }, @@ -154,7 +108,7 @@ export default [ keyChar: '3', keyModifiers: [MODIFIERS.Mod, MODIFIERS.Shift], icon: FormatHeader3, - isActive: ['heading', { level: 3 }], + isActive: { name: 'heading', attributes: { level: 3 } }, action: (command) => { return command.toggleHeading({ level: 3 }) }, @@ -164,7 +118,7 @@ export default [ label: t('text', 'Heading 4'), keyChar: '4', keyModifiers: [MODIFIERS.Mod, MODIFIERS.Shift], - isActive: ['heading', { level: 4 }], + isActive: { name: 'heading', attributes: { level: 4 } }, icon: FormatHeader4, action: (command) => { return command.toggleHeading({ level: 4 }) @@ -175,7 +129,7 @@ export default [ label: t('text', 'Heading 5'), keyChar: '5', keyModifiers: [MODIFIERS.Mod, MODIFIERS.Shift], - isActive: ['heading', { level: 5 }], + isActive: { name: 'heading', attributes: { level: 5 } }, icon: FormatHeader5, action: (command) => { return command.toggleHeading({ level: 5 }) @@ -186,7 +140,7 @@ export default [ label: t('text', 'Heading 6'), keyChar: '6', keyModifiers: [MODIFIERS.Mod, MODIFIERS.Shift], - isActive: ['heading', { level: 6 }], + isActive: { name: 'heading', attributes: { level: 6 } }, icon: FormatHeader6, action: (command) => { return command.toggleHeading({ level: 6 }) @@ -215,12 +169,60 @@ export default [ ], priority: 1, }, + { + key: 'bold', + label: t('text', 'Bold'), + keyChar: 'b', + keyModifiers: [MODIFIERS.Mod], + icon: FormatBold, + isActive: 'strong', + action: (command) => { + return command.toggleBold() + }, + priority: 8, + }, + { + key: 'italic', + label: t('text', 'Italic'), + keyChar: 'i', + keyModifiers: [MODIFIERS.Mod], + icon: FormatItalic, + isActive: 'em', + action: (command) => { + return command.toggleItalic() + }, + priority: 9, + }, + { + key: 'underline', + label: t('text', 'Underline'), + keyChar: 'u', + keyModifiers: [MODIFIERS.Mod], + icon: FormatUnderline, + isActive: 'underline', + action: (command) => { + return command.toggleUnderline() + }, + priority: 11, + }, + { + key: 'strikethrough', + label: t('text', 'Strikethrough'), + keyChar: 's', + keyModifiers: [MODIFIERS.Mod, MODIFIERS.Shift], + icon: FormatStrikethrough, + isActive: 'strike', + action: (command) => { + return command.toggleStrike() + }, + priority: 12, + }, { key: 'lists', label: t('text', 'Lists'), keyChar: '7…9', keyModifiers: [MODIFIERS.Mod, MODIFIERS.Shift], - isActive: [{ isList: true }], + isActive: ['bulletList', 'orderedList', 'taskList'], icon: FormatListBulleted, children: [ { @@ -287,36 +289,42 @@ export default [ priority: 2, }, { - key: 'insert-link', - label: t('text', 'Insert link'), - isActive: 'link', - icon: LinkIcon, - component: ActionInsertLink, - priority: 3, - }, - { - key: 'blockquote', - label: t('text', 'Blockquote'), - keyChar: 'b', - keyModifiers: [MODIFIERS.Mod, MODIFIERS.Shift], - isActive: 'blockquote', - icon: FormatQuote, - action: (command) => { - return command.toggleBlockquote() - }, - priority: 11, - }, - { - key: 'callouts', - label: t('text', 'Callouts'), + key: 'blocks', + label: t('text', 'Blocks'), visible: false, - icon: Info, - isActive: 'callout', + icon: CodeBrackets, + isActive: ['blockquote', 'codeBlock', 'callout'], children: [ + { + key: 'blockquote', + label: t('text', 'Blockquote'), + keyChar: 'b', + keyModifiers: [MODIFIERS.Mod, MODIFIERS.Shift], + isActive: 'blockquote', + icon: FormatQuote, + action: (command) => { + return command.toggleBlockquote() + }, + }, + { + key: 'code-block', + label: t('text', 'Code block'), + keyChar: 'c', + keyModifiers: [MODIFIERS.Mod, MODIFIERS.Alt], + isActive: 'codeBlock', + icon: CodeTags, + action: (command) => { + return command.toggleCodeBlock() + }, + }, + { + key: 'blocks-separator', + isSeparator: true, + }, { key: 'callout-info', - label: t('text', 'Info'), - isActive: ['callout', { type: 'info' }], + label: t('text', 'Info callout'), + isActive: { name: 'callout', attributes: { type: 'info' } }, icon: Info, action: (command) => { return command.toggleCallout({ type: 'info' }) @@ -324,8 +332,8 @@ export default [ }, { key: 'callout-success', - label: t('text', 'Success'), - isActive: ['callout', { type: 'success' }], + label: t('text', 'Success callout'), + isActive: { name: 'callout', attributes: { type: 'success' } }, icon: Positive, action: (command) => { return command.toggleCallout({ type: 'success' }) @@ -333,8 +341,8 @@ export default [ }, { key: 'callout-warn', - label: t('text', 'Warning'), - isActive: ['callout', { type: 'warn' }], + label: t('text', 'Warning callout'), + isActive: { name: 'callout', attributes: { type: 'warn' } }, icon: Warn, action: (command) => { return command.toggleCallout({ type: 'warn' }) @@ -342,27 +350,15 @@ export default [ }, { key: 'callout-error', - label: t('text', 'Danger'), - isActive: ['callout', { type: 'error' }], + label: t('text', 'Danger callout'), + isActive: { name: 'callout', attributes: { type: 'error' } }, icon: Danger, action: (command) => { return command.toggleCallout({ type: 'error' }) }, }, ], - priority: 4, - }, - { - key: 'code-block', - label: t('text', 'Code block'), - keyChar: 'c', - keyModifiers: [MODIFIERS.Mod, MODIFIERS.Alt], - isActive: 'codeBlock', - icon: CodeTags, - action: (command) => { - return command.toggleCodeBlock() - }, - priority: 12, + priority: 3, }, { key: 'table', @@ -372,7 +368,7 @@ export default [ action: (command) => { return command.insertTable() }, - priority: 15, + priority: 13, }, { key: 'details', @@ -382,7 +378,22 @@ export default [ action: (command) => { return command.toggleDetails() }, - priority: 16, + priority: 14, + }, + { + key: 'insert-link', + label: t('text', 'Insert link'), + isActive: 'link', + icon: LinkIcon, + component: ActionInsertLink, + priority: 4, + }, + { + key: 'insert-attachment', + label: t('text', 'Insert attachment'), + icon: Paperclip, + component: ActionAttachmentUpload, + priority: 5, }, { key: 'emoji-picker', @@ -392,13 +403,6 @@ export default [ action: (command, emojiObject = {}) => { return command.emoji(emojiObject) }, - priority: 5, - }, - { - key: 'insert-attachment', - label: t('text', 'Insert attachment'), - icon: Paperclip, - component: ActionAttachmentUpload, priority: 6, }, ] diff --git a/src/components/Menu/utils.js b/src/components/Menu/utils.js index 434df4fd934..95c4a51afea 100644 --- a/src/components/Menu/utils.js +++ b/src/components/Menu/utils.js @@ -44,19 +44,42 @@ const getIsActive = ({ isActive }, $editor) => { return false } - const args = Array.isArray(isActive) + // Supports either one type or an array of types + const types = Array.isArray(isActive) ? isActive : [isActive] - return $editor.isActive(...args) + for (const type of types) { + let args + // Supports either a type string or an object with type name and type attributes. + // Name can be `null`, only give `attributes` to isActive in this case. + if (type !== null && typeof type === 'object') { + args = type.name + ? [type.name, { ...type.attributes }] + : [{ ...type.attributes }] + } else { + args = [type] + } + if ($editor.isActive(...args)) { + return true + } + } + + return false } const getType = (actionEntry) => { // isActive stores the value changing on active state change (on click) - // If it is an array, the button is one of the list of alternative values for a specific option - // Like ['heading', { level: 1 }] + // If it is an array, the button is a submenu button. + // Like `['bulletList', 'orderedList']` if (Array.isArray(actionEntry.isActive)) { + return 'button' + } + + // If it is an object, the button is one of the list of alternative values for a specific option. + // Like `{ name: 'heading', attributes: { level: 1 } }` + if (actionEntry.isActive && typeof actionEntry.isActive === 'object') { return 'radio' } diff --git a/src/components/Suggestion/LinkPicker/suggestions.js b/src/components/Suggestion/LinkPicker/suggestions.js index 2d5829d9101..06e2ff16672 100644 --- a/src/components/Suggestion/LinkPicker/suggestions.js +++ b/src/components/Suggestion/LinkPicker/suggestions.js @@ -65,7 +65,7 @@ const formattingSuggestions = (query) => { ...menuEntries.find(e => e.key === 'headings').children, ...menuEntries.find(e => e.key === 'lists').children, ...menuEntries.filter(e => e.action && !filterOut(e)), - ...menuEntries.find(e => e.key === 'callouts').children, + ...menuEntries.find(e => e.key === 'blocks').children, { ...menuEntries.find(e => e.key === 'emoji-picker'), action: (command) => command.insertContent(':'), diff --git a/src/components/icons.js b/src/components/icons.js index 2d1df98e1fe..54f31b8096e 100644 --- a/src/components/icons.js +++ b/src/components/icons.js @@ -9,6 +9,7 @@ import MDI_AlphabeticalVariant from 'vue-material-design-icons/AlphabeticalVaria import MDI_Close from 'vue-material-design-icons/Close.vue' import MDI_Check from 'vue-material-design-icons/Check.vue' import MDI_CircleMedium from 'vue-material-design-icons/CircleMedium.vue' +import MDI_CodeBrackets from 'vue-material-design-icons/CodeBrackets.vue' import MDI_CodeTags from 'vue-material-design-icons/CodeTags.vue' import MDI_Danger from 'vue-material-design-icons/AlertDecagram.vue' import MDI_Delete from 'vue-material-design-icons/Delete.vue' @@ -17,6 +18,7 @@ import MDI_DotsHorizontal from 'vue-material-design-icons/DotsHorizontal.vue' import MDI_Emoticon from 'vue-material-design-icons/EmoticonOutline.vue' import MDI_Folder from 'vue-material-design-icons/Folder.vue' import MDI_FormatBold from 'vue-material-design-icons/FormatBold.vue' +import MDI_FormatSize from 'vue-material-design-icons/FormatSize.vue' import MDI_AlignHorizontalCenter from 'vue-material-design-icons/AlignHorizontalCenter.vue' import MDI_AlignHorizontalLeft from 'vue-material-design-icons/AlignHorizontalLeft.vue' import MDI_AlignHorizontalRight from 'vue-material-design-icons/AlignHorizontalRight.vue' @@ -92,6 +94,7 @@ export const AlignHorizontalLeft = makeIcon(MDI_AlignHorizontalLeft) export const AlignHorizontalRight = makeIcon(MDI_AlignHorizontalRight) export const Close = makeIcon(MDI_Close) export const Check = makeIcon(MDI_Check) +export const CodeBrackets = makeIcon(MDI_CodeBrackets) export const CodeTags = makeIcon(MDI_CodeTags) export const CircleMedium = makeIcon(MDI_CircleMedium) export const Danger = makeIcon(MDI_Danger) @@ -101,6 +104,7 @@ export const DotsHorizontal = makeIcon(MDI_DotsHorizontal) export const Emoticon = makeIcon(MDI_Emoticon) export const Folder = makeIcon(MDI_Folder) export const FormatBold = makeIcon(MDI_FormatBold) +export const FormatSize = makeIcon(MDI_FormatSize) export const FormatHeader1 = makeIcon(MDI_FormatHeader1) export const FormatHeader2 = makeIcon(MDI_FormatHeader2) export const FormatHeader3 = makeIcon(MDI_FormatHeader3)