@nextcloud/files 3.3.1 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { Entry } from './newFileMenu';
2
2
  import { Folder } from './files/folder';
3
3
 
4
- export { formatFileSize, parseFileSize } from './humanfilesize';
5
4
  export { FileAction, getFileActions, registerFileAction, DefaultType } from './fileAction';
6
5
  export { Header, getFileListHeaders, registerFileListHeaders } from './fileListHeaders';
7
6
  export { type Entry, NewMenuEntryCategory } from './newFileMenu';
@@ -13,7 +12,10 @@ export { FileType } from './files/fileType';
13
12
  export { File } from './files/file';
14
13
  export { Folder } from './files/folder';
15
14
  export { Node, NodeStatus } from './files/node';
16
- export { isFilenameValid } from './filename';
15
+ export { isFilenameValid } from './utils/filename';
16
+ export { formatFileSize, parseFileSize } from './utils/fileSize';
17
+ export { orderBy } from './utils/sorting';
18
+ export { sortNodes, FilesSortingMode, type FilesSortingOptions } from './utils/fileSorting';
17
19
  export * from './navigation/navigation';
18
20
  export * from './navigation/column';
19
21
  export * from './navigation/view';
package/dist/index.mjs CHANGED
@@ -1,11 +1,11 @@
1
1
  import { getCurrentUser, onRequestTokenUpdate, getRequestToken } from "@nextcloud/auth";
2
2
  import { getLoggerBuilder } from "@nextcloud/logger";
3
- import { getCanonicalLocale } from "@nextcloud/l10n";
4
3
  import { join, basename, extname, dirname } from "path";
5
4
  import { encodePath } from "@nextcloud/paths";
6
5
  import { generateRemoteUrl } from "@nextcloud/router";
7
6
  import { createClient, getPatcher } from "webdav";
8
7
  import { CancelablePromise } from "cancelable-promise";
8
+ import { getCanonicalLocale, getLanguage } from "@nextcloud/l10n";
9
9
  /**
10
10
  * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
11
11
  *
@@ -121,72 +121,6 @@ const getNewFileMenu = function() {
121
121
  }
122
122
  return window._nc_newfilemenu;
123
123
  };
124
- /**
125
- * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
126
- *
127
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
128
- * @author John Molakvoæ <skjnldsv@protonmail.com>
129
- *
130
- * @license AGPL-3.0-or-later
131
- *
132
- * This program is free software: you can redistribute it and/or modify
133
- * it under the terms of the GNU Affero General Public License as
134
- * published by the Free Software Foundation, either version 3 of the
135
- * License, or (at your option) any later version.
136
- *
137
- * This program is distributed in the hope that it will be useful,
138
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
139
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
140
- * GNU Affero General Public License for more details.
141
- *
142
- * You should have received a copy of the GNU Affero General Public License
143
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
144
- *
145
- */
146
- const humanList = ["B", "KB", "MB", "GB", "TB", "PB"];
147
- const humanListBinary = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"];
148
- function formatFileSize(size, skipSmallSizes = false, binaryPrefixes = false, base1000 = false) {
149
- binaryPrefixes = binaryPrefixes && !base1000;
150
- if (typeof size === "string") {
151
- size = Number(size);
152
- }
153
- let order = size > 0 ? Math.floor(Math.log(size) / Math.log(base1000 ? 1e3 : 1024)) : 0;
154
- order = Math.min((binaryPrefixes ? humanListBinary.length : humanList.length) - 1, order);
155
- const readableFormat = binaryPrefixes ? humanListBinary[order] : humanList[order];
156
- let relativeSize = (size / Math.pow(base1000 ? 1e3 : 1024, order)).toFixed(1);
157
- if (skipSmallSizes === true && order === 0) {
158
- return (relativeSize !== "0.0" ? "< 1 " : "0 ") + (binaryPrefixes ? humanListBinary[1] : humanList[1]);
159
- }
160
- if (order < 2) {
161
- relativeSize = parseFloat(relativeSize).toFixed(0);
162
- } else {
163
- relativeSize = parseFloat(relativeSize).toLocaleString(getCanonicalLocale());
164
- }
165
- return relativeSize + " " + readableFormat;
166
- }
167
- function parseFileSize(value, forceBinary = false) {
168
- try {
169
- value = `${value}`.toLocaleLowerCase().replaceAll(/\s+/g, "").replaceAll(",", ".");
170
- } catch (e) {
171
- return null;
172
- }
173
- const match = value.match(/^([0-9]*(\.[0-9]*)?)([kmgtp]?)(i?)b?$/);
174
- if (match === null || match[1] === "." || match[1] === "") {
175
- return null;
176
- }
177
- const bytesArray = {
178
- "": 0,
179
- k: 1,
180
- m: 2,
181
- g: 3,
182
- t: 4,
183
- p: 5,
184
- e: 6
185
- };
186
- const decimalString = `${match[1]}`;
187
- const base = match[4] === "i" || forceBinary ? 1024 : 1e3;
188
- return Math.round(Number.parseFloat(decimalString) * base ** bytesArray[match[3]]);
189
- }
190
124
  /**
191
125
  * @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
192
126
  *
@@ -759,23 +693,37 @@ class Node {
759
693
  _data;
760
694
  _attributes;
761
695
  _knownDavService = /(remote|public)\.php\/(web)?dav/i;
696
+ readonlyAttributes = Object.entries(Object.getOwnPropertyDescriptors(Node.prototype)).filter((e) => typeof e[1].get === "function" && e[0] !== "__proto__").map((e) => e[0]);
762
697
  handler = {
763
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
764
698
  set: (target, prop, value) => {
699
+ if (this.readonlyAttributes.includes(prop)) {
700
+ return false;
701
+ }
765
702
  this.updateMtime();
766
703
  return Reflect.set(target, prop, value);
767
704
  },
768
705
  deleteProperty: (target, prop) => {
706
+ if (this.readonlyAttributes.includes(prop)) {
707
+ return false;
708
+ }
769
709
  this.updateMtime();
770
710
  return Reflect.deleteProperty(target, prop);
711
+ },
712
+ // TODO: This is deprecated and only needed for files v3
713
+ get: (target, prop, receiver) => {
714
+ if (this.readonlyAttributes.includes(prop)) {
715
+ logger.warn(`Accessing "Node.attributes.${prop}" is deprecated, access it directly on the Node instance.`);
716
+ return Reflect.get(this, prop);
717
+ }
718
+ return Reflect.get(target, prop, receiver);
771
719
  }
772
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
773
720
  };
774
721
  constructor(data, davService) {
775
722
  validateData(data, davService || this._knownDavService);
776
- this._data = data;
777
- this._attributes = new Proxy(this.cleanAttributes(data.attributes || {}), this.handler);
778
- delete this._data.attributes;
723
+ this._data = { ...data, attributes: {} };
724
+ this._attributes = new Proxy(this._data.attributes, this.handler);
725
+ this.update(data.attributes ?? {});
726
+ this._data.mtime = data.mtime;
779
727
  if (davService) {
780
728
  this._knownDavService = davService;
781
729
  }
@@ -868,6 +816,7 @@ class Node {
868
816
  }
869
817
  /**
870
818
  * Get the file attribute
819
+ * This contains all additional attributes not provided by the Node class
871
820
  */
