Skip to content

Commit b8b992c

Browse files
committed
feat(files_sharing): use the unified sharing dialog in the files sidebar
Replace the legacy inline sharing inputs and per-share menus with the new unified sharing dialog (@nextcloud/sharing/dialog): - a single "Share" button opens the create dialog - share entries expose dedicated edit + delete actions, delete asking for confirmation before removing the share - the quick-share permission dropdown and the link "customize" entry route through the new dialog The legacy inline UI is kept as an automatic fallback: it is shown whenever the server does not advertise the unified sharing API (empty sharing capability), so no dedicated appconfig killswitch is required. Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
1 parent d4ab4d2 commit b8b992c

5 files changed

Lines changed: 174 additions & 4 deletions

File tree

apps/files_sharing/src/components/SharingEntry.vue

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,14 @@
2828
</span>
2929
</component>
3030
<SharingEntryQuickShareSelect
31+
v-if="!config.sharingDialogEnabled"
3132
:share="share"
3233
:file-info="fileInfo"
3334
@open-sharing-details="openShareDetailsForCustomSettings(share)" />
3435
</div>
3536
<ShareExpiryTime v-if="share && share.expireDate" :share="share" />
3637
<NcButton
37-
v-if="share.canEdit"
38+
v-if="share.canEdit && !config.sharingDialogEnabled"
3839
class="sharing-entry__action"
3940
data-cy-files-sharing-share-actions
4041
:aria-label="t('files_sharing', 'Open Sharing Details')"
@@ -44,27 +45,57 @@
4445
<DotsHorizontalIcon :size="20" />
4546
</template>
4647
</NcButton>
48+
<!-- Unified dialog: edit + delete as a simple dual button -->
49+
<template v-else-if="config.sharingDialogEnabled">
50+
<NcButton
51+
v-if="share.canEdit"
52+
class="sharing-entry__action"
53+
:aria-label="t('files_sharing', 'Edit share')"
54+
variant="tertiary"
55+
@click="openEditDialog">
56+
<template #icon>
57+
<PencilIcon :size="20" />
58+
</template>
59+
</NcButton>
60+
<NcButton
61+
v-if="share.canDelete"
62+
class="sharing-entry__action"
63+
:aria-label="t('files_sharing', 'Delete share')"
64+
variant="tertiary"
65+
@click="confirmDelete">
66+
<template #icon>
67+
<DeleteIcon :size="20" />
68+
</template>
69+
</NcButton>
70+
</template>
4771
</li>
4872
</template>
4973

5074
<script>
75+
import { DialogBuilder } from '@nextcloud/dialogs'
5176
import { ShareType } from '@nextcloud/sharing'
5277
import NcAvatar from '@nextcloud/vue/components/NcAvatar'
5378
import NcButton from '@nextcloud/vue/components/NcButton'
5479
import NcSelect from '@nextcloud/vue/components/NcSelect'
80+
import DeleteIcon from 'vue-material-design-icons/Delete.vue'
5581
import DotsHorizontalIcon from 'vue-material-design-icons/DotsHorizontal.vue'
82+
import PencilIcon from 'vue-material-design-icons/Pencil.vue'
5683
import ShareExpiryTime from './ShareExpiryTime.vue'
5784
import SharingEntryQuickShareSelect from './SharingEntryQuickShareSelect.vue'
5885
import ShareDetails from '../mixins/ShareDetails.js'
5986
import SharesMixin from '../mixins/SharesMixin.js'
87+
import logger from '../services/logger.ts'
88+
import { openShareEditDialog } from '../services/SharingDialog.ts'
6089
6190
export default {
6291
name: 'SharingEntry',
6392
6493
components: {
6594
NcButton,
6695
NcAvatar,
96+
DeleteIcon,
6797
DotsHorizontalIcon,
98+
PencilIcon,
6899
NcSelect,
69100
ShareExpiryTime,
70101
SharingEntryQuickShareSelect,
@@ -128,6 +159,54 @@ export default {
128159
return (typeof this.share.status === 'object' && !Array.isArray(this.share.status))
129160
},
130161
},
162+
163+
methods: {
164+
/**
165+
* Open the unified sharing dialog to edit this share.
166+
*/
167+
async openEditDialog() {
168+
try {
169+
await openShareEditDialog(this.share.id, this.fileInfo.node)
170+
} catch (error) {
171+
logger.error('Failed to open the sharing dialog', { error })
172+
}
173+
},
174+
175+
/**
176+
* Ask for confirmation before deleting the share.
177+
*/
178+
async confirmDelete() {
179+
let confirmed = false
180+
const dialog = (new DialogBuilder())
181+
.setName(t('files_sharing', 'Delete share'))
182+
.setText(t('files_sharing', 'Are you sure you want to delete this share? This operation cannot be undone.'))
183+
.setButtons([
184+
{
185+
label: t('files_sharing', 'Cancel'),
186+
variant: 'secondary',
187+
callback: () => {},
188+
},
189+
{
190+
label: t('files_sharing', 'Delete'),
191+
variant: 'error',
192+
callback: () => {
193+
confirmed = true
194+
},
195+
},
196+
])
197+
.build()
198+
199+
try {
200+
await dialog.show()
201+
} catch (error) {
202+
logger.debug('Delete confirmation dialog closed', { error })
203+
}
204+
205+
if (confirmed) {
206+
this.onDelete()
207+
}
208+
},
209+
},
131210
}
132211
</script>
133212

