Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/text.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorVinicius Reis <vinicius.reis@nextcloud.com>2022-05-23 23:41:18 +0300
committerVinicius Reis <vinicius.reis@nextcloud.com>2022-05-23 23:41:18 +0300
commit783bfa07d27c8f5aa9ba9db1ac0fa4fd4d2d85d0 (patch)
tree8ac0c4d4e358e7c429d81dc2c94ca3d32c7a0120 /src
parentd0bff88319c276a5511b7b7484c64f7b9ce33773 (diff)
♻️ (#2345): remove id selectors
Signed-off-by: Vinicius Reis <vinicius.reis@nextcloud.com>
Diffstat (limited to 'src')
-rw-r--r--src/components/EditorWrapper.vue61
-rw-r--r--src/components/Menu/Bar.v1.vue508
-rw-r--r--src/components/ViewerComponent.vue2
-rw-r--r--src/views/DirectEditing.vue4
-rw-r--r--src/views/RichWorkspace.vue27
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;