chore: update @nextcloud/files to v4.0.0-rc.0

- update library
- adjust sidebar tab handling

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
Ferdinand Thiessen 2026-01-16 00:24:31 +01:00
parent 60a43694cc
commit d3813798bf
No known key found for this signature in database
GPG key ID: 7E849AE05218500F
14 changed files with 104 additions and 121 deletions

View file

@ -11,7 +11,6 @@ import { t } from '@nextcloud/l10n'
import wrap from '@vue/web-component-wrapper'
import { createPinia, PiniaVuePlugin } from 'pinia'
import Vue from 'vue'
import FilesSidebarTab from './views/FilesSidebarTab.vue'
import { registerCommentsPlugins } from './comments-activity-tab.ts'
__webpack_nonce__ = getCSPNonce()
@ -30,28 +29,20 @@ if (loadState('comments', 'activityEnabled', false) && OCA?.Activity?.registerSi
iconSvgInline: MessageReplyText,
order: 50,
tagName,
enabled() {
if (!window.customElements.get(tagName)) {
setupSidebarTab()
}
return true
async onInit() {
const { default: FilesSidebarTab } = await import('./views/FilesSidebarTab.vue')
Vue.use(PiniaVuePlugin)
Vue.mixin({ pinia: createPinia() })
const webComponent = wrap(Vue, FilesSidebarTab)
// In Vue 2, wrap doesn't support disabling shadow. Disable with a hack
Object.defineProperty(webComponent.prototype, 'attachShadow', {
value() { return this },
})
Object.defineProperty(webComponent.prototype, 'shadowRoot', {
get() { return this },
})
window.customElements.define(tagName, webComponent)
},
})
}
/**
* Setup the sidebar tab as a web component
*/
function setupSidebarTab() {
Vue.use(PiniaVuePlugin)
Vue.mixin({ pinia: createPinia() })
const webComponent = wrap(Vue, FilesSidebarTab)
// In Vue 2, wrap doesn't support disabling shadow. Disable with a hack
Object.defineProperty(webComponent.prototype, 'attachShadow', {
value() { return this },
})
Object.defineProperty(webComponent.prototype, 'shadowRoot', {
get() { return this },
})
window.customElements.define(tagName, webComponent)
}

View file

@ -11,24 +11,16 @@ import Comments from './Comments.vue'
const props = defineProps<{
node?: INode
// eslint-disable-next-line vue/no-unused-properties -- Required on the web component interface
active?: boolean
// eslint-disable-next-line vue/no-unused-properties -- Required on the web component interface
folder?: IFolder
// eslint-disable-next-line vue/no-unused-properties -- Required on the web component interface
view?: IView
}>()
defineExpose({ setActive })
const resourceId = computed(() => props.node?.fileid)
/**
* Set this tab as active
*
* @param active - The active state
*/
function setActive(active: boolean) {
return active
}
</script>
<template>

View file

