2222
2323 <template v-else >
2424 <!-- Search or create input -->
25- <form class =" systemtags-picker__create " @submit.stop.prevent = " onNewTag " >
25+ <div class =" systemtags-picker__input " >
2626 <NcTextField :value.sync =" input"
2727 :label =" t('systemtags', 'Search or create tag')"
2828 data-cy-systemtags-picker-input >
2929 <TagIcon :size =" 20" />
3030 </NcTextField >
31- <NcButton :disabled =" status === Status.CREATING_TAG"
32- native-type =" submit"
33- data-cy-systemtags-picker-input-submit >
34- {{ t('systemtags', 'Create tag') }}
35- </NcButton >
36- </form >
31+ </div >
3732
3833 <!-- Tags list -->
39- <div v-if =" filteredTags.length > 0"
40- class =" systemtags-picker__tags"
34+ <div class =" systemtags-picker__tags"
4135 data-cy-systemtags-picker-tags >
4236 <NcCheckboxRadioSwitch v-for =" tag in filteredTags"
4337 :key =" tag.id"
4640 :indeterminate =" isIndeterminate(tag)"
4741 :disabled =" !tag.canAssign"
4842 :data-cy-systemtags-picker-tag =" tag.id"
43+ class =" systemtags-picker__tag"
4944 @update:checked =" onCheckUpdate(tag, $event)" >
5045 {{ formatTagName(tag) }}
5146 </NcCheckboxRadioSwitch >
47+ <NcButton v-if =" canCreateTag"
48+ :disabled =" status === Status.CREATING_TAG"
49+ alignment =" start"
50+ class =" systemtags-picker__tag-create"
51+ native-type =" submit"
52+ type =" tertiary"
53+ data-cy-systemtags-picker-button-create
54+ @click =" onNewTag" >
55+ {{ input.trim() }}<br >
56+ <span class =" systemtags-picker__tag-create-subline" >{{ t('systemtags', 'Create new tag') }}</span >
57+ <template #icon >
58+ <PlusIcon />
59+ </template >
60+ </NcButton >
5261 </div >
53- <NcEmptyContent v-else :name =" t('systemtags', 'No tags found')" >
54- <template #icon >
55- <TagIcon />
56- </template >
57- </NcEmptyContent >
5862
5963 <!-- Note -->
6064 <div class =" systemtags-picker__note" >
@@ -113,6 +117,7 @@ import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'
113117import NcTextField from ' @nextcloud/vue/dist/Components/NcTextField.js'
114118import TagIcon from ' vue-material-design-icons/Tag.vue'
115119import CheckIcon from ' vue-material-design-icons/CheckCircle.vue'
120+ import PlusIcon from ' vue-material-design-icons/Plus.vue'
116121
117122import { getNodeSystemTags , setNodeSystemTags } from ' ../utils'
118123import { createTag , fetchTag , fetchTags , getTagObjects , setTagObjects } from ' ../services/api'
@@ -143,6 +148,7 @@ export default defineComponent({
143148 NcLoadingIcon ,
144149 NcNoteCard ,
145150 NcTextField ,
151+ PlusIcon ,
146152 TagIcon ,
147153 },
148154
@@ -176,19 +182,29 @@ export default defineComponent({
176182 },
177183
178184 computed: {
185+ sortedTags(): TagWithId [] {
186+ return [... this .tags ]
187+ .sort ((a , b ) => a .displayName .localeCompare (b .displayName , getLanguage (), { ignorePunctuation: true }))
188+ },
189+
179190 filteredTags(): TagWithId [] {
180191 if (this .input .trim () === ' ' ) {
181- return this .tags
192+ return this .sortedTags
182193 }
183194
184- return this .tags
195+ return this .sortedTags
185196 .filter (tag => tag .displayName .normalize ().includes (this .input .normalize ()))
186197 },
187198
188199 hasChanges(): boolean {
189200 return this .toAdd .length > 0 || this .toRemove .length > 0
190201 },
191202
203+ canCreateTag(): boolean {
204+ return this .input .trim () !== ' '
205+ && ! this .tags .some (tag => tag .displayName .trim ().toLocaleLowerCase () === this .input .trim ().toLocaleLowerCase ())
206+ },
207+
192208 statusMessage(): string {
193209 if (this .toAdd .length === 0 && this .toRemove .length === 0 ) {
194210 // should not happen™
@@ -199,7 +215,7 @@ export default defineComponent({
199215 return n (
200216 ' systemtags' ,
201217 ' {tag1} will be set and {tag2} will be removed from 1 file.' ,
202- ' {tag1} and {tag2} will be set and removed from {count} files.' ,
218+ ' {tag1} will be set and {tag2} will be removed from {count} files.' ,
203219 this .nodes .length ,
204220 {
205221 tag1: this .formatTagChip (this .toAdd [0 ]),
@@ -368,6 +384,15 @@ export default defineComponent({
368384
369385 // Check the newly created tag
370386 this .onCheckUpdate (tag , true )
387+
388+ // Scroll to the newly created tag
389+ await this .$nextTick ()
390+ const newTagEl = this .$el .querySelector (` input[type="checkbox"][label="${tag .displayName }"] ` )
391+ newTagEl ?.scrollIntoView ({
392+ behavior: ' instant' ,
393+ block: ' center' ,
394+ inline: ' center' ,
395+ })
371396 } catch (error ) {
372397 showError ((error as Error )?.message || t (' systemtags' , ' Failed to create tag' ))
373398 } finally {
@@ -461,22 +486,33 @@ export default defineComponent({
461486
462487<style scoped lang="scss">
463488// Common sticky properties
464- .systemtags-picker__create ,
489+ .systemtags-picker__input ,
465490.systemtags-picker__note {
466491 position : sticky ;
467492 z-index : 9 ;
468493 background-color : var (--color-main-background );
469494}
470495
471- .systemtags-picker__create {
496+ .systemtags-picker__input {
472497 display : flex ;
473498 top : 0 ;
474499 gap : 8px ;
475500 padding-block-end : 8px ;
476501 align-items : flex-end ;
502+ }
477503
478- button {
479- flex-shrink : 0 ;
504+ .systemtags-picker__tags {
505+ padding-block : 8px ;
506+ gap : var (--default-grid-baseline );
507+ display : flex ;
508+ flex-direction : column ;
509+ .systemtags-picker__tag-create {
510+ :deep (span ) {
511+ text-align : start ;
512+ }
513+ & -subline {
514+ font-weight : normal ;
515+ }
480516 }
481517}
482518
0 commit comments