872
821
  get attributes() {
873
822
  return this._attributes;
@@ -986,22 +935,23 @@ class Node {
986
935
  /**
987
936
  * Update the attributes of the node
988
937
  *
989
- * @param attributes The new attributes to update
938
+ * @param attributes The new attributes to update on the Node attributes
990
939
  */
991
940
  update(attributes) {
992
- this._attributes = new Proxy(this.cleanAttributes(attributes), this.handler);
993
- }
994
- cleanAttributes(attributes) {
995
- const getters = Object.entries(Object.getOwnPropertyDescriptors(Node.prototype)).filter((e) => typeof e[1].get === "function" && e[0] !== "__proto__").map((e) => e[0]);
996
- const clean = {};
997
- for (const key in attributes) {
998
- if (getters.includes(key)) {
999
- logger.debug(`Discarding protected attribute ${key}`, { node: this, attributes });
1000
- continue;
941
+ for (const [name, value] of Object.entries(attributes)) {
942
+ try {
943
+ if (value === void 0) {
944
+ delete this.attributes[name];
945
+ } else {
946
+ this.attributes[name] = value;
947
+ }
948
+ } catch (e) {
949
+ if (e instanceof TypeError) {
950
+ continue;
951
+ }
952
+ throw e;
1001
953
  }
1002
- clean[key] = attributes[key];
1003
954
  }
1004
- return clean;
1005
955
  }
1006
956
  }
1007
957
  /**
@@ -1179,6 +1129,142 @@ function isFilenameValid(filename) {
1179
1129
  }
1180
1130
  return true;
1181
1131
  }
1132
+ /**
1133
+ * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
1134
+ *
1135
+ * @author Christoph Wurst <christoph@winzerhof-wurst.at>
1136
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
1137
+ *
1138
+ * @license AGPL-3.0-or-later
1139
+ *
1140
+ * This program is free software: you can redistribute it and/or modify
1141
+ * it under the terms of the GNU Affero General Public License as
1142
+ * published by the Free Software Foundation, either version 3 of the
1143
+ * License, or (at your option) any later version.
1144
+ *
1145
+ * This program is distributed in the hope that it will be useful,
1146
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1147
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1148
+ * GNU Affero General Public License for more details.
1149
+ *
1150
+ * You should have received a copy of the GNU Affero General Public License
1151
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1152
+ *
1153
+ */
1154
+ const humanList = ["B", "KB", "MB", "GB", "TB", "PB"];
1155
+ const humanListBinary = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"];
1156
+ function formatFileSize(size, skipSmallSizes = false, binaryPrefixes = false, base1000 = false) {
1157
+ binaryPrefixes = binaryPrefixes && !base1000;
1158
+ if (typeof size === "string") {
1159
+ size = Number(size);
1160
+ }
1161
+ let order = size > 0 ? Math.floor(Math.log(size) / Math.log(base1000 ? 1e3 : 1024)) : 0;
1162
+ order = Math.min((binaryPrefixes ? humanListBinary.length : humanList.length) - 1, order);
1163
+ const readableFormat = binaryPrefixes ? humanListBinary[order] : humanList[order];
1164
+ let relativeSize = (size / Math.pow(base1000 ? 1e3 : 1024, order)).toFixed(1);
1165
+ if (skipSmallSizes === true && order === 0) {
1166
+ return (relativeSize !== "0.0" ? "< 1 " : "0 ") + (binaryPrefixes ? humanListBinary[1] : humanList[1]);
1167
+ }
1168
+ if (order < 2) {
1169
+ relativeSize = parseFloat(relativeSize).toFixed(0);
1170
+ } else {
1171
+ relativeSize = parseFloat(relativeSize).toLocaleString(getCanonicalLocale());
1172
+ }
1173
+ return relativeSize + " " + readableFormat;
1174
+ }
1175
+ function parseFileSize(value, forceBinary = false) {
1176
+ try {
1177
+ value = `${value}`.toLocaleLowerCase().replaceAll(/\s+/g, "").replaceAll(",", ".");
1178
+ } catch (e) {
1179
+ return null;
1180
+ }
1181
+ const match = value.match(/^([0-9]*(\.[0-9]*)?)([kmgtp]?)(i?)b?$/);
1182
+ if (match === null || match[1] === "." || match[1] === "") {
1183
+ return null;
1184
+ }
1185
+ const bytesArray = {
1186
+ "": 0,
1187
+ k: 1,
1188
+ m: 2,
1189
+ g: 3,
1190
+ t: 4,
1191
+ p: 5,
1192
+ e: 6
1193
+ };
1194
+ const decimalString = `${match[1]}`;
1195
+ const base = match[4] === "i" || forceBinary ? 1024 : 1e3;
1196
+ return Math.round(Number.parseFloat(decimalString) * base ** bytesArray[match[3]]);
1197
+ }
1198
+ function stringify(value) {
1199
+ if (value instanceof Date) {
1200
+ return value.toISOString();
1201
+ }
1202
+ return String(value);
1203
+ }
1204
+ function orderBy(collection, identifiers, orders) {
1205
+ identifiers = identifiers ?? [(value) => value];
1206
+ orders = orders ?? [];
1207
+ const sorting = identifiers.map((_, index) => (orders[index] ?? "asc") === "asc" ? 1 : -1);
1208
+ const collator = Intl.Collator(
1209
+ [getLanguage(), getCanonicalLocale()],
1210
+ {
1211
+ // handle 10 as ten and not as one-zero
1212
+ numeric: true,
1213
+ usage: "sort"
1214
+ }
1215
+ );
1216
+ return [...collection].sort((a, b) => {
1217
+ for (const [index, identifier] of identifiers.entries()) {
1218
+ const value = collator.compare(stringify(identifier(a)), stringify(identifier(b)));
1219
+ if (value !== 0) {
1220
+ return value * sorting[index];
1221
+ }
1222
+ }
1223
+ return 0;
1224
+ });
1225
+ }
1226
+ var FilesSortingMode = /* @__PURE__ */ ((FilesSortingMode2) => {
1227
+ FilesSortingMode2["Name"] = "basename";
1228
+ FilesSortingMode2["Modified"] = "mtime";
1229
+ FilesSortingMode2["Size"] = "size";
1230
+ return FilesSortingMode2;
1231
+ })(FilesSortingMode || {});
1232
+ function sortNodes(nodes, options = {}) {
1233
+ const sortingOptions = {
1234
+ // Default to sort by name
1235
+ sortingMode: "basename",
1236
+ // Default to sort ascending
1237
+ sortingOrder: "asc",
1238
+ ...options
1239
+ };
1240
+ const identifiers = [
1241
+ // 1: Sort favorites first if enabled
1242
+ ...sortingOptions.sortFavoritesFirst ? [(v) => v.attributes?.favorite !== 1] : [],
1243
+ // 2: Sort folders first if sorting by name
1244
+ ...sortingOptions.sortFoldersFirst ? [(v) => v.type !== "folder"] : [],
1245
+ // 3: Use sorting mode if NOT basename (to be able to use displayName too)
1246
+ ...sortingOptions.sortingMode !== "basename" ? [(v) => v[sortingOptions.sortingMode]] : [],
1247
+ // 4: Use displayName if available, fallback to name
1248
+ (v) => v.attributes?.displayName || v.basename,
1249
+ // 5: Finally, use basename if all previous sorting methods failed
1250
+ (v) => v.basename
1251
+ ];
1252
+ const orders = [
1253
+ // (for 1): always sort favorites before normal files
1254
+ ...sortingOptions.sortFavoritesFirst ? ["asc"] : [],
1255
+ // (for 2): always sort folders before files
1256
+ ...sortingOptions.sortFoldersFirst ? ["asc"] : [],
1257
+ // (for 3): Reverse if sorting by mtime as mtime higher means edited more recent -> lower
1258
+ ...sortingOptions.sortingMode === "mtime" ? [sortingOptions.sortingOrder === "asc" ? "desc" : "asc"] : [],
1259
+ // (also for 3 so make sure not to conflict with 2 and 3)
1260
+ ...sortingOptions.sortingMode !== "mtime" && sortingOptions.sortingMode !== "basename" ? [sortingOptions.sortingOrder] : [],
1261
+ // for 4: use configured sorting direction
1262
+ sortingOptions.sortingOrder,
1263
+ // for 5: use configured sorting direction
1264
+ sortingOptions.sortingOrder
1265
+ ];
1266
+ return orderBy(nodes, identifiers, orders);
1267
+ }
1182
1268
  /**
1183
1269
  * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
1184
1270
  *
@@ -1419,6 +1505,8 @@ validator$2.validate = function(xmlData, options) {
1419
1505
  return getErrorObject("InvalidTag", "Closing tag '" + tagName + "' doesn't have proper closing.", getLineNumberForPosition(xmlData, i));
1420
1506
  } else if (attrStr.trim().length > 0) {
1421
1507
  return getErrorObject("InvalidTag", "Closing tag '" + tagName + "' can't have attributes or invalid starting.", getLineNumberForPosition(xmlData, tagStartPos));
1508
+ } else if (tags.length === 0) {
1509
+ return getErrorObject("InvalidTag", "Closing tag '" + tagName + "' has not been opened.", getLineNumberForPosition(xmlData, tagStartPos));
1422
1510
  } else {
1423
1511
  const otg = tags.pop();
1424
1512
  if (tagName !== otg.tagName) {
@@ -2172,6 +2260,13 @@ const parseXml = function(xmlData) {
2172
2260
  if (this.isItStopNode(this.options.stopNodes, jPath, tagName)) {
2173
2261
  let tagContent = "";
2174
2262
  if (tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1) {
2263
+ if (tagName[tagName.length - 1] === "/") {
2264
+ tagName = tagName.substr(0, tagName.length - 1);
2265
+ jPath = jPath.substr(0, jPath.length - 1);
2266
+ tagExp = tagName;
2267
+ } else {
2268
+ tagExp = tagExp.substr(0, tagExp.length - 1);
2269
+ }
2175
2270
  i = result.closeIndex;
2176
2271
  } else if (this.options.unpairedTags.indexOf(tagName) !== -1) {
2177
2272
  i = result.closeIndex;
@@ -3123,6 +3218,7 @@ export {
3123
3218
  File,
3124
3219
  FileAction,
3125
3220
  FileType,
3221
+ FilesSortingMode,
3126
3222
  Folder,
3127
3223
  Header,
3128
3224
  Navigation,
@@ -3151,9 +3247,11 @@ export {
3151
3247
  getNavigation,
3152
3248
  getNewFileMenuEntries,
3153
3249
  isFilenameValid,
3250
+ orderBy,
3154
3251
  parseFileSize,
3155
3252
  registerDavProperty,
3156
3253
  registerFileAction,
3157
3254
  registerFileListHeaders,
3158
- removeNewFileMenuEntry
3255
+ removeNewFileMenuEntry,
3256
+ sortNodes
3159
3257
  };