@nextcloud/files 3.3.1 → 3.4.1

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
  /**
@@ -59,10 +60,12 @@ export declare abstract class Node {
59
60
  get mime(): string | undefined;
60
61
  /**
61
62
  * Get the file modification time
62
- * There is no setter as the modification time is not meant to be changed manually.
63
- * It will be automatically updated when the attributes are changed.
64
63
  */
65
64
  get mtime(): Date | undefined;
65
+ /**
66
+ * Set the file modification time
67
+ */
68
+ set mtime(mtime: Date | undefined);
66
69
  /**
67
70
  * Get the file creation time
68
71
  * There is no setter as the creation time is not meant to be changed
@@ -78,6 +81,7 @@ export declare abstract class Node {
78
81
  set size(size: number | undefined);
79
82
  /**
80
83
  * Get the file attribute
84
+ * This contains all additional attributes not provided by the Node class
81
85
  */
82
86
  get attributes(): Attribute;
83
87
  /**
@@ -134,14 +138,14 @@ export declare abstract class Node {
134
138
  */
135
139
  rename(basename: string): void;
136
140
  /**
137
- * Update the mtime if exists.
141
+ * Update the mtime if exists
138
142
  */
139
- private updateMtime;
143
+ updateMtime(): void;
140
144
  /**
141
145
  * Update the attributes of the node
146
+ * Warning, updating attributes will NOT automatically update the mtime.
142
147
  *
143
- * @param attributes The new attributes to update
148
+ * @param attributes The new attributes to update on the Node attributes
144
149
  */
145
150
  update(attributes: Attribute): void;
146
- private cleanAttributes;
147
151
  }
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,34 @@ 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) => {
767
- this.updateMtime();
701
+ if (this.readonlyAttributes.includes(prop)) {
702
+ return false;
703
+ }
768
704
  return Reflect.set(target, prop, value);
769
705
  },
770
706
  deleteProperty: (target, prop) => {
771
- this.updateMtime();
707
+ if (this.readonlyAttributes.includes(prop)) {
708
+ return false;
709
+ }
772
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);
773
719
  }
774
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
775
720
  };
