aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files/src/utils
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files/src/utils')
-rw-r--r--apps/files/src/utils/actionUtils.ts4
-rw-r--r--apps/files/src/utils/fileUtils.ts38
-rw-r--r--apps/files/src/utils/filesViews.spec.ts73
-rw-r--r--apps/files/src/utils/filesViews.ts30
-rw-r--r--apps/files/src/utils/permissions.ts7
5 files changed, 132 insertions, 20 deletions
diff --git a/apps/files/src/utils/actionUtils.ts b/apps/files/src/utils/actionUtils.ts
index 730a1149229..f6d43727c29 100644
--- a/apps/files/src/utils/actionUtils.ts
+++ b/apps/files/src/utils/actionUtils.ts
@@ -49,7 +49,7 @@ export const executeAction = async (action: FileAction) => {
try {
// Set the loading marker
Vue.set(currentNode, 'status', NodeStatus.LOADING)
- activeStore.setActiveAction(action)
+ activeStore.activeAction = action
const success = await action.exec(currentNode, currentView, currentDir)
@@ -69,6 +69,6 @@ export const executeAction = async (action: FileAction) => {
} finally {
// Reset the loading marker
Vue.set(currentNode, 'status', undefined)
- activeStore.clearActiveAction()
+ activeStore.activeAction = undefined
}
}
diff --git a/apps/files/src/utils/fileUtils.ts b/apps/files/src/utils/fileUtils.ts
index 255c106740d..f0b974be21d 100644
--- a/apps/files/src/utils/fileUtils.ts
+++ b/apps/files/src/utils/fileUtils.ts
@@ -3,15 +3,15 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { FileType, type Node } from '@nextcloud/files'
-import { translate as t, translatePlural as n } from '@nextcloud/l10n'
+import { n } from '@nextcloud/l10n'
/**
* Extract dir and name from file path
*
- * @param {string} path the full path
- * @return {string[]} [dirPath, fileName]
+ * @param path - The full path
+ * @return [dirPath, fileName]
*/
-export const extractFilePaths = function(path) {
+export function extractFilePaths(path: string): [string, string] {
const pathSections = path.split('/')
const fileName = pathSections[pathSections.length - 1]
const dirPath = pathSections.slice(0, pathSections.length - 1).join('/')
@@ -20,26 +20,28 @@ export const extractFilePaths = function(path) {
/**
* Generate a translated summary of an array of nodes
- * @param {Node[]} nodes the nodes to summarize
- * @return {string}
+ *
+ * @param nodes - The nodes to summarize
+ * @param hidden - The number of hidden nodes
*/
-export const getSummaryFor = (nodes: Node[]): string => {
+export function getSummaryFor(nodes: Node[], hidden = 0): string {
const fileCount = nodes.filter(node => node.type === FileType.File).length
const folderCount = nodes.filter(node => node.type === FileType.Folder).length
- if (fileCount === 0) {
- return n('files', '{folderCount} folder', '{folderCount} folders', folderCount, { folderCount })
- } else if (folderCount === 0) {
- return n('files', '{fileCount} file', '{fileCount} files', fileCount, { fileCount })
+ const summary: string[] = []
+ if (fileCount > 0 || folderCount === 0) {
+ const fileSummary = n('files', '%n file', '%n files', fileCount)
+ summary.push(fileSummary)
}
-
- if (fileCount === 1) {
- return n('files', '1 file and {folderCount} folder', '1 file and {folderCount} folders', folderCount, { folderCount })
+ if (folderCount > 0) {
+ const folderSummary = n('files', '%n folder', '%n folders', folderCount)
+ summary.push(folderSummary)
}
-
- if (folderCount === 1) {
- return n('files', '{fileCount} file and 1 folder', '{fileCount} files and 1 folder', fileCount, { fileCount })
+ if (hidden > 0) {
+ // TRANSLATORS: This is the number of hidden files or folders
+ const hiddenSummary = n('files', '%n hidden', '%n hidden', hidden)
+ summary.push(hiddenSummary)
}
- return t('files', '{fileCount} files and {folderCount} folders', { fileCount, folderCount })
+ return summary.join(' ยท ')
}
diff --git a/apps/files/src/utils/filesViews.spec.ts b/apps/files/src/utils/filesViews.spec.ts
new file mode 100644
index 00000000000..03b0bb9aeb0
--- /dev/null
+++ b/apps/files/src/utils/filesViews.spec.ts
@@ -0,0 +1,73 @@
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import { beforeEach, describe, expect, test } from 'vitest'
+import { defaultView, hasPersonalFilesView } from './filesViews.ts'
+
+describe('hasPersonalFilesView', () => {
+ beforeEach(() => removeInitialState())
+
+ test('enabled if user has unlimited quota', () => {
+ mockInitialState('files', 'storageStats', { quota: -1 })
+ expect(hasPersonalFilesView()).toBe(true)
+ })
+
+ test('enabled if user has limited quota', () => {
+ mockInitialState('files', 'storageStats', { quota: 1234 })
+ expect(hasPersonalFilesView()).toBe(true)
+ })
+
+ test('disabled if user has no quota', () => {
+ mockInitialState('files', 'storageStats', { quota: 0 })
+ expect(hasPersonalFilesView()).toBe(false)
+ })
+})
+
+describe('defaultView', () => {
+ beforeEach(removeInitialState)
+
+ test('Returns files view if set', () => {
+ mockInitialState('files', 'config', { default_view: 'files' })
+ expect(defaultView()).toBe('files')
+ })
+
+ test('Returns personal view if set and enabled', () => {
+ mockInitialState('files', 'config', { default_view: 'personal' })
+ mockInitialState('files', 'storageStats', { quota: -1 })
+ expect(defaultView()).toBe('personal')
+ })
+
+ test('Falls back to files if personal view is disabled', () => {
+ mockInitialState('files', 'config', { default_view: 'personal' })
+ mockInitialState('files', 'storageStats', { quota: 0 })
+ expect(defaultView()).toBe('files')
+ })
+})
+
+/**
+ * Remove the mocked initial state
+ */
+function removeInitialState(): void {
+ document.querySelectorAll('input[type="hidden"]').forEach((el) => {
+ el.remove()
+ })
+ // clear the cache
+ delete globalThis._nc_initial_state
+}
+
+/**
+ * Helper to mock an initial state value
+ * @param app - The app
+ * @param key - The key
+ * @param value - The value
+ */
+function mockInitialState(app: string, key: string, value: unknown): void {
+ const el = document.createElement('input')
+ el.value = btoa(JSON.stringify(value))
+ el.id = `initial-state-${app}-${key}`
+ el.type = 'hidden'
+
+ document.head.appendChild(el)
+}
diff --git a/apps/files/src/utils/filesViews.ts b/apps/files/src/utils/filesViews.ts
new file mode 100644
index 00000000000..9489c35cbde
--- /dev/null
+++ b/apps/files/src/utils/filesViews.ts
@@ -0,0 +1,30 @@
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import type { UserConfig } from '../types.ts'
+
+import { loadState } from '@nextcloud/initial-state'
+
+/**
+ * Check whether the personal files view can be shown
+ */
+export function hasPersonalFilesView(): boolean {
+ const storageStats = loadState('files', 'storageStats', { quota: -1 })
+ // Don't show this view if the user has no storage quota
+ return storageStats.quota !== 0
+}
+
+/**
+ * Get the default files view
+ */
+export function defaultView() {
+ const { default_view: defaultView } = loadState<Partial<UserConfig>>('files', 'config', { default_view: 'files' })
+
+ // the default view - only use the personal one if it is enabled
+ if (defaultView !== 'personal' || hasPersonalFilesView()) {
+ return defaultView
+ }
+ return 'files'
+}
diff --git a/apps/files/src/utils/permissions.ts b/apps/files/src/utils/permissions.ts
index 2c85ede1659..9b4c42bf49c 100644
--- a/apps/files/src/utils/permissions.ts
+++ b/apps/files/src/utils/permissions.ts
@@ -17,6 +17,13 @@ export function isDownloadable(node: Node): boolean {
return false
}
+ // check hide-download property of shares
+ if (node.attributes['hide-download'] === true
+ || node.attributes['hide-download'] === 'true'
+ ) {
+ return false
+ }
+
// If the mount type is a share, ensure it got download permissions.
if (node.attributes['share-attributes']) {
const shareAttributes = JSON.parse(node.attributes['share-attributes'] || '[]') as Array<ShareAttribute>