diff --git a/apps/files/src/fileSortFunctions.js b/apps/files/src/fileSortFunctions.js new file mode 100644 index 00000000000..495fe0e9b72 --- /dev/null +++ b/apps/files/src/fileSortFunctions.js @@ -0,0 +1,66 @@ +/** + * Compare two strings to provide a natural sort + * @param a first string to compare + * @param b second string to compare + * @return -1 if b comes before a, 1 if a comes before b + * or 0 if the strings are identical + */ +function _chunkify (t) { + // Adapted from http://my.opera.com/GreyWyvern/blog/show.dml/1671288 + const tz = []; let x = 0; let y = -1; let n = 0; let c + + while (x < t.length) { + c = t.charAt(x) + // only include the dot in strings + const m = ((!n && c === '.') || (c >= '0' && c <= '9')) + if (m !== n) { + // next chunk + y++ + tz[y] = '' + n = m + } + tz[y] += c + x++ + } + return tz +} + +function _naturalSortCompare (a, b) { + const aa = _chunkify(a) + const bb = _chunkify(b) + let x, aNum, bNum + + for (x = 0; aa[x] && bb[x]; x++) { + if (aa[x] !== bb[x]) { + aNum = Number(aa[x]) + bNum = Number(bb[x]) + // note: == is correct here + // eslint-disable-next-line eqeqeq + if (aNum == aa[x] && bNum == bb[x]) { + return aNum - bNum + } else { + // Forcing 'en' locale to match the server-side locale which is + // always 'en'. + // + // Note: This setting isn't supported by all browsers but for the ones + // that do there will be more consistency between client-server sorting + return aa[x].localeCompare(bb[x], 'en') + } + } + } + return aa.length - bb.length +} + +function name (fileInfo1, fileInfo2) { + if (fileInfo1.type === 'folder' && fileInfo2.type !== 'folder') { + return -1 + } + if (fileInfo1.type !== 'folder' && fileInfo2.type === 'folder') { + return 1 + } + return _naturalSortCompare(fileInfo1.name, fileInfo2.name) +} + +export const fileSortFunctions = { + name +} diff --git a/apps/files/src/store/getters.js b/apps/files/src/store/getters.js index 02ed034d543..32805a3ffd9 100644 --- a/apps/files/src/store/getters.js +++ b/apps/files/src/store/getters.js @@ -1,3 +1,5 @@ +import { fileSortFunctions } from '../fileSortFunctions.js' + export default { inProgress: state => { return state.inProgress @@ -42,10 +44,7 @@ export default { } // respect filename filter for local 'search' in open folder return !(state.searchTermFilter && !file.name.toLowerCase().includes(state.searchTermFilter.toLowerCase())) - }).sort(function (a, b) { - return a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }) - } - ) + }).sort(fileSortFunctions[state.fileSortMode]) }, filesTotalSize: (state, getters) => { let totalSize = 0 diff --git a/apps/files/src/store/state.js b/apps/files/src/store/state.js index 6a459e6931d..dc927ec7eb8 100644 --- a/apps/files/src/store/state.js +++ b/apps/files/src/store/state.js @@ -4,6 +4,7 @@ export default { files: [], filesSearched: [], fileFilter: fileFilters, + fileSortMode: 'name', selected: [], inProgress: [], searchTermGlobal: '', diff --git a/tests/acceptance/features/webUIFiles/sort.feature b/tests/acceptance/features/webUIFiles/sort.feature new file mode 100644 index 00000000000..61dfde501d2 --- /dev/null +++ b/tests/acceptance/features/webUIFiles/sort.feature @@ -0,0 +1,50 @@ +Feature: Filter files/folders + + As a user + I would like to sort files/folders + So that I can make the file/folder list more clear + + Background: + Given user "user1" has been created with default attributes + And user "user1" has logged in using the webUI + And the user has created folder "test_sort" + And the user has created the following folders + | entry_name | + | test_sort/a | + | test_sort/a 文件 | + | test_sort/10 | + | test_sort/1 | + | test_sort/2 | + | test_sort/z | + And the user has created the following files + | entry_name | + | test_sort/a.txt | + | test_sort/a space.txt | + | test_sort/a space (2).txt | + | test_sort/a space 文件 | + | test_sort/a space 文件夹 | + | test_sort/b1.txt | + | test_sort/b2.txt | + | test_sort/b10.txt | + | test_sort/z.txt | + + Scenario: Folders are listed before files alphabetically by default and sorted using natural sort + When the user has browsed to the files page + Then these resources should be listed in the folder "test_sort" on the webUI + | entry_name | + | a | + | a 文件 | + | 1 | + | 2 | + | 10 | + | z | + | a.txt | + | a space.txt | + | a space (2).txt | + | a space 文件 | + | a space 文件夹 | + | b1.txt | + | b2.txt | + | b10.txt | + | z.txt | + diff --git a/tests/acceptance/helpers/webdavHelper.js b/tests/acceptance/helpers/webdavHelper.js index a77762ca84e..34ec1785b61 100644 --- a/tests/acceptance/helpers/webdavHelper.js +++ b/tests/acceptance/helpers/webdavHelper.js @@ -147,6 +147,23 @@ exports.createFolder = function (user, folderName) { davPath, { method: 'MKCOL', headers: headers } ) - .then(res => httpHelper.checkStatus(res, 'Could not create the folder.')) + .then(res => httpHelper.checkStatus(res, `Could not create the folder "${folderName}" for user "${user}".`)) + .then(res => res.text()) +} +/** + * Create a file using webDAV api. + * + * @param {string} user + * @param {string} fileName + * @param {string} contents + */ +exports.createFile = function (user, fileName, contents = '') { + const headers = httpHelper.createAuthHeader(user) + const davPath = exports.createDavPath(user, fileName) + return fetch( + davPath, + { method: 'PUT', headers: headers, body: contents } + ) + .then(res => httpHelper.checkStatus(res, `Could not create the file "${fileName}" for user "${user}".`)) .then(res => res.text()) } diff --git a/tests/acceptance/stepDefinitions/filesContext.js b/tests/acceptance/stepDefinitions/filesContext.js index 3766436337c..fdf6dd3400c 100644 --- a/tests/acceptance/stepDefinitions/filesContext.js +++ b/tests/acceptance/stepDefinitions/filesContext.js @@ -459,3 +459,18 @@ Then('file/folder {string} should be listed in shared-with-others page on the we client.page.sharedWithOthersPage().navigateAndWaitTillLoaded() return client.page.FilesPageElement.filesList().waitForFileVisible(filename) }) +Given('the user has created file {string}', function (fileName) { + return webdav.createFile(client.globals.currentUser, fileName, '') +}) +Given('the user has created the following folders', function (entryList) { + entryList.rows().forEach(entry => { + webdav.createFolder(client.globals.currentUser, entry[0]) + }) + return client +}) +Given('the user has created the following files', function (entryList) { + entryList.rows().forEach(entry => { + webdav.createFile(client.globals.currentUser, entry[0]) + }) + return client +})