apps/files_sharing/src/components/SharingEntryLink.vue

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
{{ subtitle }}
2222
</p>
2323
<SharingEntryQuickShareSelect
24-
v-if="share && share.permissions !== undefined"
24+
v-if="share && share.permissions !== undefined && !config.sharingDialogEnabled"
2525
:share="share"
2626
:file-info="fileInfo"
2727
@open-sharing-details="openShareDetailsForCustomSettings(share)" />
@@ -151,6 +151,7 @@
151151
<template v-if="share">
152152
<template v-if="share.canEdit && canReshare">
153153
<NcActionButton
154+
v-if="!config.sharingDialogEnabled"
154155
:disabled="saving"
155156
:close-after-click="true"
156157
@click.prevent="openSharingDetails">
@@ -159,6 +160,16 @@
159160
</template>
160161
{{ t('files_sharing', 'Customize link') }}
161162
</NcActionButton>
163+
<NcActionButton
164+
v-else
165+
:disabled="saving"
166+
:close-after-click="true"
167+
@click.prevent="openEditDialog">
168+
<template #icon>
169+
<Tune :size="20" />
170+
</template>
171+
{{ t('files_sharing', 'Customize link') }}
172+
</NcActionButton>
162173
</template>
163174

164175
<NcActionButton
@@ -279,6 +290,7 @@ import ShareDetails from '../mixins/ShareDetails.js'
279290
import SharesMixin from '../mixins/SharesMixin.js'
280291
import Share from '../models/Share.ts'
281292
import logger from '../services/logger.ts'
293+
import { openShareEditDialog } from '../services/SharingDialog.ts'
282294
import GeneratePassword from '../utils/GeneratePassword.ts'
283295
284296
export default {
@@ -599,6 +611,17 @@ export default {
599611
},
600612
601613
methods: {
614+
/**
615+
* Open the unified sharing dialog to edit this link share.
616+
*/
617+
async openEditDialog() {
618+
try {
619+
await openShareEditDialog(this.share.id, this.fileInfo.node)
620+
} catch (error) {
621+
logger.error('Failed to open the sharing dialog', { error })
622+
}
623+
},
624+
602625
/**
603626
* Check if the share requires review
604627
*
@@ -866,6 +889,7 @@ export default {
866889
justify-content: space-between;
867890
flex: 1 0;
868891
min-width: 0;
892+
align-items: center;
869893
}
870894
871895
&__desc {

apps/files_sharing/src/services/ConfigService.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55
import { getCapabilities } from '@nextcloud/capabilities'
66
import { loadState } from '@nextcloud/initial-state'
7+
import { isSharingDialogAvailable } from './SharingDialog.ts'
78

89
type PasswordPolicySettings = {
910
enforceNonCommonPassword: boolean
@@ -354,4 +355,13 @@ export default class Config {
354355
get showExternalSharing(): boolean {
355356
return loadState('files_sharing', 'showExternalSharing', true)
356357
}
358+
359+
/**
360+
* Whether the new unified sharing dialog replaces the legacy inline sharing UI.
361+
* Derived from the server capabilities: when the unified sharing API is not
362+
* advertised (capability empty), the legacy inputs and menus are used instead.
363+
*/
364+
get sharingDialogEnabled(): boolean {
365+
return isSharingDialogAvailable()
366+
}
357367
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import type { Node } from '@nextcloud/files'
7+
8+
import { getShare, isSharingDialogAvailable, openSharingDialog } from '@nextcloud/sharing/dialog'
9+
10+
export { isSharingDialogAvailable }
11+
12+
/**
13+
* Open the unified sharing dialog to create a new share for a node.
14+
*
15+
* @param node The file or folder to share
16+
*/
17+
export async function openShareCreateDialog(node: Node): Promise<unknown> {
18+
return openSharingDialog(node)
19+
}
20+
21+
/**
22+
* Open the unified sharing dialog to edit an existing share.
23+
*
24+
* @param shareId The share id (mapped to the unified API by the legacy bridge)
25+
* @param node The backing node, used for the dialog title
26+
*/
27+
export async function openShareEditDialog(shareId: string | number, node?: Node): Promise<unknown> {
28+
const share = await getShare(String(shareId))
29+
return share.showDialog(node)
30+
}

apps/files_sharing/src/views/SharingTab.vue

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@
2727
</SharingEntrySimple>
2828
</ul>
2929

30+
<!-- Unified sharing dialog entry point (replaces the inline inputs) -->
31+
<NcButton
32+
v-if="config.sharingDialogEnabled"
33+
class="sharingTab__share-button"
34+
variant="primary"
35+
wide
36+
@click="openShareDialog">
37+
<template #icon>
38+
<ShareVariantIcon :size="20" />
39+
</template>
40+
{{ t('files_sharing', 'Share') }}
41+
</NcButton>
42+
3043
<section>
3144
<div class="section-header">
3245
<h4>{{ t('files_sharing', 'Internal shares') }}</h4>
@@ -48,7 +61,7 @@
4861
</div>
4962
<!-- add new share input -->
5063
<SharingInput
51-
v-if="!loading"
64+
v-if="!loading && !config.sharingDialogEnabled"
5265
:can-reshare="canReshare"
5366
:file-info="fileInfo"
5467
:link-shares="linkShares"
@@ -92,7 +105,7 @@
92105
</NcPopover>
93106
</div>
94107
<SharingInput
95-
v-if="!loading"
108+
v-if="!loading && !config.sharingDialogEnabled"
96109
:can-reshare="canReshare"
97110
:file-info="fileInfo"
98111
:link-shares="linkShares"
@@ -191,6 +204,7 @@ import NcButton from '@nextcloud/vue/components/NcButton'
191204
import NcCollectionList from '@nextcloud/vue/components/NcCollectionList'
192205
import NcPopover from '@nextcloud/vue/components/NcPopover'
193206
import InfoIcon from 'vue-material-design-icons/InformationOutline.vue'
207+
import ShareVariantIcon from 'vue-material-design-icons/ShareVariant.vue'
194208
import SharingEntryInternal from '../components/SharingEntryInternal.vue'
195209
import SharingEntrySimple from '../components/SharingEntrySimple.vue'
196210
import SharingInput from '../components/SharingInput.vue'
@@ -204,6 +218,7 @@ import ShareDetails from '../mixins/ShareDetails.js'
204218
import Share from '../models/Share.ts'
205219
import Config from '../services/ConfigService.ts'
206220
import logger from '../services/logger.ts'
221+
import { openShareCreateDialog } from '../services/SharingDialog.ts'
207222
import { shareWithTitle } from '../utils/SharedWithMe.js'
208223
209224
const productName = window.OC.theme.productName
@@ -217,6 +232,7 @@ export default {
217232
NcButton,
218233
NcCollectionList,
219234
NcPopover,
235+
ShareVariantIcon,
220236
SharingEntryInternal,
221237
SharingEntrySimple,
222238
SharingInherited,
@@ -346,6 +362,17 @@ export default {
346362
},
347363
348364
methods: {
365+
/**
366+
* Open the unified sharing dialog to create a share for the current node.
367+
*/
368+
async openShareDialog() {
369+
try {
370+
await openShareCreateDialog(this.fileInfo.node)
371+
} catch (error) {
372+
logger.error('Failed to open the sharing dialog', { error })
373+
}
374+
},
375+
349376
/**
350377
* Get the existing shares infos
351378
*/

0 commit comments

Comments
 (0)