Skip to content

Commit fd05414

Browse files
Merge pull request #55174 from nextcloud/backport/54788/stable32
[stable32] feat(files_sharing): provide web components based API for sidebar
2 parents 48468be + e83fe36 commit fd05414

23 files changed

+304
-43
lines changed

apps/files/src/services/FileInfo.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export default function(node: Node) {
3131
fileInfo.get = (key) => fileInfo[key]
3232
fileInfo.isDirectory = () => fileInfo.mimetype === 'httpd/unix-directory'
3333
fileInfo.canEdit = () => Boolean(fileInfo.permissions & OC.PERMISSION_UPDATE)
34+
fileInfo.node = node
3435

3536
return fileInfo
3637
}

apps/files/src/views/Sidebar.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@
9595
</NcAppSidebar>
9696
</template>
9797
<script lang="ts">
98+
import type { INode } from '@nextcloud/files'
99+
98100
import { davRemoteURL, davRootPath, File, Folder, formatFileSize } from '@nextcloud/files'
99101
import { defineComponent } from 'vue'
100102
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
@@ -159,7 +161,7 @@ export default defineComponent({
159161
error: null,
160162
loading: true,
161163
fileInfo: null,
162-
node: null,
164+
node: null as INode | null,
163165
isFullScreen: false,
164166
hasLowHeight: false,
165167
}

apps/files_sharing/src/components/SharingEntryLink.vue

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,16 @@
159159
<NcActionSeparator />
160160

161161
<!-- external actions -->
162-
<ExternalShareAction v-for="action in externalLinkActions"
162+
<NcActionButton v-for="action in sortedExternalShareActions"
163+
:key="action.id"
164+
@click="action.exec(share, fileInfo.node)">
165+
<template #icon>
166+
<NcIconSvgWrapper :svg="action.iconSvg" />
167+
</template>
168+
{{ action.label(share, fileInfo.node) }}
169+
</NcActionButton>
170+
171+
<SidebarTabExternalActionLegacy v-for="action in externalLegacyShareActions"
163172
:id="action.id"
164173
:key="action.id"
165174
:action="action"
@@ -230,6 +239,8 @@ import { t } from '@nextcloud/l10n'
230239
import moment from '@nextcloud/moment'
231240
import { generateUrl, getBaseUrl } from '@nextcloud/router'
232241
import { ShareType } from '@nextcloud/sharing'
242+
import { getSidebarInlineActions } from '@nextcloud/sharing/ui'
243+
import { toRaw } from 'vue'
233244
234245
import VueQrcode from '@chenfengyuan/vue-qrcode'
235246
import NcActionButton from '@nextcloud/vue/components/NcActionButton'
@@ -255,8 +266,8 @@ import PlusIcon from 'vue-material-design-icons/Plus.vue'
255266
256267
import SharingEntryQuickShareSelect from './SharingEntryQuickShareSelect.vue'
257268
import ShareExpiryTime from './ShareExpiryTime.vue'
269+
import SidebarTabExternalActionLegacy from './SidebarTabExternal/SidebarTabExternalActionLegacy.vue'
258270
259-
import ExternalShareAction from './ExternalShareAction.vue'
260271
import GeneratePassword from '../utils/GeneratePassword.ts'
261272
import Share from '../models/Share.ts'
262273
import SharesMixin from '../mixins/SharesMixin.js'
@@ -267,7 +278,6 @@ export default {
267278
name: 'SharingEntryLink',
268279
269280
components: {
270-
ExternalShareAction,
271281
NcActions,
272282
NcActionButton,
273283
NcActionCheckbox,
@@ -290,6 +300,7 @@ export default {
290300
PlusIcon,
291301
SharingEntryQuickShareSelect,
292302
ShareExpiryTime,
303+
SidebarTabExternalActionLegacy,
293304
},
294305
295306
mixins: [SharesMixin, ShareDetails],
@@ -323,6 +334,7 @@ export default {
323334
324335
ExternalLegacyLinkActions: OCA.Sharing.ExternalLinkActions.state,
325336
ExternalShareActions: OCA.Sharing.ExternalShareActions.state,
337+
externalShareActions: getSidebarInlineActions(),
326338
327339
// tracks whether modal should be opened or not
328340
showQRCode: false,
@@ -568,13 +580,25 @@ export default {
568580
*
569581
* @return {Array}
570582
*/
571-
externalLinkActions() {
583+
externalLegacyShareActions() {
572584
const filterValidAction = (action) => (action.shareType.includes(ShareType.Link) || action.shareType.includes(ShareType.Email)) && !action.advanced
573585
// filter only the registered actions for said link
586+
console.error('external legacy', this.ExternalShareActions, this.ExternalShareActions.actions.filter(filterValidAction))
574587
return this.ExternalShareActions.actions
575588
.filter(filterValidAction)
576589
},
577590
591+
/**
592+
* Additional actions for the menu
593+
*
594+
* @return {import('@nextcloud/sharing/ui').ISidebarInlineAction[]}
595+
*/
596+
sortedExternalShareActions() {
597+
return this.externalShareActions
598+
.filter((action) => action.enabled(toRaw(this.share), toRaw(this.fileInfo.node)))
599+
.sort((a, b) => a.order - b.order)
600+
},
601+
578602
isPasswordPolicyEnabled() {
579603
return typeof this.config.passwordPolicy === 'object'
580604
},
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
6+
<template>
7+
<component :is="action.element"
8+
:key="action.id"
9+
ref="actionElement"
10+
:share.prop="share"
11+
:node.prop="node"
12+
:on-save.prop="onSave" />
13+
</template>
14+
15+
<script lang="ts" setup>
16+
import type { INode } from '@nextcloud/files'
17+
import type { IShare } from '@nextcloud/sharing'
18+
import type { ISidebarAction } from '@nextcloud/sharing/ui'
19+
import type { PropType } from 'vue'
20+
21+
import { ref, toRaw, watchEffect } from 'vue'
22+
23+
const props = defineProps({
24+
action: {
25+
type: Object as PropType<ISidebarAction>,
26+
required: true,
27+
},
28+
node: {
29+
type: Object as PropType<INode>,
30+
required: true,
31+
},
32+
share: {
33+
type: Object as PropType<IShare | undefined>,
34+
required: true,
35+
},
36+
})
37+
38+
defineExpose({ save })
39+
40+
const actionElement = ref()
41+
const savingCallback = ref()
42+
43+
watchEffect(() => {
44+
if (!actionElement.value) {
45+
return
46+
}
47+
48+
// This seems to be only needed in Vue 2 as the .prop modifier does not really work on the vue 2 version of web components
49+
// TODO: Remove with Vue 3
50+
actionElement.value.node = toRaw(props.node)
51+
actionElement.value.onSave = onSave
52+
actionElement.value.share = toRaw(props.share)
53+
})
54+
55+
/**
56+
* The share is reset thus save the state of the component.
57+
*/
58+
async function save() {
59+
await savingCallback.value?.()
60+
}
61+
62+
/**
63+
* Vue does not allow to call methods on wrapped web components
64+
* so we need to pass it per callback.
65+
*
66+
* @param callback - The callback to be called on save
67+
*/
68+
function onSave(callback: () => Promise<void>) {
69+
savingCallback.value = callback
70+
}
71+
</script>

apps/files_sharing/src/components/ExternalShareAction.vue renamed to apps/files_sharing/src/components/SidebarTabExternal/SidebarTabExternalActionLegacy.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,18 @@
44
-->
55

66
<template>
7-
<Component :is="data.is"
7+
<component :is="data.is"
88
v-bind="data"
99
v-on="action.handlers">
1010
{{ data.text }}
11-
</Component>
11+
</component>
1212
</template>
1313

1414
<script>
15-
import Share from '../models/Share.ts'
15+
import Share from '../../models/Share.ts'
1616
1717
export default {
18-
name: 'ExternalShareAction',
18+
name: 'SidebarTabExternalActionLegacy',
1919
2020
props: {
2121
id: {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
6+
<template>
7+
<component :is="section.element" ref="sectionElement" :node.prop="node" />
8+
</template>
9+
10+
<script lang="ts" setup>
11+
import type { INode } from '@nextcloud/files'
12+
import type { ISidebarSection } from '@nextcloud/sharing/ui'
13+
import type { PropType } from 'vue'
14+
15+
import { ref, watchEffect } from 'vue'
16+
17+
const props = defineProps({
18+
node: {
19+
type: Object as PropType<INode>,
20+
required: true,
21+
},
22+
section: {
23+
type: Object as PropType<ISidebarSection>,
24+
required: true,
25+
},
26+
})
27+
28+
// TOOD: Remove with Vue 3
29+
const sectionElement = ref()
30+
watchEffect(() => {
31+
sectionElement.value.node = props.node
32+
})
33+
</script>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
6+
<template>
7+
<div class="sharing-tab-external-section-legacy">
8+
<component :is="component" :file-info="fileInfo" />
9+
</div>
10+
</template>
11+
12+
<script lang="ts" setup>
13+
import { computed, type Component, type PropType } from 'vue'
14+
15+
const props = defineProps({
16+
fileInfo: {
17+
type: Object,
18+
required: true,
19+
},
20+
sectionCallback: {
21+
type: Function as PropType<(el: HTMLElement | undefined, fileInfo: unknown) => Component>,
22+
required: true,
23+
},
24+
})
25+
26+
const component = computed(() => props.sectionCallback(undefined, props.fileInfo))
27+
</script>
28+
29+
<style scoped>
30+
.sharing-tab-external-section-legacy {
31+
width: 100%;
32+
}
33+
</style>

apps/files_sharing/src/services/ExternalLinkActions.js

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

6+
import logger from './logger.ts'
7+
68
export default class ExternalLinkActions {
79

810
_state
@@ -13,7 +15,7 @@ export default class ExternalLinkActions {
1315

1416
// init default values
1517
this._state.actions = []
16-
console.debug('OCA.Sharing.ExternalLinkActions initialized')
18+
logger.debug('OCA.Sharing.ExternalLinkActions initialized')
1719
}
1820

1921
/**
@@ -35,13 +37,13 @@ export default class ExternalLinkActions {
3537
* @return {boolean}
3638
*/
3739
registerAction(action) {
38-
OC.debug && console.warn('OCA.Sharing.ExternalLinkActions is deprecated, use OCA.Sharing.ExternalShareAction instead')
40+
logger.warn('OCA.Sharing.ExternalLinkActions is deprecated, use `registerSidebarAction` from `@nextcloud/sharing` instead')
3941

4042
if (typeof action === 'object' && action.icon && action.name && action.url) {
4143
this._state.actions.push(action)
4244
return true
4345
}
44-
console.error('Invalid action provided', action)
46+
logger.error('Invalid action provided', action)
4547
return false
4648
}
4749

apps/files_sharing/src/services/ExternalShareActions.js

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

6+
import logger from './logger.ts'
7+
68
export default class ExternalShareActions {
79

810
_state
@@ -13,7 +15,7 @@ export default class ExternalShareActions {
1315

1416
// init default values
1517
this._state.actions = []
16-
console.debug('OCA.Sharing.ExternalShareActions initialized')
18+
logger.debug('OCA.Sharing.ExternalShareActions initialized')
1719
}
1820

1921
/**
@@ -44,21 +46,23 @@ export default class ExternalShareActions {
4446
* @return {boolean}
4547
*/
4648
registerAction(action) {
49+
logger.warn('OCA.Sharing.ExternalShareActions is deprecated, use `registerSidebarAction` from `@nextcloud/sharing` instead')
50+
4751
// Validate action
4852
if (typeof action !== 'object'
4953
|| typeof action.id !== 'string'
5054
|| typeof action.data !== 'function' // () => {disabled: true}
5155
|| !Array.isArray(action.shareType) // [\@nextcloud/sharing.Types.Link, ...]
5256
|| typeof action.handlers !== 'object' // {click: () => {}, ...}
5357
|| !Object.values(action.handlers).every(handler => typeof handler === 'function')) {
54-
console.error('Invalid action provided', action)
58+
logger.error('Invalid action provided', action)
5559
return false
5660
}
5761

5862
// Check duplicates
5963
const hasDuplicate = this._state.actions.findIndex(check => check.id === action.id) > -1
6064
if (hasDuplicate) {
61-
console.error(`An action with the same id ${action.id} already exists`, action)
65+
logger.error(`An action with the same id ${action.id} already exists`, action)
6266
return false
6367
}
6468

0 commit comments

Comments
 (0)