diff --git a/src/_h5ai/private/conf/options.json b/src/_h5ai/private/conf/options.json
index 38d9c8d7..8b9ad279 100644
--- a/src/_h5ai/private/conf/options.json
+++ b/src/_h5ai/private/conf/options.json
@@ -345,7 +345,7 @@
"column": 0,
"reverse": false,
"ignorecase": true,
- "natural": false,
+ "natural": true,
"folders": 0
},
@@ -390,7 +390,7 @@
"enabled": true,
"show": true,
"maxSubfolders": 50,
- "naturalSort": false,
+ "naturalSort": true,
"ignorecase": true
}
}
diff --git a/src/_h5ai/public/js/lib/ext/sort.js b/src/_h5ai/public/js/lib/ext/sort.js
index b2ba4240..62c3393e 100644
--- a/src/_h5ai/public/js/lib/ext/sort.js
+++ b/src/_h5ai/public/js/lib/ext/sort.js
@@ -13,51 +13,25 @@ const settings = Object.assign({
folders: 0
}, allsettings.sort);
const storekey = 'ext/sort';
-const template = '
';
+const template = `
`;
+
+const getTypeOrder = item => item.isFolder() ? settings.folders : 1;
+const columnProps = {0: 'label', 1: 'time', 2: 'size'};
+const columnClasses = {0: 'label', 1: 'date', 2: 'size'};
-const getType = item => {
- const $item = dom(item);
+const cmpFn = (prop, reverse, ignorecase, natural) => {
+ return (el1, el2) => {
+ const item1 = el1._item;
+ const item2 = el2._item;
- if ($item.hasCls('folder-parent')) {
- return 0;
- }
- if ($item.hasCls('folder')) {
- if (settings.folders === 1) {
- return 2;
- } else if (settings.folders === 2) {
- return 3;
- }
- return 1;
- }
- return 2;
-};
-
-const columnGetters = {
- 0: el => el._item.label,
- 1: el => el._item.time,
- 2: el => el._item.size
-};
-const columnClasses = {
- 0: 'label',
- 1: 'date',
- 2: 'size'
-};
-
-
-const cmpFn = (getValue, reverse, ignorecase, natural) => {
- return (item1, item2) => {
- let res;
- let val1;
- let val2;
-
- res = getType(item1) - getType(item2);
+ let res = getTypeOrder(item1) - getTypeOrder(item2);
if (res !== 0) {
return res;
}
- val1 = getValue(item1);
- val2 = getValue(item2);
+ let val1 = item1[prop];
+ let val2 = item2[prop];
if (isNaN(val1) || isNaN(val2)) {
val1 = String(val1);
@@ -77,14 +51,14 @@ const cmpFn = (getValue, reverse, ignorecase, natural) => {
const sortItems = (column, reverse) => {
const $headers = dom('#items li.header a');
const $header = dom('#items li.header a.' + columnClasses[column]);
- const fn = cmpFn(columnGetters[column], reverse, settings.ignorecase, column === 0 && settings.natural);
+ const fn = cmpFn(columnProps[column], reverse, settings.ignorecase, settings.natural);
store.put(storekey, {column, reverse});
$headers.rmCls('ascending').rmCls('descending');
$header.addCls(reverse ? 'descending' : 'ascending');
- dom(toArray(dom('#items .item')).sort(fn)).appTo('#items');
+ dom(toArray(dom('#items .item:not(.folder-parent)')).sort(fn)).appTo('#items');
};
const onContentChanged = () => {
@@ -101,11 +75,11 @@ const addToggles = () => {
each(columnClasses, (cls, idx) => {
const pos = idx === '0' ? 'app' : 'pre';
$header
- .find('a.' + cls)[pos](template)
- .on('click', ev => {
- sortItems(idx, dom(ev.currentTarget).hasCls('ascending'));
- ev.preventDefault();
- });
+ .find('a.' + cls)[pos](template)
+ .on('click', ev => {
+ sortItems(idx, dom(ev.currentTarget).hasCls('ascending'));
+ ev.preventDefault();
+ });
});
};
diff --git a/src/_h5ai/public/js/lib/ext/tree.js b/src/_h5ai/public/js/lib/ext/tree.js
index 076203a8..a9b0fbdd 100644
--- a/src/_h5ai/public/js/lib/ext/tree.js
+++ b/src/_h5ai/public/js/lib/ext/tree.js
@@ -66,7 +66,7 @@ const cmpItems = (item1, item2) => {
val2 = val2.toLowerCase();
}
- return settings.natural ? naturalCmp(val1, val2) : regularCmp(val1, val2);
+ return settings.naturalSort ? naturalCmp(val1, val2) : regularCmp(val1, val2);
};
const update = item => {
diff --git a/src/_h5ai/public/js/lib/util/naturalCmp.js b/src/_h5ai/public/js/lib/util/naturalCmp.js
index 8b6731bb..888e23a8 100644
--- a/src/_h5ai/public/js/lib/util/naturalCmp.js
+++ b/src/_h5ai/public/js/lib/util/naturalCmp.js
@@ -3,58 +3,57 @@
// Modified to make it work with h5ai
-
-const re = /(^-?[0-9]+(\.?[0-9]*)[df]?e?[0-9]?$|^0x[0-9a-f]+$|[0-9]+)/gi;
-const reStrip = /(^[ ]*|[ ]*$)/g;
-const dre = /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/;
+const reToken = /(^([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi;
+const reDate = /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/;
const reHex = /^0x[0-9a-f]+$/i;
const reLeadingZero = /^0/;
/* eslint-disable complexity */
-const naturalCmp = (val1, val2) => {
+const naturalCmp = (a, b) => {
// convert all to strings strip whitespace
- const x = String(val1).replace(reStrip, '');
- const y = String(val2).replace(reStrip, '');
- // chunk/tokenize
- const xN = x.replace(re, '\0$1\0').replace(/\0$/, '').replace(/^\0/, '').split('\0');
- const yN = y.replace(re, '\0$1\0').replace(/\0$/, '').replace(/^\0/, '').split('\0');
- // numeric, hex or date detection
- const xD = parseInt(x.match(reHex), 10) || xN.length !== 1 && x.match(dre) && Date.parse(x);
- const yD = parseInt(y.match(reHex), 10) || xD && y.match(dre) && Date.parse(y) || null;
+ const x = String(a).trim();
+ const y = String(b).trim();
- let oFxNcL;
- let oFyNcL;
+ // chunk/tokenize
+ const xTokens = x.replace(reToken, '\0$1\0').replace(/\0$/, '').replace(/^\0/, '').split('\0');
+ const yTokens = y.replace(reToken, '\0$1\0').replace(/\0$/, '').replace(/^\0/, '').split('\0');
// first try and sort Hex codes or Dates
- if (yD) {
- if (xD < yD) {
+ const xDate = parseInt(x.match(reHex), 16) || xTokens.length !== 1 && x.match(reDate) && Date.parse(x);
+ const yDate = parseInt(y.match(reHex), 16) || xDate && y.match(reDate) && Date.parse(y) || null;
+ if (yDate) {
+ if (xDate < yDate) {
return -1;
- } else if (xD > yD) {
+ }
+ if (xDate > yDate) {
return 1;
}
}
// natural sorting through split numeric strings and default strings
- for (let cLoc = 0, numS = Math.max(xN.length, yN.length); cLoc < numS; cLoc += 1) {
+ for (let idx = 0, len = Math.max(xTokens.length, yTokens.length); idx < len; idx += 1) {
// find floats not starting with '0', string or 0 if not defined (Clint Priest)
- oFxNcL = !(xN[cLoc] || '').match(reLeadingZero) && parseFloat(xN[cLoc]) || xN[cLoc] || 0;
- oFyNcL = !(yN[cLoc] || '').match(reLeadingZero) && parseFloat(yN[cLoc]) || yN[cLoc] || 0;
+ let xToken = !(xTokens[idx] || '').match(reLeadingZero) && parseFloat(xTokens[idx]) || xTokens[idx] || 0;
+ let yToken = !(yTokens[idx] || '').match(reLeadingZero) && parseFloat(yTokens[idx]) || yTokens[idx] || 0;
+
// handle numeric vs string comparison - number < string - (Kyle Adams)
- if (isNaN(oFxNcL) !== isNaN(oFyNcL)) {
- return isNaN(oFxNcL) ? 1 : -1;
- } else if (typeof oFxNcL !== typeof oFyNcL) {
- // rely on string comparison if different types - i.e. '02' < 2 != '02' < '2'
- oFxNcL = String(oFxNcL);
- oFxNcL = String(oFxNcL);
+ if (isNaN(xToken) !== isNaN(yToken)) {
+ return isNaN(xToken) ? 1 : -1;
}
- if (oFxNcL < oFyNcL) {
+
+ // rely on string comparison if different types - i.e. '02' < 2 != '02' < '2'
+ if (typeof xToken !== typeof yToken) {
+ xToken = String(xToken);
+ yToken = String(yToken);
+ }
+
+ if (xToken < yToken) {
return -1;
}
- if (oFxNcL > oFyNcL) {
+ if (xToken > yToken) {
return 1;
}
}
-
return 0;
};
/* eslint-enable */