aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files_sharing/src
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files_sharing/src')
-rw-r--r--apps/files_sharing/src/components/ShareExpiryTime.vue2
-rw-r--r--apps/files_sharing/src/components/SharingEntryLink.vue38
-rw-r--r--apps/files_sharing/src/components/SharingInput.vue11
-rw-r--r--apps/files_sharing/src/files_filters/AccountFilter.ts6
-rw-r--r--apps/files_sharing/src/files_views/publicFileDrop.ts7
-rw-r--r--apps/files_sharing/src/public-file-request.ts57
-rw-r--r--apps/files_sharing/src/public-nickname-handler.ts86
-rw-r--r--apps/files_sharing/src/services/ConfigService.ts9
-rw-r--r--apps/files_sharing/src/services/GuestNameValidity.ts45
-rw-r--r--apps/files_sharing/src/views/FilesViewFileDropEmptyContent.vue93
-rw-r--r--apps/files_sharing/src/views/PublicAuthPrompt.vue123
-rw-r--r--apps/files_sharing/src/views/SharingDetailsTab.vue29
-rw-r--r--apps/files_sharing/src/views/SharingTab.vue53
13 files changed, 320 insertions, 239 deletions
diff --git a/apps/files_sharing/src/components/ShareExpiryTime.vue b/apps/files_sharing/src/components/ShareExpiryTime.vue
index b789bc92db5..939142616e9 100644
--- a/apps/files_sharing/src/components/ShareExpiryTime.vue
+++ b/apps/files_sharing/src/components/ShareExpiryTime.vue
@@ -9,7 +9,7 @@
<NcButton v-if="expiryTime"
class="hint-icon"
type="tertiary"
- :aria-label="t('files_sharing', 'Share expiration: ') + new Date(expiryTime).toLocaleString()">
+ :aria-label="t('files_sharing', 'Share expiration: {date}', { date: new Date(expiryTime).toLocaleString() })">
<template #icon>
<ClockIcon :size="20" />
</template>
diff --git a/apps/files_sharing/src/components/SharingEntryLink.vue b/apps/files_sharing/src/components/SharingEntryLink.vue
index 781626a1ec9..702b876306f 100644
--- a/apps/files_sharing/src/components/SharingEntryLink.vue
+++ b/apps/files_sharing/src/components/SharingEntryLink.vue
@@ -223,13 +223,14 @@
</template>
<script>
+import { showError, showSuccess } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
+import { t } from '@nextcloud/l10n'
+import moment from '@nextcloud/moment'
import { generateUrl, getBaseUrl } from '@nextcloud/router'
-import { showError, showSuccess } from '@nextcloud/dialogs'
import { ShareType } from '@nextcloud/sharing'
-import VueQrcode from '@chenfengyuan/vue-qrcode'
-import moment from '@nextcloud/moment'
+import VueQrcode from '@chenfengyuan/vue-qrcode'
import NcActionButton from '@nextcloud/vue/components/NcActionButton'
import NcActionCheckbox from '@nextcloud/vue/components/NcActionCheckbox'
import NcActionInput from '@nextcloud/vue/components/NcActionInput'
@@ -258,7 +259,7 @@ import GeneratePassword from '../utils/GeneratePassword.ts'
import Share from '../models/Share.ts'
import SharesMixin from '../mixins/SharesMixin.js'
import ShareDetails from '../mixins/ShareDetails.js'
-import { getLoggerBuilder } from '@nextcloud/logger'
+import logger from '../services/logger.ts'
export default {
name: 'SharingEntryLink',
@@ -313,10 +314,6 @@ export default {
ExternalLegacyLinkActions: OCA.Sharing.ExternalLinkActions.state,
ExternalShareActions: OCA.Sharing.ExternalShareActions.state,
- logger: getLoggerBuilder()
- .setApp('files_sharing')
- .detectUser()
- .build(),
// tracks whether modal should be opened or not
showQRCode: false,
@@ -330,6 +327,8 @@ export default {
* @return {string}
*/
title() {
+ const l10nOptions = { escape: false /* no escape as this string is already escaped by Vue */ }
+
// if we have a valid existing share (not pending)
if (this.share && this.share.id) {
if (!this.isShareOwner && this.share.ownerDisplayName) {
@@ -337,26 +336,26 @@ export default {
return t('files_sharing', '{shareWith} by {initiator}', {
shareWith: this.share.shareWith,
initiator: this.share.ownerDisplayName,
- })
+ }, l10nOptions)
}
return t('files_sharing', 'Shared via link by {initiator}', {
initiator: this.share.ownerDisplayName,
- })
+ }, l10nOptions)
}
if (this.share.label && this.share.label.trim() !== '') {
if (this.isEmailShareType) {
if (this.isFileRequest) {
return t('files_sharing', 'File request ({label})', {
label: this.share.label.trim(),
- })
+ }, l10nOptions)
}
return t('files_sharing', 'Mail share ({label})', {
label: this.share.label.trim(),
- })
+ }, l10nOptions)
}
return t('files_sharing', 'Share link ({label})', {
label: this.share.label.trim(),
- })
+ }, l10nOptions)
}
if (this.isEmailShareType) {
if (!this.share.shareWith || this.share.shareWith.trim() === '') {
@@ -391,6 +390,7 @@ export default {
}
return null
},
+
passwordExpirationTime() {
if (this.share.passwordExpirationTime === null) {
return null
@@ -613,7 +613,7 @@ export default {
* @param {boolean} shareReviewComplete if the share was reviewed
*/
async onNewLinkShare(shareReviewComplete = false) {
- this.logger.debug('onNewLinkShare called (with this.share)', this.share)
+ logger.debug('onNewLinkShare called (with this.share)', this.share)
// do not run again if already loading
if (this.loading) {
return
@@ -628,7 +628,7 @@ export default {
shareDefaults.expiration = this.formatDateToString(this.config.defaultExpirationDate)
}
- this.logger.debug('Missing required properties?', this.enforcedPropertiesMissing)
+ logger.debug('Missing required properties?', this.enforcedPropertiesMissing)
// Do not push yet if we need a password or an expiration date: show pending menu
// A share would require a review for example is default expiration date is set but not enforced, this allows
// the user to review the share and remove the expiration date if they don't want it
@@ -636,7 +636,7 @@ export default {
this.pending = true
this.shareCreationComplete = false
- this.logger.info('Share policy requires a review or has mandated properties (password, expirationDate)...')
+ logger.info('Share policy requires a review or has mandated properties (password, expirationDate)...')
// ELSE, show the pending popovermenu
// if password default or enforced, pre-fill with random one
@@ -664,13 +664,13 @@ export default {
// if the share is valid, create it on the server
if (this.checkShare(this.share)) {
try {
- this.logger.info('Sending existing share to server', this.share)
+ logger.info('Sending existing share to server', this.share)
await this.pushNewLinkShare(this.share, true)
this.shareCreationComplete = true
- this.logger.info('Share created on server', this.share)
+ logger.info('Share created on server', this.share)
} catch (e) {
this.pending = false
- this.logger.error('Error creating share', e)
+ logger.error('Error creating share', e)
return false
}
return true
diff --git a/apps/files_sharing/src/components/SharingInput.vue b/apps/files_sharing/src/components/SharingInput.vue
index 49a39915e5e..b886ba95a17 100644
--- a/apps/files_sharing/src/components/SharingInput.vue
+++ b/apps/files_sharing/src/components/SharingInput.vue
@@ -194,11 +194,11 @@ export default {
let shareType = []
- if (this.isExternal) {
- shareType.push(ShareType.Remote)
- shareType.push(ShareType.RemoteGroup)
+ const remoteTypes = [ShareType.Remote, ShareType.RemoteGroup]
+
+ if (this.isExternal && !this.config.showFederatedSharesAsInternal) {
+ shareType.push(...remoteTypes)
} else {
- // Merge shareType array
shareType = shareType.concat([
ShareType.User,
ShareType.Group,
@@ -209,6 +209,9 @@ export default {
ShareType.ScienceMesh,
])
+ if (this.config.showFederatedSharesAsInternal) {
+ shareType.push(...remoteTypes)
+ }
}
if (getCapabilities().files_sharing.public.enabled === true && this.isExternal) {
diff --git a/apps/files_sharing/src/files_filters/AccountFilter.ts b/apps/files_sharing/src/files_filters/AccountFilter.ts
index 56bcef07200..4f185d9fd9c 100644
--- a/apps/files_sharing/src/files_filters/AccountFilter.ts
+++ b/apps/files_sharing/src/files_filters/AccountFilter.ts
@@ -10,6 +10,7 @@ import { ShareType } from '@nextcloud/sharing'
import Vue from 'vue'
import FileListFilterAccount from '../components/FileListFilterAccount.vue'
+import { isPublicShare } from '@nextcloud/sharing/public'
export interface IAccountData {
uid: string
@@ -152,5 +153,10 @@ class AccountFilter extends FileListFilter {
* Register the file list filter by owner or sharees
*/
export function registerAccountFilter() {
+ if (isPublicShare()) {
+ // We do not show the filter on public pages - it makes no sense
+ return
+ }
+
registerFileListFilter(new AccountFilter())
}
diff --git a/apps/files_sharing/src/files_views/publicFileDrop.ts b/apps/files_sharing/src/files_views/publicFileDrop.ts
index 0d782d48fc7..65756e83c74 100644
--- a/apps/files_sharing/src/files_views/publicFileDrop.ts
+++ b/apps/files_sharing/src/files_views/publicFileDrop.ts
@@ -4,7 +4,8 @@
*/
import type { VueConstructor } from 'vue'
-import { Folder, Permission, View, davRemoteURL, davRootPath, getNavigation } from '@nextcloud/files'
+import { Folder, Permission, View, getNavigation } from '@nextcloud/files'
+import { defaultRemoteURL, defaultRootPath } from '@nextcloud/files/dav'
import { loadState } from '@nextcloud/initial-state'
import { translate as t } from '@nextcloud/l10n'
import svgCloudUpload from '@mdi/svg/svg/cloud-upload.svg?raw'
@@ -45,8 +46,8 @@ export default () => {
// Fake a writeonly folder as root
folder: new Folder({
id: 0,
- source: `${davRemoteURL}${davRootPath}`,
- root: davRootPath,
+ source: `${defaultRemoteURL}${defaultRootPath}`,
+ root: defaultRootPath,
owner: null,
permissions: Permission.CREATE,
}),
diff --git a/apps/files_sharing/src/public-file-request.ts b/apps/files_sharing/src/public-file-request.ts
deleted file mode 100644
index 1d640c5ea5e..00000000000
--- a/apps/files_sharing/src/public-file-request.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-
-import { defineAsyncComponent } from 'vue'
-import { getBuilder } from '@nextcloud/browser-storage'
-import { getGuestNickname, setGuestNickname } from '@nextcloud/auth'
-import { getUploader } from '@nextcloud/upload'
-import { spawnDialog } from '@nextcloud/dialogs'
-
-import logger from './services/logger'
-
-const storage = getBuilder('files_sharing').build()
-
-/**
- * Setup file-request nickname header for the uploader
- * @param nickname The nickname
- */
-function registerFileRequestHeader(nickname: string) {
- const uploader = getUploader()
- uploader.setCustomHeader('X-NC-Nickname', encodeURIComponent(nickname))
- logger.debug('Nickname header registered for uploader', { headers: uploader.customHeaders })
-}
-
-/**
- * Callback when a nickname was chosen
- * @param nickname The chosen nickname
- */
-function onSetNickname(nickname: string): void {
- // Set the nickname
- setGuestNickname(nickname)
- // Set the dialog as shown
- storage.setItem('public-auth-prompt-shown', 'true')
- // Register header for uploader
- registerFileRequestHeader(nickname)
-}
-
-window.addEventListener('DOMContentLoaded', () => {
- const nickname = getGuestNickname() ?? ''
- const dialogShown = storage.getItem('public-auth-prompt-shown') !== null
-
- // If we don't have a nickname or the public auth prompt hasn't been shown yet, show it
- // We still show the prompt if the user has a nickname to double check
- if (!nickname || !dialogShown) {
- spawnDialog(
- defineAsyncComponent(() => import('./views/PublicAuthPrompt.vue')),
- {
- nickname,
- },
- onSetNickname as (...rest: unknown[]) => void,
- )
- } else {
- logger.debug('Public auth prompt already shown.', { nickname })
- registerFileRequestHeader(nickname)
- }
-})
diff --git a/apps/files_sharing/src/public-nickname-handler.ts b/apps/files_sharing/src/public-nickname-handler.ts
new file mode 100644
index 00000000000..02bdc641aaf
--- /dev/null
+++ b/apps/files_sharing/src/public-nickname-handler.ts
@@ -0,0 +1,86 @@
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import { getBuilder } from '@nextcloud/browser-storage'
+import { getGuestNickname, type NextcloudUser } from '@nextcloud/auth'
+import { getUploader } from '@nextcloud/upload'
+import { loadState } from '@nextcloud/initial-state'
+import { showGuestUserPrompt } from '@nextcloud/dialogs'
+import { t } from '@nextcloud/l10n'
+
+import logger from './services/logger'
+import { subscribe } from '@nextcloud/event-bus'
+
+const storage = getBuilder('files_sharing').build()
+
+// Setup file-request nickname header for the uploader
+const registerFileRequestHeader = (nickname: string) => {
+ const uploader = getUploader()
+ uploader.setCustomHeader('X-NC-Nickname', encodeURIComponent(nickname))
+ logger.debug('Nickname header registered for uploader', { headers: uploader.customHeaders })
+}
+
+// Callback when a nickname was chosen
+const onUserInfoChanged = (guest: NextcloudUser) => {
+ logger.debug('User info changed', { guest })
+ registerFileRequestHeader(guest.displayName ?? '')
+}
+
+// Monitor nickname changes
+subscribe('user:info:changed', onUserInfoChanged)
+
+window.addEventListener('DOMContentLoaded', () => {
+ const nickname = getGuestNickname() ?? ''
+ const dialogShown = storage.getItem('public-auth-prompt-shown') !== null
+
+ // Check if a nickname is mandatory
+ const isFileRequest = loadState('files_sharing', 'isFileRequest', false)
+
+ const owner = loadState('files_sharing', 'owner', '')
+ const ownerDisplayName = loadState('files_sharing', 'ownerDisplayName', '')
+ const label = loadState('files_sharing', 'label', '')
+ const filename = loadState('files_sharing', 'filename', '')
+
+ // If the owner provided a custom label, use it instead of the filename
+ const folder = label || filename
+
+ const options = {
+ nickname,
+ notice: t('files_sharing', 'To upload files to {folder}, you need to provide your name first.', { folder }),
+ subtitle: undefined as string | undefined,
+ title: t('files_sharing', 'Upload files to {folder}', { folder }),
+ }
+
+ // If the guest already has a nickname, we just make them double check
+ if (nickname) {
+ options.notice = t('files_sharing', 'Please confirm your name to upload files to {folder}', { folder })
+ }
+
+ // If the account owner set their name as public,
+ // we show it in the subtitle
+ if (owner) {
+ options.subtitle = t('files_sharing', '{ownerDisplayName} shared a folder with you.', { ownerDisplayName })
+ }
+
+ // If this is a file request, then we need a nickname
+ if (isFileRequest) {
+ // If we don't have a nickname or the public auth prompt hasn't been shown yet, show it
+ // We still show the prompt if the user has a nickname to double check
+ if (!nickname || !dialogShown) {
+ logger.debug('Showing public auth prompt.', { nickname })
+ showGuestUserPrompt(options)
+ }
+ return
+ }
+
+ if (!dialogShown && !nickname) {
+ logger.debug('Public auth prompt not shown yet but nickname is not mandatory.', { nickname })
+ return
+ }
+
+ // Else, we just register the nickname header if any.
+ logger.debug('Public auth prompt already shown.', { nickname })
+ registerFileRequestHeader(nickname)
+})
diff --git a/apps/files_sharing/src/services/ConfigService.ts b/apps/files_sharing/src/services/ConfigService.ts
index 09fdca13598..2114e2d1bae 100644
--- a/apps/files_sharing/src/services/ConfigService.ts
+++ b/apps/files_sharing/src/services/ConfigService.ts
@@ -3,6 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { getCapabilities } from '@nextcloud/capabilities'
+import { loadState } from '@nextcloud/initial-state'
type PasswordPolicyCapabilities = {
enforceNonCommonPassword: boolean
@@ -306,4 +307,12 @@ export default class Config {
return this._capabilities?.files_sharing?.public?.custom_tokens
}
+ /**
+ * Show federated shares as internal shares
+ * @return {boolean}
+ */
+ get showFederatedSharesAsInternal(): boolean {
+ return loadState('files_sharing', 'showFederatedSharesAsInternal', false)
+ }
+
}
diff --git a/apps/files_sharing/src/services/GuestNameValidity.ts b/apps/files_sharing/src/services/GuestNameValidity.ts
new file mode 100644
index 00000000000..0557c5253ca
--- /dev/null
+++ b/apps/files_sharing/src/services/GuestNameValidity.ts
@@ -0,0 +1,45 @@
+/*!
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+import { InvalidFilenameError, InvalidFilenameErrorReason, validateFilename } from '@nextcloud/files'
+import { t } from '@nextcloud/l10n'
+
+/**
+ * Get the validity of a filename (empty if valid).
+ * This can be used for `setCustomValidity` on input elements
+ * @param name The filename
+ * @param escape Escape the matched string in the error (only set when used in HTML)
+ */
+export function getGuestNameValidity(name: string, escape = false): string {
+ if (name.trim() === '') {
+ return t('files', 'Names must not be empty.')
+ }
+
+ if (name.startsWith('.')) {
+ return t('files', 'Names must not start with a dot.')
+ }
+
+ try {
+ validateFilename(name)
+ return ''
+ } catch (error) {
+ if (!(error instanceof InvalidFilenameError)) {
+ throw error
+ }
+
+ switch (error.reason) {
+ case InvalidFilenameErrorReason.Character:
+ return t('files', '"{char}" is not allowed inside a name.', { char: error.segment }, undefined, { escape })
+ case InvalidFilenameErrorReason.ReservedName:
+ return t('files', '"{segment}" is a reserved name and not allowed.', { segment: error.segment }, undefined, { escape: false })
+ case InvalidFilenameErrorReason.Extension:
+ if (error.segment.match(/\.[a-z]/i)) {
+ return t('files', '"{extension}" is not an allowed name.', { extension: error.segment }, undefined, { escape: false })
+ }
+ return t('files', 'Names must not end with "{extension}".', { extension: error.segment }, undefined, { escape: false })
+ default:
+ return t('files', 'Invalid name.')
+ }
+ }
+}
diff --git a/apps/files_sharing/src/views/FilesViewFileDropEmptyContent.vue b/apps/files_sharing/src/views/FilesViewFileDropEmptyContent.vue
index 5571e5e9f5d..33fec9af028 100644
--- a/apps/files_sharing/src/views/FilesViewFileDropEmptyContent.vue
+++ b/apps/files_sharing/src/views/FilesViewFileDropEmptyContent.vue
@@ -5,13 +5,29 @@
<template>
<NcEmptyContent class="file-drop-empty-content"
data-cy-files-sharing-file-drop
- :name="t('files_sharing', 'File drop')">
+ :name="name">
<template #icon>
<NcIconSvgWrapper :svg="svgCloudUpload" />
</template>
<template #description>
- {{ t('files_sharing', 'Upload files to {foldername}.', { foldername }) }}
- {{ disclaimer === '' ? '' : t('files_sharing', 'By uploading files, you agree to the terms of service.') }}
+ <p>
+ {{ shareNote || t('files_sharing', 'Upload files to {foldername}.', { foldername }) }}
+ </p>
+ <p v-if="disclaimer">
+ {{ t('files_sharing', 'By uploading files, you agree to the terms of service.') }}
+ </p>
+ <NcNoteCard v-if="getSortedUploads().length"
+ class="file-drop-empty-content__note-card"
+ type="success">
+ <h2 id="file-drop-empty-content__heading">
+ {{ t('files_sharing', 'Successfully uploaded files') }}
+ </h2>
+ <ul aria-labelledby="file-drop-empty-content__heading" class="file-drop-empty-content__list">
+ <li v-for="file in getSortedUploads()" :key="file">
+ {{ file }}
+ </li>
+ </ul>
+ </NcNoteCard>
</template>
<template #action>
<template v-if="disclaimer">
@@ -34,16 +50,24 @@
</NcEmptyContent>
</template>
+<script lang="ts">
+/* eslint-disable import/first */
+
+// We need this on module level rather than on the instance as view will be refreshed by the files app after uploading
+const uploads = new Set<string>()
+</script>
+
<script setup lang="ts">
import { loadState } from '@nextcloud/initial-state'
import { translate as t } from '@nextcloud/l10n'
-import { getUploader, UploadPicker } from '@nextcloud/upload'
+import { getUploader, UploadPicker, UploadStatus } from '@nextcloud/upload'
import { ref } from 'vue'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcDialog from '@nextcloud/vue/components/NcDialog'
import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
+import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
import svgCloudUpload from '@mdi/svg/svg/cloud-upload.svg?raw'
defineProps<{
@@ -51,17 +75,62 @@ defineProps<{
}>()
const disclaimer = loadState<string>('files_sharing', 'disclaimer', '')
+const shareLabel = loadState<string>('files_sharing', 'label', '')
+const shareNote = loadState<string>('files_sharing', 'note', '')
+
+const name = shareLabel || t('files_sharing', 'File drop')
+
const showDialog = ref(false)
const uploadDestination = getUploader().destination
-</script>
-<style scoped>
-:deep(.terms-of-service-dialog) {
- min-height: min(100px, 20vh);
+getUploader()
+ .addNotifier((upload) => {
+ if (upload.status === UploadStatus.FINISHED && upload.file.name) {
+ // if a upload is finished and is not a meta upload (name is set)
+ // then we add the upload to the list of finished uploads to be shown to the user
+ uploads.add(upload.file.name)
+ }
+ })
+
+/**
+ * Get the previous uploads as sorted list
+ */
+function getSortedUploads() {
+ return [...uploads].sort((a, b) => a.localeCompare(b))
}
-/* TODO fix in library */
-.file-drop-empty-content :deep(.empty-content__action) {
- display: flex;
- gap: var(--default-grid-baseline);
+</script>
+
+<style scoped lang="scss">
+.file-drop-empty-content {
+ margin: auto;
+ max-width: max(50vw, 300px);
+
+ .file-drop-empty-content__note-card {
+ width: fit-content;
+ margin-inline: auto;
+ }
+
+ #file-drop-empty-content__heading {
+ margin-block: 0 10px;
+ font-weight: bold;
+ font-size: 20px;
+ }
+
+ .file-drop-empty-content__list {
+ list-style: inside;
+ max-height: min(350px, 33vh);
+ overflow-y: scroll;
+ padding-inline-end: calc(2 * var(--default-grid-baseline));
+ }
+
+ :deep(.terms-of-service-dialog) {
+ min-height: min(100px, 20vh);
+ }
+
+ /* TODO fix in library */
+ :deep(.empty-content__action) {
+ display: flex;
+ gap: var(--default-grid-baseline);
+ }
}
</style>
diff --git a/apps/files_sharing/src/views/PublicAuthPrompt.vue b/apps/files_sharing/src/views/PublicAuthPrompt.vue
deleted file mode 100644
index 39f5adc4650..00000000000
--- a/apps/files_sharing/src/views/PublicAuthPrompt.vue
+++ /dev/null
@@ -1,123 +0,0 @@
-<!--
- - SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
- - SPDX-License-Identifier: AGPL-3.0-or-later
--->
-
-<template>
- <NcDialog :buttons="dialogButtons"
- class="public-auth-prompt"
- data-cy-public-auth-prompt-dialog
- is-form
- :can-close="false"
- :name="dialogName"
- @submit="$emit('close', name)">
- <p v-if="owner" class="public-auth-prompt__subtitle">
- {{ t('files_sharing', '{ownerDisplayName} shared a folder with you.', { ownerDisplayName }) }}
- </p>
-
- <!-- Header -->
- <NcNoteCard class="public-auth-prompt__header"
- :text="t('files_sharing', 'To upload files, you need to provide your name first.')"
- type="info" />
-
- <!-- Form -->
- <NcTextField ref="input"
- class="public-auth-prompt__input"
- data-cy-public-auth-prompt-dialog-name
- :label="t('files_sharing', 'Name')"
- :placeholder="t('files_sharing', 'Enter your name')"
- minlength="2"
- name="name"
- required
- :value.sync="name" />
- </NcDialog>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue'
-import { t } from '@nextcloud/l10n'
-
-import NcDialog from '@nextcloud/vue/components/NcDialog'
-import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
-import NcTextField from '@nextcloud/vue/components/NcTextField'
-import { loadState } from '@nextcloud/initial-state'
-
-export default defineComponent({
- name: 'PublicAuthPrompt',
-
- components: {
- NcDialog,
- NcNoteCard,
- NcTextField,
- },
-
- props: {
- /**
- * Preselected nickname
- * @default '' No name preselected by default
- */
- nickname: {
- type: String,
- default: '',
- },
- },
-
- setup() {
- return {
- t,
-
- owner: loadState('files_sharing', 'owner', ''),
- ownerDisplayName: loadState('files_sharing', 'ownerDisplayName', ''),
- label: loadState('files_sharing', 'label', ''),
- note: loadState('files_sharing', 'note', ''),
- filename: loadState('files_sharing', 'filename', ''),
- }
- },
-
- data() {
- return {
- name: '',
- }
- },
-
- computed: {
- dialogName() {
- return this.t('files_sharing', 'Upload files to {folder}', { folder: this.label || this.filename })
- },
- dialogButtons() {
- return [{
- label: t('files_sharing', 'Submit name'),
- type: 'primary',
- nativeType: 'submit',
- }]
- },
- },
-
- watch: {
- /** Reset name to pre-selected nickname (e.g. Talk / Collabora ) */
- nickname: {
- handler() {
- this.name = this.nickname
- },
- immediate: true,
- },
- },
-})
-</script>
-<style scoped lang="scss">
-.public-auth-prompt {
- &__subtitle {
- // Smaller than dialog title
- font-size: 1.25em;
- margin-block: 0 calc(3 * var(--default-grid-baseline));
- }
-
- &__header {
- margin-block: 0 calc(3 * var(--default-grid-baseline));
- }
-
- &__input {
- margin-block: calc(4 * var(--default-grid-baseline)) calc(2 * var(--default-grid-baseline));
- }
-}
-</style>
diff --git a/apps/files_sharing/src/views/SharingDetailsTab.vue b/apps/files_sharing/src/views/SharingDetailsTab.vue
index 5b778223c8b..f1fb78e548b 100644
--- a/apps/files_sharing/src/views/SharingDetailsTab.vue
+++ b/apps/files_sharing/src/views/SharingDetailsTab.vue
@@ -226,19 +226,6 @@
{{ t('files_sharing', 'Delete') }}
</NcCheckboxRadioSwitch>
</section>
- <div class="sharingTabDetailsView__delete">
- <NcButton v-if="!isNewShare"
- :aria-label="t('files_sharing', 'Delete share')"
- :disabled="false"
- :readonly="false"
- type="tertiary"
- @click.prevent="removeShare">
- <template #icon>
- <CloseIcon :size="16" />
- </template>
- {{ t('files_sharing', 'Delete share') }}
- </NcButton>
- </div>
</section>
</div>
</div>
@@ -249,6 +236,19 @@
@click="cancel">
{{ t('files_sharing', 'Cancel') }}
</NcButton>
+ <div class="sharingTabDetailsView__delete">
+ <NcButton v-if="!isNewShare"
+ :aria-label="t('files_sharing', 'Delete share')"
+ :disabled="false"
+ :readonly="false"
+ variant="tertiary"
+ @click.prevent="removeShare">
+ <template #icon>
+ <CloseIcon :size="20" />
+ </template>
+ {{ t('files_sharing', 'Delete share') }}
+ </NcButton>
+ </div>
<NcButton type="primary"
data-cy-files-sharing-share-editor-action="save"
:disabled="creating"
@@ -905,8 +905,9 @@ export default {
this.advancedSectionAccordionExpanded = true
}
- if (this.share.note) {
+ if (this.isValidShareAttribute(this.share.note)) {
this.writeNoteToRecipientIsChecked = true
+ this.advancedSectionAccordionExpanded = true
}
},
diff --git a/apps/files_sharing/src/views/SharingTab.vue b/apps/files_sharing/src/views/SharingTab.vue
index e9e068a7c1d..82a11dea2e0 100644
--- a/apps/files_sharing/src/views/SharingTab.vue
+++ b/apps/files_sharing/src/views/SharingTab.vue
@@ -50,7 +50,7 @@
:link-shares="linkShares"
:reshare="reshare"
:shares="shares"
- :placeholder="t('files_sharing', 'Share with accounts and teams')"
+ :placeholder="internalShareInputPlaceholder"
@open-sharing-details="toggleShareDetailsView" />
<!-- other shares list -->
@@ -90,7 +90,7 @@
:file-info="fileInfo"
:link-shares="linkShares"
:is-external="true"
- :placeholder="t('files_sharing', 'Email, federated cloud id')"
+ :placeholder="externalShareInputPlaceholder"
:reshare="reshare"
:shares="shares"
@open-sharing-details="toggleShareDetailsView" />
@@ -100,7 +100,7 @@
:file-info="fileInfo"
@open-sharing-details="toggleShareDetailsView" />
<!-- link shares list -->
- <SharingLinkList v-if="!loading"
+ <SharingLinkList v-if="!loading && isLinkSharingAllowed"
ref="linkShareList"
:can-reshare="canReshare"
:file-info="fileInfo"
@@ -157,6 +157,7 @@
<script>
import { getCurrentUser } from '@nextcloud/auth'
+import { getCapabilities } from '@nextcloud/capabilities'
import { orderBy } from '@nextcloud/files'
import { loadState } from '@nextcloud/initial-state'
import { generateOcsUrl } from '@nextcloud/router'
@@ -242,13 +243,45 @@ export default {
* @return {boolean}
*/
isSharedWithMe() {
- return Object.keys(this.sharedWithMe).length > 0
+ return this.sharedWithMe !== null
+ && this.sharedWithMe !== undefined
+ },
+
+ /**
+ * Is link sharing allowed for the current user?
+ *
+ * @return {boolean}
+ */
+ isLinkSharingAllowed() {
+ const currentUser = getCurrentUser()
+ if (!currentUser) {
+ return false
+ }
+
+ const capabilities = getCapabilities()
+ const publicSharing = capabilities.files_sharing?.public || {}
+ return publicSharing.enabled === true
},
canReshare() {
return !!(this.fileInfo.permissions & OC.PERMISSION_SHARE)
|| !!(this.reshare && this.reshare.hasSharePermission && this.config.isResharingAllowed)
},
+
+ internalShareInputPlaceholder() {
+ return this.config.showFederatedSharesAsInternal
+ ? t('files_sharing', 'Share with accounts, teams, federated cloud IDs')
+ : t('files_sharing', 'Share with accounts and teams')
+ },
+
+ externalShareInputPlaceholder() {
+ if (!this.isLinkSharingAllowed) {
+ return t('files_sharing', 'Federated cloud ID')
+ }
+ return this.config.showFederatedSharesAsInternal
+ ? t('files_sharing', 'Email')
+ : t('files_sharing', 'Email, federated cloud ID')
+ },
},
methods: {
@@ -369,7 +402,11 @@ export default {
if ([ShareType.Link, ShareType.Email].includes(share.type)) {
this.linkShares.push(share)
} else if ([ShareType.Remote, ShareType.RemoteGroup].includes(share.type)) {
- this.externalShares.push(share)
+ if (this.config.showFederatedSharesAsInternal) {
+ this.shares.push(share)
+ } else {
+ this.externalShares.push(share)
+ }
} else {
this.shares.push(share)
}
@@ -439,7 +476,11 @@ export default {
if (share.type === ShareType.Email) {
this.linkShares.unshift(share)
} else if ([ShareType.Remote, ShareType.RemoteGroup].includes(share.type)) {
- this.externalShares.unshift(share)
+ if (this.config.showFederatedSharesAsInternal) {
+ this.shares.unshift(share)
+ } else {
+ this.externalShares.unshift(share)
+ }
} else {
this.shares.unshift(share)
}