diff --git a/package-lock.json b/package-lock.json
index 3bbe87fd565..db2e795651e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -49,6 +49,7 @@
"@tiptap/suggestion": "^2.0.0-beta.91",
"@tiptap/vue-2": "^2.0.0-beta.78",
"core-js": "^3.22.3",
+ "debounce": "^1.2.1",
"escape-html": "^1.0.3",
"highlight.js": "^10.7.2",
"lowlight": "^1.20.0",
diff --git a/package.json b/package.json
index bf4bba30add..8e70129ee7b 100644
--- a/package.json
+++ b/package.json
@@ -66,6 +66,7 @@
"@tiptap/suggestion": "^2.0.0-beta.91",
"@tiptap/vue-2": "^2.0.0-beta.78",
"core-js": "^3.22.3",
+ "debounce": "^1.2.1",
"escape-html": "^1.0.3",
"highlight.js": "^10.7.2",
"lowlight": "^1.20.0",
diff --git a/src/components/EditorDraggable.provider.js b/src/components/EditorDraggable.provider.js
new file mode 100644
index 00000000000..2bb879a3d2a
--- /dev/null
+++ b/src/components/EditorDraggable.provider.js
@@ -0,0 +1,21 @@
+export const IS_UPLOADING_IMAGES = Symbol('editor:is-uploading-images')
+export const ACTION_IMAGE_PROMPT = Symbol('editor:action:image-prompt')
+export const ACTION_CHOOSE_LOCAL_IMAGE = Symbol('editor:action:upload-image')
+
+export const useIsUploadingImagesMixin = {
+ inject: {
+ $isUploadingImages: { from: IS_UPLOADING_IMAGES, default: false },
+ },
+}
+
+export const useActionImagePromptMixin = {
+ inject: {
+ $callImagePrompt: { from: ACTION_IMAGE_PROMPT, default: () => {} },
+ },
+}
+
+export const useActionChooseLocalImageMixin = {
+ inject: {
+ $callChooseLocalImage: { from: ACTION_CHOOSE_LOCAL_IMAGE, default: () => {} },
+ },
+}
diff --git a/src/components/EditorDraggable.vue b/src/components/EditorDraggable.vue
new file mode 100644
index 00000000000..2928ba038a3
--- /dev/null
+++ b/src/components/EditorDraggable.vue
@@ -0,0 +1,193 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/EditorWrapper.provider.js b/src/components/EditorWrapper.provider.js
index c19ebdc0dbc..ccdb2408b18 100644
--- a/src/components/EditorWrapper.provider.js
+++ b/src/components/EditorWrapper.provider.js
@@ -1,13 +1,93 @@
+/*
+ * @copyright Copyright (c) 2022 Vinicius Reis
+ *
+ * @author Vinicius Reis
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
export const EDITOR = Symbol('tiptap:editor')
export const SYNC_SERVICE = Symbol('sync:service')
+export const DOCUMENT = Symbol('editor:document')
+export const IS_PUBLIC = Symbol('editor:is-public')
+export const FILE = Symbol('editor:file')
+export const IS_RICH_EDITOR = Symbol('editor:is-rich-editor')
+export const IS_MOBILE = Symbol('editor:is-mobile')
+export const RELATIVE_PATH = Symbol('editor:relative-path')
+export const IS_UPLOADING_IMAGES = Symbol('editor:is-uploading-images')
+export const ACTION_IMAGE_PROMPT = Symbol('action:image-prompt')
export const useEditorMixin = {
inject: {
$editor: { from: EDITOR, default: null },
},
}
+
export const useSyncServiceMixin = {
inject: {
$syncService: { from: SYNC_SERVICE, default: null },
},
}
+
+export const useIsPublic = {
+ inject: {
+ $isPublic: { from: IS_PUBLIC, default: false },
+ },
+}
+
+export const useIsRichEditorMixin = {
+ inject: {
+ $isRichEditor: { from: IS_RICH_EDITOR, default: false },
+ },
+}
+
+export const useIsMobileMixin = {
+ inject: {
+ $isMobile: { from: IS_MOBILE, default: false },
+ },
+}
+
+export const useDocumentMixin = {
+ inject: {
+ $document: { from: DOCUMENT, default: null },
+ },
+}
+
+export const useIsUploadingImagesMixin = {
+ inject: {
+ $isUploadingImages: { from: IS_UPLOADING_IMAGES, default: false },
+ },
+}
+
+export const useRelativePathMixin = {
+ inject: {
+ $relativePath: { from: RELATIVE_PATH, default: null },
+ },
+}
+
+export const useFileMixin = {
+ inject: {
+ $file: {
+ from: FILE,
+ default: () => ({
+ fileId: 0,
+ relativePath: null,
+ document: null,
+ }),
+ },
+ },
+}
diff --git a/src/components/EditorWrapper.vue b/src/components/EditorWrapper.vue
index d3ebf7d3d4c..d81468d333a 100644
--- a/src/components/EditorWrapper.vue
+++ b/src/components/EditorWrapper.vue
@@ -37,19 +37,12 @@
+
@@ -90,9 +83,20 @@
import Vue from 'vue'
import escapeHtml from 'escape-html'
import moment from '@nextcloud/moment'
+import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
import { showError } from '@nextcloud/dialogs'
+import { EditorContent } from '@tiptap/vue-2'
+import { getCurrentUser } from '@nextcloud/auth'
+import { getVersion, receiveTransaction } from 'prosemirror-collab'
-import { EDITOR, SYNC_SERVICE } from './EditorWrapper.provider.js'
+import {
+ EDITOR,
+ SYNC_SERVICE,
+ IS_PUBLIC,
+ IS_RICH_EDITOR,
+ IS_MOBILE,
+ FILE,
+} from './EditorWrapper.provider.js'
import { SyncService, ERROR_TYPE, IDLE_TIMEOUT } from './../services/SyncService.js'
import { endpointUrl, getRandomGuestName } from './../helpers/index.js'
@@ -101,14 +105,14 @@ import { createEditor, serializePlainText, loadSyntaxHighlight } from './../Edit
import { createMarkdownSerializer } from './../extensions/Markdown.js'
import markdownit from './../markdownit/index.js'
-import { EditorContent } from '@tiptap/vue-2'
import { Collaboration, Keymap, UserColor } from './../extensions/index.js'
import isMobile from './../mixins/isMobile.js'
import store from './../mixins/store.js'
-import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
-import { getVersion, receiveTransaction } from 'prosemirror-collab'
import { Step } from 'prosemirror-transform'
import Lock from 'vue-material-design-icons/Lock'
+// import MenuBar from './Menu/Bar.vue'
+import EditorDraggable from './EditorDraggable.vue'
+
const EDITOR_PUSH_DEBOUNCE = 200
const IMAGE_MIMES = [
@@ -127,6 +131,7 @@ export default {
name: 'EditorWrapper',
components: {
EditorContent,
+ EditorDraggable,
MenuBar: () => import(/* webpackChunkName: "editor-rich" */'./Menu/Bar.vue'),
MenuBubble: () => import(/* webpackChunkName: "editor-rich" */'./MenuBubble.vue'),
ReadOnlyEditor: () => import(/* webpackChunkName: "editor" */'./ReadOnlyEditor.vue'),
@@ -149,16 +154,25 @@ export default {
// providers aren't naturally reactive
// and $editor will start as null
// using getters we can always provide the
- // actual $editor without being reactive
- Object.defineProperty(val, EDITOR, {
- get: () => {
- return this.$editor
+ // actual $editor, and other values without being reactive
+ Object.defineProperties(val, {
+ [EDITOR]: {
+ get: () => this.$editor,
},
- })
-
- Object.defineProperty(val, SYNC_SERVICE, {
- get: () => {
- return this.$syncService
+ [SYNC_SERVICE]: {
+ get: () => this.$syncService,
+ },
+ [FILE]: {
+ get: () => this.fileData,
+ },
+ [IS_PUBLIC]: {
+ get: () => this.isPublic,
+ },
+ [IS_RICH_EDITOR]: {
+ get: () => this.isRichEditor,
+ },
+ [IS_MOBILE]: {
+ get: () => this.isMobile,
},
})
@@ -295,6 +309,18 @@ export default {
&& !this.syncError
&& !this.readOnly
},
+ imagePath() {
+ return this.relativePath.split('/').slice(0, -1).join('/')
+ },
+ fileData() {
+ return {
+ fileId: this.fileId,
+ relativePath: this.relativePath,
+ document: {
+ ...this.document,
+ },
+ }
+ },
},
watch: {
lastSavedStatus() {
@@ -591,6 +617,7 @@ export default {
this.displayHelp = false
},
onPaste(e) {
+ // emit('files:past-files', e)
this.uploadImageFiles(e.detail.files)
},
onEditorDrop(e) {
@@ -622,9 +649,20 @@ export default {
showError(error?.response?.data?.error)
})
},
+ showImagePrompt() {
+ const currentUser = getCurrentUser()
+ if (!currentUser) {
+ return
+ }
+
+ OC.dialogs.filepicker(t('text', 'Insert an image'), (filePath) => {
+ this.insertImagePath(filePath)
+ }, false, [], true, undefined, this.imagePath)
+ },
insertImagePath(imagePath) {
this.uploadingImages = true
- this.$syncService.insertImageFile(imagePath).then((response) => {
+
+ return this.$syncService.insertImageFile(imagePath).then((response) => {
this.insertAttachmentImage(response.data?.name, response.data?.id)
}).catch((error) => {
console.error(error)
diff --git a/src/components/Menu/ActionEntry.js b/src/components/Menu/ActionEntry.js
new file mode 100644
index 00000000000..928110a7044
--- /dev/null
+++ b/src/components/Menu/ActionEntry.js
@@ -0,0 +1,37 @@
+/*
+ * @copyright Copyright (c) 2022 Vinicius Reis
+ *
+ * @author Vinicius Reis
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+import SingleAction from './SingleAction.vue'
+
+export default {
+ name: 'ActionEntry',
+ functional: true,
+ render(h, ctx) {
+ const { actionEntry } = ctx.props
+
+ if (actionEntry.component) {
+ return h(actionEntry.component, ctx)
+ }
+
+ return h(SingleAction, ctx)
+ },
+}
diff --git a/src/components/Menu/ActionEntry.mixin.js b/src/components/Menu/ActionEntry.mixin.js
new file mode 100644
index 00000000000..ccb86748017
--- /dev/null
+++ b/src/components/Menu/ActionEntry.mixin.js
@@ -0,0 +1,74 @@
+/*
+ * @copyright Copyright (c) 2022 Vinicius Reis
+ *
+ * @author Vinicius Reis
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+/* eslint-disable jsdoc/valid-types */
+
+import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
+import debounce from 'debounce'
+
+import { useDocumentMixin, useEditorMixin, useIsMobileMixin } from '../EditorWrapper.provider.js'
+import { getActionState, getKeys } from './utils.js'
+
+/**
+ * @type {import("vue").ComponentOptions} BaseActionEntry
+ */
+const BaseActionEntry = {
+ directives: {
+ Tooltip,
+ },
+ mixins: [useEditorMixin, useIsMobileMixin, useDocumentMixin],
+ props: {
+ actionEntry: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ state: getActionState(this.actionEntry, this.$editor),
+ }
+ },
+ computed: {
+ icon() {
+ return this.actionEntry.icon
+ },
+ tooltip() {
+ return [this.actionEntry.label, getKeys(this.isMobile, this.actionEntry)].join(' ')
+ },
+ },
+ mounted() {
+ this.$_updateState = debounce(this.updateState.bind(this), 50)
+ this.$editor.on('update', this.$_updateState)
+ this.$editor.on('selectionUpdate', this.$_updateState)
+ },
+ beforeDestroy() {
+ this.$editor.off('update', this.$_updateState)
+ this.$editor.off('selectionUpdate', this.$_updateState)
+ },
+ methods: {
+ updateState() {
+ this.state = getActionState(this.actionEntry, this.$editor)
+ },
+ },
+}
+
+export { BaseActionEntry }
diff --git a/src/components/Menu/ActionEntry.scss b/src/components/Menu/ActionEntry.scss
new file mode 100644
index 00000000000..d69baf1d037
--- /dev/null
+++ b/src/components/Menu/ActionEntry.scss
@@ -0,0 +1,40 @@
+button.entry-action,
+.entry-action > button,
+.entry-action > div > button {
+ position: relative;
+ width: 44px;
+ height: 44px;
+ margin: 0;
+ background-size: 16px;
+ border: 0;
+ background-color: transparent;
+ opacity: .5;
+ color: var(--color-main-text);
+ background-position: center center;
+ vertical-align: top;
+ padding: 0.7em;
+ &:hover,
+ &:focus,
+ &:active {
+ background-color: var(--color-background-dark);
+ }
+
+ &.is-active::before {
+ transform: translateX(-50%);
+ border-radius: 100%;
+ position: absolute;
+ background: var(--color-primary-element);
+ bottom: 3px;
+ height: 6px;
+ width: 6px;
+ content: '';
+ left: 50%;
+
+ }
+
+ &.is-active,
+ &:hover,
+ &:focus {
+ opacity: 1;
+ }
+}
diff --git a/src/components/Menu/Bar.vue b/src/components/Menu/Bar.vue
index 3c6df47a35e..4d965256d4a 100644
--- a/src/components/Menu/Bar.vue
+++ b/src/components/Menu/Bar.vue
@@ -1,6 +1,7 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Menu/ImageUploadAction.vue b/src/components/Menu/ImageUploadAction.vue
new file mode 100644
index 00000000000..9888629f155
--- /dev/null
+++ b/src/components/Menu/ImageUploadAction.vue
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+ {{ t('text', 'Upload from computer') }}
+
+
+
+
+
+ {{ t('text', 'Insert from Files') }}
+
+
+
+
+
diff --git a/src/components/Menu/SingleAction.vue b/src/components/Menu/SingleAction.vue
new file mode 100644
index 00000000000..5d0651782d6
--- /dev/null
+++ b/src/components/Menu/SingleAction.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/mixins/menubar.js b/src/components/Menu/entries.js
similarity index 88%
rename from src/mixins/menubar.js
rename to src/components/Menu/entries.js
index 26f6f6fe801..2a70f9e595a 100644
--- a/src/mixins/menubar.js
+++ b/src/components/Menu/entries.js
@@ -46,10 +46,13 @@ import {
Emoticon,
Help,
Images,
-} from '../components/icons.js'
+} from '../icons.js'
+import EmojiPickerAction from './EmojiPickerAction.vue'
+import ImageUploadAction from './ImageUploadAction.vue'
export default [
{
+ _key: 'undo',
label: t('text', 'Undo'),
keyChar: 'z',
keyModifiers: ['ctrl'],
@@ -59,6 +62,7 @@ export default [
priority: 5,
},
{
+ _key: 'redo',
label: t('text', 'Redo'),
keyChar: 'y',
keyModifiers: ['ctrl'],
@@ -68,6 +72,7 @@ export default [
priority: 11,
},
{
+ _key: 'bold',
label: t('text', 'Bold'),
keyChar: 'b',
keyModifiers: ['ctrl'],
@@ -80,6 +85,7 @@ export default [
priority: 6,
},
{
+ _key: 'italic',
label: t('text', 'Italic'),
keyChar: 'i',
keyModifiers: ['ctrl'],
@@ -92,6 +98,7 @@ export default [
priority: 7,
},
{
+ _key: 'underline',
label: t('text', 'Underline'),
keyChar: 'u',
keyModifiers: ['ctrl'],
@@ -104,6 +111,7 @@ export default [
priority: 14,
},
{
+ _key: 'strikethrough',
label: t('text', 'Strikethrough'),
keyChar: 'd',
keyModifiers: ['ctrl'],
@@ -116,6 +124,7 @@ export default [
priority: 15,
},
{
+ _key: 'headings',
label: t('text', 'Headings'),
keyChar: '1…6',
keyModifiers: ['ctrl', 'shift'],
@@ -123,6 +132,7 @@ export default [
icon: FormatHeader1,
children: [
{
+ _key: 'headings-01',
label: t('text', 'Heading 1'),
class: 'icon-h1',
icon: FormatHeader1,
@@ -132,6 +142,7 @@ export default [
},
},
{
+ _key: 'headings-02',
label: t('text', 'Heading 2'),
class: 'icon-h2',
icon: FormatHeader2,
@@ -141,6 +152,7 @@ export default [
},
},
{
+ _key: 'headings-03',
label: t('text', 'Heading 3'),
class: 'icon-h3',
icon: FormatHeader3,
@@ -150,6 +162,7 @@ export default [
},
},
{
+ _key: 'headings-04',
label: t('text', 'Heading 4'),
class: 'icon-h4',
isActive: ['heading', { level: 4 }],
@@ -159,6 +172,7 @@ export default [
},
},
{
+ _key: 'headings-05',
label: t('text', 'Heading 5'),
class: 'icon-h5',
isActive: ['heading', { level: 5 }],
@@ -168,6 +182,7 @@ export default [
},
},
{
+ _key: 'headings-06',
label: t('text', 'Heading 6'),
class: 'icon-h6',
isActive: ['heading', { level: 6 }],
@@ -180,6 +195,7 @@ export default [
priority: 1,
},
{
+ _key: 'unordered-list',
label: t('text', 'Unordered list'),
keyChar: '8',
keyModifiers: ['ctrl', 'shift'],
@@ -192,6 +208,7 @@ export default [
priority: 8,
},
{
+ _key: 'ordered-list',
label: t('text', 'Ordered list'),
keyChar: '9',
keyModifiers: ['ctrl', 'shift'],
@@ -204,6 +221,7 @@ export default [
priority: 9,
},
{
+ _key: 'todo-list',
label: t('text', 'ToDo list'),
class: 'icon-tasklist',
isActive: 'taskList',
@@ -212,6 +230,7 @@ export default [
priority: 10,
},
{
+ _key: 'blockquote',
label: t('text', 'Blockquote'),
keyChar: '>',
keyModifiers: ['ctrl'],
@@ -224,11 +243,13 @@ export default [
priority: 12,
},
{
+ _key: 'callouts',
label: t('text', 'Callouts'),
visible: false,
icon: Info,
children: [
{
+ _key: 'info',
label: t('text', 'Info'),
class: 'icon-info',
isActive: ['callout', { type: 'info' }],
@@ -238,6 +259,7 @@ export default [
},
},
{
+ _key: 'success',
label: t('text', 'Success'),
class: 'icon-success',
isActive: ['callout', { type: 'success' }],
@@ -247,6 +269,7 @@ export default [
},
},
{
+ _key: 'warning',
label: t('text', 'Warning'),
class: 'icon-warn',
isActive: ['callout', { type: 'warn' }],
@@ -256,6 +279,7 @@ export default [
},
},
{
+ _key: 'danger',
label: t('text', 'Danger'),
class: 'icon-error',
isActive: ['callout', { type: 'error' }],
@@ -268,6 +292,7 @@ export default [
priority: 3,
},
{
+ _key: 'code-block',
label: t('text', 'Code block'),
class: 'icon-code',
isActive: 'codeBlock',
@@ -278,6 +303,7 @@ export default [
priority: 13,
},
{
+ _key: 'table',
label: t('text', 'Table'),
class: 'icon-table',
isActive: 'table',
@@ -288,21 +314,26 @@ export default [
priority: 16,
},
{
- label: t('text', 'Emoji picker'),
+ _key: 'emoji-picker',
+ label: t('text', 'Insert emoji'),
class: 'icon-emoji',
icon: Emoticon,
- action: (command, emojiObject) => {
+ component: EmojiPickerAction,
+ action: (command, emojiObject = {}) => {
return command.emoji(emojiObject)
},
priority: 4,
},
{
+ _key: 'insert-image',
label: t('text', 'Insert image'),
class: 'icon-image',
icon: Images,
+ component: ImageUploadAction,
priority: 2,
},
{
+ _key: 'formatting-help',
label: t('text', 'Formatting help'),
class: 'icon-help',
icon: Help,
diff --git a/src/components/Menu/utils.js b/src/components/Menu/utils.js
new file mode 100644
index 00000000000..896230fc854
--- /dev/null
+++ b/src/components/Menu/utils.js
@@ -0,0 +1,80 @@
+/*
+ * @copyright Copyright (c) 2022 Vinicius Reis
+ *
+ * @author Vinicius Reis
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+const translations = {
+ ctrl: t('text', 'Ctrl'),
+ alt: t('text', 'Alt'),
+ shift: t('text', 'Shift'),
+}
+
+const getEntryClasses = (actionEntry, isActive) => {
+ return {
+ 'is-active': isActive,
+ [`action-menu-${actionEntry._key}`]: true,
+ }
+}
+
+const keysString = (keyChar, modifiers = []) => {
+ return Object.entries(translations)
+ .filter(([k, v]) => modifiers.includes(k))
+ .map(([k, v]) => v)
+ .concat(keyChar.toUpperCase())
+ .join('+')
+}
+
+const getKeys = (isMobile, { keyChar, keyModifiers }) => {
+ return (!isMobile && keyChar)
+ ? `(${keysString(keyChar, keyModifiers)})`
+ : ''
+}
+
+const isDisabled = (actionEntry, $editor) => {
+ return actionEntry.action && !actionEntry.action($editor.can())
+}
+
+const getIsActive = ({ isActive }, $editor) => {
+ if (!isActive) {
+ return false
+ }
+
+ const args = Array.isArray(isActive)
+ ? isActive
+ : [isActive]
+
+ return $editor.isActive(...args)
+}
+
+const getActionState = (actionEntry, $editor) => {
+ const active = getIsActive(actionEntry, $editor)
+
+ return {
+ disabled: isDisabled(actionEntry, $editor),
+ class: getEntryClasses(actionEntry, active),
+ }
+}
+
+export {
+ isDisabled,
+ getKeys,
+ getEntryClasses,
+ getActionState,
+}
diff --git a/src/components/icons.js b/src/components/icons.js
index 43e63bfd132..bfc53cde2e3 100644
--- a/src/components/icons.js
+++ b/src/components/icons.js
@@ -1,3 +1,25 @@
+/*
+ * @copyright Copyright (c) 2022 Vinicius Reis
+ *
+ * @author Vinicius Reis
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
import MdiLoading from 'vue-material-design-icons/Loading'
export { default as Lock } from 'vue-material-design-icons/Lock'