@nextcloud/files 3.3.0 → 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,10 +1,9 @@
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
- export { type Entry } from './newFileMenu';
6
+ export { type Entry, NewMenuEntryCategory } from './newFileMenu';
8
7
  export { Permission } from './permissions';
9
8
  export * from './dav/davProperties';
10
9
  export * from './dav/davPermissions';
@@ -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
  *
@@ -55,6 +55,12 @@ const logger = getLogger(getCurrentUser());
55
55
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
56
56
  *
57
57
  */
58
+ var NewMenuEntryCategory = /* @__PURE__ */ ((NewMenuEntryCategory2) => {
59
+ NewMenuEntryCategory2[NewMenuEntryCategory2["UploadFromDevice"] = 0] = "UploadFromDevice";
60
+ NewMenuEntryCategory2[NewMenuEntryCategory2["CreateNew"] = 1] = "CreateNew";
61
+ NewMenuEntryCategory2[NewMenuEntryCategory2["Other"] = 2] = "Other";
62
+ return NewMenuEntryCategory2;
63
+ })(NewMenuEntryCategory || {});
58
64
  class NewFileMenu {
59
65
  _entries = [];
60
66
  registerEntry(entry) {
@@ -115,72 +121,6 @@ const getNewFileMenu = function() {
115
121
  }
116
122
  return window._nc_newfilemenu;
117
123
  };
118
- /**
119
- * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
120
- *
121
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
122
- * @author John Molakvoæ <skjnldsv@protonmail.com>
123
- *
124
- * @license AGPL-3.0-or-later
125
- *
126
- * This program is free software: you can redistribute it and/or modify
127
- * it under the terms of the GNU Affero General Public License as
128
- * published by the Free Software Foundation, either version 3 of the
129
- * License, or (at your option) any later version.
130
- *
131
- * This program is distributed in the hope that it will be useful,
132
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
133
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
134
- * GNU Affero General Public License for more details.
135
- *
136
- * You should have received a copy of the GNU Affero General Public License
137
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
138
- *
139
- */
140
- const humanList = ["B", "KB", "MB", "GB", "TB", "PB"];
141
- const humanListBinary = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"];
142
- function formatFileSize(size, skipSmallSizes = false, binaryPrefixes = false, base1000 = false) {
143
- binaryPrefixes = binaryPrefixes && !base1000;
144
- if (typeof size === "string") {
145
- size = Number(size);
146
- }
147
- let order = size > 0 ? Math.floor(Math.log(size) / Math.log(base1000 ? 1e3 : 1024)) : 0;
148
- order = Math.min((binaryPrefixes ? humanListBinary.length : humanList.length) - 1, order);
149
- const readableFormat = binaryPrefixes ? humanListBinary[order] : humanList[order];
150
- let relativeSize = (size / Math.pow(base1000 ? 1e3 : 1024, order)).toFixed(1);
151
- if (skipSmallSizes === true && order === 0) {
152
- return (relativeSize !== "0.0" ? "< 1 " : "0 ") + (binaryPrefixes ? humanListBinary[1] : humanList[1]);
153
- }
154
- if (order < 2) {
155
- relativeSize = parseFloat(relativeSize).toFixed(0);
156
- } else {
157
- relativeSize = parseFloat(relativeSize).toLocaleString(getCanonicalLocale());
158
- }
159
- return relativeSize + " " + readableFormat;
160
- }
161
- function parseFileSize(value, forceBinary = false) {
162
- try {
163
- value = `${value}`.toLocaleLowerCase().replaceAll(/\s+/g, "").replaceAll(",", ".");
164
- } catch (e) {
165
- return null;
166
- }
167
- const match = value.match(/^([0-9]*(\.[0-9]*)?)([kmgtp]?)(i?)b?$/);
168
- if (match === null || match[1] === "." || match[1] === "") {
169
- return null;
170
- }
171
- const bytesArray = {
172
- "": 0,
173
- k: 1,
174
- m: 2,
175
- g: 3,
176
- t: 4,
177
- p: 5,
178
- e: 6
179
- };
180
- const decimalString = `${match[1]}`;
181
- const base = match[4] === "i" || forceBinary ? 1024 : 1e3;
182
- return Math.round(Number.parseFloat(decimalString) * base ** bytesArray[match[3]]);
183
- }
184
124
  /**
185
125
  * @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
186
126
  *
@@ -753,23 +693,37 @@ class Node {
753
693
  _data;
754
694
  _attributes;
755
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]);
756
697
  handler = {
757
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
758
698
  set: (target, prop, value) => {
699
+ if (this.readonlyAttributes.includes(prop)) {
700
+ return false;
701
+ }
759
702
  this.updateMtime();
760
703
  return Reflect.set(target, prop, value);
761
704
  },
762
705
  deleteProperty: (target, prop) => {
706
+ if (this.readonlyAttributes.includes(prop)) {
707
+ return false;
708
+ }
763
709
  this.updateMtime();
764
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);
765
719
  }
766
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
767
720
  };
768
721
  constructor(data, davService) {
769
722
  validateData(data, davService || this._knownDavService);
770
- this._data = data;
771
- this._attributes = new Proxy(this.cleanAttributes(data.attributes || {}), this.handler);
772
- 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;
773
727
  if (davService) {
774
728
  this._knownDavService = davService;
775
729
  }
@@ -862,6 +816,7 @@ class Node {
862
816
  }
863
817
  /**
864
818
  * Get the file attribute
819
+ * This contains all additional attributes not provided by the Node class
865
820
  */
866
821
  get attributes() {
867
822
  return this._attributes;
@@ -980,22 +935,23 @@ class Node {
980
935
  /**
981
936
  * Update the attributes of the node
982
937
  *
983
- * @param attributes The new attributes to update
938
+ * @param attributes The new attributes to update on the Node attributes
984
939
  */
985
940
  update(attributes) {
986
- this._attributes = new Proxy(this.cleanAttributes(attributes), this.handler);
987
- }
988
- cleanAttributes(attributes) {
989
- const getters = Object.entries(Object.getOwnPropertyDescriptors(Node.prototype)).filter((e) => typeof e[1].get === "function" && e[0] !== "__proto__").map((e) => e[0]);
990
- const clean = {};
991
- for (const key in attributes) {
992
- if (getters.includes(key)) {
993
- logger.debug(`Discarding protected attribute ${key}`, { node: this, attributes });
994
- 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;
995
953
  }
996
- clean[key] = attributes[key];
997
954
  }
998
- return clean;
999
955
  }
1000
956
  }
1001
957
  /**
@@ -1173,6 +1129,142 @@ function isFilenameValid(filename) {
1173
1129
  }
1174
1130
  return true;
1175
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
+ }
1176
1268
  /**
1177
1269
  * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
1178
1270
  *
@@ -1413,6 +1505,8 @@ validator$2.validate = function(xmlData, options) {
1413
1505
  return getErrorObject("InvalidTag", "Closing tag '" + tagName + "' doesn't have proper closing.", getLineNumberForPosition(xmlData, i));
1414
1506
  } else if (attrStr.trim().length > 0) {
1415
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));
1416
1510
  } else {
1417
1511
  const otg = tags.pop();
1418
1512
  if (tagName !== otg.tagName) {
@@ -2166,6 +2260,13 @@ const parseXml = function(xmlData) {
2166
2260
  if (this.isItStopNode(this.options.stopNodes, jPath, tagName)) {
2167
2261
  let tagContent = "";
2168
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
+ }
2169
2270
  i = result.closeIndex;
2170
2271
  } else if (this.options.unpairedTags.indexOf(tagName) !== -1) {
2171
2272
  i = result.closeIndex;
@@ -3117,9 +3218,11 @@ export {
3117
3218
  File,
3118
3219
  FileAction,
3119
3220
  FileType,
3221
+ FilesSortingMode,
3120
3222
  Folder,
3121
3223
  Header,
3122
3224
  Navigation,
3225
+ NewMenuEntryCategory,
3123
3226
  Node,
3124
3227
  NodeStatus,
3125
3228
  Permission,
@@ -3144,9 +3247,11 @@ export {
3144
3247
  getNavigation,
3145
3248
  getNewFileMenuEntries,
3146
3249
  isFilenameValid,
3250
+ orderBy,
3147
3251
  parseFileSize,
3148
3252
  registerDavProperty,
3149
3253
  registerFileAction,
3150
3254
  registerFileListHeaders,
3151
- removeNewFileMenuEntry
3255
+ removeNewFileMenuEntry,
3256
+ sortNodes
3152
3257
  };