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