diff options
author | skjnldsv <skjnldsv@protonmail.com> | 2024-11-07 11:47:09 +0100 |
---|---|---|
committer | skjnldsv <skjnldsv@protonmail.com> | 2024-11-15 11:09:56 +0100 |
commit | 99a734636e712e56610bc383b6c43f4aa439d1ff (patch) | |
tree | d3fa675aa28c9df1b5e41b672b3e0a68a1b59cb0 /apps | |
parent | d61d62b64f3cb1a22cc322fc747f2af3319280c3 (diff) | |
download | nextcloud-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.vue | 78 | ||||
-rw-r--r-- | apps/systemtags/src/services/api.ts | 2 |
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) { |