Skip to content

Commit 1436075

Browse files
authored
Merge pull request #49129 from nextcloud/feat/systemtags-bulk-create-list
2 parents adc182c + c6c1da0 commit 1436075

File tree

14 files changed

+71
-32
lines changed

14 files changed

+71
-32
lines changed

apps/systemtags/src/components/SystemTagPicker.vue

Lines changed: 57 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,16 @@
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"
@@ -46,15 +40,25 @@
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'
113117
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
114118
import TagIcon from 'vue-material-design-icons/Tag.vue'
115119
import CheckIcon from 'vue-material-design-icons/CheckCircle.vue'
120+
import PlusIcon from 'vue-material-design-icons/Plus.vue'
116121
117122
import { getNodeSystemTags, setNodeSystemTags } from '../utils'
118123
import { 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

apps/systemtags/src/services/api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export const fetchTag = async (tagId: number): Promise<TagWithId> => {
4646
try {
4747
const { data: tag } = await davClient.stat(path, {
4848
data: fetchTagsPayload,
49-
details: true
49+
details: true,
5050
}) as ResponseDataDetailed<Required<FileStat>>
5151
return parseTags([tag])[0]
5252
} catch (error) {

cypress/e2e/systemtags/files-bulk-action.cy.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,10 @@ describe('Systemtags: Files bulk action', { testIsolation: false }, () => {
326326

327327
const newTag = randomBytes(8).toString('base64').slice(0, 6)
328328
cy.get('[data-cy-systemtags-picker-input]').type(newTag)
329-
cy.get('[data-cy-systemtags-picker-input-submit]').click()
329+
330+
cy.get('[data-cy-systemtags-picker-tag]').should('have.length', 0)
331+
cy.get('[data-cy-systemtags-picker-button-create]').should('be.visible')
332+
cy.get('[data-cy-systemtags-picker-button-create]').click()
330333

331334
cy.wait('@createTag')
332335
cy.get('[data-cy-systemtags-picker-tag]').should('have.length', 6)

dist/3245-3245.js

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/3245-3245.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/3245-3245.js.map.license

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3245-3245.js.license

0 commit comments

Comments
 (0)