mirror of
https://github.com/nextcloud/server.git
synced 2026-02-03 20:41:22 -05:00
refactor(systemtags): migrate to new files sidebar API
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
parent
f9a137ea87
commit
3726596ad0
10 changed files with 110 additions and 159 deletions
|
|
@ -141,7 +141,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { Node } from '@nextcloud/files'
|
||||
import type { INode } from '@nextcloud/files'
|
||||
import type { PropType } from 'vue'
|
||||
import type { Tag, TagWithId } from '../types.ts'
|
||||
|
||||
|
|
@ -216,11 +216,13 @@ export default defineComponent({
|
|||
|
||||
props: {
|
||||
nodes: {
|
||||
type: Array as PropType<Node[]>,
|
||||
type: Array as PropType<INode[]>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
emits: ['close'],
|
||||
|
||||
setup() {
|
||||
return {
|
||||
emit,
|
||||
|
|
@ -381,7 +383,7 @@ export default defineComponent({
|
|||
})
|
||||
|
||||
// Efficient way of counting tags and their occurrences
|
||||
this.tagList = this.nodes.reduce((acc: TagListCount, node: Node) => {
|
||||
this.tagList = this.nodes.reduce((acc: TagListCount, node: INode) => {
|
||||
const tags = getNodeSystemTags(node) || []
|
||||
tags.forEach((tag) => {
|
||||
acc[tag] = (acc[tag] || 0) + 1
|
||||
|
|
@ -531,7 +533,7 @@ export default defineComponent({
|
|||
return
|
||||
}
|
||||
|
||||
const nodes = [] as Node[]
|
||||
const nodes = [] as INode[]
|
||||
|
||||
// Update nodes
|
||||
this.toAdd.forEach((tag) => {
|
||||
|
|
|
|||
4
apps/systemtags/src/event-bus.d.ts
vendored
4
apps/systemtags/src/event-bus.d.ts
vendored
|
|
@ -3,12 +3,12 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { Node } from '@nextcloud/files'
|
||||
import type { INode } from '@nextcloud/files'
|
||||
import type { TagWithId } from './types.ts'
|
||||
|
||||
declare module '@nextcloud/event-bus' {
|
||||
interface NextcloudEvents {
|
||||
'systemtags:node:updated': Node
|
||||
'systemtags:node:updated': INode
|
||||
'systemtags:tag:deleted': TagWithId
|
||||
'systemtags:tag:updated': TagWithId
|
||||
'systemtags:tag:created': TagWithId
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { Node } from '@nextcloud/files'
|
||||
import type { INode } from '@nextcloud/files'
|
||||
|
||||
import TagMultipleSvg from '@mdi/svg/svg/tag-multiple-outline.svg?raw'
|
||||
import { FileAction, Permission } from '@nextcloud/files'
|
||||
|
|
@ -18,7 +18,7 @@ import { defineAsyncComponent } from 'vue'
|
|||
* @param nodes Nodes to modify tags for
|
||||
* @param nodes.nodes
|
||||
*/
|
||||
async function execBatch({ nodes }: { nodes: Node[] }): Promise<(null | boolean)[]> {
|
||||
async function execBatch({ nodes }: { nodes: INode[] }): Promise<(null | boolean)[]> {
|
||||
const response = await new Promise<null | boolean>((resolve) => {
|
||||
spawnDialog(defineAsyncComponent(() => import('../components/SystemTagPicker.vue')), {
|
||||
nodes,
|
||||
|
|
|
|||
37
apps/systemtags/src/files_actions/filesSidebarAction.ts
Normal file
37
apps/systemtags/src/files_actions/filesSidebarAction.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import tagSvg from '@mdi/svg/svg/tag-outline.svg?raw'
|
||||
import { registerSidebarAction } from '@nextcloud/files'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { spawnDialog } from '@nextcloud/vue/functions/dialog'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
/**
|
||||
* Register the "Add tags" action in the file sidebar
|
||||
*/
|
||||
export function registerFileSidebarAction() {
|
||||
registerSidebarAction({
|
||||
id: 'systemtags',
|
||||
order: 20,
|
||||
displayName() {
|
||||
return t('systemtags', 'Add tags')
|
||||
},
|
||||
enabled() {
|
||||
return true
|
||||
},
|
||||
iconSvgInline() {
|
||||
return tagSvg
|
||||
},
|
||||
onClick({ node }) {
|
||||
return spawnDialog(
|
||||
defineAsyncComponent(() => import('../components/SystemTagPicker.vue')),
|
||||
{
|
||||
nodes: [node],
|
||||
},
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
/**
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { registerFileAction } from '@nextcloud/files'
|
||||
import { registerDavProperty } from '@nextcloud/files/dav'
|
||||
import { action as bulkSystemTagsAction } from './files_actions/bulkSystemTagsAction.ts'
|
||||
import { registerFileSidebarAction } from './files_actions/filesSidebarAction.ts'
|
||||
import { action as inlineSystemTagsAction } from './files_actions/inlineSystemTagsAction.ts'
|
||||
import { action as openInFilesAction } from './files_actions/openInFilesAction.ts'
|
||||
import { registerSystemTagsView } from './files_views/systemtagsView.ts'
|
||||
|
|
@ -16,6 +18,7 @@ registerFileAction(inlineSystemTagsAction)
|
|||
registerFileAction(openInFilesAction)
|
||||
|
||||
registerSystemTagsView()
|
||||
registerFileSidebarAction()
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
registerHotkeys()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { Node } from '@nextcloud/files'
|
||||
import type { INode } from '@nextcloud/files'
|
||||
import type { DAVResultResponseProps } from 'webdav'
|
||||
import type { BaseTag, ServerTag, Tag, TagWithId } from './types.js'
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ export function formatTag(initialTag: Tag | ServerTag): ServerTag {
|
|||
*
|
||||
* @param node
|
||||
*/
|
||||
export function getNodeSystemTags(node: Node): string[] {
|
||||
export function getNodeSystemTags(node: INode): string[] {
|
||||
const attribute = node.attributes?.['system-tags']?.['system-tag']
|
||||
if (attribute === undefined) {
|
||||
return []
|
||||
|
|
@ -92,7 +92,7 @@ export function getNodeSystemTags(node: Node): string[] {
|
|||
* @param node
|
||||
* @param tags
|
||||
*/
|
||||
export function setNodeSystemTags(node: Node, tags: string[]): void {
|
||||
export function setNodeSystemTags(node: INode, tags: string[]): void {
|
||||
Vue.set(node.attributes, 'system-tags', {
|
||||
'system-tag': tags,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
/**
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { User } from '@nextcloud/e2e-test-server/cypress'
|
||||
|
||||
import { randomBytes } from 'crypto'
|
||||
import { closeSidebar, getRowForFile, triggerActionForFile } from '../files/FilesUtils.ts'
|
||||
import { getRowForFile } from '../files/FilesUtils.ts'
|
||||
import { addTagToFile } from './utils.ts'
|
||||
|
||||
describe('Systemtags: Files integration', { testIsolation: true }, () => {
|
||||
let user: User
|
||||
|
|
@ -21,31 +23,7 @@ describe('Systemtags: Files integration', { testIsolation: true }, () => {
|
|||
|
||||
it('See first assigned tag in the file list', () => {
|
||||
const tag = randomBytes(8).toString('base64')
|
||||
|
||||
cy.intercept('PROPFIND', `**/remote.php/dav/files/${user.userId}/file.txt`).as('getNode')
|
||||
getRowForFile('file.txt').should('be.visible')
|
||||
triggerActionForFile('file.txt', 'details')
|
||||
cy.wait('@getNode')
|
||||
|
||||
cy.get('[data-cy-sidebar]')
|
||||
.should('be.visible')
|
||||
.findByRole('button', { name: 'Actions' })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
cy.findByRole('menuitem', { name: 'Tags' })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
cy.intercept('PUT', '**/remote.php/dav/systemtags-relations/files/**').as('assignTag')
|
||||
|
||||
getCollaborativeTagsInput()
|
||||
.type(`{selectAll}${tag}{enter}`)
|
||||
cy.wait('@assignTag')
|
||||
cy.wait('@getNode')
|
||||
|
||||
// Close the sidebar and reload to check the file list
|
||||
closeSidebar()
|
||||
addTagToFile('file.txt', tag)
|
||||
cy.reload()
|
||||
|
||||
getRowForFile('file.txt')
|
||||
|
|
@ -58,38 +36,8 @@ describe('Systemtags: Files integration', { testIsolation: true }, () => {
|
|||
it('See two assigned tags are also shown in the file list', () => {
|
||||
const tag1 = randomBytes(5).toString('base64')
|
||||
const tag2 = randomBytes(5).toString('base64')
|
||||
|
||||
cy.intercept('PROPFIND', `**/remote.php/dav/files/${user.userId}/file.txt`).as('getNode')
|
||||
getRowForFile('file.txt').should('be.visible')
|
||||
triggerActionForFile('file.txt', 'details')
|
||||
cy.wait('@getNode')
|
||||
|
||||
cy.get('[data-cy-sidebar]')
|
||||
.should('be.visible')
|
||||
.findByRole('button', { name: 'Actions' })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
cy.findByRole('menuitem', { name: 'Tags' })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
cy.intercept('PUT', '**/remote.php/dav/systemtags-relations/files/**').as('assignTag')
|
||||
|
||||
// Assign first tag
|
||||
getCollaborativeTagsInput()
|
||||
.type(`{selectAll}${tag1}{enter}`)
|
||||
cy.wait('@assignTag')
|
||||
cy.wait('@getNode')
|
||||
|
||||
// Assign second tag
|
||||
getCollaborativeTagsInput()
|
||||
.type(`{selectAll}${tag2}{enter}`)
|
||||
cy.wait('@assignTag')
|
||||
cy.wait('@getNode')
|
||||
|
||||
// Close the sidebar and reload to check the file list
|
||||
closeSidebar()
|
||||
addTagToFile('file.txt', tag1)
|
||||
addTagToFile('file.txt', tag2)
|
||||
cy.reload()
|
||||
|
||||
getRowForFile('file.txt')
|
||||
|
|
@ -104,44 +52,9 @@ describe('Systemtags: Files integration', { testIsolation: true }, () => {
|
|||
const tag1 = randomBytes(4).toString('base64')
|
||||
const tag2 = randomBytes(4).toString('base64')
|
||||
const tag3 = randomBytes(4).toString('base64')
|
||||
|
||||
cy.intercept('PROPFIND', `**/remote.php/dav/files/${user.userId}/file.txt`).as('getNode')
|
||||
getRowForFile('file.txt').should('be.visible')
|
||||
triggerActionForFile('file.txt', 'details')
|
||||
cy.wait('@getNode')
|
||||
|
||||
cy.get('[data-cy-sidebar]')
|
||||
.should('be.visible')
|
||||
.findByRole('button', { name: 'Actions' })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
cy.findByRole('menuitem', { name: 'Tags' })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
cy.intercept('PUT', '**/remote.php/dav/systemtags-relations/files/**').as('assignTag')
|
||||
|
||||
// Assign first tag
|
||||
getCollaborativeTagsInput()
|
||||
.type(`{selectAll}${tag1}{enter}`)
|
||||
cy.wait('@assignTag')
|
||||
cy.wait('@getNode')
|
||||
|
||||
// Assign second tag
|
||||
getCollaborativeTagsInput()
|
||||
.type(`{selectAll}${tag2}{enter}`)
|
||||
cy.wait('@assignTag')
|
||||
cy.wait('@getNode')
|
||||
|
||||
// Assign third tag
|
||||
getCollaborativeTagsInput()
|
||||
.type(`{selectAll}${tag3}{enter}`)
|
||||
cy.wait('@assignTag')
|
||||
cy.wait('@getNode')
|
||||
|
||||
// Close the sidebar and reload to check the file list
|
||||
closeSidebar()
|
||||
addTagToFile('file.txt', tag1)
|
||||
addTagToFile('file.txt', tag2)
|
||||
addTagToFile('file.txt', tag3)
|
||||
cy.reload()
|
||||
|
||||
getRowForFile('file.txt')
|
||||
|
|
@ -163,10 +76,3 @@ describe('Systemtags: Files integration', { testIsolation: true }, () => {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
function getCollaborativeTagsInput(): Cypress.Chainable<JQuery<HTMLElement>> {
|
||||
return cy.get('[data-cy-sidebar]')
|
||||
.findByRole('combobox', { name: /collaborative tags/i })
|
||||
.should('be.visible')
|
||||
.should('not.have.attr', 'disabled', { timeout: 5000 })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import type { User } from '@nextcloud/e2e-test-server/cypress'
|
|||
|
||||
import { randomBytes } from 'crypto'
|
||||
import { getRowForFile, triggerActionForFile } from '../files/FilesUtils.ts'
|
||||
import { createNewTagInDialog } from './utils.ts'
|
||||
|
||||
describe('Systemtags: Files sidebar integration', { testIsolation: true }, () => {
|
||||
let user: User
|
||||
|
|
@ -32,14 +33,9 @@ describe('Systemtags: Files sidebar integration', { testIsolation: true }, () =>
|
|||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
cy.findByRole('menuitem', { name: 'Tags' })
|
||||
cy.findByRole('menuitem', { name: 'Add tags' })
|
||||
.click()
|
||||
|
||||
cy.intercept('PUT', '**/remote.php/dav/systemtags-relations/files/**').as('assignTag')
|
||||
cy.get('[data-cy-sidebar]')
|
||||
.findByRole('combobox', { name: /collaborative tags/i })
|
||||
.should('be.visible')
|
||||
.type(`${tag}{enter}`)
|
||||
cy.wait('@assignTag')
|
||||
createNewTagInDialog(tag)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@
|
|||
import type { User } from '@nextcloud/e2e-test-server/cypress'
|
||||
|
||||
import { randomBytes } from 'crypto'
|
||||
import { closeSidebar, getRowForFile, getRowForFileId, triggerActionForFile } from '../files/FilesUtils.ts'
|
||||
import { getRowForFile } from '../files/FilesUtils.ts'
|
||||
import { addTagToFile } from './utils.ts'
|
||||
|
||||
describe('Systemtags: Files view', { testIsolation: true }, () => {
|
||||
let user: User
|
||||
|
|
@ -22,51 +23,20 @@ describe('Systemtags: Files view', { testIsolation: true }, () => {
|
|||
|
||||
it('See first assigned tag in the file list', () => {
|
||||
const tag = randomBytes(8).toString('base64')
|
||||
let tagId
|
||||
|
||||
// Tag the file
|
||||
tagNode(tag, 'folder')
|
||||
.then((id) => { tagId = id })
|
||||
addTagToFile('folder', tag)
|
||||
|
||||
// open the tags view
|
||||
cy.visit('/apps/files/tags').then(() => {
|
||||
// see the tag
|
||||
getRowForFileId(tagId).should('be.visible')
|
||||
getRowForFile('folder').should('not.exist')
|
||||
getRowForFile('file.txt').should('not.exist')
|
||||
cy.findByRole('cell', { name: tag })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
// see that the tag has its content
|
||||
getRowForFileId(tagId).find('[data-cy-files-list-row-name-link]').click()
|
||||
getRowForFile('folder').should('be.visible')
|
||||
getRowForFile('file.txt').should('not.exist')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function getCollaborativeTagsInput(): Cypress.Chainable<JQuery<HTMLElement>> {
|
||||
return cy.get('[data-cy-sidebar]')
|
||||
.findByRole('combobox', { name: /collaborative tags/i })
|
||||
.should('be.visible')
|
||||
.should('not.have.attr', 'disabled', { timeout: 5000 })
|
||||
}
|
||||
|
||||
function tagNode(tag: string, node: string): Cypress.Chainable<number> {
|
||||
getRowForFile(node).should('be.visible')
|
||||
|
||||
triggerActionForFile(node, 'details')
|
||||
cy.get('[data-cy-sidebar]')
|
||||
.should('be.visible')
|
||||
.findByRole('button', { name: 'Actions' })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
cy.findByRole('menuitem', { name: 'Tags' })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
cy.intercept('PUT', '**/remote.php/dav/systemtags-relations/files/**').as('assignTag')
|
||||
getCollaborativeTagsInput()
|
||||
.type(`{selectAll}${tag}{enter}`)
|
||||
cy.wait('@assignTag')
|
||||
closeSidebar()
|
||||
return cy.get('@assignTag')
|
||||
.then(({ request }) => request.body.id)
|
||||
}
|
||||
|
|
|
|||
37
cypress/e2e/systemtags/utils.ts
Normal file
37
cypress/e2e/systemtags/utils.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { getRowForFile, triggerActionForFile } from '../files/FilesUtils.ts'
|
||||
|
||||
export function addTagToFile(fileName: string, newTag: string): void {
|
||||
getRowForFile(fileName).should('be.visible')
|
||||
triggerActionForFile(fileName, 'systemtags:bulk')
|
||||
|
||||
createNewTagInDialog(newTag)
|
||||
}
|
||||
|
||||
export function createNewTagInDialog(newTag: string): void {
|
||||
cy.intercept('POST', '/remote.php/dav/systemtags').as('createTag')
|
||||
cy.intercept('PROPFIND', '/remote.php/dav/systemtags/*/files').as('getTagData')
|
||||
cy.intercept('PROPPATCH', '/remote.php/dav/systemtags/*/files').as('assignTagData')
|
||||
|
||||
cy.get('[data-cy-systemtags-picker-input]').type(newTag)
|
||||
|
||||
cy.get('[data-cy-systemtags-picker-tag]').should('have.length', 0)
|
||||
cy.get('[data-cy-systemtags-picker-button-create]').should('be.visible')
|
||||
cy.get('[data-cy-systemtags-picker-button-create]').click()
|
||||
|
||||
cy.wait('@createTag')
|
||||
// Verify the new tag is selected by default
|
||||
cy.get('[data-cy-systemtags-picker-tag]').contains(newTag)
|
||||
.parents('[data-cy-systemtags-picker-tag]')
|
||||
.findByRole('checkbox', { hidden: true }).should('be.checked')
|
||||
|
||||
// Apply changes
|
||||
cy.get('[data-cy-systemtags-picker-button-submit]').click()
|
||||
|
||||
cy.wait('@assignTagData')
|
||||
cy.get('[data-cy-systemtags-picker]').should('not.exist')
|
||||
}
|
||||
Loading…
Reference in a new issue