diff --git a/apps/files/src/components/FileEntryMixin.ts b/apps/files/src/components/FileEntryMixin.ts index f645b1f5df0c4..95740efe185f3 100644 --- a/apps/files/src/components/FileEntryMixin.ts +++ b/apps/files/src/components/FileEntryMixin.ts @@ -88,8 +88,24 @@ export default defineComponent({ * Either the nodes filename or a custom display name (e.g. for shares) */ displayName() { - const ext = this.extension - const name = String(this.source.attributes.displayname || this.source.basename) + return this.source.displayname + }, + /** + * The display name without extension + */ + basename() { + if (this.extension === '') { + return this.displayName + } + return this.displayName.slice(0, 0 - this.extension.length) + }, + /** + * The extension of the file + */ + extension() { + if (this.source.type === FileType.Folder) { + return '' + } return extname(this.displayName) }, diff --git a/apps/files/src/services/Files.ts b/apps/files/src/services/Files.ts index 0710c00585200..127c3d9686d3d 100644 --- a/apps/files/src/services/Files.ts +++ b/apps/files/src/services/Files.ts @@ -54,6 +54,12 @@ export const resultToNode = function(node: FileStat): File | Folder { ? hashCode(source) : props?.fileid as number || 0 + // TODO remove this hack with nextcloud-files v3.7 + // just needed because of a bug in the webdav client + if (node.props?.displayname !== undefined) { + node.props.displayname = String(node.props.displayname) + } + const nodeData = { id, source, diff --git a/apps/files/src/views/FilesList.vue b/apps/files/src/views/FilesList.vue index 9f8ae294f9cb7..825c71c19bd2e 100644 --- a/apps/files/src/views/FilesList.vue +++ b/apps/files/src/views/FilesList.vue @@ -126,7 +126,7 @@ import type { UserConfig } from '../types.ts' import { getCapabilities } from '@nextcloud/capabilities' import { showError } from '@nextcloud/dialogs' import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus' -import { Folder, Node, Permission } from '@nextcloud/files' +import { Folder, Node, Permission, sortNodes } from '@nextcloud/files' import { loadState } from '@nextcloud/initial-state' import { translate as t, translatePlural as n } from '@nextcloud/l10n' import { ShareType } from '@nextcloud/sharing' @@ -272,40 +272,6 @@ export default defineComponent({ return this.filesStore.getNode(source) as Folder }, - /** - * Directory content sorting parameters - * Provided by an extra computed property for caching - */ - sortingParameters() { - const identifiers = [ - // 1: Sort favorites first if enabled - ...(this.userConfig.sort_favorites_first ? [v => v.attributes?.favorite !== 1] : []), - // 2: Sort folders first if sorting by name - ...(this.userConfig.sort_folders_first ? [v => v.type !== 'folder'] : []), - // 3: Use sorting mode if NOT basename (to be able to use displayName too) - ...(this.sortingMode !== 'basename' ? [v => v[this.sortingMode]] : []), - // 4: Use displayname if available, fallback to name - v => v.attributes?.displayname || v.basename, - // 5: Finally, use basename if all previous sorting methods failed - v => v.basename, - ] - const orders = [ - // (for 1): always sort favorites before normal files - ...(this.userConfig.sort_favorites_first ? ['asc'] : []), - // (for 2): always sort folders before files - ...(this.userConfig.sort_folders_first ? ['asc'] : []), - // (for 3): Reverse if sorting by mtime as mtime higher means edited more recent -> lower - ...(this.sortingMode === 'mtime' ? [this.isAscSorting ? 'desc' : 'asc'] : []), - // (also for 3 so make sure not to conflict with 2 and 3) - ...(this.sortingMode !== 'mtime' && this.sortingMode !== 'basename' ? [this.isAscSorting ? 'asc' : 'desc'] : []), - // for 4: use configured sorting direction - this.isAscSorting ? 'asc' : 'desc', - // for 5: use configured sorting direction - this.isAscSorting ? 'asc' : 'desc', - ] - return [identifiers, orders] as const - }, - /** * The current directory contents. */ diff --git a/cypress/e2e/files/files_sorting.cy.ts b/cypress/e2e/files/files_sorting.cy.ts index 3e46d868c1ed9..9cb6fe3fe2f3c 100644 --- a/cypress/e2e/files/files_sorting.cy.ts +++ b/cypress/e2e/files/files_sorting.cy.ts @@ -58,6 +58,31 @@ describe('Files: Sorting the file list', { testIsolation: true }, () => { }) }) + /** + * Regression test of https://github.com/nextcloud/server/issues/45829 + */ + it('Filesnames with numbers are sorted by name ascending by default', () => { + cy.uploadContent(currentUser, new Blob(), 'text/plain', '/name.txt') + .uploadContent(currentUser, new Blob(), 'text/plain', '/name_03.txt') + .uploadContent(currentUser, new Blob(), 'text/plain', '/name_02.txt') + .uploadContent(currentUser, new Blob(), 'text/plain', '/name_01.txt') + cy.login(currentUser) + cy.visit('/apps/files') + + cy.get('[data-cy-files-list-row]').each(($row, index) => { + switch (index) { + case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('name.txt') + break + case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('name_01.txt') + break + case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('name_02.txt') + break + case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('name_03.txt') + break + } + }) + }) + it('Can sort by size', () => { cy.uploadContent(currentUser, new Blob(), 'text/plain', '/1 tiny.txt') .uploadContent(currentUser, new Blob(['a'.repeat(1024)]), 'text/plain', '/z big.txt') diff --git a/package-lock.json b/package-lock.json index d0be3bb93d843..a619db5f90ec6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "@nextcloud/capabilities": "^1.2.0", "@nextcloud/dialogs": "^5.3.5", "@nextcloud/event-bus": "^3.3.1", - "@nextcloud/files": "^3.5.1", + "@nextcloud/files": "^3.6.0", "@nextcloud/initial-state": "^2.2.0", "@nextcloud/l10n": "^3.1.0", "@nextcloud/logger": "^2.5.0", @@ -4474,18 +4474,20 @@ } }, "node_modules/@nextcloud/files": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@nextcloud/files/-/files-3.5.1.tgz", - "integrity": "sha512-GkVWUgkBSVt27Carmp/DbnDiqHq03w3VQWt8xszacp/IQSB9G+8/KCvi8zxldac2q7lQ8NpHlB/Bqy8o+OOc0A==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@nextcloud/files/-/files-3.7.0.tgz", + "integrity": "sha512-u7Hwt7/13empViLvwHPQk1AnKjhDYf7tkXeCLaO6e03am2uqBlYwc3iUS4cZye5CuaEeJeW251jPUGTtRXjjWQ==", "dependencies": { "@nextcloud/auth": "^2.3.0", + "@nextcloud/capabilities": "^1.2.0", "@nextcloud/l10n": "^3.1.0", "@nextcloud/logger": "^3.0.2", - "@nextcloud/paths": "^2.1.0", + "@nextcloud/paths": "^2.2.0", "@nextcloud/router": "^3.0.1", - "@nextcloud/sharing": "^0.2.1", + "@nextcloud/sharing": "^0.2.2", "cancelable-promise": "^4.3.1", "is-svg": "^5.0.1", + "typedoc-plugin-missing-exports": "^3.0.0", "typescript-event-target": "^1.1.1", "webdav": "^5.6.0" }, @@ -4638,11 +4640,12 @@ } }, "node_modules/@nextcloud/paths": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@nextcloud/paths/-/paths-2.1.0.tgz", - "integrity": "sha512-8wX0gqwez0bTuAS8A0OEiqbbp0ZsqLr07zSErmS6OYhh9KZcSt/kO6lQV5tnrFqIqJVsxwz4kHUjtZXh6DSf9Q==", - "dependencies": { - "core-js": "^3.6.4" + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@nextcloud/paths/-/paths-2.2.1.tgz", + "integrity": "sha512-M3ShLjrxR7B48eKThLMoqbxTqTKyQXcwf9TgeXQGbCIhiHoXU6as5j8l5qNv/uZlANokVdowpuWHBi3b2+YNNA==", + "engines": { + "node": "^20.0.0", + "npm": "^10.0.0" } }, "node_modules/@nextcloud/router": { @@ -5140,6 +5143,15 @@ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", "dev": true }, + "node_modules/@shikijs/core": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.12.1.tgz", + "integrity": "sha512-biCz/mnkMktImI6hMfMX3H9kOeqsInxWEyCHbSlL8C/2TR1FqfmGxTLRNwYCKsyCyxWLbB8rEqXRVZuyxuLFmA==", + "peer": true, + "dependencies": { + "@types/hast": "^3.0.4" + } + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -18937,7 +18949,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", - "dev": true, "dependencies": { "uc.micro": "^2.0.0" } @@ -19309,6 +19320,12 @@ "yallist": "^3.0.2" } }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "peer": true + }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", @@ -19390,7 +19407,6 @@ "version": "14.1.0", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", - "dev": true, "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", @@ -19416,8 +19432,7 @@ "node_modules/markdown-it/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/markdown-table": { "version": "3.0.3", @@ -19705,8 +19720,7 @@ "node_modules/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", - "dev": true + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" }, "node_modules/media-typer": { "version": "0.3.0", @@ -22480,7 +22494,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", - "dev": true, "engines": { "node": ">=6" } @@ -24310,6 +24323,16 @@ "node": ">=4" } }, + "node_modules/shiki": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.12.1.tgz", + "integrity": "sha512-nwmjbHKnOYYAe1aaQyEBHvQymJgfm86ZSS7fT8OaPRr4sbAcBNz7PbfAikMEFSDQ6se2j2zobkXvVKcBOm0ysg==", + "peer": true, + "dependencies": { + "@shikijs/core": "1.12.1", + "@types/hast": "^3.0.4" + } + }, "node_modules/side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", @@ -26796,11 +26819,55 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typedoc": { + "version": "0.26.5", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.26.5.tgz", + "integrity": "sha512-Vn9YKdjKtDZqSk+by7beZ+xzkkr8T8CYoiasqyt4TTRFy5+UHzL/mF/o4wGBjRF+rlWQHDb0t6xCpA3JNL5phg==", + "peer": true, + "dependencies": { + "lunr": "^2.3.9", + "markdown-it": "^14.1.0", + "minimatch": "^9.0.5", + "shiki": "^1.9.1", + "yaml": "^2.4.5" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x" + } + }, + "node_modules/typedoc-plugin-missing-exports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/typedoc-plugin-missing-exports/-/typedoc-plugin-missing-exports-3.0.0.tgz", + "integrity": "sha512-R7D8fYrK34mBFZSlF1EqJxfqiUSlQSmyrCiQgTQD52nNm6+kUtqwiaqaNkuJ2rA2wBgWFecUA8JzHT7x2r7ePg==", + "peerDependencies": { + "typedoc": "0.26.x" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "peer": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/typescript": { "version": "5.4.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", - "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -26852,8 +26919,7 @@ "node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", - "dev": true + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" }, "node_modules/uglify-js": { "version": "3.17.4", @@ -28854,6 +28920,18 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, + "node_modules/yaml": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", + "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==", + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",