@ -4,12 +4,13 @@
-->
<script setup lang="ts">
import type { ISidebarTab, SidebarComponent } from '@nextcloud/files'
import type { ISidebarTab } from '@nextcloud/files'
import { NcIconSvgWrapper, NcLoadingIcon } from '@nextcloud/vue'
import { ref, toRef, watch, watchEffect } from 'vue'
import { ref, toRef, watch } from 'vue'
import NcAppSidebarTab from '@nextcloud/vue/components/NcAppSidebarTab'
import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
import logger from '../../logger.ts'
import { useActiveStore } from '../../store/active.ts'
import { useSidebarStore } from '../../store/sidebar.ts'
@ -29,19 +30,31 @@ const sidebar = useSidebarStore()
const activeStore = useActiveStore()
const loading = ref(true)
watch(toRef(props, 'tab'), async () => {
loading.value = true
await window.customElements.whenDefined(props.tab.tagName)
loading.value = false
}, { immediate: true })
const tabElement = ref<SidebarComponent>()
watchEffect(async () => {
if (tabElement.value) {
// Mark as active
await tabElement.value.setActive?.(props.active)
watch(toRef(props, 'active'), async (active) => {
if (!active) {
return
}
})
logger.debug('sidebar: activating files sidebar tab ' + props.tab.id, { tab: props.tab })
loading.value = true
try {
if (!initializedTabs.has(props.tab.tagName)) {
initializedTabs.add(props.tab.tagName)
logger.debug('sidebar: initializing ' + props.tab.id)
await props.tab.onInit?.()
}
logger.debug('sidebar: waiting for sidebar tab component becoming defined ' + props.tab.id)
await window.customElements.whenDefined(props.tab.tagName)
logger.debug('sidebar: tab component defined and loaded ' + props.tab.id)
loading.value = false
} catch (error) {
logger.error('Failed to get sidebar tab web component', { error })
}
}, { immediate: true })
</script>
<script lang="ts">
const initializedTabs = new Set<string>()
</script>
<template>
@ -61,7 +74,7 @@ watchEffect(async () => {
<component
:is="tab.tagName"
v-else
ref="tabElement"
:active.prop="active"
:node.prop="sidebar.currentNode"
:folder.prop="activeStore.activeFolder"
:view.prop="activeStore.activeView" />

View file

@ -84,7 +84,7 @@ export const useSidebarStore = defineStore('sidebar', () => {
function getTabs(context?: ISidebarContext) {
let tabs = getSidebarTabs()
if (context) {
tabs = tabs.filter((tab) => tab.enabled(context))
tabs = tabs.filter((tab) => tab.enabled === undefined || tab.enabled(context))
}
return tabs.sort((a, b) => a.order - b.order)
}
@ -98,7 +98,7 @@ export const useSidebarStore = defineStore('sidebar', () => {
function getActions(context?: ISidebarContext) {
let actions = getSidebarActions()
if (context) {
actions = actions.filter((tab) => tab.enabled(context))
actions = actions.filter((action) => action.enabled === undefined || action.enabled(context))
}
return actions.sort((a, b) => a.order - b.order)
}

View file

@ -5,11 +5,10 @@
import ShareVariant from '@mdi/svg/svg/share-variant.svg?raw'
import { getCSPNonce } from '@nextcloud/auth'
import { registerSidebarTab } from '@nextcloud/files'
import { getSidebar } from '@nextcloud/files'
import { n, t } from '@nextcloud/l10n'
import wrap from '@vue/web-component-wrapper'
import Vue from 'vue'
import FilesSidebarTab from './views/FilesSidebarTab.vue'
import ExternalShareActions from './services/ExternalShareActions.js'
import ShareSearch from './services/ShareSearch.js'
import TabSections from './services/TabSections.js'
@ -27,32 +26,25 @@ Vue.prototype.n = n
const tagName = 'files_sharing-sidebar-tab'
registerSidebarTab({
getSidebar().registerTab({
id: 'sharing',
displayName: t('files_sharing', 'Sharing'),
iconSvgInline: ShareVariant,
order: 10,
tagName,
enabled() {
if (!window.customElements.get(tagName)) {
setupSidebarTab()
}
return true
async onInit() {
const { default: FilesSidebarTab } = await import('./views/FilesSidebarTab.vue')
const webComponent = wrap(Vue, FilesSidebarTab)
// In Vue 2, wrap doesn't support diseabling shadow. Disable with a hack
Object.defineProperty(webComponent.prototype, 'attachShadow', {
value() { return this },
})
Object.defineProperty(webComponent.prototype, 'shadowRoot', {
get() { return this },
})
window.customElements.define(tagName, webComponent)
},
})
/**
* Setup the sidebar tab as a web component
*/
function setupSidebarTab() {
const webComponent = wrap(Vue, FilesSidebarTab)
// In Vue 2, wrap doesn't support diseabling shadow. Disable with a hack
Object.defineProperty(webComponent.prototype, 'attachShadow', {
value() { return this },
})
Object.defineProperty(webComponent.prototype, 'shadowRoot', {
get() { return this },
})
window.customElements.define(tagName, webComponent)
}

View file

@ -12,6 +12,9 @@ import FileInfo from '../services/FileInfo.ts'
const props = defineProps<{
node?: INode
// eslint-disable-next-line vue/no-unused-properties -- Required on the web component interface
active?: boolean
// eslint-disable-next-line vue/no-unused-properties -- Required on the web component interface
folder?: IFolder
// eslint-disable-next-line vue/no-unused-properties -- Required on the web component interface

View file

@ -10,10 +10,10 @@ import { isPublicShare } from '@nextcloud/sharing/public'
import { defineAsyncComponent, defineCustomElement } from 'vue'
const tagName = 'files-versions_sidebar-tab'
const FilesVersionsSidebarTab = defineAsyncComponent(() => import('./views/FilesVersionsSidebarTab.vue'))
registerSidebarTab({
id: 'files_versions',
tagName,
order: 90,
displayName: t('files_versions', 'Versions'),
iconSvgInline: BackupRestore,
@ -24,23 +24,13 @@ registerSidebarTab({
if (node.type !== FileType.File) {
return false
}
// setup tab
setupTab()
return true
},
tagName,
async onInit() {
const FilesVersionsSidebarTab = defineAsyncComponent(() => import('./views/FilesVersionsSidebarTab.vue'))
window.customElements.define(tagName, defineCustomElement(FilesVersionsSidebarTab, {
shadowRoot: false,
}))
},
})
/**
* Setup the custom element for the Files Versions sidebar tab.
*/
function setupTab() {
if (window.customElements.get(tagName)) {
// already defined
return
}
window.customElements.define(tagName, defineCustomElement(FilesVersionsSidebarTab, {
shadowRoot: false,
}))
}

View file

@ -15,7 +15,7 @@
:key="row.items[0].version.mtime"
:can-view="canView"
:can-compare="canCompare"
:load-preview="isActive"
:load-preview="active"
:version="row.items[0].version"
:node="node"
:is-current="row.items[0].version.mtime === currentVersionMtime"
@ -57,15 +57,16 @@ import logger from '../utils/logger.ts'
import { deleteVersion, fetchVersions, restoreVersion, setVersionLabel } from '../utils/versions.ts'
const props = defineProps<{
node?: INode
folder?: IFolder
view?: IView
active: boolean
node: INode
// eslint-disable-next-line vue/no-unused-properties -- required by SidebarTab but we do not need it
folder: IFolder
// eslint-disable-next-line vue/no-unused-properties -- required by SidebarTab but we do not need it
view: IView
}>()
defineExpose({ setActive })
const isMobile = useIsMobile()
const isActive = ref<boolean>(false)
const versions = ref<Version[]>([])
const loading = ref(false)
const showVersionLabelForm = ref(false)
@ -138,15 +139,6 @@ const canCompare = computed(() => {
&& window.OCA.Viewer?.mimetypesCompare?.includes(props.node?.mime)
})
/**
* This method is called by the files app if the sidebar tab state changes.
*
* @param active - The new active state
*/
function setActive(active: boolean) {
isActive.value = active
}
/**
* Handle restored event from Version.vue
*

View file

@ -19,7 +19,7 @@
"@nextcloud/capabilities": "^1.2.1",
"@nextcloud/dialogs": "^7.1.0",
"@nextcloud/event-bus": "^3.3.3",
"@nextcloud/files": "^4.0.0-beta.9",
"@nextcloud/files": "^4.0.0-rc.0",
"@nextcloud/initial-state": "^3.0.0",
"@nextcloud/l10n": "^3.4.1",
"@nextcloud/logger": "^3.0.3",
@ -3244,9 +3244,9 @@
}
},
"node_modules/@nextcloud/files": {
"version": "4.0.0-beta.9",
"resolved": "https://registry.npmjs.org/@nextcloud/files/-/files-4.0.0-beta.9.tgz",
"integrity": "sha512-Mbcl+eO2PEsOkBmQIxHtol6O8uzesJYQYb5kIZiqyfJ0yProSqJaMIBkBmAjHUwv+EOjswwRSXhlc8A4DfuVSg==",
"version": "4.0.0-rc.0",
"resolved": "https://registry.npmjs.org/@nextcloud/files/-/files-4.0.0-rc.0.tgz",
"integrity": "sha512-zg/TQH4oQQYlntzkcWokjKIkTX39maiYXceDYrE3OnzCYZv0IKmOH3+pQer58/Z9QfsU8huTnzHblhzVNsJvAA==",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@nextcloud/auth": "^2.5.3",

View file

@ -35,7 +35,7 @@
"@nextcloud/capabilities": "^1.2.1",
"@nextcloud/dialogs": "^7.1.0",
"@nextcloud/event-bus": "^3.3.3",
"@nextcloud/files": "^4.0.0-beta.9",
"@nextcloud/files": "^4.0.0-rc.0",
"@nextcloud/initial-state": "^3.0.0",
"@nextcloud/l10n": "^3.4.1",
"@nextcloud/logger": "^3.0.3",

View file

@ -46,7 +46,7 @@ module.exports = {
files_sharing: {
additionalScripts: path.join(__dirname, 'apps/files_sharing/src', 'additionalScripts.js'),
collaboration: path.join(__dirname, 'apps/files_sharing/src', 'collaborationresourceshandler.js'),
files_sharing_tab: path.join(__dirname, 'apps/files_sharing/src', 'files_sharing_tab.js'),
files_sharing_tab: path.join(__dirname, 'apps/files_sharing/src', 'files-sidebar.ts'),
init: path.join(__dirname, 'apps/files_sharing/src', 'init.ts'),
'init-public': path.join(__dirname, 'apps/files_sharing/src', 'init-public.ts'),
main: path.join(__dirname, 'apps/files_sharing/src', 'main.ts'),

View file

@ -55,6 +55,9 @@ describe('Files: Sidebar', { testIsolation: true }, () => {
.findByRole('heading', { name: 'file' })
.should('be.visible')
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(600) // wait for a bit to avoid flakiness
triggerActionForFile('folder', 'details')
cy.get('[data-cy-sidebar]')
.should('be.visible')
@ -89,7 +92,11 @@ describe('Files: Sidebar', { testIsolation: true }, () => {
// open the sidebar
triggerActionForFile('file', 'details')
// validate it is open
cy.get('[data-cy-sidebar]').should('be.visible')
cy.get('[data-cy-sidebar]')
.should('be.visible')
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(600) // wait for a bit to avoid flakiness
// delete the file
triggerActionForFile('file', 'delete')
cy.wait('@deleteFile', { timeout: 10000 })
@ -116,6 +123,9 @@ describe('Files: Sidebar', { testIsolation: true }, () => {
cy.get('[data-cy-sidebar]').should('be.visible')
cy.url().should('contain', `apps/files/files/${otherFileId}`)
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(600) // wait for a bit to avoid flakiness
triggerActionForFile('other', 'delete')
cy.wait('@deleteFile')

8
package-lock.json generated
View file

@ -18,7 +18,7 @@
"@nextcloud/capabilities": "^1.2.1",
"@nextcloud/dialogs": "^7.2.0",
"@nextcloud/event-bus": "^3.3.3",
"@nextcloud/files": "^4.0.0-beta.9",
"@nextcloud/files": "^4.0.0-rc.0",
"@nextcloud/initial-state": "^3.0.0",
"@nextcloud/l10n": "^3.4.1",
"@nextcloud/logger": "^3.0.3",
@ -2443,9 +2443,9 @@
}
},
"node_modules/@nextcloud/files": {
"version": "4.0.0-beta.9",
"resolved": "https://registry.npmjs.org/@nextcloud/files/-/files-4.0.0-beta.9.tgz",
"integrity": "sha512-Mbcl+eO2PEsOkBmQIxHtol6O8uzesJYQYb5kIZiqyfJ0yProSqJaMIBkBmAjHUwv+EOjswwRSXhlc8A4DfuVSg==",
"version": "4.0.0-rc.0",
"resolved": "https://registry.npmjs.org/@nextcloud/files/-/files-4.0.0-rc.0.tgz",
"integrity": "sha512-zg/TQH4oQQYlntzkcWokjKIkTX39maiYXceDYrE3OnzCYZv0IKmOH3+pQer58/Z9QfsU8huTnzHblhzVNsJvAA==",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@nextcloud/auth": "^2.5.3",

View file

@ -47,7 +47,7 @@
"@nextcloud/capabilities": "^1.2.1",
"@nextcloud/dialogs": "^7.2.0",
"@nextcloud/event-bus": "^3.3.3",
"@nextcloud/files": "^4.0.0-beta.9",
"@nextcloud/files": "^4.0.0-rc.0",
"@nextcloud/initial-state": "^3.0.0",
"@nextcloud/l10n": "^3.4.1",
"@nextcloud/logger": "^3.0.3",