refactor(files_sharing): migrate to new Files Sidebar API

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
Ferdinand Thiessen 2025-12-28 19:01:18 +01:00
parent 34511e9036
commit 7077685bf8
No known key found for this signature in database
GPG key ID: 7E849AE05218500F
7 changed files with 98 additions and 69 deletions

View file

@ -3,28 +3,28 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { Node } from '@nextcloud/files'
import type { INode } from '@nextcloud/files'
import AccountGroupSvg from '@mdi/svg/svg/account-group-outline.svg?raw'
import AccountPlusSvg from '@mdi/svg/svg/account-plus-outline.svg?raw'
import LinkSvg from '@mdi/svg/svg/link.svg?raw'
import { getCurrentUser } from '@nextcloud/auth'
import { showError } from '@nextcloud/dialogs'
import { FileAction, Permission, registerFileAction } from '@nextcloud/files'
import { FileAction, getSidebar, Permission, registerFileAction } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import { ShareType } from '@nextcloud/sharing'
import { isPublicShare } from '@nextcloud/sharing/public'
import CircleSvg from '../../../../core/img/apps/circles.svg?raw'
import { action as sidebarAction } from '../../../files/src/actions/sidebarAction.ts'
import { generateAvatarSvg } from '../utils/AccountIcon.ts'
import './sharingStatusAction.scss'
/**
* Check if the node is external (federated)
*
* @param node
* @param node - The node to check
*/
function isExternal(node: Node) {
function isExternal(node: INode) {
return node.attributes?.['is-federated'] ?? false
}
@ -136,12 +136,12 @@ export const action = new FileAction({
&& (node.permissions & Permission.READ) !== 0
},
async exec({ nodes, view, folder, contents }) {
async exec({ nodes }) {
// You need read permissions to see the sidebar
const node = nodes[0]
if ((node.permissions & Permission.READ) !== 0) {
window.OCA?.Files?.Sidebar?.setActiveTab?.('sharing')
sidebarAction.exec({ nodes, view, folder, contents })
const sidebar = getSidebar()
sidebar.open(node, 'sharing')
return null
}

View file

@ -5,8 +5,11 @@
import ShareVariant from '@mdi/svg/svg/share-variant.svg?raw'
import { getCSPNonce } from '@nextcloud/auth'
import { registerSidebarTab } 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'
@ -14,9 +17,7 @@ import TabSections from './services/TabSections.js'
__webpack_nonce__ = getCSPNonce()
// Init Sharing Tab Service
if (!window.OCA.Sharing) {
window.OCA.Sharing = {}
}
window.OCA.Sharing ??= {}
Object.assign(window.OCA.Sharing, { ShareSearch: new ShareSearch() })
Object.assign(window.OCA.Sharing, { ExternalShareActions: new ExternalShareActions() })
Object.assign(window.OCA.Sharing, { ShareTabSections: new TabSections() })
@ -24,42 +25,34 @@ Object.assign(window.OCA.Sharing, { ShareTabSections: new TabSections() })
Vue.prototype.t = t
Vue.prototype.n = n
// Init Sharing tab component
let TabInstance = null
const tagName = 'files_sharing-sidebar-tab'
window.addEventListener('DOMContentLoaded', function() {
if (OCA.Files && OCA.Files.Sidebar) {
OCA.Files.Sidebar.registerTab(new OCA.Files.Sidebar.Tab({
id: 'sharing',
name: t('files_sharing', 'Sharing'),
iconSvg: ShareVariant,
async mount(el, fileInfo, context) {
const SharingTab = (await import('./views/SharingTab.vue')).default
const View = Vue.extend(SharingTab)
if (TabInstance) {
TabInstance.$destroy()
}
TabInstance = new View({
// Better integration with vue parent component
parent: context,
})
// Only mount after we have all the info we need
await TabInstance.update(fileInfo)
TabInstance.$mount(el)
},
update(fileInfo) {
TabInstance.update(fileInfo)
},
destroy() {
if (TabInstance) {
TabInstance.$destroy()
TabInstance = null
}
},
}))
}
registerSidebarTab({
id: 'sharing',
displayName: t('files_sharing', 'Sharing'),
iconSvgInline: ShareVariant,
order: 10,
tagName,
enabled() {
if (!window.customElements.get(tagName)) {
setupSidebarTab()
}
return true
},
})
/**
* 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

@ -1,11 +1,9 @@
/**
/*!
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/* eslint-disable jsdoc/require-jsdoc */
import type { Attribute, Node } from '@nextcloud/files'
import type { Attribute, INode } from '@nextcloud/files'
interface RawLegacyFileInfo {
id: number
@ -30,11 +28,16 @@ export type LegacyFileInfo = RawLegacyFileInfo & {
get: (key: keyof RawLegacyFileInfo) => unknown
isDirectory: () => boolean
canEdit: () => boolean
node: Node
node: INode
canDownload: () => boolean
}
export default function(node: Node): LegacyFileInfo {
/**
* Convert Node to legacy file info
*
* @param node - The Node to convert
*/
export default function(node: INode): LegacyFileInfo {
const rawFileInfo: RawLegacyFileInfo = {
id: node.fileid!,
path: node.dirname,

View file

@ -0,0 +1,21 @@
<script setup lang="ts">
import type { IFolder, INode, IView } from '@nextcloud/files'
import { computed } from 'vue'
import SharingTab from './SharingTab.vue'
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
folder?: IFolder
// eslint-disable-next-line vue/no-unused-properties -- Required on the web component interface
view?: IView
}>()
const fileInfo = computed(() => props.node && FileInfo(props.node))
</script>
<template>
<SharingTab v-if="fileInfo" :file-info="fileInfo" />
</template>

View file

@ -230,6 +230,13 @@ export default {
mixins: [ShareDetails],
props: {
fileInfo: {
type: Object,
required: true,
},
},
data() {
return {
config: new Config(),
@ -238,8 +245,6 @@ export default {
expirationInterval: null,
loading: true,
fileInfo: null,
// reshare Share object
reshare: null,
sharedWithMe: {},
@ -328,18 +333,19 @@ export default {
},
},
methods: {
/**
* Update current fileInfo and fetch new data
*
* @param {object} fileInfo the current file FileInfo
*/
async update(fileInfo) {
this.fileInfo = fileInfo
this.resetState()
this.getShares()
watch: {
fileInfo: {
immediate: true,
handler(newValue, oldValue) {
if (oldValue?.id === undefined || oldValue?.id !== newValue?.id) {
this.resetState()
this.getShares()
}
},
},
},
methods: {
/**
* Get the existing shares infos
*/

View file

@ -35,8 +35,12 @@ export function createShare(fileName: string, username: string, shareSettings: P
export function openSharingDetails(index: number) {
cy.get('#app-sidebar-vue').within(() => {
cy.get('[data-cy-files-sharing-share-actions]').eq(index).click({ force: true })
cy.get('[data-cy-files-sharing-share-permissions-bundle="custom"]').click()
cy.findAllByRole('button', { name: /open sharing details/i })
.should('have.length.at.least', index + 1)
.eq(index)
.click({ force: true })
cy.get('[data-cy-files-sharing-share-permissions-bundle="custom"]')
.click()
})
}

View file

@ -90,11 +90,13 @@ describe('files_sharing: Expiry date', () => {
prepareDirectory(dir)
updateShare(dir, 0, { expiryDate: fortnight })
validateExpiryDate(dir, fortnightString)
closeSidebar()
cy.log('Upadate share and validate expiry date is kept')
updateShare(dir, 0, { note: 'Only note changed' })
validateExpiryDate(dir, fortnightString)
cy.log('Reload page and validate expiry date is kept')
cy.visit('/apps/files')
validateExpiryDate(dir, fortnightString)
})