Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 5 additions & 0 deletions apps/files_sharing/lib/Config/ConfigLexicon.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class ConfigLexicon implements ILexicon {
public const EXCLUDE_RESHARE_FROM_EDIT = 'shareapi_exclude_reshare_from_edit';
public const UPDATE_CUTOFF_TIME = 'update_cutoff_time';
public const USER_NEEDS_SHARE_REFRESH = 'user_needs_share_refresh';
public const SHARING_DIALOG_ENABLED = 'sharing_dialog_enabled';

#[\Override]
public function getStrictness(): Strictness {
Expand All @@ -40,6 +41,10 @@ public function getAppConfigs(): array {
new Entry(self::EXCLUDE_RESHARE_FROM_EDIT, ValueType::BOOL, false, 'Exclude reshare permission from "Allow editing" bundled permissions'),

new Entry(self::UPDATE_CUTOFF_TIME, ValueType::FLOAT, 3.0, 'Maximum time in second during which we update the share data immediately before switching to only marking the user'),

// Hidden killswitch: enables the new unified sharing dialog UI in the sidebar.
// Set to false to temporarily restore the legacy sharing inputs and menus.
new Entry(self::SHARING_DIALOG_ENABLED, ValueType::BOOL, true, 'Use the new unified sharing dialog in the files sidebar instead of the legacy inline UI', true),
];
}

Expand Down
3 changes: 3 additions & 0 deletions apps/files_sharing/lib/Listener/LoadSidebarListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,11 @@ public function handle(Event $event): void {
$showExternalSharing = $appConfig->getValueBool('files_sharing', 'outgoing_server2server_share_enabled', true)
|| $appConfig->getValueBool('core', 'shareapi_allow_links', true);

$sharingDialogEnabled = $appConfig->getValueBool('files_sharing', ConfigLexicon::SHARING_DIALOG_ENABLED, true);

$this->initialState->provideInitialState('showFederatedSharesAsInternal', $showFederatedAsInternal);
$this->initialState->provideInitialState('showFederatedSharesToTrustedServersAsInternal', $showFederatedToTrustedAsInternal);
$this->initialState->provideInitialState('showExternalSharing', $showExternalSharing);
$this->initialState->provideInitialState('sharingDialogEnabled', $sharingDialogEnabled);
}
}
79 changes: 78 additions & 1 deletion apps/files_sharing/src/components/SharingEntry.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@
</span>
</component>
<SharingEntryQuickShareSelect
v-if="!config.sharingDialogEnabled"
:share="share"
:file-info="fileInfo"
@open-sharing-details="openShareDetailsForCustomSettings(share)" />
</div>
<ShareExpiryTime v-if="share && share.expireDate" :share="share" />
<NcButton
v-if="share.canEdit"
v-if="share.canEdit && !config.sharingDialogEnabled"
class="sharing-entry__action"
data-cy-files-sharing-share-actions
:aria-label="t('files_sharing', 'Open Sharing Details')"
Expand All @@ -44,27 +45,57 @@
<DotsHorizontalIcon :size="20" />
</template>
</NcButton>
<!-- Unified dialog: edit + delete as a simple dual button -->
<template v-else-if="config.sharingDialogEnabled">
<NcButton
v-if="share.canEdit"
class="sharing-entry__action"
:aria-label="t('files_sharing', 'Edit share')"
variant="tertiary"
@click="openEditDialog">
<template #icon>
<PencilIcon :size="20" />
</template>
</NcButton>
<NcButton
v-if="share.canDelete"
class="sharing-entry__action"
:aria-label="t('files_sharing', 'Delete share')"
variant="tertiary"
@click="confirmDelete">
<template #icon>
<DeleteIcon :size="20" />
</template>
</NcButton>
</template>
</li>
</template>

