mirror of
https://github.com/nextcloud/server.git
synced 2026-06-07 07:43:18 -04:00
Merge pull request #45533 from nextcloud/backport/45419/stable28
[stable28] fix(files): Implement `SortingService` to fix sorting of files
This commit is contained in:
commit
8f0bbcd4e8
7 changed files with 163 additions and 14 deletions
100
apps/files/src/services/SortingService.spec.ts
Normal file
100
apps/files/src/services/SortingService.spec.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
/**
|
||||
* 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'])
|
||||
})
|
||||
})
|
||||
59
apps/files/src/services/SortingService.ts
Normal file
59
apps/files/src/services/SortingService.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* 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
|
||||
})
|
||||
}
|
||||
|
|
@ -125,7 +125,6 @@ import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
|
|||
import { Folder, Node, Permission } from '@nextcloud/files'
|
||||
import { getCapabilities } from '@nextcloud/capabilities'
|
||||
import { join, dirname } from 'path'
|
||||
import { orderBy } from 'natural-orderby'
|
||||
import { Parser } from 'xml2js'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
import { translate, translatePlural } from '@nextcloud/l10n'
|
||||
|
|
@ -152,6 +151,7 @@ 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'
|
||||
|
|
|
|||
4
dist/files-main.js
vendored
4
dist/files-main.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-main.js.map
vendored
2
dist/files-main.js.map
vendored
File diff suppressed because one or more lines are too long
9
package-lock.json
generated
9
package-lock.json
generated
|
|
@ -59,7 +59,6 @@
|
|||
"marked": "^9.1.5",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"natural-orderby": "^3.0.2",
|
||||
"nextcloud-vue-collections": "^0.12.0",
|
||||
"node-vibrant": "^3.1.6",
|
||||
"p-limit": "^4.0.0",
|
||||
|
|
@ -20784,14 +20783,6 @@
|
|||
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/natural-orderby": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/natural-orderby/-/natural-orderby-3.0.2.tgz",
|
||||
"integrity": "sha512-x7ZdOwBxZCEm9MM7+eQCjkrNLrW3rkBKNHVr78zbtqnMGVNlnDi6C/eUEYgxHNrcbu0ymvjzcwIL/6H1iHri9g==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
|
|
|
|||
|
|
@ -86,7 +86,6 @@
|
|||
"marked": "^9.1.5",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"natural-orderby": "^3.0.2",
|
||||
"nextcloud-vue-collections": "^0.12.0",
|
||||
"node-vibrant": "^3.1.6",
|
||||
"p-limit": "^4.0.0",
|
||||
|
|
|
|||
Loading…
Reference in a new issue