diff options
author | Vinicius Reis <vinicius.reis@nextcloud.com> | 2022-05-23 23:41:18 +0300 |
---|---|---|
committer | Vinicius Reis <vinicius.reis@nextcloud.com> | 2022-05-23 23:41:18 +0300 |
commit | 783bfa07d27c8f5aa9ba9db1ac0fa4fd4d2d85d0 (patch) | |
tree | 8ac0c4d4e358e7c429d81dc2c94ca3d32c7a0120 /src | |
parent | d0bff88319c276a5511b7b7484c64f7b9ce33773 (diff) |
♻️ (#2345): remove id selectors
Signed-off-by: Vinicius Reis <vinicius.reis@nextcloud.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/components/EditorWrapper.vue | 61 | ||||
-rw-r--r-- | src/components/Menu/Bar.v1.vue | 508 | ||||
-rw-r--r-- | src/components/ViewerComponent.vue | 2 | ||||
-rw-r--r-- | src/views/DirectEditing.vue | 4 | ||||
-rw-r--r-- | src/views/RichWorkspace.vue | 27 |
5 files changed, 38 insertions, 564 deletions
diff --git a/src/components/EditorWrapper.vue b/src/components/EditorWrapper.vue index b6357290f..1a33ea154 100644 --- a/src/components/EditorWrapper.vue +++ b/src/components/EditorWrapper.vue @@ -21,7 +21,7 @@ --> <template> - <div id="editor-container"> + <div id="editor-container" data-text-app="editor-container" class="text-editor"> <div v-if="displayed" class="document-status"> <p v-if="idle" class="msg"> {{ t('text', 'Document idle for {timeout} minutes, click to continue editing', { timeout: IDLE_TIMEOUT }) }} <a class="button primary" @click="reconnect">{{ t('text', 'Reconnect') }}</a> @@ -38,19 +38,22 @@ </div> <div v-if="displayed" id="editor-wrapper" + class="text-editor__wrapper" :class="{ 'has-conflicts': hasSyncCollission, 'icon-loading': !contentLoaded && !hasConnectionIssue, - 'richEditor': isRichEditor, + 'is-rich-workspace': isRichWorkspace, + 'is-rich-editor': isRichEditor, 'show-color-annotations': showAuthorAnnotations }"> <EditorDraggable v-if="$editor" - id="editor"> + id="editor" + class="text-editor__main"> <MenuBar v-if="renderMenus" ref="menubar" :autohide="autohide" :loaded.sync="menubarLoaded"> - <div id="editor-session-list"> + <div class="text-editor__session-list"> <div v-tooltip="lastSavedStatusTooltip" class="save-status" :class="lastSavedStatusClass"> {{ lastSavedStatus }} </div> @@ -60,13 +63,13 @@ </div> <slot name="header" /> </MenuBar> - <div v-if="!menubarLoaded" class="menubar placeholder" /> - <div ref="contentWrapper" class="content-wrapper"> + <div v-if="!menubarLoaded" class="menubar-placeholder" /> + <div ref="contentWrapper" class="content-wrapper text-editor__content-wrapper"> <MenuBubble v-if="renderMenus" :content-wrapper="contentWrapper" :file-path="relativePath" /> <EditorContent v-show="contentLoaded" - class="editor__content" + class="editor__content text-editor__content" :editor="$editor" /> </div> </EditorDraggable> @@ -611,12 +614,12 @@ export default { </script> <style scoped lang="scss"> - .modal-container #editor-container { + .modal-container .text-editor { top: 0; height: calc(100vh - var(--header-height)); } - #editor-container { + .text-editor { display: block; width: 100%; max-width: 100%; @@ -627,7 +630,7 @@ export default { background-color: var(--color-main-background); } - #editor-wrapper { + .text-editor__wrapper { display: flex; width: 100%; height: 100%; @@ -648,13 +651,13 @@ export default { margin-top: 0 !important; } &.icon-loading { - #editor { + .text-editor__main { opacity: 0.3; } } } - #editor, .editor { + .text-editor__main, .editor { background: var(--color-main-background); color: var(--color-main-text); background-clip: padding-box; @@ -707,16 +710,16 @@ export default { } } - #editor-container #editor-wrapper.has-conflicts { + .text-editor .text-editor__wrapper.has-conflicts { height: calc(100% - 50px); - #editor, #read-only-editor { + .text-editor__main, #read-only-editor { width: 50%; height: 100%; } } - #editor-session-list { + .text-editor__session-list { display: flex; input, div { @@ -736,39 +739,27 @@ export default { } #files-public-content { - #editor-container { + .text-editor { top: 0; width: 100%; - #editor::v-deep .menubar { - position: sticky; - top: 0px; - width: 100%; - } - - #editor { + .text-editor__main { overflow: auto; z-index: 20; } - .has-conflicts #editor { + .has-conflicts .text-editor__main { padding-top: 0; } } } .ie { - #editor::v-deep .menubar { - // sticky position is not working as body is our scroll container - position: fixed; - top: 50px; - width: 100%; - } .editor__content::v-deep .ProseMirror { padding-top: 50px; } } - .menubar.placeholder { + .menubar-placeholder { position: fixed; position: -webkit-sticky; position: sticky; @@ -784,10 +775,10 @@ export default { <style lang="scss"> @import './../../css/style'; - #editor-wrapper { + .text-editor__wrapper { @import './../../css/prosemirror'; - &:not(.richEditor) .ProseMirror { + &:not(.is-rich-editor) .ProseMirror { pre { background-color: var(--color-main-background); @@ -848,11 +839,11 @@ export default { } // relative position for the alignment of the menububble - #editor { + .text-editor__main { &.draggedOver { background-color: var(--color-primary-light); } - .content-wrapper { + .text-editor__content-wrapper { position: relative; } } diff --git a/src/components/Menu/Bar.v1.vue b/src/components/Menu/Bar.v1.vue deleted file mode 100644 index 096e33e88..000000000 --- a/src/components/Menu/Bar.v1.vue +++ /dev/null @@ -1,508 +0,0 @@ -<!-- - - @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/>. - - - --> - -<template> - <div class="menubar" :class="{ 'show': isVisible, 'autohide': autohide }"> - <input ref="imageFileInput" - type="file" - accept="image/*" - aria-hidden="true" - class="hidden-visually" - multiple - @change="onImageUploadFilePicked"> - <div v-if="isRichEditor" ref="menubar" class="menubar-icons"> - <template v-for="(icon) in icons"> - <EmojiPicker v-if="icon.class === 'icon-emoji'" - v-show="icon.priority <= iconCount" - :key="icon.label" - class="menuitem-emoji action-menu-emoji" - @selectData="emojiObject => addEmoji(icon, emojiObject)"> - <button v-tooltip="t('text', 'Insert emoji')" - :aria-label="t('text', 'Insert emoji')" - :aria-haspopup="true" - @click="toggleChildMenu(icon)"> - <component :is="icon.icon" /> - </button> - </EmojiPicker> - <Actions v-else-if="icon.class === 'icon-image'" - :key="`action-images-${icon.label}`" - ref="imageActions" - class="submenu action-menu-image" - :title="icon.label" - :aria-label="icon.label" - :aria-haspopup="true" - @open="toggleChildMenu(icon)" - @close="toggleChildMenu(icon)"> - <template #icon> - <component :is="uploadingImages ? 'Loading' : icon.icon" - :title="icon.label" - :aria-label="icon.label" - :aria-haspopup="true" /> - </template> - <ActionButton :close-after-click="true" - :disabled="uploadingImages" - @click="onUploadImage()"> - <template #icon> - <Upload /> - </template> - {{ t('text', 'Upload from computer') }} - </ActionButton> - <ActionButton v-if="!isPublic" - :close-after-click="true" - :disabled="uploadingImages" - @click="showImagePrompt()"> - <template #icon> - <Folder /> - </template> - {{ t('text', 'Insert from Files') }} - </ActionButton> - </Actions> - <button v-else-if="icon.class" - v-show="icon.priority <= iconCount" - :key="`action-${icon.label}`" - v-tooltip="getLabelAndKeys(icon)" - class="action-menu-icon" - :class="getIconClasses(icon)" - :disabled="disabled(icon)" - @click="clickIcon(icon)"> - <component :is="icon.icon" /> - </button> - <template v-else> - <Actions v-show="icon.priority <= iconCount" - :key="icon.label" - :title="icon.label" - class="action-menu-sub"> - <template #icon> - <component :is="icon.icon" /> - </template> - <ActionButton v-for="child in childPopoverMenu(icon.children, icon)" - :key="child.label" - @click="child.action"> - <template #icon> - <component :is="child.icon" /> - </template> - {{ child.label }} - </ActionButton> - </Actions> - </template> - </template> - <Actions class="remaining-actions" - @open="toggleChildMenu({ label: 'Remaining Actions' })" - @close="toggleChildMenu({ label: 'Remaining Actions' })"> - <template v-for="(icon) in icons"> - <ActionButton v-if="icon.class && isHiddenInMenu(icon) && !hasSubmenu(icon)" - :key="`remaining-action-${icon.class}`" - v-tooltip="getKeys(icon)" - :close-after-click="true" - @click="clickIcon(icon)"> - <template #icon> - <component :is="icon.icon" /> - </template> - {{ icon.label }} - </ActionButton> - </template> - </Actions> - </div> - <slot /> - </div> -</template> - -<script> -import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip' -import menuBarIcons from './entries.js' -import isMobile from './../../mixins/isMobile.js' -import { Loading, Folder, Upload } from '../../components/icons.js' -import { useEditorMixin } from '../EditorWrapper.provider.js' - -import Actions from '@nextcloud/vue/dist/Components/Actions' -import ActionButton from '@nextcloud/vue/dist/Components/ActionButton' -import PopoverMenu from '@nextcloud/vue/dist/Components/PopoverMenu' -import EmojiPicker from '@nextcloud/vue/dist/Components/EmojiPicker' -import ClickOutside from 'vue-click-outside' -import { getCurrentUser } from '@nextcloud/auth' -import { subscribe, unsubscribe } from '@nextcloud/event-bus' - -export default { - // eslint-disable-next-line vue/match-component-file-name - name: 'MenuBar', - components: { - ActionButton, - PopoverMenu, - Actions, - EmojiPicker, - Loading, - Folder, - Upload, - }, - directives: { - Tooltip, - ClickOutside, - }, - mixins: [ - isMobile, - useEditorMixin, - ], - props: { - isRichEditor: { - type: Boolean, - default: true, - }, - autohide: { - type: Boolean, - default: false, - }, - isPublic: { - type: Boolean, - default: false, - }, - filePath: { - type: String, - required: false, - default: '', - }, - fileId: { - type: Number, - required: false, - default: 0, - }, - uploadingImages: { - type: Boolean, - default: false, - }, - }, - data: () => { - return { - windowWidth: 0, - windowHeight: 0, - forceRecompute: 0, - submenuVisibility: {}, - lastImagePath: null, - // @deprecated - icons: [...menuBarIcons], - } - }, - computed: { - menuEntries() { - return [...menuBarIcons] - }, - isHiddenInMenu() { - return (icon) => icon.priority > this.iconCount - }, - getIconClasses() { - return (icon) => { - const classes = {} - classes[icon.class] = true - classes['is-active'] = this.isActive(icon) - return classes - } - }, - isActive() { - return ({ isActive }) => { - if (!isActive) { - return false - } - const args = Array.isArray(isActive) ? isActive : [isActive] - return this.$editor.isActive(...args) - } - }, - isVisible() { - return this.$editor.isFocused - || Object.values(this.submenuVisibility).find((v) => v) - }, - disabled() { - return (menuItem) => { - return menuItem.action && !menuItem.action(this.$editor.can()) - } - }, - isChildMenuVisible() { - return (icon) => { - return Object.prototype.hasOwnProperty.call(this.submenuVisibility, icon.label) ? this.submenuVisibility[icon.label] : false - } - }, - childPopoverMenu() { - return (icons, parent) => { - return icons.map(icon => { - return { - label: icon.label, - class: icon.class, - icon: icon.icon, - active: this.isActive(icon), - action: () => { - this.clickIcon(icon) - this.hideChildMenu(parent) - }, - } - }) - } - }, - childIconClasses() { - return (icons) => { - const icon = this.childIcon(icons) - return this.getIconClasses(icon) - } - }, - childIcon() { - return (icons) => icons.find(icon => this.isActive(icon)) || icons[0] - }, - iconCount() { - this.forceRecompute // eslint-disable-line - this.windowWidth // eslint-disable-line - const menuBarWidth = this.$refs.menubar && this.$refs.menubar.clientWidth > 200 - ? this.$refs.menubar.clientWidth - : 200 - // leave some buffer - this is necessary so the bar does not wrap during resizing - const spaceToFill = menuBarWidth - 4 - const slots = Math.floor(spaceToFill / 44) - // Leave one slot empty for the three dot menu - return slots - 1 - }, - imagePath() { - return this.lastImagePath - || this.filePath.split('/').slice(0, -1).join('/') - }, - }, - mounted() { - window.addEventListener('resize', this.getWindowWidth) - subscribe('files:sidebar:opened', this.redrawAfterTransition) - subscribe('files:sidebar:closed', this.redrawAfterTransition) - this.checkInterval = setInterval(() => { - const isWidthAvailable = (this.$refs.menubar && this.$refs.menubar.clientWidth > 0) - if (this.isRichEditor && isWidthAvailable) { - this.redrawMenuBar() - } - if (!this.isRichEditor || isWidthAvailable) { - clearInterval(this.checkInterval) - } - }, 100) - this.$emit('update:loaded', true) - }, - beforeDestroy() { - window.removeEventListener('resize', this.getWindowWidth) - unsubscribe('files:sidebar:opened', this.redrawAfterTransition) - unsubscribe('files:sidebar:closed', this.redrawAfterTransition) - }, - methods: { - redrawAfterTransition() { - // wait for transition to complete (100ms) - setTimeout(this.redrawMenuBar, 110) - }, - redrawMenuBar() { - this.$nextTick(() => { - this.getWindowWidth() - this.forceRecompute++ - }) - }, - refocus() { - this.$editor.chain().focus().run() - }, - clickIcon(icon) { - if (icon.click) { - return icon.click(this) - } - // Some actions run themselves. - // others still need to have .run() called upon them. - const action = icon.action(this.$editor.chain().focus()) - action && action.run() - }, - getWindowWidth(event) { - this.windowWidth = document.documentElement.clientWidth - }, - getWindowHeight(event) { - this.windowHeight = document.documentElement.clientHeight - }, - hideChildMenu({ label }) { - this.$set(this.submenuVisibility, label, false) - }, - hasSubmenu(icon) { - return icon.class === 'icon-emoji' - || icon.children - }, - toggleChildMenu({ label }) { - const lastValue = Object.prototype.hasOwnProperty.call(this.submenuVisibility, label) ? this.submenuVisibility[label] : false - this.$set(this.submenuVisibility, label, !lastValue) - if (lastValue) { - this.refocus() - } - }, - onUploadImage() { - this.$refs.imageFileInput.click() - }, - onImageUploadFilePicked(event) { - this.$emit('image-upload', event.target.files) - // Clear input to ensure that the change event will be emitted if - // the same file is picked again. - event.target.value = '' - }, - showImagePrompt() { - const currentUser = getCurrentUser() - if (!currentUser) { - return - } - OC.dialogs.filepicker(t('text', 'Insert an image'), (filePath) => { - this.$emit('image-insert', filePath) - }, false, [], true, undefined, this.imagePath) - }, - optimalPathTo(targetFile) { - const absolutePath = targetFile.split('/') - const relativePath = this.relativePathTo(targetFile).split('/') - return relativePath.length < absolutePath.length - ? relativePath.join('/') - : targetFile - }, - relativePathTo(targetFile) { - const current = this.filePath.split('/') - const target = targetFile.split('/') - current.pop() // ignore filename - while (current[0] === target[0]) { - current.shift() - target.shift() - } - return current.fill('..').concat(target).join('/') - }, - addEmoji(icon, emojiObject) { - return icon.action(this.$editor.chain(), { id: emojiObject.id, native: emojiObject.native }) - .focus() - .run() - }, - keysString(keyChar, modifiers = []) { - const translations = { - ctrl: t('text', 'Ctrl'), - alt: t('text', 'Alt'), - shift: t('text', 'Shift'), - } - return Object.entries(translations) - .filter(([k, v]) => modifiers.includes(k)) - .map(([k, v]) => v) - .concat(keyChar.toUpperCase()) - .join('+') - }, - getKeys(icon) { - return (icon.keyChar && !this.isMobile) - ? `(${this.keysString(icon.keyChar, icon.keyModifiers)})` - : '' - }, - getLabelAndKeys(icon) { - return [icon.label, this.getKeys(icon)].join(' ') - }, - }, -} -</script> - -<style scoped lang="scss"> - .menubar { - --background-blur: blur(10px); - position: fixed; - position: -webkit-sticky; - position: sticky; - top: 0; - display: flex; - justify-content: flex-end; - z-index: 10021; // above modal-header and menububble so menubar is always on top - background-color: var(--color-main-background-translucent); - -webkit-backdrop-filter: var(--background-blur); - backdrop-filter: var(--background-blur); - max-height: 44px; // important for mobile so that the buttons are always inside the container - padding-top:3px; - padding-bottom: 3px; - - &.autohide { - visibility: hidden; - opacity: 0; - transition: visibility 0.2s 0.4s, opacity 0.2s 0.4s; - &.show { - visibility: visible; - opacity: 1; - } - } - .menubar-icons { - flex-grow: 1; - margin-left: calc((100% - 660px) / 2); - } - @media (max-width: 660px) { - .menubar-icons { - margin-left: 0; - } - } - &::v-deep .action-item__menu ul { - max-height: calc(100vh - 88px); - overflow: scroll; - } - - .remaining-actions ::v-deep .material-design-icon { - height: auto; - width: auto; - } - } - - .menubar 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; - } - - &.icon-undo, - &.icon-redo { - opacity: .8; - - &:disabled { - opacity: .4; - } - } - } - - .menubar .submenu, .menubar .menuitem-emoji { - display: inline-block; - width: 44px; - height: 44px; - position: relative; - vertical-align: top; - } -</style> diff --git a/src/components/ViewerComponent.vue b/src/components/ViewerComponent.vue index 10c48a8e5..1d8533c5c 100644 --- a/src/components/ViewerComponent.vue +++ b/src/components/ViewerComponent.vue @@ -72,7 +72,7 @@ export default { <style lang="scss"> @media only screen and (max-width: 512px) { // on mobile, modal-container has top: 50px - #editor-container { + .text-editor { top: auto; } } diff --git a/src/views/DirectEditing.vue b/src/views/DirectEditing.vue index eac9bd4e1..776236cf8 100644 --- a/src/views/DirectEditing.vue +++ b/src/views/DirectEditing.vue @@ -136,11 +136,11 @@ export default { position: fixed; overflow: hidden; - &::v-deep #editor-container { + &::v-deep .text-editor { height: 100%; top: 0; } - &::v-deep #editor-wrapper div.ProseMirror { + &::v-deep .text-editor__wrapper div.ProseMirror { margin-top: 0; } } diff --git a/src/views/RichWorkspace.vue b/src/views/RichWorkspace.vue index 62fc9e19f..d3ab48a15 100644 --- a/src/views/RichWorkspace.vue +++ b/src/views/RichWorkspace.vue @@ -92,6 +92,7 @@ export default { watch: { focus(newValue) { if (!newValue) { + // TODO: change document.querySelector('#editor').scrollTo(0, 0) } }, @@ -162,18 +163,18 @@ export default { border: none; } - #rich-workspace::v-deep #editor-container { + #rich-workspace::v-deep .text-editor { height: 100%; position: unset !important; top: auto !important; } - #rich-workspace::v-deep #editor-wrapper { + #rich-workspace::v-deep .text-editor__wrapper { position: unset !important; overflow: visible; } - #rich-workspace::v-deep #editor { + #rich-workspace::v-deep .text-editor__main { overflow: visible !important; } @@ -184,21 +185,11 @@ export default { padding-bottom: 60px; /* ensure menububble fits below */ } - #rich-workspace::v-deep #editor-wrapper .ProseMirror { + #rich-workspace::v-deep .text-editor__wrapper .ProseMirror { padding: 0px; margin: 0; } - #rich-workspace::v-deep .menubar { - z-index: 61; - /* Slightly reduce vertical space */ - margin-bottom: -10px; - } - - #rich-workspace::v-deep .menubar .menubar-icons { - margin-left: 0; - } - #rich-workspace::v-deep .editor__content { margin: 0; } @@ -237,16 +228,16 @@ export default { html.ie { #rich-workspace::v-deep { - #editor-container { + .text-editor { position: initial; } - #editor-wrapper { + .text-editor__wrapper { position: relative !important; top: auto !important; } - #editor { + .text-editor__main { display: flex; flex-direction: column; overflow: hidden !important; @@ -260,7 +251,7 @@ export default { top: auto; } - #editor > div:nth-child(2) { + .text-editor__main > div:nth-child(2) { min-height: 44px; overflow-x: hidden; overflow-y: auto; |