<script>
import { DialogBuilder } from '@nextcloud/dialogs'
import { ShareType } from '@nextcloud/sharing'
import NcAvatar from '@nextcloud/vue/components/NcAvatar'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcSelect from '@nextcloud/vue/components/NcSelect'
import DeleteIcon from 'vue-material-design-icons/Delete.vue'
import DotsHorizontalIcon from 'vue-material-design-icons/DotsHorizontal.vue'
import PencilIcon from 'vue-material-design-icons/Pencil.vue'
import ShareExpiryTime from './ShareExpiryTime.vue'
import SharingEntryQuickShareSelect from './SharingEntryQuickShareSelect.vue'
import ShareDetails from '../mixins/ShareDetails.js'
import SharesMixin from '../mixins/SharesMixin.js'
import { openShareEditDialog } from '../services/SharingDialog.ts'
import logger from '../services/logger.ts'

export default {
name: 'SharingEntry',

components: {
NcButton,
NcAvatar,
DeleteIcon,
DotsHorizontalIcon,
PencilIcon,
NcSelect,
ShareExpiryTime,
SharingEntryQuickShareSelect,
Expand Down Expand Up @@ -130,6 +161,52 @@ export default {
},

methods: {
/**
* Open the unified sharing dialog to edit this share.
*/
async openEditDialog() {
try {
await openShareEditDialog(this.share.id, this.fileInfo.node)
} catch (error) {
logger.error('Failed to open the sharing dialog', { error })
}
},

/**
* Ask for confirmation before deleting the share.
*/
async confirmDelete() {
let confirmed = false
const dialog = (new DialogBuilder())
.setName(t('files_sharing', 'Delete share'))
.setText(t('files_sharing', 'Are you sure you want to delete this share? This operation cannot be undone.'))
.setButtons([
{
label: t('files_sharing', 'Cancel'),
variant: 'secondary',
callback: () => {},
},
{
label: t('files_sharing', 'Delete'),
variant: 'error',
callback: () => {
confirmed = true
},
},
])
.build()

try {
await dialog.show()
} catch (error) {
logger.debug('Delete confirmation dialog closed', { error })
}

if (confirmed) {
this.onDelete()
}
},

/**
* Save potential changed data on menu close
*/
Expand Down
25 changes: 24 additions & 1 deletion apps/files_sharing/src/components/SharingEntryLink.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
{{ subtitle }}
</p>
<SharingEntryQuickShareSelect
v-if="share && share.permissions !== undefined"
v-if="share && share.permissions !== undefined && !config.sharingDialogEnabled"
:share="share"
:file-info="fileInfo"
@open-sharing-details="openShareDetailsForCustomSettings(share)" />
Expand Down Expand Up @@ -152,6 +152,7 @@
<template v-if="share">
<template v-if="share.canEdit && canReshare">
<NcActionButton
v-if="!config.sharingDialogEnabled"
:disabled="saving"
:close-after-click="true"
@click.prevent="openSharingDetails">
Expand All @@ -160,6 +161,16 @@
</template>
{{ t('files_sharing', 'Customize link') }}
</NcActionButton>
<NcActionButton
v-else
:disabled="saving"
:close-after-click="true"
@click.prevent="openEditDialog">
<template #icon>
<Tune :size="20" />
</template>
{{ t('files_sharing', 'Customize link') }}
</NcActionButton>
</template>

<NcActionButton
Expand Down Expand Up @@ -279,6 +290,7 @@ import SidebarTabExternalActionLegacy from './SidebarTabExternal/SidebarTabExter
import ShareDetails from '../mixins/ShareDetails.js'
import SharesMixin from '../mixins/SharesMixin.js'
import Share from '../models/Share.ts'
import { openShareEditDialog } from '../services/SharingDialog.ts'
import logger from '../services/logger.ts'
import GeneratePassword from '../utils/GeneratePassword.ts'

Expand Down Expand Up @@ -628,6 +640,17 @@ export default {
},

methods: {
/**
* Open the unified sharing dialog to edit this link share.
*/
async openEditDialog() {
try {
await openShareEditDialog(this.share.id, this.fileInfo.node)
} catch (error) {
logger.error('Failed to open the sharing dialog', { error })
}
},

/**
* Check if the share requires review
*
Expand Down
9 changes: 9 additions & 0 deletions apps/files_sharing/src/services/ConfigService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,4 +344,13 @@ export default class Config {
get showExternalSharing(): boolean {
return loadState('files_sharing', 'showExternalSharing', true)
}

/**
* Whether the new unified sharing dialog replaces the legacy inline sharing UI.
* Hidden killswitch (appconfig files_sharing sharing_dialog_enabled); set to
* false to temporarily restore the legacy inputs and menus.
*/
get sharingDialogEnabled(): boolean {
return loadState('files_sharing', 'sharingDialogEnabled', true)
}
}
28 changes: 28 additions & 0 deletions apps/files_sharing/src/services/SharingDialog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { Node } from '@nextcloud/files'

import { getShare, openSharingDialog } from '@nextcloud/sharing/dialog'

/**
* Open the unified sharing dialog to create a new share for a node.
*
* @param node The file or folder to share
*/
export async function openShareCreateDialog(node: Node): Promise<unknown> {
return openSharingDialog(node)
}

/**
* Open the unified sharing dialog to edit an existing share.
*
* @param shareId The share id (mapped to the unified API by the legacy bridge)
* @param node The backing node, used for the dialog title
*/
export async function openShareEditDialog(shareId: string | number, node?: Node): Promise<unknown> {
const share = await getShare(String(shareId))
return share.showDialog(node)
}
31 changes: 29 additions & 2 deletions apps/files_sharing/src/views/SharingTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@
</SharingEntrySimple>
</ul>

<!-- Unified sharing dialog entry point (replaces the inline inputs) -->
<NcButton
v-if="config.sharingDialogEnabled"
class="sharingTab__share-button"
variant="primary"
wide
@click="openShareDialog">
<template #icon>
<ShareVariantIcon :size="20" />
</template>
{{ t('files_sharing', 'Share') }}
</NcButton>

<section>
<div class="section-header">
<h4>{{ t('files_sharing', 'Internal shares') }}</h4>
Expand All @@ -48,7 +61,7 @@
</div>
<!-- add new share input -->
<SharingInput
v-if="!loading"
v-if="!loading && !config.sharingDialogEnabled"
:can-reshare="canReshare"
:file-info="fileInfo"
:link-shares="linkShares"
Expand Down Expand Up @@ -92,7 +105,7 @@
</NcPopover>
</div>
<SharingInput
v-if="!loading"
v-if="!loading && !config.sharingDialogEnabled"
:can-reshare="canReshare"
:file-info="fileInfo"
:link-shares="linkShares"
Expand Down Expand Up @@ -191,6 +204,7 @@ import NcButton from '@nextcloud/vue/components/NcButton'
import NcCollectionList from '@nextcloud/vue/components/NcCollectionList'
import NcPopover from '@nextcloud/vue/components/NcPopover'
import InfoIcon from 'vue-material-design-icons/InformationOutline.vue'
import ShareVariantIcon from 'vue-material-design-icons/ShareVariant.vue'
import SharingEntryInternal from '../components/SharingEntryInternal.vue'
import SharingEntrySimple from '../components/SharingEntrySimple.vue'
import SharingInput from '../components/SharingInput.vue'
Expand All @@ -203,6 +217,7 @@ import SharingList from './SharingList.vue'
import ShareDetails from '../mixins/ShareDetails.js'
import Share from '../models/Share.ts'
import Config from '../services/ConfigService.ts'
import { openShareCreateDialog } from '../services/SharingDialog.ts'
import logger from '../services/logger.ts'
import { shareWithTitle } from '../utils/SharedWithMe.js'

Expand All @@ -217,6 +232,7 @@ export default {
NcButton,
NcCollectionList,
NcPopover,
ShareVariantIcon,
SharingEntryInternal,
SharingEntrySimple,
SharingInherited,
Expand Down Expand Up @@ -346,6 +362,17 @@ export default {
},

methods: {
/**
* Open the unified sharing dialog to create a share for the current node.
*/
async openShareDialog() {
try {
await openShareCreateDialog(this.fileInfo.node)
} catch (error) {
logger.error('Failed to open the sharing dialog', { error })
}
},

/**
* Get the existing shares infos
*/
Expand Down
Loading