@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.
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,34 @@ 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) => {
765
- this.updateMtime();
699
+ if (this.readonlyAttributes.includes(prop)) {
700
+ return false;
701
+ }
766
702
  return Reflect.set(target, prop, value);
767
703
  },
768
704
  deleteProperty: (target, prop) => {
769
- this.updateMtime();
705
+ if (this.readonlyAttributes.includes(prop)) {
706
+ return false;
707
+ }
770
708
  return Reflect.deleteProperty(target, prop);
709
+ },
710
+ // TODO: This is deprecated and only needed for files v3
711
+ get: (target, prop, receiver) => {
712
+ if (this.readonlyAttributes.includes(prop)) {
713
+ logger.warn(`Accessing "Node.attributes.${prop}" is deprecated, access it directly on the Node instance.`);
714
+ return Reflect.get(this, prop);
715
+ }
716
+ return Reflect.get(target, prop, receiver);
771
717
  }
772
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
773
718
  };
774
719
  constructor(data, davService) {
775
720
  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;
721
+ this._data = { ...data, attributes: {} };
722
+ this._attributes = new Proxy(this._data.attributes, this.handler);
723
+ this.update(data.attributes ?? {});
779
724
  if (davService) {
780
725
  this._knownDavService = davService;
781
726
  }
@@ -840,12 +785,16 @@ class Node {
840
785
  }
841
786
  /**
842
787
  * Get the file modification time
843
- * There is no setter as the modification time is not meant to be changed manually.
844
- * It will be automatically updated when the attributes are changed.
845
788
  */
846
789
  get mtime() {
847
790
  return this._data.mtime;
848
791
  }
792
+ /**
793
+ * Set the file modification time
794
+ */
795
+ set mtime(mtime) {
796
+ this._data.mtime = mtime;
797
+ }
849
798
  /**
850
799
  * Get the file creation time
851
800
  * There is no setter as the creation time is not meant to be changed
@@ -868,6 +817,7 @@ class Node {
868
817
  }
869
818
  /**
870
819
  * Get the file attribute
820
+ * This contains all additional attributes not provided by the Node class
871
821
  */
872
822
  get attributes() {
873
823
  return this._attributes;
@@ -976,7 +926,7 @@ class Node {
976
926
  this.move(dirname(this.source) + "/" + basename2);
977
927
  }
978
928
  /**
979
- * Update the mtime if exists.
929
+ * Update the mtime if exists
980
930
  */
981
931
  updateMtime() {
982
932
  if (this._data.mtime) {
@@ -985,23 +935,25 @@ class Node {
985
935
  }
986
936
  /**
987
937
  * Update the attributes of the node
938
+ * Warning, updating attributes will NOT automatically update the mtime.
988
939
  *
989
- * @param attributes The new attributes to update
940
+ * @param attributes The new attributes to update on the Node attributes
990
941
  */
991
942
  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;
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;
1001
955
  }
1002
- clean[key] = attributes[key];
1003
956
  }
1004
- return clean;
1005
957
  }
1006
958
  }
1007
959
  /**
@@ -1179,6 +1131,142 @@ function isFilenameValid(filename) {
1179
1131
  }
1180
1132
  return true;
1181
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(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
+ [getLanguage(), 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
+ }
1182
1270
  /**
1183
1271
  * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
1184
1272
  *
@@ -1419,6 +1507,8 @@ validator$2.validate = function(xmlData, options) {
1419
1507
  return getErrorObject("InvalidTag", "Closing tag '" + tagName + "' doesn't have proper closing.", getLineNumberForPosition(xmlData, i));
1420
1508
  } else if (attrStr.trim().length > 0) {
1421
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));
1422
1512
  } else {
1423
1513
  const otg = tags.pop();
1424
1514
  if (tagName !== otg.tagName) {
@@ -2172,6 +2262,13 @@ const parseXml = function(xmlData) {
2172
2262
  if (this.isItStopNode(this.options.stopNodes, jPath, tagName)) {
2173
2263
  let tagContent = "";
2174
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
+ }
2175
2272
  i = result.closeIndex;
2176
2273
  } else if (this.options.unpairedTags.indexOf(tagName) !== -1) {
2177
2274
  i = result.closeIndex;
@@ -3123,6 +3220,7 @@ export {
3123
3220
  File,
3124
3221
  FileAction,
3125
3222
  FileType,
3223
+ FilesSortingMode,
3126
3224
  Folder,
3127
3225
  Header,
3128
3226
  Navigation,
@@ -3151,9 +3249,11 @@ export {
3151
3249
  getNavigation,
3152
3250
  getNewFileMenuEntries,
3153
3251
  isFilenameValid,
3252
+ orderBy,
3154
3253
  parseFileSize,
3155
3254
  registerDavProperty,
3156
3255
  registerFileAction,
3157
3256
  registerFileListHeaders,
3158
- removeNewFileMenuEntry
3257
+ removeNewFileMenuEntry,
3258
+ sortNodes
3159
3259
  };