Skip to content

Commit b8e3040

Browse files
committed
refactor(systemtags): migrate to new files sidebar API
Signed-off-by: Ferdinand Thiessen <[email protected]>
1 parent 56ec801 commit b8e3040

File tree

9 files changed

+104
-123
lines changed

9 files changed

+104
-123
lines changed

apps/systemtags/src/components/SystemTagPicker.vue

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@
141141
</template>
142142

143143
<script lang="ts">
144-
import type { Node } from '@nextcloud/files'
144+
import type { INode } from '@nextcloud/files'
145145
import type { PropType } from 'vue'
146146
import type { Tag, TagWithId } from '../types.ts'
147147
@@ -216,11 +216,13 @@ export default defineComponent({
216216
217217
props: {
218218
nodes: {
219-
type: Array as PropType<Node[]>,
219+
type: Array as PropType<INode[]>,
220220
required: true,
221221
},
222222
},
223223
224+
emits: ['close'],
225+
224226
setup() {
225227
return {
226228
emit,
@@ -381,7 +383,7 @@ export default defineComponent({
381383
})
382384
383385
// Efficient way of counting tags and their occurrences
384-
this.tagList = this.nodes.reduce((acc: TagListCount, node: Node) => {
386+
this.tagList = this.nodes.reduce((acc: TagListCount, node: INode) => {
385387
const tags = getNodeSystemTags(node) || []
386388
tags.forEach((tag) => {
387389
acc[tag] = (acc[tag] || 0) + 1
@@ -531,7 +533,7 @@ export default defineComponent({
531533
return
532534
}
533535
534-
const nodes = [] as Node[]
536+
const nodes = [] as INode[]
535537
536538
// Update nodes
537539
this.toAdd.forEach((tag) => {

apps/systemtags/src/event-bus.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6-
import type { Node } from '@nextcloud/files'
6+
import type { INode } from '@nextcloud/files'
77
import type { TagWithId } from './types.ts'
88

99
declare module '@nextcloud/event-bus' {
1010
interface NextcloudEvents {
11-
'systemtags:node:updated': Node
11+
'systemtags:node:updated': INode
1212
'systemtags:tag:deleted': TagWithId
1313
'systemtags:tag:updated': TagWithId
1414
'systemtags:tag:created': TagWithId

apps/systemtags/src/files_actions/bulkSystemTagsAction.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6-
import type { Node } from '@nextcloud/files'
6+
import type { INode } from '@nextcloud/files'
77

88
import TagMultipleSvg from '@mdi/svg/svg/tag-multiple-outline.svg?raw'
99
import { FileAction, Permission } from '@nextcloud/files'
@@ -18,7 +18,7 @@ import { defineAsyncComponent } from 'vue'
1818
* @param nodes Nodes to modify tags for
1919
* @param nodes.nodes
2020
*/
21-
async function execBatch({ nodes }: { nodes: Node[] }): Promise<(null | boolean)[]> {
21+
async function execBatch({ nodes }: { nodes: INode[] }): Promise<(null | boolean)[]> {
2222
const response = await new Promise<null | boolean>((resolve) => {
2323
spawnDialog(defineAsyncComponent(() => import('../components/SystemTagPicker.vue')), {
2424
nodes,
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*!
2+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import tagSvg from '@mdi/svg/svg/tag-outline.svg?raw'
7+
import { registerSidebarAction } from '@nextcloud/files'
8+
import { t } from '@nextcloud/l10n'
9+
import { spawnDialog } from '@nextcloud/vue/functions/dialog'
10+
import { defineAsyncComponent } from 'vue'
11+
12+
/**
13+
* Register the "Add tags" action in the file sidebar
14+
*/
15+
export function registerFileSidebarAction() {
16+
registerSidebarAction({
17+
id: 'systemtags',
18+
order: 20,
19+
displayName() {
20+
return t('systemtags', 'Add tags')
21+
},
22+
enabled() {
23+
return true
24+
},
25+
iconSvgInline() {
26+
return tagSvg
27+
},
28+
onClick({ node }) {
29+
return spawnDialog(
30+
defineAsyncComponent(() => import('../components/SystemTagPicker.vue')),
31+
{
32+
nodes: [node],
33+
},
34+
)
35+
},
36+
})
37+
}

apps/systemtags/src/init.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
/**
1+
/*!
22
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
5+
56
import { registerFileAction } from '@nextcloud/files'
67
import { registerDavProperty } from '@nextcloud/files/dav'
78
import { action as bulkSystemTagsAction } from './files_actions/bulkSystemTagsAction.ts'
9+
import { registerFileSidebarAction } from './files_actions/filesSidebarAction.ts'
810
import { action as inlineSystemTagsAction } from './files_actions/inlineSystemTagsAction.ts'
911
import { action as openInFilesAction } from './files_actions/openInFilesAction.ts'
1012
import { registerSystemTagsView } from './files_views/systemtagsView.ts'
@@ -16,6 +18,7 @@ registerFileAction(inlineSystemTagsAction)
1618
registerFileAction(openInFilesAction)
1719

1820
registerSystemTagsView()
21+
registerFileSidebarAction()
1922

2023
document.addEventListener('DOMContentLoaded', () => {
2124
registerHotkeys()

apps/systemtags/src/utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6-
import type { Node } from '@nextcloud/files'
6+
import type { INode } from '@nextcloud/files'
77
import type { DAVResultResponseProps } from 'webdav'
88
import type { BaseTag, ServerTag, Tag, TagWithId } from './types.js'
99

@@ -68,7 +68,7 @@ export function formatTag(initialTag: Tag | ServerTag): ServerTag {
6868
*
6969
* @param node
7070
*/
71-
export function getNodeSystemTags(node: Node): string[] {
71+
export function getNodeSystemTags(node: INode): string[] {
7272
const attribute = node.attributes?.['system-tags']?.['system-tag']
7373
if (attribute === undefined) {
7474
return []
@@ -92,7 +92,7 @@ export function getNodeSystemTags(node: Node): string[] {
9292
* @param node
9393
* @param tags
9494
*/
95-
export function setNodeSystemTags(node: Node, tags: string[]): void {
95+
export function setNodeSystemTags(node: INode, tags: string[]): void {
9696
Vue.set(node.attributes, 'system-tags', {
9797
'system-tag': tags,
9898
})
Lines changed: 10 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
/**
1+
/*
22
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
5+
56
import type { User } from '@nextcloud/e2e-test-server/cypress'
67

78
import { randomBytes } from 'crypto'
8-
import { closeSidebar, getRowForFile, triggerActionForFile } from '../files/FilesUtils.ts'
9+
import { getRowForFile } from '../files/FilesUtils.ts'
10+
import { addTagToFile } from './utils.ts'
911

1012
describe('Systemtags: Files integration', { testIsolation: true }, () => {
1113
let user: User
@@ -21,31 +23,7 @@ describe('Systemtags: Files integration', { testIsolation: true }, () => {
2123

2224
it('See first assigned tag in the file list', () => {
2325
const tag = randomBytes(8).toString('base64')
24-
25-
cy.intercept('PROPFIND', `**/remote.php/dav/files/${user.userId}/file.txt`).as('getNode')
26-
getRowForFile('file.txt').should('be.visible')
27-
triggerActionForFile('file.txt', 'details')
28-
cy.wait('@getNode')
29-
30-
cy.get('[data-cy-sidebar]')
31-
.should('be.visible')
32-
.findByRole('button', { name: 'Actions' })
33-
.should('be.visible')
34-
.click()
35-
36-
cy.findByRole('menuitem', { name: 'Tags' })
37-
.should('be.visible')
38-
.click()
39-
40-
cy.intercept('PUT', '**/remote.php/dav/systemtags-relations/files/**').as('assignTag')
41-
42-
getCollaborativeTagsInput()
43-
.type(`{selectAll}${tag}{enter}`)
44-
cy.wait('@assignTag')
45-
cy.wait('@getNode')
46-
47-
// Close the sidebar and reload to check the file list
48-
closeSidebar()
26+
addTagToFile('file.txt', tag)
4927
cy.reload()
5028

5129
getRowForFile('file.txt')
@@ -58,38 +36,8 @@ describe('Systemtags: Files integration', { testIsolation: true }, () => {
5836
it('See two assigned tags are also shown in the file list', () => {
5937
const tag1 = randomBytes(5).toString('base64')
6038
const tag2 = randomBytes(5).toString('base64')
61-
62-
cy.intercept('PROPFIND', `**/remote.php/dav/files/${user.userId}/file.txt`).as('getNode')
63-
getRowForFile('file.txt').should('be.visible')
64-
triggerActionForFile('file.txt', 'details')
65-
cy.wait('@getNode')
66-
67-
cy.get('[data-cy-sidebar]')
68-
.should('be.visible')
69-
.findByRole('button', { name: 'Actions' })
70-
.should('be.visible')
71-
.click()
72-
73-
cy.findByRole('menuitem', { name: 'Tags' })
74-
.should('be.visible')
75-
.click()
76-
77-
cy.intercept('PUT', '**/remote.php/dav/systemtags-relations/files/**').as('assignTag')
78-
79-
// Assign first tag
80-
getCollaborativeTagsInput()
81-
.type(`{selectAll}${tag1}{enter}`)
82-
cy.wait('@assignTag')
83-
cy.wait('@getNode')
84-
85-
// Assign second tag
86-
getCollaborativeTagsInput()
87-
.type(`{selectAll}${tag2}{enter}`)
88-
cy.wait('@assignTag')
89-
cy.wait('@getNode')
90-
91-
// Close the sidebar and reload to check the file list
92-
closeSidebar()
39+
addTagToFile('file.txt', tag1)
40+
addTagToFile('file.txt', tag2)
9341
cy.reload()
9442

9543
getRowForFile('file.txt')
@@ -104,44 +52,9 @@ describe('Systemtags: Files integration', { testIsolation: true }, () => {
10452
const tag1 = randomBytes(4).toString('base64')
10553
const tag2 = randomBytes(4).toString('base64')
10654
const tag3 = randomBytes(4).toString('base64')
107-
108-
cy.intercept('PROPFIND', `**/remote.php/dav/files/${user.userId}/file.txt`).as('getNode')
109-
getRowForFile('file.txt').should('be.visible')
110-
triggerActionForFile('file.txt', 'details')
111-
cy.wait('@getNode')
112-
113-
cy.get('[data-cy-sidebar]')
114-
.should('be.visible')
115-
.findByRole('button', { name: 'Actions' })
116-
.should('be.visible')
117-
.click()
118-
119-
cy.findByRole('menuitem', { name: 'Tags' })
120-
.should('be.visible')
121-
.click()
122-
123-
cy.intercept('PUT', '**/remote.php/dav/systemtags-relations/files/**').as('assignTag')
124-
125-
// Assign first tag
126-
getCollaborativeTagsInput()
127-
.type(`{selectAll}${tag1}{enter}`)
128-
cy.wait('@assignTag')
129-
cy.wait('@getNode')
130-
131-
// Assign second tag
132-
getCollaborativeTagsInput()
133-
.type(`{selectAll}${tag2}{enter}`)
134-
cy.wait('@assignTag')
135-
cy.wait('@getNode')
136-
137-
// Assign third tag
138-
getCollaborativeTagsInput()
139-
.type(`{selectAll}${tag3}{enter}`)
140-
cy.wait('@assignTag')
141-
cy.wait('@getNode')
142-
143-
// Close the sidebar and reload to check the file list
144-
closeSidebar()
55+
addTagToFile('file.txt', tag1)
56+
addTagToFile('file.txt', tag2)
57+
addTagToFile('file.txt', tag3)
14558
cy.reload()
14659

14760
getRowForFile('file.txt')
@@ -163,10 +76,3 @@ describe('Systemtags: Files integration', { testIsolation: true }, () => {
16376
})
16477
})
16578
})
166-
167-
function getCollaborativeTagsInput(): Cypress.Chainable<JQuery<HTMLElement>> {
168-
return cy.get('[data-cy-sidebar]')
169-
.findByRole('combobox', { name: /collaborative tags/i })
170-
.should('be.visible')
171-
.should('not.have.attr', 'disabled', { timeout: 5000 })
172-
}

cypress/e2e/systemtags/files-sidebar.cy.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { User } from '@nextcloud/e2e-test-server/cypress'
77

88
import { randomBytes } from 'crypto'
99
import { getRowForFile, triggerActionForFile } from '../files/FilesUtils.ts'
10+
import { createNewTagInDialog } from './utils.ts'
1011

1112
describe('Systemtags: Files sidebar integration', { testIsolation: true }, () => {
1213
let user: User
@@ -32,14 +33,9 @@ describe('Systemtags: Files sidebar integration', { testIsolation: true }, () =>
3233
.should('be.visible')
3334
.click()
3435

35-
cy.findByRole('menuitem', { name: 'Tags' })
36+
cy.findByRole('menuitem', { name: 'Add tags' })
3637
.click()
3738

38-
cy.intercept('PUT', '**/remote.php/dav/systemtags-relations/files/**').as('assignTag')
39-
cy.get('[data-cy-sidebar]')
40-
.findByRole('combobox', { name: /collaborative tags/i })
41-
.should('be.visible')
42-
.type(`${tag}{enter}`)
43-
cy.wait('@assignTag')
39+
createNewTagInDialog(tag)
4440
})
4541
})

cypress/e2e/systemtags/utils.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { getRowForFile, triggerActionForFile } from '../files/FilesUtils.ts'
7+
8+
export function addTagToFile(fileName: string, newTag: string): void {
9+
getRowForFile(fileName).should('be.visible')
10+
triggerActionForFile(fileName, 'systemtags:bulk')
11+
12+
createNewTagInDialog(newTag)
13+
}
14+
15+
export function createNewTagInDialog(newTag: string): void {
16+
cy.intercept('POST', '/remote.php/dav/systemtags').as('createTag')
17+
cy.intercept('PROPFIND', '/remote.php/dav/systemtags/*/files').as('getTagData')
18+
cy.intercept('PROPPATCH', '/remote.php/dav/systemtags/*/files').as('assignTagData')
19+
20+
cy.get('[data-cy-systemtags-picker-input]').type(newTag)
21+
22+
cy.get('[data-cy-systemtags-picker-tag]').should('have.length', 0)
23+
cy.get('[data-cy-systemtags-picker-button-create]').should('be.visible')
24+
cy.get('[data-cy-systemtags-picker-button-create]').click()
25+
26+
cy.wait('@createTag')
27+
// Verify the new tag is selected by default
28+
cy.get('[data-cy-systemtags-picker-tag]').contains(newTag)
29+
.parents('[data-cy-systemtags-picker-tag]')
30+
.findByRole('checkbox', { hidden: true }).should('be.checked')
31+
32+
// Apply changes
33+
cy.get('[data-cy-systemtags-picker-button-submit]').click()
34+
35+
cy.wait('@assignTagData')
36+
cy.get('[data-cy-systemtags-picker]').should('not.exist')
37+
}

0 commit comments

Comments
 (0)