aboutsummaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorFerdinand Thiessen <opensource@fthiessen.de>2024-07-25 02:09:53 +0200
committerGitHub <noreply@github.com>2024-07-25 02:09:53 +0200
commitd843d671d5dc3ae4023e80ae5e923f7f7b9cf946 (patch)
treed0bb9d799523b283d3481a700a9be1b8fc2a4766 /apps
parentca5b746504fe37139b321021345a5640db70e3f6 (diff)
parentfe1aa830575bf5ef88532d894297c557c69baf20 (diff)
downloadnextcloud-server-d843d671d5dc3ae4023e80ae5e923f7f7b9cf946.tar.gz
nextcloud-server-d843d671d5dc3ae4023e80ae5e923f7f7b9cf946.zip
Merge pull request #46690 from nextcloud/fix/files-displayname
Update `@nextcloud/files` to 3.6.0 and fix display name handling of folders (breadcrumbs and filename)
Diffstat (limited to 'apps')
-rw-r--r--apps/files/src/actions/openFolderAction.ts2
-rw-r--r--apps/files/src/components/BreadCrumbs.vue6
-rw-r--r--apps/files/src/components/FileEntry.vue2
-rw-r--r--apps/files/src/components/FileEntry/FileEntryName.vue14
-rw-r--r--apps/files/src/components/FileEntryGrid.vue2
-rw-r--r--apps/files/src/components/FileEntryMixin.ts32
-rw-r--r--apps/files/src/services/Files.ts9
-rw-r--r--apps/files/src/services/SortingService.spec.ts100
-rw-r--r--apps/files/src/services/SortingService.ts59
-rw-r--r--apps/files/src/views/FilesList.vue49
10 files changed, 54 insertions, 221 deletions
diff --git a/apps/files/src/actions/openFolderAction.ts b/apps/files/src/actions/openFolderAction.ts
index ab2d280eb73..8719f7a93fb 100644
--- a/apps/files/src/actions/openFolderAction.ts
+++ b/apps/files/src/actions/openFolderAction.ts
@@ -10,7 +10,7 @@ export const action = new FileAction({
id: 'open-folder',
displayName(files: Node[]) {
// Only works on single node
- const displayName = files[0].attributes.displayname || files[0].basename
+ const displayName = files[0].displayname
return t('files', 'Open folder {displayName}', { displayName })
},
iconSvgInline: () => FolderSvg,
diff --git a/apps/files/src/components/BreadCrumbs.vue b/apps/files/src/components/BreadCrumbs.vue
index 02ccac8b669..169bc504ab9 100644
--- a/apps/files/src/components/BreadCrumbs.vue
+++ b/apps/files/src/components/BreadCrumbs.vue
@@ -158,9 +158,9 @@ export default defineComponent({
return this.$navigation?.active?.name || t('files', 'Home')
}
- const source: FileSource | null = this.getFileSourceFromPath(path)
- const node: Node | undefined = source ? this.getNodeFromSource(source) : undefined
- return node?.attributes?.displayname || basename(path)
+ const source = this.getFileSourceFromPath(path)
+ const node = source ? this.getNodeFromSource(source) : undefined
+ return node?.displayname || basename(path)
},
onClick(to) {
diff --git a/apps/files/src/components/FileEntry.vue b/apps/files/src/components/FileEntry.vue
index fdc800b3464..48b6dcfd8a0 100644
--- a/apps/files/src/components/FileEntry.vue
+++ b/apps/files/src/components/FileEntry.vue
@@ -34,7 +34,7 @@
@click.native="execDefaultAction" />
<FileEntryName ref="name"
- :display-name="displayName"
+ :basename="basename"
:extension="extension"
:files-list-width="filesListWidth"
:nodes="nodes"
diff --git a/apps/files/src/components/FileEntry/FileEntryName.vue b/apps/files/src/components/FileEntry/FileEntryName.vue
index 4fb907dd005..875c0892a72 100644
--- a/apps/files/src/components/FileEntry/FileEntryName.vue
+++ b/apps/files/src/components/FileEntry/FileEntryName.vue
@@ -29,8 +29,8 @@
v-bind="linkTo.params">
<!-- File name -->
<span class="files-list__row-name-text">
- <!-- Keep the displayName stuck to the extension to avoid whitespace rendering issues-->
- <span class="files-list__row-name-" v-text="displayName" />
+ <!-- Keep the filename stuck to the extension to avoid whitespace rendering issues-->
+ <span class="files-list__row-name-" v-text="basename" />
<span class="files-list__row-name-ext" v-text="extension" />
</span>
</component>
@@ -64,10 +64,16 @@ export default defineComponent({
},
props: {
- displayName: {
+ /**
+ * The filename without extension
+ */
+ basename: {
type: String,
required: true,
},
+ /**
+ * The extension of the filename
+ */
extension: {
type: String,
required: true,
@@ -155,7 +161,7 @@ export default defineComponent({
params: {
download: this.source.basename,
href: this.source.source,
- title: t('files', 'Download file {name}', { name: this.displayName }),
+ title: t('files', 'Download file {name}', { name: `${this.basename}${this.extension}` }),
tabindex: '0',
},
}
diff --git a/apps/files/src/components/FileEntryGrid.vue b/apps/files/src/components/FileEntryGrid.vue
index ed8175fcda7..1f0992bc851 100644
--- a/apps/files/src/components/FileEntryGrid.vue
+++ b/apps/files/src/components/FileEntryGrid.vue
@@ -36,7 +36,7 @@
@click.native="execDefaultAction" />
<FileEntryName ref="name"
- :display-name="displayName"
+ :basename="basename"
:extension="extension"
:files-list-width="filesListWidth"
:grid-mode="true"
diff --git a/apps/files/src/components/FileEntryMixin.ts b/apps/files/src/components/FileEntryMixin.ts
index 6c0b278c61b..da9b93107c7 100644
--- a/apps/files/src/components/FileEntryMixin.ts
+++ b/apps/files/src/components/FileEntryMixin.ts
@@ -74,19 +74,31 @@ export default defineComponent({
return this.source.status === NodeStatus.LOADING
},
- extension() {
- if (this.source.attributes?.displayname) {
- return extname(this.source.attributes.displayname)
+ /**
+ * The display name of the current node
+ * Either the nodes filename or a custom display name (e.g. for shares)
+ */
+ displayName() {
+ return this.source.displayname
+ },
+ /**
+ * The display name without extension
+ */
+ basename() {
+ if (this.extension === '') {
+ return this.displayName
}
- return this.source.extension || ''
+ return this.displayName.slice(0, 0 - this.extension.length)
},
- displayName() {
- const ext = this.extension
- const name = String(this.source.attributes.displayname
- || this.source.basename)
+ /**
+ * The extension of the file
+ */
+ extension() {
+ if (this.source.type === FileType.Folder) {
+ return ''
+ }
- // Strip extension from name if defined
- return !ext ? name : name.slice(0, 0 - ext.length)
+ return extname(this.displayName)
},
draggingFiles() {
diff --git a/apps/files/src/services/Files.ts b/apps/files/src/services/Files.ts
index dc83f16187b..10e553592fe 100644
--- a/apps/files/src/services/Files.ts
+++ b/apps/files/src/services/Files.ts
@@ -14,7 +14,14 @@ import logger from '../logger.js'
* Slim wrapper over `@nextcloud/files` `davResultToNode` to allow using the function with `Array.map`
* @param node The node returned by the webdav library
*/
-export const resultToNode = (node: FileStat): File | Folder => davResultToNode(node)
+export const resultToNode = (node: FileStat): File | Folder => {
+ // 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)
+ }
+ return davResultToNode(node)
+}
export const getContents = (path = '/'): CancelablePromise<ContentsWithRoot> => {
const controller = new AbortController()
diff --git a/apps/files/src/services/SortingService.spec.ts b/apps/files/src/services/SortingService.spec.ts
deleted file mode 100644
index 5d20c43ed0a..00000000000
--- a/apps/files/src/services/SortingService.spec.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-/**
- * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-import { describe, expect } from '@jest/globals'
-import { orderBy } from './SortingService'
-
-describe('SortingService', () => {
- test('By default the identify and ascending order is used', () => {
- const array = ['a', 'z', 'b']
- expect(orderBy(array)).toEqual(['a', 'b', 'z'])
- })
-
- test('Use identifiy but descending', () => {
- const array = ['a', 'z', 'b']
- expect(orderBy(array, undefined, ['desc'])).toEqual(['z', 'b', 'a'])
- })
-
- test('Can set identifier function', () => {
- const array = [
- { text: 'a', order: 2 },
- { text: 'z', order: 1 },
- { text: 'b', order: 3 },
- ] as const
- expect(orderBy(array, [(v) => v.order]).map((v) => v.text)).toEqual(['z', 'a', 'b'])
- })
-
- test('Can set multiple identifier functions', () => {
- const array = [
- { text: 'a', order: 2, secondOrder: 2 },
- { text: 'z', order: 1, secondOrder: 3 },
- { text: 'b', order: 2, secondOrder: 1 },
- ] as const
- expect(orderBy(array, [(v) => v.order, (v) => v.secondOrder]).map((v) => v.text)).toEqual(['z', 'b', 'a'])
- })
-
- test('Can set order partially', () => {
- const array = [
- { text: 'a', order: 2, secondOrder: 2 },
- { text: 'z', order: 1, secondOrder: 3 },
- { text: 'b', order: 2, secondOrder: 1 },
- ] as const
-
- expect(
- orderBy(
- array,
- [(v) => v.order, (v) => v.secondOrder],
- ['desc'],
- ).map((v) => v.text),
- ).toEqual(['b', 'a', 'z'])
- })
-
- test('Can set order array', () => {
- const array = [
- { text: 'a', order: 2, secondOrder: 2 },
- { text: 'z', order: 1, secondOrder: 3 },
- { text: 'b', order: 2, secondOrder: 1 },
- ] as const
-
- expect(
- orderBy(
- array,
- [(v) => v.order, (v) => v.secondOrder],
- ['desc', 'desc'],
- ).map((v) => v.text),
- ).toEqual(['a', 'b', 'z'])
- })
-
- test('Numbers are handled correctly', () => {
- const array = [
- { text: '2.3' },
- { text: '2.10' },
- { text: '2.0' },
- { text: '2.2' },
- ] as const
-
- expect(
- orderBy(
- array,
- [(v) => v.text],
- ).map((v) => v.text),
- ).toEqual(['2.0', '2.2', '2.3', '2.10'])
- })
-
- test('Numbers with suffixes are handled correctly', () => {
- const array = [
- { text: '2024-01-05' },
- { text: '2024-05-01' },
- { text: '2024-01-10' },
- { text: '2024-01-05 Foo' },
- ] as const
-
- expect(
- orderBy(
- array,
- [(v) => v.text],
- ).map((v) => v.text),
- ).toEqual(['2024-01-05', '2024-01-05 Foo', '2024-01-10', '2024-05-01'])
- })
-})
diff --git a/apps/files/src/services/SortingService.ts b/apps/files/src/services/SortingService.ts
deleted file mode 100644
index 392f35efc9f..00000000000
--- a/apps/files/src/services/SortingService.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-/**
- * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-import { getCanonicalLocale, getLanguage } from '@nextcloud/l10n'
-
-type IdentifierFn<T> = (v: T) => unknown
-type SortingOrder = 'asc'|'desc'
-
-/**
- * Helper to create string representation
- * @param value Value to stringify
- */
-function stringify(value: unknown) {
- // The default representation of Date is not sortable because of the weekday names in front of it
- if (value instanceof Date) {
- return value.toISOString()
- }
- return String(value)
-}
-
-/**
- * Natural order a collection
- * You can define identifiers as callback functions, that get the element and return the value to sort.
- *
- * @param collection The collection to order
- * @param identifiers An array of identifiers to use, by default the identity of the element is used
- * @param orders Array of orders, by default all identifiers are sorted ascening
- */
-export function orderBy<T>(collection: readonly T[], identifiers?: IdentifierFn<T>[], orders?: SortingOrder[]): T[] {
- // If not identifiers are set we use the identity of the value
- identifiers = identifiers ?? [(value) => value]
- // By default sort the collection ascending
- orders = orders ?? []
- const sorting = identifiers.map((_, index) => (orders[index] ?? 'asc') === 'asc' ? 1 : -1)
-
- const collator = Intl.Collator(
- [getLanguage(), getCanonicalLocale()],
- {
- // handle 10 as ten and not as one-zero
- numeric: true,
- usage: 'sort',
- },
- )
-
- return [...collection].sort((a, b) => {
- for (const [index, identifier] of identifiers.entries()) {
- // Get the local compare of stringified value a and b
- const value = collator.compare(stringify(identifier(a)), stringify(identifier(b)))
- // If they do not match return the order
- if (value !== 0) {
- return value * sorting[index]
- }
- // If they match we need to continue with the next identifier
- }
- // If all are equal we need to return equality
- return 0
- })
-}
diff --git a/apps/files/src/views/FilesList.vue b/apps/files/src/views/FilesList.vue
index 4fe33c61375..6d9a6cf60a0 100644
--- a/apps/files/src/views/FilesList.vue
+++ b/apps/files/src/views/FilesList.vue
@@ -120,7 +120,7 @@ import type { UserConfig } from '../types.ts'
import { getCapabilities } from '@nextcloud/capabilities'
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
-import { Folder, Node, Permission } from '@nextcloud/files'
+import { Folder, Node, Permission, sortNodes } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import { join, dirname, normalize } from 'path'
import { showError, showWarning } from '@nextcloud/dialogs'
@@ -148,7 +148,6 @@ import { useSelectionStore } from '../store/selection.ts'
import { useUploaderStore } from '../store/uploader.ts'
import { useUserConfigStore } from '../store/userconfig.ts'
import { useViewConfigStore } from '../store/viewConfig.ts'
-import { orderBy } from '../services/SortingService.ts'
import BreadCrumbs from '../components/BreadCrumbs.vue'
import FilesListVirtual from '../components/FilesListVirtual.vue'
import filesListWidthMixin from '../mixins/filesListWidth.ts'
@@ -292,43 +291,9 @@ export default defineComponent({
},
/**
- * 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',
- ] as ('asc'|'desc')[]
- return [identifiers, orders] as const
- },
-
- /**
* The current directory contents.
*/
- dirContentsSorted(): Node[] {
+ dirContentsSorted() {
if (!this.currentView) {
return []
}
@@ -351,10 +316,12 @@ export default defineComponent({
return this.isAscSorting ? results : results.reverse()
}
- return orderBy(
- filteredDirContent,
- ...this.sortingParameters,
- )
+ return sortNodes(filteredDirContent, {
+ sortFavoritesFirst: this.userConfig.sort_favorites_first,
+ sortFoldersFirst: this.userConfig.sort_folders_first,
+ sortingMode: this.sortingMode,
+ sortingOrder: this.isAscSorting ? 'asc' : 'desc',
+ })
},
dirContents(): Node[] {