aboutsummaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorskjnldsv <skjnldsv@protonmail.com>2024-11-07 11:47:09 +0100
committerskjnldsv <skjnldsv@protonmail.com>2024-11-15 11:09:56 +0100
commit99a734636e712e56610bc383b6c43f4aa439d1ff (patch)
treed3fa675aa28c9df1b5e41b672b3e0a68a1b59cb0 /apps
parentd61d62b64f3cb1a22cc322fc747f2af3319280c3 (diff)
downloadnextcloud-server-99a734636e712e56610bc383b6c43f4aa439d1ff.tar.gz
nextcloud-server-99a734636e712e56610bc383b6c43f4aa439d1ff.zip
fix(systemtags): enhance create tag in tag picker UX
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
Diffstat (limited to 'apps')
-rw-r--r--apps/systemtags/src/components/SystemTagPicker.vue78
-rw-r--r--apps/systemtags/src/services/api.ts2
2 files changed, 58 insertions, 22 deletions
diff --git a/apps/systemtags/src/components/SystemTagPicker.vue b/apps/systemtags/src/components/SystemTagPicker.vue
index 07ce186d349..8ed26ce9cb3 100644
--- a/apps/systemtags/src/components/SystemTagPicker.vue
+++ b/apps/systemtags/src/components/SystemTagPicker.vue
@@ -22,22 +22,16 @@
<template v-else>
<!-- Search or create input -->
- <form class="systemtags-picker__create" @submit.stop.prevent="onNewTag">
+ <div class="systemtags-picker__input">
<NcTextField :value.sync="input"
:label="t('systemtags', 'Search or create tag')"
data-cy-systemtags-picker-input>
<TagIcon :size="20" />
</NcTextField>
- <NcButton :disabled="status === Status.CREATING_TAG"
- native-type="submit"
- data-cy-systemtags-picker-input-submit>
- {{ t('systemtags', 'Create tag') }}
- </NcButton>
- </form>
+ </div>
<!-- Tags list -->
- <div v-if="filteredTags.length > 0"
- class="systemtags-picker__tags"
+ <div class="systemtags-picker__tags"
data-cy-systemtags-picker-tags>
<NcCheckboxRadioSwitch v-for="tag in filteredTags"
:key="tag.id"
@@ -46,15 +40,25 @@
:indeterminate="isIndeterminate(tag)"
:disabled="!tag.canAssign"
:data-cy-systemtags-picker-tag="tag.id"
+ class="systemtags-picker__tag"
@update:checked="onCheckUpdate(tag, $event)">
{{ formatTagName(tag) }}
</NcCheckboxRadioSwitch>
+ <NcButton v-if="canCreateTag"
+ :disabled="status === Status.CREATING_TAG"
+ alignment="start"
+ class="systemtags-picker__tag-create"
+ native-type="submit"
+ type="tertiary"
+ data-cy-systemtags-picker-button-create
+ @click="onNewTag">
+ {{ input.trim() }}<br>
+ <span class="systemtags-picker__tag-create-subline">{{ t('systemtags', 'Create new tag') }}</span>
+ <template #icon>
+ <PlusIcon />
+ </template>
+ </NcButton>
</div>
- <NcEmptyContent v-else :name="t('systemtags', 'No tags found')">
- <template #icon>
- <TagIcon />
- </template>
- </NcEmptyContent>
<!-- Note -->
<div class="systemtags-picker__note">
@@ -113,6 +117,7 @@ import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
import TagIcon from 'vue-material-design-icons/Tag.vue'
import CheckIcon from 'vue-material-design-icons/CheckCircle.vue'
+import PlusIcon from 'vue-material-design-icons/Plus.vue'
import { getNodeSystemTags, setNodeSystemTags } from '../utils'
import { createTag, fetchTag, fetchTags, getTagObjects, setTagObjects } from '../services/api'
@@ -143,6 +148,7 @@ export default defineComponent({
NcLoadingIcon,
NcNoteCard,
NcTextField,
+ PlusIcon,
TagIcon,
},
@@ -176,12 +182,17 @@ export default defineComponent({
},
computed: {
+ sortedTags(): TagWithId[] {
+ return [...this.tags]
+ .sort((a, b) => a.displayName.localeCompare(b.displayName, getLanguage(), { ignorePunctuation: true }))
+ },
+
filteredTags(): TagWithId[] {
if (this.input.trim() === '') {
- return this.tags
+ return this.sortedTags
}
- return this.tags
+ return this.sortedTags
.filter(tag => tag.displayName.normalize().includes(this.input.normalize()))
},
@@ -189,6 +200,11 @@ export default defineComponent({
return this.toAdd.length > 0 || this.toRemove.length > 0
},
+ canCreateTag(): boolean {
+ return this.input.trim() !== ''
+ && !this.tags.some(tag => tag.displayName.trim().toLocaleLowerCase() === this.input.trim().toLocaleLowerCase())
+ },
+
statusMessage(): string {
if (this.toAdd.length === 0 && this.toRemove.length === 0) {
// should not happen™
@@ -199,7 +215,7 @@ export default defineComponent({
return n(
'systemtags',
'{tag1} will be set and {tag2} will be removed from 1 file.',
- '{tag1} and {tag2} will be set and removed from {count} files.',
+ '{tag1} will be set and {tag2} will be removed from {count} files.',
this.nodes.length,
{
tag1: this.formatTagChip(this.toAdd[0]),
@@ -368,6 +384,15 @@ export default defineComponent({
// Check the newly created tag
this.onCheckUpdate(tag, true)
+
+ // Scroll to the newly created tag
+ await this.$nextTick()
+ const newTagEl = this.$el.querySelector(`input[type="checkbox"][label="${tag.displayName}"]`)
+ newTagEl?.scrollIntoView({
+ behavior: 'instant',
+ block: 'center',
+ inline: 'center',
+ })
} catch (error) {
showError((error as Error)?.message || t('systemtags', 'Failed to create tag'))
} finally {
@@ -461,22 +486,33 @@ export default defineComponent({
<style scoped lang="scss">
// Common sticky properties
-.systemtags-picker__create,
+.systemtags-picker__input,
.systemtags-picker__note {
position: sticky;
z-index: 9;
background-color: var(--color-main-background);
}
-.systemtags-picker__create {
+.systemtags-picker__input {
display: flex;
top: 0;
gap: 8px;
padding-block-end: 8px;
align-items: flex-end;
+}
- button {
- flex-shrink: 0;
+.systemtags-picker__tags {
+ padding-block: 8px;
+ gap: var(--default-grid-baseline);
+ display: flex;
+ flex-direction: column;
+ .systemtags-picker__tag-create {
+ :deep(span) {
+ text-align: start;
+ }
+ &-subline {
+ font-weight: normal;
+ }
}
}
diff --git a/apps/systemtags/src/services/api.ts b/apps/systemtags/src/services/api.ts
index 4f202e07522..3262ccd3a87 100644
--- a/apps/systemtags/src/services/api.ts
+++ b/apps/systemtags/src/services/api.ts
@@ -46,7 +46,7 @@ export const fetchTag = async (tagId: number): Promise<TagWithId> => {
try {
const { data: tag } = await davClient.stat(path, {
data: fetchTagsPayload,
- details: true
+ details: true,
}) as ResponseDataDetailed<Required<FileStat>>
return parseTags([tag])[0]
} catch (error) {