Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
fix(files): auto reload sidebar tags on update
Signed-off-by: skjnldsv <[email protected]>
  • Loading branch information
skjnldsv committed Jun 17, 2025
commit 7c39f5fd1ac86aaf97238d4b67440023056d2815
91 changes: 66 additions & 25 deletions apps/systemtags/src/components/SystemTags.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,52 +8,56 @@
<NcLoadingIcon v-if="loadingTags"
:name="t('systemtags', 'Loading collaborative tags …')"
:size="32" />
<template v-else>
<NcSelectTags class="system-tags__select"
:input-label="t('systemtags', 'Search or create collaborative tags')"
:placeholder="t('systemtags', 'Collaborative tags …')"
:options="sortedTags"
:value="selectedTags"
:create-option="createOption"
:disabled="disabled"
:taggable="true"
:passthru="true"
:fetch-tags="false"
:loading="loading"
@input="handleInput"
@option:selected="handleSelect"
@option:created="handleCreate"
@option:deselected="handleDeselect">
<template #no-options>
{{ t('systemtags', 'No tags to select, type to create a new tag') }}
</template>
</NcSelectTags>
</template>

<NcSelectTags v-show="!loadingTags"
class="system-tags__select"
:input-label="t('systemtags', 'Search or create collaborative tags')"
:placeholder="t('systemtags', 'Collaborative tags …')"
:options="sortedTags"
:value="selectedTags"
:create-option="createOption"
:disabled="disabled"
:taggable="true"
:passthru="true"
:fetch-tags="false"
:loading="loading"
@input="handleInput"
@option:selected="handleSelect"
@option:created="handleCreate"
@option:deselected="handleDeselect">
<template #no-options>
{{ t('systemtags', 'No tags to select, type to create a new tag') }}
</template>
</NcSelectTags>
</div>
</template>

<script lang="ts">
// FIXME Vue TypeScript ESLint errors
/* eslint-disable */
import type { Node } from '@nextcloud/files'
import type { Tag, TagWithId } from '../types.js'

import Vue from 'vue'
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
import NcSelectTags from '@nextcloud/vue/components/NcSelectTags'

import { translate as t } from '@nextcloud/l10n'
import { emit, subscribe } from '@nextcloud/event-bus'
import { loadState } from '@nextcloud/initial-state'
import { showError } from '@nextcloud/dialogs'
import { t } from '@nextcloud/l10n'

import { defaultBaseTag } from '../utils.js'
import { fetchLastUsedTagIds, fetchTags } from '../services/api.js'
import { fetchNode } from '../../../files/src/services/WebdavClient.js'
import {
createTagForFile,
deleteTagForFile,
fetchTagsForFile,
setTagForFile,
} from '../services/files.js'
import logger from '../logger.js'

import { loadState } from '@nextcloud/initial-state'

import type { Tag, TagWithId } from '../types.js'

export default Vue.extend({
name: 'SystemTags',
Expand Down Expand Up @@ -125,6 +129,10 @@ export default Vue.extend({
},
},

mounted() {
subscribe('systemtags:node:updated', this.onTagUpdated)
},

methods: {
t,

Expand Down Expand Up @@ -179,6 +187,8 @@ export default Vue.extend({
showError(t('systemtags', 'Failed to select tag'))
}
this.loading = false

this.updateAndDispatchNodeTagsEvent(this.fileId)
},

async handleCreate(tag: Tag) {
Expand All @@ -197,6 +207,8 @@ export default Vue.extend({
showError(t('systemtags', 'Failed to create tag'))
}
this.loading = false

this.updateAndDispatchNodeTagsEvent(this.fileId)
},

async handleDeselect(tag: TagWithId) {
Expand All @@ -207,6 +219,35 @@ export default Vue.extend({
showError(t('systemtags', 'Failed to delete tag'))
}
this.loading = false

this.updateAndDispatchNodeTagsEvent(this.fileId)
},

async onTagUpdated(node: Node) {
if (node.fileid !== this.fileId) {
return
}

this.loadingTags = true
try {
this.selectedTags = await fetchTagsForFile(this.fileId)
} catch (error) {
showError(t('systemtags', 'Failed to load selected tags'))
}

this.loadingTags = false
},

async updateAndDispatchNodeTagsEvent(fileId: number) {
const path = window.OCA?.Files?.Sidebar?.file || ''
try {
const node = await fetchNode(path)
if (node) {
emit('systemtags:node:updated', node)
}
} catch (error) {
logger.error('Failed to fetch node for system tags update', { error, fileId })
}
},
},
})
Expand Down
85 changes: 51 additions & 34 deletions cypress/e2e/systemtags/files-inline-action.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ 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')
Expand All @@ -36,13 +38,14 @@ describe('Systemtags: Files integration', { testIsolation: true }, () => {
.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}`)

getCollaborativeTagsInput()
.type(`{selectAll}${tag}{enter}`)
cy.wait('@assignTag')
closeSidebar()
cy.wait('@getNode')

// Close the sidebar and reload to check the file list
closeSidebar()
cy.reload()

getRowForFile('file.txt')
Expand All @@ -56,8 +59,10 @@ describe('Systemtags: Files integration', { testIsolation: true }, () => {
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')
Expand All @@ -70,17 +75,20 @@ describe('Systemtags: Files integration', { testIsolation: true }, () => {
.click()

cy.intercept('PUT', '**/remote.php/dav/systemtags-relations/files/**').as('assignTag')
cy.get('[data-cy-sidebar]').within(() => {
cy.findByRole('combobox', { name: /collaborative tags/i })
.should('be.visible')
.type(`${tag1}{enter}`)
cy.wait('@assignTag')
cy.findByRole('combobox', { name: /collaborative tags/i })
.should('be.visible')
.type(`${tag2}{enter}`)
cy.wait('@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()
cy.reload()

Expand All @@ -97,11 +105,10 @@ describe('Systemtags: Files integration', { testIsolation: true }, () => {
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')

cy.intercept('PROPFIND', '**/remote.php/dav/**').as('sidebarLoaded')
triggerActionForFile('file.txt', 'details')
cy.wait('@sidebarLoaded')
cy.wait('@getNode')

cy.get('[data-cy-sidebar]')
.should('be.visible')
Expand All @@ -114,23 +121,26 @@ describe('Systemtags: Files integration', { testIsolation: true }, () => {
.click()

cy.intercept('PUT', '**/remote.php/dav/systemtags-relations/files/**').as('assignTag')
cy.get('[data-cy-sidebar]').within(() => {
cy.findByRole('combobox', { name: /collaborative tags/i })
.should('be.visible')
.type(`${tag1}{enter}`)
cy.wait('@assignTag')

cy.findByRole('combobox', { name: /collaborative tags/i })
.should('be.visible')
.type(`${tag2}{enter}`)
cy.wait('@assignTag')

cy.findByRole('combobox', { name: /collaborative tags/i })
.should('be.visible')
.type(`${tag3}{enter}`)
cy.wait('@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()
cy.reload()

Expand All @@ -153,3 +163,10 @@ 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 })
}
Loading