diff --git a/src/components/EditorWrapper.vue b/src/components/EditorWrapper.vue index 1ec1d0cb1a2..de517e6c3d5 100644 --- a/src/components/EditorWrapper.vue +++ b/src/components/EditorWrapper.vue @@ -54,7 +54,7 @@ :autohide="autohide" :loaded.sync="menubarLoaded">
-
+
import(/* webpackChunkName: "editor-guest" */'./GuestNameDialog.vue'), SessionList: () => import(/* webpackChunkName: "editor-collab" */'./SessionList.vue'), Lock, + ContentSave, }, directives: { Tooltip, @@ -267,6 +269,12 @@ export default { } return this.dirtyStateIndicator ? t('text', 'Saving …') : t('text', 'Saved') }, + savedColor() { + if (this.hasConnectionIssue) { + return 'var(--color-error)' + } + return this.dirtyStateIndicator ? 'var(--color-background-dark)' : 'var(--color-primary)' + }, lastSavedStatusClass() { return this.syncError && this.lastSavedString !== '' ? 'error' : '' }, diff --git a/src/components/Menu/ActionEntry.scss b/src/components/Menu/ActionEntry.scss index 643d2c45201..3a669685898 100644 --- a/src/components/Menu/ActionEntry.scss +++ b/src/components/Menu/ActionEntry.scss @@ -72,3 +72,9 @@ } } } + +@media (max-width: 660px) { + .emoji-popover { + top: calc(-55vh) !important; + } +} diff --git a/src/components/Menu/MenuBar.vue b/src/components/Menu/MenuBar.vue index e2b66036184..0d37083e6d0 100644 --- a/src/components/Menu/MenuBar.vue +++ b/src/components/Menu/MenuBar.vue @@ -33,10 +33,26 @@
- + +
@@ -50,6 +66,7 @@ import debounce from 'debounce' import HelpModal from '../HelpModal.vue' import actionsFullEntries from './entries.js' +import mobileEntries from './mobileEntries.js' import ActionEntry from './ActionEntry.js' import { DotsHorizontal } from '../icons.js' import { @@ -57,6 +74,7 @@ import { useIsRichEditorMixin, useIsRichWorkspaceMixin, } from '../EditorWrapper.provider.js' +import isMobile from '../../mixins/isMobile.js' export default { name: 'MenuBar', @@ -65,6 +83,7 @@ export default { useEditorMixin, useIsRichEditorMixin, useIsRichWorkspaceMixin, + isMobile, ], props: { autohide: { @@ -128,6 +147,12 @@ export default { return priority !== undefined && priority > this.iconsLimit }) }, + topEntries() { + return [...mobileEntries].filter(({ position }) => position === 'top') + }, + bottomEntries() { + return [...mobileEntries].filter(({ position }) => position === 'bottom') + }, remainAction() { return { key: 'remain', @@ -262,6 +287,18 @@ export default { .text-menubar__entries { margin-left: 0; } + .top-menubar { + display: flex; + } + + .bottom-menubar { + display: flex; + position: fixed; + top: calc(100vh - 15vh); + left: 0; + width: 100%; + overflow-y: scroll; + } } } diff --git a/src/components/Menu/entries.js b/src/components/Menu/entries.js index 9af3dd04a71..bf9aa63417e 100644 --- a/src/components/Menu/entries.js +++ b/src/components/Menu/entries.js @@ -301,14 +301,14 @@ export default [ action: (command, emojiObject = {}) => { return command.emoji(emojiObject) }, - priority: 4, + priority: 19, }, { key: 'insert-image', label: t('text', 'Insert image'), icon: Images, component: ActionImageUpload, - priority: 2, + priority: 18, }, { key: 'formatting-help', diff --git a/src/components/Menu/mobileEntries.js b/src/components/Menu/mobileEntries.js new file mode 100644 index 00000000000..4d67e8420df --- /dev/null +++ b/src/components/Menu/mobileEntries.js @@ -0,0 +1,336 @@ +/* + * @copyright Copyright (c) 2019 Julius Härtl + * + * @author Julius Härtl + * + * @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 { + Undo, + Redo, + FormatBold, + FormatItalic, + FormatUnderline, + FormatStrikethrough, + FormatHeader1, + FormatHeader2, + FormatHeader3, + FormatHeader4, + FormatHeader5, + FormatHeader6, + FormatListNumbered, + FormatListBulleted, + FormatListCheckbox, + FormatQuote, + Info, + Positive, + Warn, + Danger, + CodeTags, + Table, + Emoticon, + Help, + Images, +} from '../icons.js' +import EmojiPickerAction from './EmojiPickerAction.vue' +import ActionImageUpload from './ActionImageUpload.vue' + +export default [ + { + key: 'undo', + label: t('text', 'Undo'), + keyChar: 'z', + keyModifiers: ['ctrl'], + icon: Undo, + action: (command) => command.undo(), + priority: 5, + position: 'top', + }, + { + key: 'redo', + label: t('text', 'Redo'), + keyChar: 'y', + keyModifiers: ['ctrl'], + icon: Redo, + action: (command) => command.redo(), + priority: 11, + position: 'top', + }, + { + key: 'bold', + label: t('text', 'Bold'), + keyChar: 'b', + keyModifiers: ['ctrl'], + icon: FormatBold, + isActive: 'strong', + action: (command) => { + return command.toggleBold() + }, + priority: 6, + position: 'bottom', + }, + { + key: 'italic', + label: t('text', 'Italic'), + keyChar: 'i', + keyModifiers: ['ctrl'], + icon: FormatItalic, + isActive: 'em', + action: (command) => { + return command.toggleItalic() + }, + priority: 7, + position: 'bottom', + }, + { + key: 'underline', + label: t('text', 'Underline'), + keyChar: 'u', + keyModifiers: ['ctrl'], + icon: FormatUnderline, + isActive: 'underline', + action: (command) => { + return command.toggleUnderline() + }, + priority: 14, + position: 'bottom', + }, + { + key: 'strikethrough', + label: t('text', 'Strikethrough'), + keyChar: 'd', + keyModifiers: ['ctrl'], + icon: FormatStrikethrough, + isActive: 'strike', + action: (command) => { + return command.toggleStrike() + }, + priority: 15, + position: 'bottom', + }, + { + key: 'headings', + label: t('text', 'Headings'), + keyChar: '1…6', + keyModifiers: ['ctrl', 'shift'], + visible: false, + icon: FormatHeader1, + isActive: 'heading', + children: [ + { + key: 'headings-h1', + label: t('text', 'Heading 1'), + icon: FormatHeader1, + isActive: ['heading', { level: 1 }], + action: (command) => { + return command.toggleHeading({ level: 1 }) + }, + }, + { + key: 'headings-h2', + label: t('text', 'Heading 2'), + icon: FormatHeader2, + isActive: ['heading', { level: 2 }], + action: (command) => { + return command.toggleHeading({ level: 2 }) + }, + }, + { + key: 'headings-h3', + label: t('text', 'Heading 3'), + icon: FormatHeader3, + isActive: ['heading', { level: 3 }], + action: (command) => { + return command.toggleHeading({ level: 3 }) + }, + }, + { + key: 'headings-h4', + label: t('text', 'Heading 4'), + isActive: ['heading', { level: 4 }], + icon: FormatHeader4, + action: (command) => { + return command.toggleHeading({ level: 4 }) + }, + }, + { + key: 'headings-h5', + label: t('text', 'Heading 5'), + isActive: ['heading', { level: 5 }], + icon: FormatHeader5, + action: (command) => { + return command.toggleHeading({ level: 5 }) + }, + }, + { + key: 'headings-h6', + label: t('text', 'Heading 6'), + isActive: ['heading', { level: 6 }], + icon: FormatHeader6, + action: (command) => { + return command.toggleHeading({ level: 6 }) + }, + }, + ], + priority: 1, + position: 'bottom', + }, + { + key: 'unordered-list', + label: t('text', 'Unordered list'), + keyChar: '8', + keyModifiers: ['ctrl', 'shift'], + isActive: 'bulletList', + icon: FormatListBulleted, + action: (command) => { + return command.toggleBulletList() + }, + priority: 8, + position: 'bottom', + }, + { + key: 'ordered-list', + label: t('text', 'Ordered list'), + keyChar: '9', + keyModifiers: ['ctrl', 'shift'], + isActive: 'orderedList', + icon: FormatListNumbered, + action: (command) => { + return command.toggleOrderedList() + }, + priority: 9, + position: 'bottom', + }, + { + key: 'task-list', + label: t('text', 'To-Do list'), + isActive: 'taskList', + icon: FormatListCheckbox, + action: (command) => command.toggleTaskList(), + priority: 10, + }, + { + key: 'blockquote', + label: t('text', 'Blockquote'), + keyChar: '>', + keyModifiers: ['ctrl'], + isActive: 'blockquote', + icon: FormatQuote, + action: (command) => { + return command.toggleBlockquote() + }, + priority: 12, + position: 'bottom', + }, + { + key: 'callouts', + label: t('text', 'Callouts'), + visible: false, + icon: Info, + isActive: 'callout', + children: [ + { + key: 'callout-info', + label: t('text', 'Info'), + isActive: ['callout', { type: 'info' }], + icon: Info, + action: (command) => { + return command.toggleCallout({ type: 'info' }) + }, + }, + { + key: 'callout-success', + label: t('text', 'Success'), + isActive: ['callout', { type: 'success' }], + icon: Positive, + action: (command) => { + return command.toggleCallout({ type: 'success' }) + }, + }, + { + key: 'callout-warn', + label: t('text', 'Warning'), + isActive: ['callout', { type: 'warn' }], + icon: Warn, + action: (command) => { + return command.toggleCallout({ type: 'warn' }) + }, + }, + { + key: 'callout-error', + label: t('text', 'Danger'), + isActive: ['callout', { type: 'error' }], + icon: Danger, + action: (command) => { + return command.toggleCallout({ type: 'error' }) + }, + }, + ], + priority: 3, + position: 'bottom', + }, + { + key: 'code-block', + label: t('text', 'Code block'), + isActive: 'codeBlock', + icon: CodeTags, + action: (command) => { + return command.toggleCodeBlock() + }, + priority: 13, + position: 'bottom', + }, + { + key: 'table', + label: t('text', 'Table'), + isActive: 'table', + icon: Table, + action: (command) => { + return command.insertTable() + }, + priority: 16, + position: 'bottom', + }, + { + key: 'emoji-picker', + label: t('text', 'Insert emoji'), + icon: Emoticon, + component: EmojiPickerAction, + action: (command, emojiObject = {}) => { + return command.emoji(emojiObject) + }, + priority: 19, + position: 'bottom', + }, + { + key: 'insert-image', + label: t('text', 'Insert image'), + icon: Images, + component: ActionImageUpload, + priority: 18, + position: 'bottom', + }, + { + key: 'formatting-help', + label: t('text', 'Formatting help'), + icon: Help, + click: (view) => view.$emit('call:help'), + priority: 17, + position: 'bottom', + }, +]