776
721
  constructor(data, davService) {
777
722
  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;
723
+ this._data = { ...data, attributes: {} };
724
+ this._attributes = new Proxy(this._data.attributes, this.handler);
725
+ this.update(data.attributes ?? {});
781
726
  if (davService) {
782
727
  this._knownDavService = davService;
783
728
  }
@@ -842,12 +787,16 @@ class Node {
842
787
  }
843
788
  /**
844
789
  * Get the file modification time
845
- * There is no setter as the modification time is not meant to be changed manually.
846
- * It will be automatically updated when the attributes are changed.
847
790
  */
848
791
  get mtime() {
849
792
  return this._data.mtime;
850
793
  }
794
+ /**
795
+ * Set the file modification time
796
+ */
797
+ set mtime(mtime) {
798
+ this._data.mtime = mtime;
799
+ }
851
800
  /**
852
801
  * Get the file creation time
853
802
  * There is no setter as the creation time is not meant to be changed
@@ -870,6 +819,7 @@ class Node {
870
819
  }
871
820
  /**
872
821
  * Get the file attribute
822
+ * This contains all additional attributes not provided by the Node class
873
823
  */
874
824
  get attributes() {
875
825
  return this._attributes;
@@ -978,7 +928,7 @@ class Node {
978
928
  this.move(path.dirname(this.source) + "/" + basename2);
979
929
  }
980
930
  /**
981
- * Update the mtime if exists.
931
+ * Update the mtime if exists
982
932
  */
983
933
  updateMtime() {
984
934
  if (this._data.mtime) {
@@ -987,23 +937,25 @@ class Node {
987
937
  }
988
938
  /**
989
939
  * Update the attributes of the node
940
+ * Warning, updating attributes will NOT automatically update the mtime.
990
941
  *
991
- * @param attributes The new attributes to update
942
+ * @param attributes The new attributes to update on the Node attributes
992
943
  */
993
944
  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;
945
+ for (const [name, value] of Object.entries(attributes)) {
946
+ try {
947
+ if (value === void 0) {
948
+ delete this.attributes[name];
949
+ } else {
950
+ this.attributes[name] = value;
951
+ }
952
+ } catch (e) {
953
+ if (e instanceof TypeError) {
954
+ continue;
955
+ }
956
+ throw e;
1003
957
  }
1004
- clean[key] = attributes[key];
1005
958
  }
1006
- return clean;
1007
959
  }
1008
960
  }
1009
961
  /**
@@ -1181,6 +1133,142 @@ function isFilenameValid(filename) {
1181
1133
  }
1182
1134
  return true;
1183
1135
  }
1136
+ /**
1137
+ * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
1138
+ *
1139
+ * @author Christoph Wurst <christoph@winzerhof-wurst.at>
1140
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
1141
+ *
1142
+ * @license AGPL-3.0-or-later
1143
+ *
1144
+ * This program is free software: you can redistribute it and/or modify
1145
+ * it under the terms of the GNU Affero General Public License as
1146
+ * published by the Free Software Foundation, either version 3 of the
1147
+ * License, or (at your option) any later version.
1148
+ *
1149
+ * This program is distributed in the hope that it will be useful,
1150
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1151
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1152
+ * GNU Affero General Public License for more details.
1153
+ *
1154
+ * You should have received a copy of the GNU Affero General Public License
1155
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1156
+ *
1157
+ */
1158
+ const humanList = ["B", "KB", "MB", "GB", "TB", "PB"];
1159
+ const humanListBinary = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"];
1160
+ function formatFileSize(size, skipSmallSizes = false, binaryPrefixes = false, base1000 = false) {
1161
+ binaryPrefixes = binaryPrefixes && !base1000;
1162
+ if (typeof size === "string") {
1163
+ size = Number(size);
1164
+ }
1165
+ let order = size > 0 ? Math.floor(Math.log(size) / Math.log(base1000 ? 1e3 : 1024)) : 0;
1166
+ order = Math.min((binaryPrefixes ? humanListBinary.length : humanList.length) - 1, order);
1167
+ const readableFormat = binaryPrefixes ? humanListBinary[order] : humanList[order];
1168
+ let relativeSize = (size / Math.pow(base1000 ? 1e3 : 1024, order)).toFixed(1);
1169
+ if (skipSmallSizes === true && order === 0) {
1170
+ return (relativeSize !== "0.0" ? "< 1 " : "0 ") + (binaryPrefixes ? humanListBinary[1] : humanList[1]);
1171
+ }
1172
+ if (order < 2) {
1173
+ relativeSize = parseFloat(relativeSize).toFixed(0);
1174
+ } else {
1175
+ relativeSize = parseFloat(relativeSize).toLocaleString(l10n.getCanonicalLocale());
1176
+ }
1177
+ return relativeSize + " " + readableFormat;
1178
+ }
1179
+ function parseFileSize(value, forceBinary = false) {
1180
+ try {
1181
+ value = `${value}`.toLocaleLowerCase().replaceAll(/\s+/g, "").replaceAll(",", ".");
1182
+ } catch (e) {
1183
+ return null;
1184
+ }
1185
+ const match = value.match(/^([0-9]*(\.[0-9]*)?)([kmgtp]?)(i?)b?$/);
1186
+ if (match === null || match[1] === "." || match[1] === "") {
1187
+ return null;
1188
+ }
1189
+ const bytesArray = {
1190
+ "": 0,
1191
+ k: 1,
1192
+ m: 2,
1193
+ g: 3,
1194
+ t: 4,
1195
+ p: 5,
1196
+ e: 6
1197
+ };
1198
+ const decimalString = `${match[1]}`;
1199
+ const base = match[4] === "i" || forceBinary ? 1024 : 1e3;
1200
+ return Math.round(Number.parseFloat(decimalString) * base ** bytesArray[match[3]]);
1201
+ }
1202
+ function stringify(value) {
1203
+ if (value instanceof Date) {
1204
+ return value.toISOString();
1205
+ }
1206
+ return String(value);
1207
+ }
1208
+ function orderBy(collection, identifiers, orders) {
1209
+ identifiers = identifiers ?? [(value) => value];
1210
+ orders = orders ?? [];
1211
+ const sorting = identifiers.map((_, index) => (orders[index] ?? "asc") === "asc" ? 1 : -1);
1212
+ const collator = Intl.Collator(
1213
+ [l10n.getLanguage(), l10n.getCanonicalLocale()],
1214
+ {
1215
+ // handle 10 as ten and not as one-zero
1216
+ numeric: true,
1217
+ usage: "sort"
1218
+ }
1219
+ );
1220
+ return [...collection].sort((a, b) => {
1221
+ for (const [index, identifier] of identifiers.entries()) {
1222
+ const value = collator.compare(stringify(identifier(a)), stringify(identifier(b)));
1223
+ if (value !== 0) {
1224
+ return value * sorting[index];
1225
+ }
1226
+ }
1227
+ return 0;
1228
+ });
1229
+ }
1230
+ var FilesSortingMode = /* @__PURE__ */ ((FilesSortingMode2) => {
1231
+ FilesSortingMode2["Name"] = "basename";
1232
+ FilesSortingMode2["Modified"] = "mtime";
1233
+ FilesSortingMode2["Size"] = "size";
1234
+ return FilesSortingMode2;
1235
+ })(FilesSortingMode || {});
1236
+ function sortNodes(nodes, options = {}) {
1237
+ const sortingOptions = {
1238
+ // Default to sort by name
1239
+ sortingMode: "basename",
1240
+ // Default to sort ascending
1241
+ sortingOrder: "asc",
1242
+ ...options
1243
+ };
1244
+ const identifiers = [
1245
+ // 1: Sort favorites first if enabled
1246
+ ...sortingOptions.sortFavoritesFirst ? [(v) => v.attributes?.favorite !== 1] : [],
1247
+ // 2: Sort folders first if sorting by name
1248
+ ...sortingOptions.sortFoldersFirst ? [(v) => v.type !== "folder"] : [],
1249
+ // 3: Use sorting mode if NOT basename (to be able to use displayName too)
1250
+ ...sortingOptions.sortingMode !== "basename" ? [(v) => v[sortingOptions.sortingMode]] : [],
1251
+ // 4: Use displayName if available, fallback to name
1252
+ (v) => v.attributes?.displayName || v.basename,
1253
+ // 5: Finally, use basename if all previous sorting methods failed
1254
+ (v) => v.basename
1255
+ ];
1256
+ const orders = [
1257
+ // (for 1): always sort favorites before normal files
1258
+ ...sortingOptions.sortFavoritesFirst ? ["asc"] : [],
1259
+ // (for 2): always sort folders before files
1260
+ ...sortingOptions.sortFoldersFirst ? ["asc"] : [],
1261
+ // (for 3): Reverse if sorting by mtime as mtime higher means edited more recent -> lower
1262
+ ...sortingOptions.sortingMode === "mtime" ? [sortingOptions.sortingOrder === "asc" ? "desc" : "asc"] : [],
1263
+ // (also for 3 so make sure not to conflict with 2 and 3)
1264
+ ...sortingOptions.sortingMode !== "mtime" && sortingOptions.sortingMode !== "basename" ? [sortingOptions.sortingOrder] : [],
1265
+ // for 4: use configured sorting direction
1266
+ sortingOptions.sortingOrder,
1267
+ // for 5: use configured sorting direction
1268
+ sortingOptions.sortingOrder
1269
+ ];
1270
+ return orderBy(nodes, identifiers, orders);
1271
+ }
1184
1272
  /**
1185
1273
  * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
1186
1274
  *
@@ -1421,6 +1509,8 @@ validator$2.validate = function(xmlData, options) {
1421
1509
  return getErrorObject("InvalidTag", "Closing tag '" + tagName + "' doesn't have proper closing.", getLineNumberForPosition(xmlData, i));
1422
1510
  } else if (attrStr.trim().length > 0) {
1423
1511
  return getErrorObject("InvalidTag", "Closing tag '" + tagName + "' can't have attributes or invalid starting.", getLineNumberForPosition(xmlData, tagStartPos));
1512
+ } else if (tags.length === 0) {
1513
+ return getErrorObject("InvalidTag", "Closing tag '" + tagName + "' has not been opened.", getLineNumberForPosition(xmlData, tagStartPos));
1424
1514
  } else {
1425
1515
  const otg = tags.pop();
1426
1516
  if (tagName !== otg.tagName) {
@@ -2174,6 +2264,13 @@ const parseXml = function(xmlData) {
2174
2264
  if (this.isItStopNode(this.options.stopNodes, jPath, tagName)) {
2175
2265
  let tagContent = "";
2176
2266
  if (tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1) {
2267
+ if (tagName[tagName.length - 1] === "/") {
2268
+ tagName = tagName.substr(0, tagName.length - 1);
2269
+ jPath = jPath.substr(0, jPath.length - 1);
2270
+ tagExp = tagName;
2271
+ } else {
2272
+ tagExp = tagExp.substr(0, tagExp.length - 1);
2273
+ }
2177
2274
  i = result.closeIndex;
2178
2275
  } else if (this.options.unpairedTags.indexOf(tagName) !== -1) {
2179
2276
  i = result.closeIndex;
@@ -3124,6 +3221,7 @@ exports.DefaultType = DefaultType;
3124
3221
  exports.File = File;
3125
3222
  exports.FileAction = FileAction;
3126
3223
  exports.FileType = FileType;
3224
+ exports.FilesSortingMode = FilesSortingMode;
3127
3225
  exports.Folder = Folder;
3128
3226
  exports.Header = Header;
3129
3227
  exports.Navigation = Navigation;
@@ -3152,8 +3250,10 @@ exports.getFileListHeaders = getFileListHeaders;
3152
3250
  exports.getNavigation = getNavigation;
3153
3251
  exports.getNewFileMenuEntries = getNewFileMenuEntries;
3154
3252
  exports.isFilenameValid = isFilenameValid;
3253
+ exports.orderBy = orderBy;
3155
3254
  exports.parseFileSize = parseFileSize;
3156
3255
  exports.registerDavProperty = registerDavProperty;
3157
3256
  exports.registerFileAction = registerFileAction;
3158
3257
  exports.registerFileListHeaders = registerFileListHeaders;
3159
3258
  exports.removeNewFileMenuEntry = removeNewFileMenuEntry;
3259
+ exports.sortNodes = sortNodes;