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-21 01:31:58 +0300
committerVinicius Reis <vinicius.reis@nextcloud.com>2022-05-21 01:31:58 +0300
commit8528429d1e3fcfb371fbd89cd4add3192798216c (patch)
treec70d972c30fdc09decb04b73114f516a1286d020 /src
parent3b8aaec4b1f04507e1899386f97f30947fde5f4f (diff)
✨ (#2345): add ActionList
improve style/ux Signed-off-by: Vinicius Reis <vinicius.reis@nextcloud.com>
Diffstat (limited to 'src')
-rw-r--r--src/components/Menu/ActionEntry.js15
-rw-r--r--src/components/Menu/ActionEntry.mixin.js2
-rw-r--r--src/components/Menu/ActionEntry.scss89
-rw-r--r--src/components/Menu/ActionImageUpload.vue4
-rw-r--r--src/components/Menu/ActionList.vue84
-rw-r--r--src/components/Menu/ActionSingle.vue98
-rw-r--r--src/components/Menu/Bar.vue14
-rw-r--r--src/components/Menu/EmojiPickerAction.vue5
-rw-r--r--src/components/Menu/entries.js2
-rw-r--r--src/components/Menu/utils.js3
10 files changed, 257 insertions, 59 deletions
diff --git a/src/components/Menu/ActionEntry.js b/src/components/Menu/ActionEntry.js
index 1d20a6a9b..8f9136c2b 100644
--- a/src/components/Menu/ActionEntry.js
+++ b/src/components/Menu/ActionEntry.js
@@ -20,18 +20,27 @@
*
*/
-import SingleAction from './ActionSingle.vue'
+import ActionSingle from './ActionSingle.vue'
+import ActionList from './ActionList.vue'
export default {
name: 'ActionEntry',
functional: true,
render(h, ctx) {
const { actionEntry } = ctx.props
+ const { key } = ctx.data
+
+ const params = {
+ ...ctx,
+ key,
+ }
if (actionEntry.component) {
- return h(actionEntry.component, ctx)
+ return h(actionEntry.component, params)
}
- return h(SingleAction, ctx)
+ return actionEntry.children
+ ? h(ActionList, params)
+ : h(ActionSingle, params)
},
}
diff --git a/src/components/Menu/ActionEntry.mixin.js b/src/components/Menu/ActionEntry.mixin.js
index f3fb75818..b334c2115 100644
--- a/src/components/Menu/ActionEntry.mixin.js
+++ b/src/components/Menu/ActionEntry.mixin.js
@@ -28,6 +28,8 @@ import debounce from 'debounce'
import { useEditorMixin, useIsMobileMixin } from '../EditorWrapper.provider.js'
import { getActionState, getKeys } from './utils.js'
+import './ActionEntry.scss'
+
/**
* @type {import("vue").ComponentOptions} BaseActionEntry
*/
diff --git a/src/components/Menu/ActionEntry.scss b/src/components/Menu/ActionEntry.scss
index d69baf1d0..77de40478 100644
--- a/src/components/Menu/ActionEntry.scss
+++ b/src/components/Menu/ActionEntry.scss
@@ -1,40 +1,67 @@
-button.entry-action,
-.entry-action > button,
-.entry-action > div > button {
- position: relative;
- width: 44px;
+.text__is-active-item-btn {
+ opacity: 1;
+
+ &::before {
+ transform: translateX(-50%);
+ border-radius: 100%;
+ position: absolute;
+ background: var(--color-primary-element);
+ bottom: 3px;
+ height: 6px;
+ width: 6px;
+ content: "";
+ left: 50%;
+ }
+}
+
+.text-menubar, .popover {
+ button.entry-action__button {
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);
- }
+ opacity: 0.5;
+ position: relative;
+ color: var(--color-main-text);
+ background-color: transparent;
+ vertical-align: top;
+ padding: 0;
- &.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%;
+ p {
+ padding: 0;
+ }
+ &:is(li.entry-action-item button) {
+ padding: 0 0.5em 0 0;
}
- &.is-active,
- &:hover,
- &:focus {
- opacity: 1;
+ &:not(li.entry-action-item button) {
+ width: 44px;
}
+
+ &:hover,
+ &:focus,
+ &:active {
+ background-color: var(--color-background-dark);
+ }
+
+ &:hover,
+ &:focus {
+ opacity: 1;
+ }
+
+ &.is-active {
+ @extend .text__is-active-item-btn;
+ }
+ }
+
+ .entry-action.is-active:not(.entry-action-item) {
+ @extend .text__is-active-item-btn;
+ }
+
+ .entry-action.entry-action-item {
+ &.is-active {
+ background-color: var(--color-primary-light);
+ box-shadow: inset 2px 0 var(--color-primary);
+ }
+ }
}
diff --git a/src/components/Menu/ActionImageUpload.vue b/src/components/Menu/ActionImageUpload.vue
index f2c72ccd3..25a337ef9 100644
--- a/src/components/Menu/ActionImageUpload.vue
+++ b/src/components/Menu/ActionImageUpload.vue
@@ -20,7 +20,7 @@
-
-->
<template>
- <Actions class="entry-image-upload-action entry-action"
+ <Actions class="entry-action entry-action__image-upload"
:data-action-entry="actionEntry._key"
:title="actionEntry.label"
:aria-label="actionEntry.label"
@@ -33,6 +33,7 @@
</template>
<ActionButton close-after-click
:disabled="$isUploadingImages"
+ :data-action-entry="`${actionEntry._key}-upload`"
@click="$callChooseLocalImage">
<template #icon>
<Upload />
@@ -42,6 +43,7 @@
<ActionButton v-if="!$isPublic"
close-after-click
:disabled="$isUploadingImages"
+ :data-action-entry="`${actionEntry._key}-insert`"
@click="$callImagePrompt">
<template #icon>
<Folder />
diff --git a/src/components/Menu/ActionList.vue b/src/components/Menu/ActionList.vue
new file mode 100644
index 000000000..79798a89b
--- /dev/null
+++ b/src/components/Menu/ActionList.vue
@@ -0,0 +1,84 @@
+<!--
+ - @copyright Copyright (c) 2022 Vinicius Reis <vinicius@nextcloud.com>
+ -
+ - @author Vinicius Reis <vinicius@nextcloud.com>
+ -
+ - @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>
+ <Actions v-tooltip="tooltip"
+ class="entry-list-action entry-action"
+ v-bind="state"
+ :title="actionEntry.label"
+ :data-action-entry="actionEntry._key"
+ v-on="$listeners">
+ <template #icon>
+ <component :is="icon" />
+ </template>
+ <ActionSingle v-for="child in actionEntry.children"
+ :key="`child-${child._key}`"
+ is-item
+ :action-entry="child" />
+ </Actions>
+</template>
+
+<script>
+import Actions from '@nextcloud/vue/dist/Components/Actions'
+import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
+import { BaseActionEntry } from './ActionEntry.mixin.js'
+import ActionSingle from './ActionSingle.vue'
+import { getIsActive } from './utils.js'
+
+export default {
+ name: 'ActionList',
+ components: { Actions, ActionButton, ActionSingle },
+ extends: BaseActionEntry,
+ data: () => ({
+ activeChild: null,
+ }),
+ computed: {
+ currentChind() {
+ const {
+ state,
+ $editor,
+ actionEntry: { children },
+ } = this
+
+ if (!state.active) {
+ return null
+ }
+
+ return children.find(child => {
+ return getIsActive(child, $editor)
+ })
+ },
+ icon() {
+ if (this.currentChind) {
+ return this.currentChind.icon
+ }
+
+ return this.actionEntry.icon
+ },
+ },
+ methods: {
+ runAction() {
+
+ },
+ },
+}
+</script>
diff --git a/src/components/Menu/ActionSingle.vue b/src/components/Menu/ActionSingle.vue
index d9e68d8ac..5e96c8558 100644
--- a/src/components/Menu/ActionSingle.vue
+++ b/src/components/Menu/ActionSingle.vue
@@ -20,24 +20,47 @@
-
-->
-<template>
- <button v-tooltip="tooltip"
- class="entry-single-action entry-action"
- v-bind="state"
- :title="actionEntry.label"
- :data-action-entry="actionEntry._key"
- v-on="$listeners"
- @click="runAction">
- <component :is="icon" />
- </button>
-</template>
-
<script>
+import Button from '@nextcloud/vue/dist/Components/Button'
+import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
import { BaseActionEntry } from './ActionEntry.mixin.js'
export default {
name: 'ActionSingle',
extends: BaseActionEntry,
+ props: {
+ isItem: {
+ type: Boolean,
+ default: false,
+ },
+ },
+ computed: {
+ component() {
+ // Button and ActionButton shares styles and behaviours
+ // to keep it simple, this component handle the small differences
+ return this.isItem
+ ? ActionButton
+ : Button
+ },
+ bindState() {
+ const state = {
+ ...this.state,
+ }
+
+ state.class = {
+ ...state.class,
+ // inject a extra class
+ 'entry-action-item': this.isItem,
+ }
+
+ // item list bejaviour
+ if (this.isItem) {
+ state.closeAfterClick = true
+ }
+
+ return state
+ },
+ },
methods: {
runAction() {
const { actionEntry } = this
@@ -50,7 +73,54 @@ export default {
actionEntry.action(this.$editor.chain().focus())?.run()
},
},
+
+ render(h) {
+ const {
+ $listeners,
+ actionEntry,
+ bindState,
+ component,
+ icon,
+ isItem,
+ runAction,
+ tooltip,
+ } = this
+
+ const { class: classes, ...attrs } = bindState
+
+ // do not use tooltip if is a item of action list
+ const directives = isItem
+ ? []
+ : [{
+ name: 'tooltip',
+ value: tooltip,
+ }]
+
+ const children = [h(icon, { slot: 'icon' })]
+
+ // do not use title if is a item of action list
+ const title = isItem ? undefined : actionEntry.label
+
+ if (isItem) {
+ // add label
+ children.push(actionEntry.label)
+ }
+
+ return h(component, {
+ directives,
+ staticClass: 'entry-single-action entry-action',
+ class: classes,
+ attrs: {
+ title,
+ type: 'tertiary',
+ 'data-action-entry': actionEntry._key,
+ ...attrs,
+ },
+ on: {
+ ...$listeners,
+ click: runAction,
+ },
+ }, children)
+ },
}
</script>
-
-<style scoped lang="scss" src="./ActionEntry.scss" />
diff --git a/src/components/Menu/Bar.vue b/src/components/Menu/Bar.vue
index 4d965256d..041051d8c 100644
--- a/src/components/Menu/Bar.vue
+++ b/src/components/Menu/Bar.vue
@@ -22,11 +22,11 @@
-->
<template>
- <div class="menubar" :class="{ 'show': isVisible, 'autohide': autohide }">
- <div v-if="$isRichEditor" ref="menubar" class="menubar-entries">
+ <div class="text-menubar" :class="{ 'show': isVisible, 'text-menubar--autohide': autohide }">
+ <div v-if="$isRichEditor" ref="menubar" class="text-menubar__entries">
<ActionEntry v-for="actionEntry of visibleEntries"
v-bind="{ actionEntry }"
- :key="`single-action-${actionEntry._key}`" />
+ :key="`text-action--${actionEntry._key}`" />
</div>
</div>
</template>
@@ -137,7 +137,7 @@ export default {
</script>
<style scoped lang="scss">
- .menubar {
+ .text-menubar {
--background-blur: blur(10px);
position: sticky;
top: 0;
@@ -148,7 +148,7 @@ export default {
padding-top:3px;
padding-bottom: 3px;
- &.autohide {
+ &.text-menubar--autohide {
visibility: hidden;
opacity: 0;
transition: visibility 0.2s 0.4s, opacity 0.2s 0.4s;
@@ -157,12 +157,12 @@ export default {
opacity: 1;
}
}
- .menubar-entries {
+ .text-menubar__entries {
display: flex;
justify-content: flex-start;
}
@media (max-width: 660px) {
- .menubar-entries {
+ .text-menubar__entries {
margin-left: 0;
}
}
diff --git a/src/components/Menu/EmojiPickerAction.vue b/src/components/Menu/EmojiPickerAction.vue
index eaa4f29b7..044300cdc 100644
--- a/src/components/Menu/EmojiPickerAction.vue
+++ b/src/components/Menu/EmojiPickerAction.vue
@@ -20,10 +20,11 @@
-
-->
<template>
- <EmojiPicker class="entry-emoji-action entry-action"
+ <EmojiPicker class="entry-action entry-action__emoji"
:data-action-entry="actionEntry._key"
@selectData="addEmoji">
<button v-tooltip="actionEntry.label"
+ class="entry-action__button"
:title="actionEntry.label"
:aria-label="actionEntry.label"
:aria-haspopup="true">
@@ -55,5 +56,3 @@ export default {
},
}
</script>
-
-<style scoped lang="scss" src="./ActionEntry.scss" />
diff --git a/src/components/Menu/entries.js b/src/components/Menu/entries.js
index 35f89b313..0a39e1d49 100644
--- a/src/components/Menu/entries.js
+++ b/src/components/Menu/entries.js
@@ -130,6 +130,7 @@ export default [
keyModifiers: ['ctrl', 'shift'],
visible: false,
icon: FormatHeader1,
+ isActive: 'heading',
children: [
{
_key: 'headings-01',
@@ -247,6 +248,7 @@ export default [
label: t('text', 'Callouts'),
visible: false,
icon: Info,
+ isActive: 'callout',
children: [
{
_key: 'info',
diff --git a/src/components/Menu/utils.js b/src/components/Menu/utils.js
index 896230fc8..2b3f85887 100644
--- a/src/components/Menu/utils.js
+++ b/src/components/Menu/utils.js
@@ -67,13 +67,16 @@ const getActionState = (actionEntry, $editor) => {
const active = getIsActive(actionEntry, $editor)
return {
+ ariaLabel: actionEntry.label,
disabled: isDisabled(actionEntry, $editor),
class: getEntryClasses(actionEntry, active),
+ active,
}
}
export {
isDisabled,
+ getIsActive,
getKeys,
getEntryClasses,
getActionState,