diff options
author | Julius Härtl <jus@bitgrid.net> | 2019-06-07 15:02:18 +0300 |
---|---|---|
committer | Julius Härtl <jus@bitgrid.net> | 2019-06-11 11:40:52 +0300 |
commit | 97749c7405d58129f2cd2e93dea5bc95037b1be8 (patch) | |
tree | d8d1c7d61b288e60bfa2e1e94f510eb33761568d | |
parent | 6087995cb8ed34c72c2b59dc2e4fdf7626025029 (diff) |
Frontend code cleanup
Signed-off-by: Julius Härtl <jus@bitgrid.net>
-rw-r--r-- | css/style.scss | 362 | ||||
-rw-r--r-- | src/EditorFactory.js | 67 | ||||
-rw-r--r-- | src/components/EditorWrapper.vue (renamed from src/components/Editor.vue) | 117 | ||||
-rw-r--r-- | src/components/ReadOnlyEditor.vue | 40 | ||||
-rw-r--r-- | src/files.js | 2 | ||||
-rw-r--r-- | src/public.js | 2 | ||||
-rw-r--r-- | src/services/SyncService.js | 6 |
7 files changed, 120 insertions, 476 deletions
diff --git a/css/style.scss b/css/style.scss index fdfb0c17b..cf2d2aa81 100644 --- a/css/style.scss +++ b/css/style.scss @@ -1,38 +1,15 @@ -.ProseMirror.ProseMirror-example-setup-style { - overflow: scroll; -} - -.ProseMirror.ProseMirror-example-setup-style, -.ProseMirror-menubar-wrapper { - width: 100%; - max-width: 900px; - margin: auto; - display: flex; +#files-public-content { + width: 100% !important; height: 100%; - flex-direction: column; - - * { - box-sizing: border-box; - } } -.ProseMirror { - word-wrap: break-word; - white-space: pre-wrap; - -webkit-font-variant-ligatures: none; - font-variant-ligatures: none; +#viewer-content.modal-mask .modal-wrapper .modal-container { + border-radius: 3px !important; } -.ProseMirror pre { - white-space: pre-wrap; - background-color: var(--color-background-dark); - border-radius: 5px; - padding: 5px; - padding-left: 11px; -} - -.ProseMirror li { - position: relative; +.modal-container #editor-container { + position: absolute; + max-height: calc(100% - 100px); } .ProseMirror-hideselection *::selection { background: transparent; } @@ -44,7 +21,6 @@ } /* Make sure li selections wrap around markers */ - li.ProseMirror-selectednode { outline: none; } @@ -57,139 +33,6 @@ li.ProseMirror-selectednode:after { border: 2px solid #8cf; pointer-events: none; } -.ProseMirror-textblock-dropdown { - min-width: 3em; -} - -.ProseMirror-menu { - margin: 0 -4px; - line-height: 1; -} - -.ProseMirror-tooltip .ProseMirror-menu { - width: fit-content; - white-space: pre; -} - -.ProseMirror-menuitem { - margin-right: 3px; - padding: 4px; - display: inline-block; - vertical-align: top; -} - -.ProseMirror-menuseparator { - border: 0; - margin-right: 3px; -} - -.ProseMirror-menu-dropdown, .ProseMirror-menu-dropdown-menu { - white-space: nowrap; -} - -.ProseMirror-menu-dropdown { - vertical-align: 1px; - cursor: pointer; - position: relative; - padding: 8px; - padding-right: 30px; -} - -.ProseMirror-menu-dropdown-wrap { - padding: 1px 0 1px 4px; - display: inline-block; - position: relative; -} - -.ProseMirror-menu-dropdown:after { - content: ""; - border-left: 4px solid transparent; - border-right: 4px solid transparent; - border-top: 4px solid currentColor; - opacity: .6; - position: absolute; - right: 9px; - top: calc(50% - 2px); -} - -.ProseMirror-menu-dropdown-menu, .ProseMirror-menu-submenu { - position: absolute; - background: var(--color-main-background); - color: var(--color-main-text); - box-shadow: 0 0 3px var(--color-box-shadow); - padding: 2px; -} - -.ProseMirror-menu-dropdown-menu { - z-index: 15; - min-width: 6em; -} - -.ProseMirror-menu-dropdown-item { - cursor: pointer; - padding: 2px 8px 2px 4px; -} - -.ProseMirror-menu-dropdown-item:hover { - background: var(--color-background-dark); -} - -.ProseMirror-menu-submenu-wrap { - position: relative; - margin-right: -4px; -} - -.ProseMirror-menu-submenu-label { - padding-right: 20px; -} - -.ProseMirror-menu-submenu-label:after { - content: ""; - border-top: 4px solid transparent; - border-bottom: 4px solid transparent; - border-left: 4px solid currentColor; - opacity: .6; - position: absolute; - right: 4px; - top: calc(50% - 4px); -} - -.ProseMirror-menu-submenu { - display: none; - min-width: 4em; - left: 100%; - top: -3px; -} - -.ProseMirror-menu-active { - background: var(--color-background-darker); - border-radius: 4px; -} - -.ProseMirror-menu-disabled { - opacity: .3; -} - -.ProseMirror-menu-submenu-wrap:hover .ProseMirror-menu-submenu, .ProseMirror-menu-submenu-wrap-active .ProseMirror-menu-submenu { - display: block; -} - - - -.ProseMirror-menubar { - border-top-left-radius: inherit; - border-top-right-radius: inherit; - position: relative; - min-height: 1em; - color: var(--color-text-light); - padding: 1px 6px; - top: 0; left: 0; right: 0; - background: var(--color-main-background-translucent); - z-index: 10; - -moz-box-sizing: border-box; - box-sizing: border-box; - overflow: visible; -} .has-conflicts, #editor-wrapper.icon-loading { @@ -198,49 +41,6 @@ li.ProseMirror-selectednode:after { } } -.ProseMirror { - margin-top: 44px; - height: 100%; - position: relative; -} - -.ProseMirror-icon, .ProseMirror-menu-dropdown-wrap { - display: inline-block; - line-height: .8; - border-radius: 2px; - cursor: pointer; - &:hover, &:focus { - background-color: var(--color-background-dark); - } -} - -.ProseMirror-icon, .Prosemirror-menu-dropdown { - padding: 8px; - width: 34px; - text-align: center; -} - -.ProseMirror-menu-dropdown-menu { - padding: 8px; - border-radius: 2px; - cursor: pointer; - .ProseMirror-menu-dropdown-item { - padding: 8px; - } -} - -.ProseMirror-menu-disabled.ProseMirror-icon { - cursor: default; -} - -.ProseMirror-icon svg { - fill: currentColor; - height: 1em; -} - -.ProseMirror-icon span { - vertical-align: text-top; -} .ProseMirror-gapcursor { display: none; pointer-events: none; @@ -263,93 +63,6 @@ li.ProseMirror-selectednode:after { } } -.ProseMirror-focused .ProseMirror-gapcursor { - display: block; -} -/* Add space around the hr to make clicking it easier */ - -.ProseMirror-example-setup-style hr { - padding: 2px 10px; - border: none; - margin: 1em 0; - width: 100%; -} - -.ProseMirror-example-setup-style hr:after { - content: ""; - display: block; - height: 1px; - background-color: silver; - line-height: 2px; -} - -.ProseMirror ul, .ProseMirror ol { - padding-left: 30px; -} -.ProseMirror ul li { - list-style-type: disc; -} -.ProseMirror blockquote { - padding-left: 1em; - border-left: 3px solid var(--color-text-lighter); - margin-left: 0; margin-right: 0; -} - -.ProseMirror-example-setup-style img, -.ProseMirror img { - cursor: default; - max-height: 50vh; - max-width: 100%; -} - -.ProseMirror-prompt { - background: var(--color-main-background); - padding: 20px; - position: fixed; - border-radius: 3px; - z-index: 20000; - box-shadow: -.5px 2px 5px var(--color-box-shadow); -} - -.ProseMirror-prompt h5 { - margin: 0; - font-weight: normal; - font-size: 100%; - color: var(--color-main-text); -} - -.ProseMirror-prompt-close { - position: absolute; - left: 2px; top: 1px; - color: var(--color-main-text); - border: none; background: transparent; padding: 0; -} - -.ProseMirror-prompt-close:after { - content: "✕"; - font-size: 12px; -} - -.ProseMirror-invalid { - background: #ffc; - border: 1px solid #cc7; - border-radius: 4px; - padding: 5px 10px; - position: absolute; - min-width: 10em; -} - -.ProseMirror-prompt-buttons { - margin-top: 5px; - float: right; -} - -.ProseMirror-prompt-submit { - color: var(--color-primary-text); - background-color: var(--color-primary); - border: 0; -} - #editor, .editor { background: var(--color-main-background); color: var(--color-main-text); @@ -359,6 +72,7 @@ li.ProseMirror-selectednode:after { position: relative; overflow-y: scroll; overflow-x: hidden; + width: 100%; } div[contenteditable=true], @@ -370,66 +84,6 @@ div[contenteditable=false] { opacity: 1; } -.ProseMirror p:first-child, -.ProseMirror h1:first-child, -.ProseMirror h2:first-child, -.ProseMirror h3:first-child, -.ProseMirror h4:first-child, -.ProseMirror h5:first-child, -.ProseMirror h6:first-child { - margin-top: 10px; -} - -.ProseMirror { - padding: 4px 8px 4px 14px; - line-height: 150%; - font-size: 14px; - outline: none; -} - -.ProseMirror a { - color: var(--color-primary); - text-decoration: underline; -} - -.ProseMirror p { - margin-bottom: 1em; - line-height: 150%; -} -.ProseMirror em { - font-style: italic; -} - - -.ProseMirror h1 { - font-size: 24px; -} -.ProseMirror h2 { - font-size: 22px; -} -.ProseMirror h3 { - font-size: 20px; -} -.ProseMirror h4 { - font-size: 18px; -} -.ProseMirror h5 { - font-size: 16px; -} -.ProseMirror h6 { - font-size: 14px; -} -.ProseMirror h1, -.ProseMirror h2, -.ProseMirror h3, -.ProseMirror h4, -.ProseMirror h5, -.ProseMirror h6 { - font-weight: 600; - margin-top: 10px; - margin-bottom: 20px; -} - .icon-bold { background-image: url('./../../img/icons/bold.svg'); diff --git a/src/EditorFactory.js b/src/EditorFactory.js new file mode 100644 index 000000000..2e2e435fc --- /dev/null +++ b/src/EditorFactory.js @@ -0,0 +1,67 @@ +/* + * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net> + * + * @author Julius Härtl <jus@bitgrid.net> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ +import {Editor} from 'tiptap'; +import { + HardBreak, + Heading, + Bold, + Code, + Link, + BulletList, + OrderedList, + ListItem, + Blockquote, + CodeBlock, + Image, + History +} from 'tiptap-extensions' +import { Strong, Italic } from './marks' +import MarkdownIt from 'markdown-it' + +const createEditor = ({content, onUpdate, extensions}) => { + extensions = extensions ? extensions : [] + return new Editor({ + content: content, + onUpdate: onUpdate, + extensions: [ + new HardBreak, + new Heading, + new Code, + new Strong, + new Italic, + new BulletList, + new OrderedList, + new Blockquote, + new CodeBlock, + new ListItem, + new Link, + new Image, + new History() + ].concat(extensions), + }) +} + +const markdownit = MarkdownIt('commonmark', {html: false}); + + +export default createEditor +export { markdownit, createEditor} diff --git a/src/components/Editor.vue b/src/components/EditorWrapper.vue index c81c96c6b..a9592b3f3 100644 --- a/src/components/Editor.vue +++ b/src/components/EditorWrapper.vue @@ -65,7 +65,7 @@ <div class="menububble" :class="{ 'is-active': menu.isActive }" :style="`left: ${menu.left}px; bottom: ${menu.bottom}px;`"> <form class="menububble__form" v-if="linkMenuIsActive" @submit.prevent="setLinkUrl(commands.link, linkUrl)"> - <input class="menububble__input" type="text" v-model="linkUrl" placeholder="https://" ref="linkInput" @keydown.esc="hideLinkMenu"/> + <input class="menububble__input" type="text" v-model="linkUrl" placeholder="https://" ref="linkInput" @keydown.esc="hideLinkMenu" /> <button class="menububble__button" @click="setLinkUrl(commands.link, null)" type="button"></button> </form> @@ -92,54 +92,32 @@ </button> </div> - <div v-if="isPublic && !guestNameConfirmed" class="guest-name-dialog"> - <p>{{ t('text', 'Please enter a name to identify you as a public editor:') }}</p> - <form @submit.prevent="setGuestName()"> - <input type="text" v-model="guestName" /> - <input type="submit" class="icon-confirm" value="" /> - </form> - </div> + <guest-name-dialog v-if="isPublic && !guestNameConfirmed" :value="guestName" @input="setGuestName($event)"></guest-name-dialog> </div> </template> <script> import Vue from 'vue' -import debounce from 'lodash/debounce' -import { EditorSync, ERROR_TYPE } from './../EditorSync' -import SyncService from './../services/SyncService' +import { SyncService, ERROR_TYPE } from './../services/SyncService' import { endpointUrl } from './../helpers' +import { createEditor, markdownit } from './../EditorFactory' -import { getVersion } from 'prosemirror-collab' import { defaultMarkdownParser, defaultMarkdownSerializer } from 'prosemirror-markdown' import { Editor, EditorContent, EditorMenuBar, EditorMenuBubble } from 'tiptap' -import { - HardBreak, - Heading, - Bold, - Code, - Link, - BulletList, - OrderedList, - ListItem, - Blockquote, - CodeBlock, - Image, - History, - Collaboration, -} from 'tiptap-extensions' -import { Strong, Italic } from './../marks' +import { Collaboration } from 'tiptap-extensions' import { Keymap } from './../extensions' -import MarkdownIt from 'markdown-it' - +import { getVersion } from 'prosemirror-collab' import Avatar from 'nextcloud-vue/dist/Components/Avatar' -import ReadOnlyEditor from './ReadOnlyEditor' import Tooltip from 'nextcloud-vue/dist/Directives/Tooltip' import Actions from 'nextcloud-vue/dist/Components/Actions' import ActionButton from 'nextcloud-vue/dist/Components/ActionButton' +import ReadOnlyEditor from './ReadOnlyEditor' +import GuestNameDialog from './GuestNameDialog' + const COLLABORATOR_IDLE_TIME = 5 const COLLABORATOR_DISCONNECT_TIME = 20 const EDITOR_PUSH_DEBOUNCE = 200 @@ -153,7 +131,8 @@ export default { EditorContent, EditorMenuBar, EditorMenuBubble, - ActionButton + ActionButton, + GuestNameDialog }, directives: { Tooltip @@ -178,15 +157,16 @@ export default { }, data() { return { - editor: null, tiptap: null, /** @type SyncService */ syncService: null, + document: null, - currentSession: null, sessions: [], + currentSession: null, + filteredSessions: {}, - name: null, + dirty: false, initialLoading: false, lastSavedString: '', @@ -253,12 +233,13 @@ export default { return document.getElementById('isPublic') && document.getElementById('isPublic').value === '1' } }, - mounted() { + beforeMount() { const guestName = localStorage.getItem('text-guestName') if (guestName !== null) { this.guestName = guestName } - + }, + mounted() { if (this.active && (this.hasDocumentParameters) && !this.isPublic) { this.initSession() } @@ -272,7 +253,8 @@ export default { } }, methods: { - setGuestName() { + setGuestName(guestName) { + this.guestName = guestName this.guestNameConfirmed = true localStorage.setItem('text-guestName', this.guestName) this.initSession() @@ -307,34 +289,20 @@ export default { this.updateSessions.bind(this)(sessions); this.document = document this.syncError = null - this.tiptap.setOptions({editable: true && !this.readOnly}) + this.tiptap.setOptions({editable: !this.readOnly}) }) .on('loaded', ({document, session, documentSource}) => { const documentData = {document, session} const initialDocument = defaultMarkdownParser.parse(documentSource) - this.markdownit = MarkdownIt('commonmark', {html: false}); - this.tiptap = new Editor({ - content: this.markdownit.render(documentSource), + this.tiptap = createEditor({ + content: markdownit.render(documentSource), onUpdate: ({state}) => { console.log("=> FROM doc") console.log(defaultMarkdownSerializer.serialize(state.doc)) this.syncService.state = state }, extensions: [ - new HardBreak, - new Heading, - new Code, - new Strong, - new Italic, - new BulletList, - new OrderedList, - new Blockquote, - new CodeBlock, - new ListItem, - new Link, - new Image, - new History(), new Collaboration({ // the initial version we start with // version is an integer which is incremented with every change @@ -523,14 +491,10 @@ export default { #editor-container #editor-wrapper.has-conflicts { height: calc(100% - 50px); - #remote, #editor { + #editor, #read-only-editor { width: 50%; height: 100%; } - - .ProseMirror-menubar { - visibility: hidden; - } } #editor-session-list { @@ -637,39 +601,8 @@ export default { margin: auto; } - .guest-name-dialog { - padding: 30px; - text-align: center; - - form { - display: flex; - width: 100%; - max-width: 200px; - margin: auto; - margin-top: 30px; - - input[type=text] { - flex-grow: 1; - } - } - } - </style> <style lang="scss"> @import './../../css/style'; - - #files-public-content { - width: 100% !important; - height: 100%; - } - - #viewer-content.modal-mask .modal-wrapper .modal-container { - border-radius: 3px !important; - } - - .modal-container #editor-container { - position: absolute; - max-height: calc(100% - 100px); - } - + @import './../../css/prosemirror'; </style> diff --git a/src/components/ReadOnlyEditor.vue b/src/components/ReadOnlyEditor.vue index 47883ace1..6272abdef 100644 --- a/src/components/ReadOnlyEditor.vue +++ b/src/components/ReadOnlyEditor.vue @@ -21,17 +21,16 @@ --> <template> - <div id="remote" ref="remote" /> + <EditorContent id="read-only-editor" v-if="editor" :editor="editor"></EditorContent> </template> <script> -import { EditorState } from 'prosemirror-state' -import { EditorView } from 'prosemirror-view' -import { exampleSetup } from 'prosemirror-example-setup' -import { schema, defaultMarkdownParser } from 'prosemirror-markdown' +import { EditorContent } from 'tiptap' +import {createEditor, markdownit} from '../EditorFactory' export default { name: 'ReadOnlyEditor', + components: {EditorContent}, props: { content: { type: String, @@ -40,34 +39,25 @@ export default { }, data: () => { return { - remoteView: null + editor: null } }, mounted() { - this.initRemoteView() + this.editor = createEditor({ + content: markdownit.render(this.content) + }) + this.editor.setOptions({editable: false}) }, beforeDestroy() { - this.remoteView.destroy() - }, - methods: { - initRemoteView() { - if (this.remoteView) { - return - } - this.remoteView = new EditorView(this.$refs.remote, { - state: EditorState.create({ - doc: defaultMarkdownParser.parse(this.content), - plugins: [ - ...exampleSetup({ schema }) - ] - }) - }) - this.remoteView.setProps({ editable: () => false }) - } + this.editor.destroy() } } </script> -<style scoped> +<style scoped lang="scss"> + + #read-only-editor { + overflow: scroll; + } </style> diff --git a/src/files.js b/src/files.js index b2a70a2d2..829dfefc6 100644 --- a/src/files.js +++ b/src/files.js @@ -20,7 +20,7 @@ * */ -import Editor from './components/Editor' +import Editor from './components/EditorWrapper' import { documentReady } from './helpers' const newFileMenuPlugin = { diff --git a/src/public.js b/src/public.js index dda717a0a..313d84788 100644 --- a/src/public.js +++ b/src/public.js @@ -1,5 +1,5 @@ import Vue from 'vue' -import Editor from './components/Editor' +import Editor from './components/EditorWrapper' import { documentReady } from './helpers' __webpack_nonce__ = btoa(OC.requestToken) // eslint-disable-line diff --git a/src/services/SyncService.js b/src/services/SyncService.js index ab77b0ca9..0207555f2 100644 --- a/src/services/SyncService.js +++ b/src/services/SyncService.js @@ -23,8 +23,7 @@ import axios from 'nextcloud-axios' import PollingBackend from './PollingBackend' import { endpointUrl } from './../helpers' -import {Step} from 'prosemirror-transform'; -import {getVersion, sendableSteps} from 'prosemirror-collab' +import { getVersion, sendableSteps } from 'prosemirror-collab' const defaultOptions = { shareToken: null, @@ -222,4 +221,5 @@ class SyncService { } } -export default SyncService; +export default SyncService +export { SyncService, ERROR_TYPE } |