<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"
: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">
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'
NcLoadingIcon,
NcNoteCard,
NcTextField,
+ PlusIcon,
TagIcon,
},
},
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()))
},
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™
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]),
// 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 {
<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;
+ }
}
}