From e6fe4df8a4a4517dec7136095af19a8231c9038b Mon Sep 17 00:00:00 2001 From: Fauzan Date: Sun, 6 Sep 2026 20:04:19 +0700 Subject: [PATCH] fix(files): do not calculate hidden files count on public shares Hidden files visibility can be configured internally, but on public shares this information is irrelevant and leaks the presence of hidden files. This passes the isPublic flag to getSummaryFor to skip generating the hidden files summary. Fixes #39027 Assisted-by: Antigravity:Gemini-3.1-Pro Signed-off-by: Fauzan Febriansyah --- apps/files/src/utils/fileUtils.spec.ts | 45 ++++++++++++++++++++++++++ apps/files/src/utils/fileUtils.ts | 5 +-- apps/files/src/views/FilesList.vue | 2 +- 3 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 apps/files/src/utils/fileUtils.spec.ts diff --git a/apps/files/src/utils/fileUtils.spec.ts b/apps/files/src/utils/fileUtils.spec.ts new file mode 100644 index 0000000000000..66fe14e3a0d00 --- /dev/null +++ b/apps/files/src/utils/fileUtils.spec.ts @@ -0,0 +1,45 @@ +/*! + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { FileType } from '@nextcloud/files' +import type { Node } from '@nextcloud/files' +import { describe, expect, it, vi } from 'vitest' +import { getSummaryFor } from './fileUtils.ts' + +// Mock translation function used in getSummaryFor +vi.mock('@nextcloud/l10n', () => ({ + n: vi.fn((app, single, plural, count) => { + if (count === 1) return single.replace('%n', count) + return plural.replace('%n', count) + }), +})) + +describe('fileUtils', () => { + describe('getSummaryFor', () => { + it('returns correct summary for files and folders', () => { + const nodes = [ + { type: FileType.File } as Node, + { type: FileType.Folder } as Node, + ] + expect(getSummaryFor(nodes, 1)).toBe('1 file · 1 folder · 1 hidden') + }) + + it('does not display hidden string when hideHiddenString is true', () => { + const nodes = [ + { type: FileType.File } as Node, + { type: FileType.Folder } as Node, + ] + expect(getSummaryFor(nodes, 1, true)).toBe('1 file · 1 folder') + }) + + it('returns correct summary when there are no hidden files', () => { + const nodes = [ + { type: FileType.File } as Node, + { type: FileType.File } as Node, + ] + expect(getSummaryFor(nodes, 0)).toBe('2 files') + }) + }) +}) diff --git a/apps/files/src/utils/fileUtils.ts b/apps/files/src/utils/fileUtils.ts index 83f91a6abfcd1..94ab943c9910e 100644 --- a/apps/files/src/utils/fileUtils.ts +++ b/apps/files/src/utils/fileUtils.ts @@ -26,8 +26,9 @@ export function extractFilePaths(path: string): [string, string] { * * @param nodes - The nodes to summarize * @param hidden - The number of hidden nodes + * @param hideHiddenString - Whether to omit the hidden files text from the summary */ -export function getSummaryFor(nodes: Node[], hidden = 0): string { +export function getSummaryFor(nodes: Node[], hidden = 0, hideHiddenString = false): string { const fileCount = nodes.filter((node) => node.type === FileType.File).length const folderCount = nodes.filter((node) => node.type === FileType.Folder).length @@ -40,7 +41,7 @@ export function getSummaryFor(nodes: Node[], hidden = 0): string { const folderSummary = n('files', '%n folder', '%n folders', folderCount) summary.push(folderSummary) } - if (hidden > 0) { + if (hidden > 0 && !hideHiddenString) { // TRANSLATORS: This is the number of hidden files or folders const hiddenSummary = n('files', '%n hidden', '%n hidden', hidden) summary.push(hiddenSummary) diff --git a/apps/files/src/views/FilesList.vue b/apps/files/src/views/FilesList.vue index 55e4ef64dbc0a..7130db75c8aba 100644 --- a/apps/files/src/views/FilesList.vue +++ b/apps/files/src/views/FilesList.vue @@ -446,7 +446,7 @@ export default defineComponent({ */ summary() { const hidden = this.dirContents.length - this.dirContentsFiltered.length - return getSummaryFor(this.dirContentsFiltered, hidden) + return getSummaryFor(this.dirContentsFiltered, hidden, this.isPublic) }, debouncedFetchContent() {