diff options
Diffstat (limited to 'apps/files/src/utils')
-rw-r--r-- | apps/files/src/utils/actionUtils.ts | 4 | ||||
-rw-r--r-- | apps/files/src/utils/fileUtils.ts | 38 | ||||
-rw-r--r-- | apps/files/src/utils/filesViews.spec.ts | 73 | ||||
-rw-r--r-- | apps/files/src/utils/filesViews.ts | 30 | ||||
-rw-r--r-- | apps/files/src/utils/permissions.ts | 7 |
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> |