@nextcloud/files 4.0.0-rc.2 → 4.0.0-rc.3
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/chunks/{folder-C9vrC6sc.mjs → folder-D9CBQngR.mjs} +6 -2
- package/dist/chunks/folder-D9CBQngR.mjs.map +1 -0
- package/dist/dav.mjs +11 -17
- package/dist/dav.mjs.map +1 -1
- package/dist/globalScope.d.ts +27 -0
- package/dist/index.mjs +49 -48
- package/dist/index.mjs.map +1 -1
- package/dist/registry.d.ts +37 -10
- package/package.json +1 -1
- package/dist/chunks/folder-C9vrC6sc.mjs.map +0 -1
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { getLoggerBuilder } from "@nextcloud/logger";
|
|
2
2
|
import { join, encodePath, basename, extname, dirname } from "@nextcloud/paths";
|
|
3
|
+
window._nc_files_scope ??= {};
|
|
4
|
+
window._nc_files_scope.v4_0 ??= {};
|
|
5
|
+
const scopedGlobals = window._nc_files_scope.v4_0;
|
|
3
6
|
const logger = getLoggerBuilder().setApp("@nextcloud/files").detectUser().build();
|
|
4
7
|
const FileType = Object.freeze({
|
|
5
8
|
Folder: "folder",
|
|
@@ -466,6 +469,7 @@ export {
|
|
|
466
469
|
File as a,
|
|
467
470
|
Folder as b,
|
|
468
471
|
NodeStatus as c,
|
|
469
|
-
logger as l
|
|
472
|
+
logger as l,
|
|
473
|
+
scopedGlobals as s
|
|
470
474
|
};
|
|
471
|
-
//# sourceMappingURL=folder-
|
|
475
|
+
//# sourceMappingURL=folder-D9CBQngR.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"folder-D9CBQngR.mjs","sources":["../../lib/globalScope.ts","../../lib/utils/logger.ts","../../lib/node/fileType.ts","../../lib/permissions.ts","../../lib/node/nodeData.ts","../../lib/node/node.ts","../../lib/node/file.ts","../../lib/node/folder.ts"],"sourcesContent":["/*\n * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { IFileAction, IFileListAction } from './actions/index.ts'\nimport type { DavProperty } from './dav/index.ts'\nimport type {\n\tIFileListFilter,\n\tIFileListHeader,\n\tNavigation,\n\tNewMenu,\n} from './index.ts'\nimport type { FilesRegistry } from './registry.ts'\nimport type { ISidebarTab } from './sidebar/index.ts'\nimport type { ISidebarAction } from './sidebar/SidebarAction.ts'\n\ninterface InternalGlobalScope {\n\tdavNamespaces?: DavProperty\n\tdavProperties?: string[]\n\n\tnewFileMenu?: NewMenu\n\tnavigation?: Navigation\n\tregistry?: FilesRegistry\n\n\tfileActions?: Map<string, IFileAction>\n\tfileListActions?: Map<string, IFileListAction>\n\tfileListFilters?: Map<string, IFileListFilter>\n\tfileListHeaders?: Map<string, IFileListHeader>\n\n\tfilesSidebarActions?: Map<string, ISidebarAction>\n\tfilesSidebarTabs?: Map<string, ISidebarTab>\n}\n\nwindow._nc_files_scope ??= {}\nwindow._nc_files_scope.v4_0 ??= {}\n\n/**\n * Get the global scope for the files library.\n * This is used to store global variables scoped to prevent breaking changes in the future.\n *\n * @internal\n */\nexport const scopedGlobals = window._nc_files_scope.v4_0 as InternalGlobalScope\n","/**\n * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\nimport { getLoggerBuilder } from '@nextcloud/logger'\n\nexport default getLoggerBuilder()\n\t.setApp('@nextcloud/files')\n\t.detectUser()\n\t.build()\n","/**\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nexport const FileType = Object.freeze({\n\tFolder: 'folder',\n\tFile: 'file',\n})\n\nexport type IFileType = typeof FileType[keyof typeof FileType]\n","/**\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\n/**\n * Node permissions\n */\nexport const Permission = Object.freeze({\n\t/**\n\t * No permissions granted\n\t */\n\tNONE: 0,\n\t/**\n\t * Can read the file content\n\t */\n\tREAD: 1,\n\t/**\n\t * Can modify the file itself (move, rename, etc)\n\t */\n\tUPDATE: 2,\n\t/**\n\t * Can create new files/folders inside a folder\n\t */\n\tCREATE: 4,\n\t/**\n\t * Can change the file content\n\t */\n\tWRITE: 4,\n\t/**\n\t * Can delete the node\n\t */\n\tDELETE: 8,\n\t/**\n\t * Can share the node\n\t */\n\tSHARE: 16,\n\t/**\n\t * All permissions are granted\n\t */\n\tALL: 31,\n})\n","/**\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport { join } from '@nextcloud/paths'\nimport { Permission } from '../permissions.ts'\n\nexport const NodeStatus = Object.freeze({\n\t/** This is a new node and it doesn't exists on the filesystem yet */\n\tNEW: 'new',\n\t/** This node has failed and is unavailable */\n\tFAILED: 'failed',\n\t/** This node is currently loading or have an operation in progress */\n\tLOADING: 'loading',\n\t/** This node is locked and cannot be modified */\n\tLOCKED: 'locked',\n})\n\nexport type INodeStatus = typeof NodeStatus[keyof typeof NodeStatus]\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport interface Attribute { [key: string]: any }\n\nexport interface NodeData {\n\t/**\n\t * URL to the resource.\n\t * e.g. https://cloud.domain.com/remote.php/dav/files/emma/Photos/picture.jpg\n\t * or https://domain.com/Photos/picture.jpg\n\t */\n\tsource: string\n\n\t/**\n\t * The absolute root of the home relative to the service.\n\t * It is highly recommended to provide that information.\n\t * e.g. /files/emma\n\t */\n\troot: string\n\n\t/** Unique ID */\n\tid?: number\n\n\t/** Last modified time */\n\tmtime?: Date\n\n\t/** Creation time */\n\tcrtime?: Date\n\n\t/** The mime type Optional for folders only */\n\tmime?: string\n\n\t/** The node size type */\n\tsize?: number\n\n\t/**\n\t * The node permissions.\n\t *\n\t * A binary mask of `Permission` values.\n\t *\n\t * @see Permission\n\t */\n\tpermissions?: number\n\n\t/** The owner UID of this node */\n\towner: string | null\n\n\t/** Optional the displayname of this node */\n\tdisplayname?: string\n\n\t/** The node attributes */\n\tattributes?: Attribute\n\n\t/** The node status */\n\tstatus?: INodeStatus\n}\n\n/**\n * Check if a node source is from a specific DAV service\n *\n * @param source The source to check\n * @param davService Pattern to check if source is DAV resource\n */\nexport function isDavResource(source: string, davService: RegExp): boolean {\n\treturn source.match(davService) !== null\n}\n\n/**\n * Validate Node construct data\n *\n * @param data The node data\n * @param davService Pattern to check if source is DAV ressource\n */\nexport function validateData(data: NodeData, davService: RegExp) {\n\tif (data.id && typeof data.id !== 'number') {\n\t\tthrow new Error('Invalid id type of value')\n\t}\n\n\tif (!data.source) {\n\t\tthrow new Error('Missing mandatory source')\n\t}\n\n\ttry {\n\t\tnew URL(data.source)\n\t} catch {\n\t\tthrow new Error('Invalid source format, source must be a valid URL')\n\t}\n\n\tif (!data.source.startsWith('http')) {\n\t\tthrow new Error('Invalid source format, only http(s) is supported')\n\t}\n\n\tif (!data.root) {\n\t\tthrow new Error('Missing mandatory root')\n\t}\n\n\tif (typeof data.root !== 'string') {\n\t\tthrow new Error('Invalid root type')\n\t}\n\n\tif (!data.root.startsWith('/')) {\n\t\tthrow new Error('Root must start with a leading slash')\n\t}\n\n\tif (!data.source.includes(data.root)) {\n\t\tthrow new Error('Root must be part of the source')\n\t}\n\n\tif (isDavResource(data.source, davService)) {\n\t\tconst service = data.source.match(davService)![0]\n\t\tif (!data.source.includes(join(service, data.root))) {\n\t\t\tthrow new Error('The root must be relative to the service. e.g /files/emma')\n\t\t}\n\t}\n\n\tif (data.displayname && typeof data.displayname !== 'string') {\n\t\tthrow new Error('Invalid displayname type')\n\t}\n\n\tif (data.mtime && !(data.mtime instanceof Date)) {\n\t\tthrow new Error('Invalid mtime type')\n\t}\n\n\tif (data.crtime && !(data.crtime instanceof Date)) {\n\t\tthrow new Error('Invalid crtime type')\n\t}\n\n\tif (!data.mime || typeof data.mime !== 'string'\n\t\t|| !data.mime.match(/^[-\\w.]+\\/[-+\\w.]+$/gi)) {\n\t\tthrow new Error('Missing or invalid mandatory mime')\n\t}\n\n\t// Allow size to be 0\n\tif ('size' in data && typeof data.size !== 'number' && data.size !== undefined) {\n\t\tthrow new Error('Invalid size type')\n\t}\n\n\t// Allow permissions to be 0\n\tif ('permissions' in data\n\t\t&& data.permissions !== undefined\n\t\t&& !(typeof data.permissions === 'number'\n\t\t\t&& data.permissions >= Permission.NONE\n\t\t\t&& data.permissions <= Permission.ALL\n\t\t)) {\n\t\tthrow new Error('Invalid permissions')\n\t}\n\n\tif (data.owner\n\t\t&& data.owner !== null\n\t\t&& typeof data.owner !== 'string') {\n\t\tthrow new Error('Invalid owner type')\n\t}\n\n\tif (data.attributes && typeof data.attributes !== 'object') {\n\t\tthrow new Error('Invalid attributes type')\n\t}\n\n\tif (data.status && !Object.values(NodeStatus).includes(data.status)) {\n\t\tthrow new Error('Status must be a valid NodeStatus')\n\t}\n}\n\n/**\n * In case we try to create a node from deserialized data,\n * we need to fix date types.\n *\n * @param data The internal node data\n */\nexport function fixDates(data: NodeData) {\n\tif (data.mtime && typeof data.mtime === 'string') {\n\t\tif (!isNaN(Date.parse(data.mtime))\n\t\t\t&& JSON.stringify(new Date(data.mtime)) === JSON.stringify(data.mtime)) {\n\t\t\tdata.mtime = new Date(data.mtime)\n\t\t}\n\t}\n\n\tif (data.crtime && typeof data.crtime === 'string') {\n\t\tif (!isNaN(Date.parse(data.crtime))\n\t\t\t&& JSON.stringify(new Date(data.crtime)) === JSON.stringify(data.crtime)) {\n\t\t\tdata.crtime = new Date(data.crtime)\n\t\t}\n\t}\n}\n\n/**\n * Fix a RegExp pattern from string or RegExp to RegExp\n *\n * @param pattern The pattern as string or RegExp\n */\nexport function fixRegExp(pattern: string | RegExp): RegExp {\n\tif (pattern instanceof RegExp) {\n\t\treturn pattern\n\t}\n\n\t// Extract the pattern and flags if it's a string\n\t// Pulled from https://www.npmjs.com/package/regex-parser\n\tconst matches = pattern.match(/(\\/?)(.+)\\1([a-z]*)/i)\n\n\t// If there's no match, throw an error\n\tif (!matches) {\n\t\tthrow new Error('Invalid regular expression format.')\n\t}\n\n\t// Filter valid flags: 'g', 'i', 'm', 's', 'u', and 'y'\n\tconst validFlags = Array.from(new Set(matches[3]))\n\t\t.filter((flag) => 'gimsuy'.includes(flag))\n\t\t.join('')\n\n\t// Create the regular expression\n\treturn new RegExp(matches[2], validFlags)\n}\n","/**\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { IFileType } from './fileType.ts'\nimport type { Attribute, INodeStatus, NodeData } from './nodeData.ts'\n\nimport { basename, dirname, encodePath, extname } from '@nextcloud/paths'\nimport { Permission } from '../permissions.ts'\nimport { fixDates, fixRegExp, isDavResource, validateData } from './nodeData.ts'\n\nexport type NodeConstructorData = [NodeData, RegExp?]\n\nexport abstract class Node {\n\tprivate _attributes: Attribute\n\n\tprotected _data: NodeData\n\tprotected _knownDavService = /(remote|public)\\.php\\/(web)?dav/i\n\n\tprivate readonlyAttributes = Object.entries(Object.getOwnPropertyDescriptors(Node.prototype))\n\t\t.filter((e) => typeof e[1].get === 'function' && e[0] !== '__proto__')\n\t\t.map((e) => e[0])\n\n\tprivate handler = {\n\t\tset: (target: Attribute, prop: string, value: unknown): boolean => {\n\t\t\tif (this.readonlyAttributes.includes(prop)) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Apply original changes\n\t\t\treturn Reflect.set(target, prop, value)\n\t\t},\n\t\tdeleteProperty: (target: Attribute, prop: string): boolean => {\n\t\t\tif (this.readonlyAttributes.includes(prop)) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Apply original changes\n\t\t\treturn Reflect.deleteProperty(target, prop)\n\t\t},\n\t} as ProxyHandler<Attribute>\n\n\tprotected constructor(...[data, davService]: NodeConstructorData) {\n\t\tif (!data.mime) {\n\t\t\tdata.mime = 'application/octet-stream'\n\t\t}\n\n\t\t// Fix primitive types if needed\n\t\tfixDates(data)\n\t\tdavService = fixRegExp(davService || this._knownDavService)\n\n\t\t// Validate data\n\t\tvalidateData(data, davService)\n\n\t\tthis._data = {\n\t\t\t...data,\n\t\t\tattributes: {},\n\t\t}\n\n\t\t// Proxy the attributes to update the mtime on change\n\t\tthis._attributes = new Proxy(this._data.attributes!, this.handler)\n\n\t\t// Update attributes, this sanitizes the attributes to only contain valid attributes\n\t\tthis.update(data.attributes ?? {})\n\n\t\tif (davService) {\n\t\t\tthis._knownDavService = davService\n\t\t}\n\t}\n\n\t/**\n\t * Get the source url to this object\n\t * There is no setter as the source is not meant to be changed manually.\n\t * You can use the rename or move method to change the source.\n\t */\n\tget source(): string {\n\t\t// strip any ending slash\n\t\treturn this._data.source.replace(/\\/$/i, '')\n\t}\n\n\t/**\n\t * Get the encoded source url to this object for requests purposes\n\t */\n\tget encodedSource(): string {\n\t\tconst { origin } = new URL(this.source)\n\t\treturn origin + encodePath(this.source.slice(origin.length))\n\t}\n\n\t/**\n\t * Get this object name\n\t * There is no setter as the source is not meant to be changed manually.\n\t * You can use the rename or move method to change the source.\n\t */\n\tget basename(): string {\n\t\treturn basename(this.source)\n\t}\n\n\t/**\n\t * The nodes displayname\n\t * By default the display name and the `basename` are identical,\n\t * but it is possible to have a different name. This happens\n\t * on the files app for example for shared folders.\n\t */\n\tget displayname(): string {\n\t\treturn this._data.displayname || this.basename\n\t}\n\n\t/**\n\t * Set the displayname\n\t */\n\tset displayname(displayname: string) {\n\t\tvalidateData({ ...this._data, displayname }, this._knownDavService)\n\t\tthis._data.displayname = displayname\n\t}\n\n\t/**\n\t * Get this object's extension\n\t * There is no setter as the source is not meant to be changed manually.\n\t * You can use the rename or move method to change the source.\n\t */\n\tget extension(): string | null {\n\t\treturn extname(this.source)\n\t}\n\n\t/**\n\t * Get the directory path leading to this object\n\t * Will use the relative path to root if available\n\t *\n\t * There is no setter as the source is not meant to be changed manually.\n\t * You can use the rename or move method to change the source.\n\t */\n\tget dirname(): string {\n\t\treturn dirname(this.path)\n\t}\n\n\t/**\n\t * Is it a file or a folder ?\n\t */\n\tabstract get type(): IFileType\n\n\t/**\n\t * Get the file mime\n\t */\n\tget mime(): string {\n\t\treturn this._data.mime || 'application/octet-stream'\n\t}\n\n\t/**\n\t * Set the file mime\n\t * Removing the mime type will set it to `application/octet-stream`\n\t */\n\tset mime(mime: string | undefined) {\n\t\tmime ??= 'application/octet-stream'\n\n\t\tvalidateData({ ...this._data, mime }, this._knownDavService)\n\t\tthis._data.mime = mime\n\t}\n\n\t/**\n\t * Get the file modification time\n\t */\n\tget mtime(): Date | undefined {\n\t\treturn this._data.mtime\n\t}\n\n\t/**\n\t * Set the file modification time\n\t */\n\tset mtime(mtime: Date | undefined) {\n\t\tvalidateData({ ...this._data, mtime }, this._knownDavService)\n\t\tthis._data.mtime = mtime\n\t}\n\n\t/**\n\t * Get the file creation time\n\t * There is no setter as the creation time is not meant to be changed\n\t */\n\tget crtime(): Date | undefined {\n\t\treturn this._data.crtime\n\t}\n\n\t/**\n\t * Get the file size\n\t */\n\tget size(): number | undefined {\n\t\treturn this._data.size\n\t}\n\n\t/**\n\t * Set the file size\n\t */\n\tset size(size: number | undefined) {\n\t\tvalidateData({ ...this._data, size }, this._knownDavService)\n\t\tthis.updateMtime()\n\t\tthis._data.size = size\n\t}\n\n\t/**\n\t * Get the file attribute\n\t * This contains all additional attributes not provided by the Node class\n\t */\n\tget attributes(): Attribute {\n\t\treturn this._attributes\n\t}\n\n\t/**\n\t * Get the file permissions\n\t */\n\tget permissions(): number {\n\t\t// If this is not a dav resource, we can only read it\n\t\tif (this.owner === null && !this.isDavResource) {\n\t\t\treturn Permission.READ\n\t\t}\n\n\t\t// If the permissions are not defined, we have none\n\t\treturn this._data.permissions !== undefined\n\t\t\t? this._data.permissions\n\t\t\t: Permission.NONE\n\t}\n\n\t/**\n\t * Set the file permissions\n\t */\n\tset permissions(permissions: number) {\n\t\tvalidateData({ ...this._data, permissions }, this._knownDavService)\n\t\tthis.updateMtime()\n\t\tthis._data.permissions = permissions\n\t}\n\n\t/**\n\t * Get the file owner\n\t * There is no setter as the owner is not meant to be changed\n\t */\n\tget owner(): string | null {\n\t\t// Remote resources have no owner\n\t\tif (!this.isDavResource) {\n\t\t\treturn null\n\t\t}\n\t\treturn this._data.owner\n\t}\n\n\t/**\n\t * Is this a dav-related resource ?\n\t */\n\tget isDavResource(): boolean {\n\t\treturn isDavResource(this.source, this._knownDavService)\n\t}\n\n\t/**\n\t * Get the dav root of this object\n\t * There is no setter as the root is not meant to be changed\n\t */\n\tget root(): string {\n\t\treturn this._data.root.replace(/^(.+)\\/$/, '$1')\n\t}\n\n\t/**\n\t * Get the absolute path of this object relative to the root\n\t */\n\tget path(): string {\n\t\t// Extract the path part from the source URL\n\t\t// e.g. https://cloud.domain.com/remote.php/dav/files/username/Path/To/File.txt\n\t\tconst idx = this.source.indexOf('://')\n\t\tconst protocol = this.source.slice(0, idx) // e.g. https\n\t\tconst remainder = this.source.slice(idx + 3) // e.g. cloud.domain.com/remote.php/dav/files/username/Path/To/File.txt\n\n\t\tconst slashIndex = remainder.indexOf('/')\n\t\tconst host = remainder.slice(0, slashIndex) // e.g. cloud.domain.com\n\t\tconst rawPath = remainder.slice(slashIndex) // e.g. /remote.php/dav/files/username/Path/To/File.txt\n\n\t\t// Rebuild a safe URL with encoded path\n\t\tconst safeUrl = `${protocol}://${host}${encodePath(rawPath)}`\n\t\tconst url = new URL(safeUrl)\n\n\t\tlet source = decodeURIComponent(url.pathname)\n\n\t\tif (this.isDavResource) {\n\t\t\t// ensure we only work on the real path in case root is not distinct\n\t\t\tsource = source.split(this._knownDavService).pop()!\n\t\t}\n\t\t// Using replace would remove all part matching root\n\t\tconst firstMatch = source.indexOf(this.root)\n\t\t// Ensure we do not remove the leading slash\n\t\tconst root = this.root.replace(/\\/$/, '')\n\t\treturn source.slice(firstMatch + root.length) || '/'\n\t}\n\n\t/**\n\t * Get the node id if defined.\n\t * There is no setter as the fileid is not meant to be changed\n\t */\n\tget fileid(): number | undefined {\n\t\treturn this._data?.id\n\t}\n\n\t/**\n\t * Get the node status.\n\t */\n\tget status(): INodeStatus | undefined {\n\t\treturn this._data?.status\n\t}\n\n\t/**\n\t * Set the node status.\n\t */\n\tset status(status: INodeStatus | undefined) {\n\t\tvalidateData({ ...this._data, status }, this._knownDavService)\n\t\tthis._data.status = status\n\t}\n\n\t/**\n\t * Move the node to a new destination\n\t *\n\t * @param destination the new source.\n\t * e.g. https://cloud.domain.com/remote.php/dav/files/emma/Photos/picture.jpg\n\t */\n\tmove(destination: string) {\n\t\tvalidateData({ ...this._data, source: destination }, this._knownDavService)\n\t\tconst oldBasename = this.basename\n\n\t\tthis._data.source = destination\n\t\t// Check if the displayname and the (old) basename were the same\n\t\t// meaning no special displayname was set but just a fallback to the basename by Nextcloud's WebDAV server\n\t\tif (this.displayname === oldBasename\n\t\t\t&& this.basename !== oldBasename) {\n\t\t\t// We have to assume that the displayname was not set but just a copy of the basename\n\t\t\t// this can not be guaranteed, so to be sure users should better refetch the node\n\t\t\tthis.displayname = this.basename\n\t\t}\n\t}\n\n\t/**\n\t * Rename the node\n\t * This aliases the move method for easier usage\n\t *\n\t * @param basename The new name of the node\n\t */\n\trename(basename: string) {\n\t\tif (basename.includes('/')) {\n\t\t\tthrow new Error('Invalid basename')\n\t\t}\n\t\tthis.move(dirname(this.source) + '/' + basename)\n\t}\n\n\t/**\n\t * Update the mtime if exists\n\t */\n\tupdateMtime() {\n\t\tif (this._data.mtime) {\n\t\t\tthis._data.mtime = new Date()\n\t\t}\n\t}\n\n\t/**\n\t * Update the attributes of the node\n\t * Warning, updating attributes will NOT automatically update the mtime.\n\t *\n\t * @param attributes The new attributes to update on the Node attributes\n\t */\n\tupdate(attributes: Attribute) {\n\t\tfor (const [name, value] of Object.entries(attributes)) {\n\t\t\ttry {\n\t\t\t\tif (value === undefined) {\n\t\t\t\t\tdelete this.attributes[name]\n\t\t\t\t} else {\n\t\t\t\t\tthis.attributes[name] = value\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\t// Ignore readonly attributes\n\t\t\t\tif (e instanceof TypeError) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\t// Throw all other exceptions\n\t\t\t\tthrow e\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Returns a clone of the node\n\t */\n\tclone(): this {\n\t\t// @ts-expect-error -- this class is abstract and cannot be instantiated directly but all its children can\n\t\treturn new this.constructor(structuredClone(this._data), this._knownDavService)\n\t}\n\n\t/**\n\t * JSON representation of the node\n\t */\n\ttoJSON(): string {\n\t\treturn JSON.stringify([structuredClone(this._data), this._knownDavService.toString()])\n\t}\n}\n\n/**\n * Interface of the node class\n */\nexport type INode = Pick<Node, keyof Node>\n","/**\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { NodeConstructorData } from './node.ts'\n\nimport { FileType } from './fileType.ts'\nimport { Node } from './node.ts'\n\nexport class File extends Node {\n\tpublic constructor(...[data, davService]: NodeConstructorData) {\n\t\tsuper(data, davService)\n\t}\n\n\tget type(): typeof FileType.File {\n\t\treturn FileType.File\n\t}\n}\n\n/**\n * Interface of the File class\n */\nexport type IFile = Pick<File, keyof File>\n","/**\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { NodeConstructorData } from './node.ts'\n\nimport { FileType } from './fileType.ts'\nimport { Node } from './node.ts'\n\nexport class Folder extends Node {\n\tconstructor(...[data, davService]: NodeConstructorData) {\n\t\t// enforcing mimes\n\t\tsuper({\n\t\t\t...data,\n\t\t\tmime: 'httpd/unix-directory',\n\t\t}, davService)\n\t}\n\n\tget type(): typeof FileType.Folder {\n\t\treturn FileType.Folder\n\t}\n\n\tget extension(): null {\n\t\treturn null\n\t}\n\n\tget mime(): 'httpd/unix-directory' {\n\t\treturn 'httpd/unix-directory'\n\t}\n}\n\n/**\n * Interface of the folder class\n */\nexport type IFolder = Pick<Folder, keyof Folder>\n"],"names":["basename"],"mappings":"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"}
|
package/dist/dav.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { getCurrentUser, onRequestTokenUpdate, getRequestToken } from "@nextclou
|
|
|
2
2
|
import { generateRemoteUrl } from "@nextcloud/router";
|
|
3
3
|
import { isPublicShare, getSharingToken } from "@nextcloud/sharing/public";
|
|
4
4
|
import { createClient, getPatcher } from "webdav";
|
|
5
|
-
import { P as Permission, l as logger, c as NodeStatus, a as File, b as Folder } from "./chunks/folder-
|
|
5
|
+
import { P as Permission, s as scopedGlobals, l as logger, c as NodeStatus, a as File, b as Folder } from "./chunks/folder-D9CBQngR.mjs";
|
|
6
6
|
import "@nextcloud/paths";
|
|
7
7
|
/*!
|
|
8
8
|
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
|
@@ -60,12 +60,10 @@ const defaultDavNamespaces = {
|
|
|
60
60
|
ocs: "http://open-collaboration-services.org/ns"
|
|
61
61
|
};
|
|
62
62
|
function registerDavProperty(prop, namespace = { nc: "http://nextcloud.org/ns" }) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const namespaces = { ...window._nc_dav_namespaces, ...namespace };
|
|
68
|
-
if (window._nc_dav_properties.find((search) => search === prop)) {
|
|
63
|
+
scopedGlobals.davNamespaces ??= { ...defaultDavNamespaces };
|
|
64
|
+
scopedGlobals.davProperties ??= [...defaultDavProperties];
|
|
65
|
+
const namespaces = { ...scopedGlobals.davNamespaces, ...namespace };
|
|
66
|
+
if (scopedGlobals.davProperties.find((search) => search === prop)) {
|
|
69
67
|
logger.warn(`${prop} already registered`, { prop });
|
|
70
68
|
return false;
|
|
71
69
|
}
|
|
@@ -78,21 +76,17 @@ function registerDavProperty(prop, namespace = { nc: "http://nextcloud.org/ns" }
|
|
|
78
76
|
logger.error(`${prop} namespace unknown`, { prop, namespaces });
|
|
79
77
|
return false;
|
|
80
78
|
}
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
scopedGlobals.davProperties.push(prop);
|
|
80
|
+
scopedGlobals.davNamespaces = namespaces;
|
|
83
81
|
return true;
|
|
84
82
|
}
|
|
85
83
|
function getDavProperties() {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
return window._nc_dav_properties.map((prop) => `<${prop} />`).join(" ");
|
|
84
|
+
scopedGlobals.davProperties ??= [...defaultDavProperties];
|
|
85
|
+
return scopedGlobals.davProperties.map((prop) => `<${prop} />`).join(" ");
|
|
90
86
|
}
|
|
91
87
|
function getDavNameSpaces() {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
return Object.keys(window._nc_dav_namespaces).map((ns) => `xmlns:${ns}="${window._nc_dav_namespaces?.[ns]}"`).join(" ");
|
|
88
|
+
scopedGlobals.davNamespaces ??= { ...defaultDavNamespaces };
|
|
89
|
+
return Object.keys(scopedGlobals.davNamespaces).map((ns) => `xmlns:${ns}="${scopedGlobals.davNamespaces?.[ns]}"`).join(" ");
|
|
96
90
|
}
|
|
97
91
|
function getDefaultPropfind() {
|
|
98
92
|
return `<?xml version="1.0"?>
|
package/dist/dav.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dav.mjs","sources":["../lib/dav/davPermissions.ts","../lib/dav/davProperties.ts","../lib/dav/dav.ts"],"sourcesContent":["/*!\n * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport { Permission } from '../permissions.ts'\n\n/**\n * Parse the WebDAV permission string to a permission enum\n *\n * @param permString - The DAV permission string\n */\nexport function parsePermissions(permString = ''): number {\n\tlet permissions = Permission.NONE\n\tif (!permString) {\n\t\treturn permissions\n\t}\n\n\tif (permString.includes('G')) {\n\t\tpermissions |= Permission.READ\n\t}\n\tif (permString.includes('W')) {\n\t\tpermissions |= Permission.WRITE\n\t}\n\tif (permString.includes('CK')) {\n\t\tpermissions |= Permission.CREATE\n\t}\n\tif (permString.includes('NV')) {\n\t\tpermissions |= Permission.UPDATE\n\t}\n\tif (permString.includes('D')) {\n\t\tpermissions |= Permission.DELETE\n\t}\n\tif (permString.includes('R')) {\n\t\tpermissions |= Permission.SHARE\n\t}\n\n\treturn permissions\n}\n","/**\n * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport { getCurrentUser } from '@nextcloud/auth'\nimport logger from '../utils/logger.ts'\n\nexport type DavProperty = { [key: string]: string }\n\nexport const defaultDavProperties = [\n\t'd:getcontentlength',\n\t'd:getcontenttype',\n\t'd:getetag',\n\t'd:getlastmodified',\n\t'd:creationdate',\n\t'd:displayname',\n\t'd:quota-available-bytes',\n\t'd:resourcetype',\n\t'nc:has-preview',\n\t'nc:is-encrypted',\n\t'nc:mount-type',\n\t'oc:comments-unread',\n\t'oc:favorite',\n\t'oc:fileid',\n\t'oc:owner-display-name',\n\t'oc:owner-id',\n\t'oc:permissions',\n\t'oc:size',\n]\n\nexport const defaultDavNamespaces = {\n\td: 'DAV:',\n\tnc: 'http://nextcloud.org/ns',\n\toc: 'http://owncloud.org/ns',\n\tocs: 'http://open-collaboration-services.org/ns',\n}\n\n/**\n * Register custom DAV properties\n *\n * Can be used if your app introduces custom DAV properties, so e.g. the files app can make use of it.\n *\n * @param prop The property\n * @param namespace The namespace of the property\n */\nexport function registerDavProperty(prop: string, namespace: DavProperty = { nc: 'http://nextcloud.org/ns' }): boolean {\n\tif (typeof window._nc_dav_properties === 'undefined') {\n\t\twindow._nc_dav_properties = [...defaultDavProperties]\n\t\twindow._nc_dav_namespaces = { ...defaultDavNamespaces }\n\t}\n\n\tconst namespaces = { ...window._nc_dav_namespaces, ...namespace }\n\n\t// Check duplicates\n\tif (window._nc_dav_properties.find((search) => search === prop)) {\n\t\tlogger.warn(`${prop} already registered`, { prop })\n\t\treturn false\n\t}\n\n\tif (prop.startsWith('<') || prop.split(':').length !== 2) {\n\t\tlogger.error(`${prop} is not valid. See example: 'oc:fileid'`, { prop })\n\t\treturn false\n\t}\n\n\tconst ns = prop.split(':')[0]\n\tif (!namespaces[ns]) {\n\t\tlogger.error(`${prop} namespace unknown`, { prop, namespaces })\n\t\treturn false\n\t}\n\n\twindow._nc_dav_properties.push(prop)\n\twindow._nc_dav_namespaces = namespaces\n\treturn true\n}\n\n/**\n * Get the registered dav properties\n */\nexport function getDavProperties(): string {\n\tif (typeof window._nc_dav_properties === 'undefined') {\n\t\twindow._nc_dav_properties = [...defaultDavProperties]\n\t}\n\n\treturn window._nc_dav_properties.map((prop) => `<${prop} />`).join(' ')\n}\n\n/**\n * Get the registered dav namespaces\n */\nexport function getDavNameSpaces(): string {\n\tif (typeof window._nc_dav_namespaces === 'undefined') {\n\t\twindow._nc_dav_namespaces = { ...defaultDavNamespaces }\n\t}\n\n\treturn Object.keys(window._nc_dav_namespaces)\n\t\t.map((ns) => `xmlns:${ns}=\"${window._nc_dav_namespaces?.[ns]}\"`)\n\t\t.join(' ')\n}\n\n/**\n * Get the default PROPFIND request body\n */\nexport function getDefaultPropfind(): string {\n\treturn `<?xml version=\"1.0\"?>\n\t\t<d:propfind ${getDavNameSpaces()}>\n\t\t\t<d:prop>\n\t\t\t\t${getDavProperties()}\n\t\t\t</d:prop>\n\t\t</d:propfind>`\n}\n\n/**\n * Get the REPORT body to filter for favorite nodes\n */\nexport function getFavoritesReport(): string {\n\treturn `<?xml version=\"1.0\"?>\n\t\t<oc:filter-files ${getDavNameSpaces()}>\n\t\t\t<d:prop>\n\t\t\t\t${getDavProperties()}\n\t\t\t</d:prop>\n\t\t\t<oc:filter-rules>\n\t\t\t\t<oc:favorite>1</oc:favorite>\n\t\t\t</oc:filter-rules>\n\t\t</oc:filter-files>`\n}\n\n/**\n * Get the SEARCH body to search for recently modified files\n *\n * @param lastModified Oldest timestamp to include (Unix timestamp)\n * @example\n * ```ts\n * // SEARCH for recent files need a different DAV endpoint\n * const client = davGetClient(generateRemoteUrl('dav'))\n * // Timestamp of last week\n * const lastWeek = Math.round(Date.now() / 1000) - (60 * 60 * 24 * 7)\n * const contentsResponse = await client.getDirectoryContents(path, {\n * details: true,\n * data: davGetRecentSearch(lastWeek),\n * headers: {\n * method: 'SEARCH',\n * 'Content-Type': 'application/xml; charset=utf-8',\n * },\n * deep: true,\n * }) as ResponseDataDetailed<FileStat[]>\n * ```\n */\nexport function getRecentSearch(lastModified: number): string {\n\treturn `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<d:searchrequest ${getDavNameSpaces()}\n\txmlns:ns=\"https://github.com/icewind1991/SearchDAV/ns\">\n\t<d:basicsearch>\n\t\t<d:select>\n\t\t\t<d:prop>\n\t\t\t\t${getDavProperties()}\n\t\t\t</d:prop>\n\t\t</d:select>\n\t\t<d:from>\n\t\t\t<d:scope>\n\t\t\t\t<d:href>/files/${getCurrentUser()?.uid}/</d:href>\n\t\t\t\t<d:depth>infinity</d:depth>\n\t\t\t</d:scope>\n\t\t</d:from>\n\t\t<d:where>\n\t\t\t<d:and>\n\t\t\t\t<d:or>\n\t\t\t\t\t<d:not>\n\t\t\t\t\t\t<d:eq>\n\t\t\t\t\t\t\t<d:prop>\n\t\t\t\t\t\t\t\t<d:getcontenttype/>\n\t\t\t\t\t\t\t</d:prop>\n\t\t\t\t\t\t\t<d:literal>httpd/unix-directory</d:literal>\n\t\t\t\t\t\t</d:eq>\n\t\t\t\t\t</d:not>\n\t\t\t\t\t<d:eq>\n\t\t\t\t\t\t<d:prop>\n\t\t\t\t\t\t\t<oc:size/>\n\t\t\t\t\t\t</d:prop>\n\t\t\t\t\t\t<d:literal>0</d:literal>\n\t\t\t\t\t</d:eq>\n\t\t\t\t</d:or>\n\t\t\t\t<d:gt>\n\t\t\t\t\t<d:prop>\n\t\t\t\t\t\t<d:getlastmodified/>\n\t\t\t\t\t</d:prop>\n\t\t\t\t\t<d:literal>${lastModified}</d:literal>\n\t\t\t\t</d:gt>\n\t\t\t</d:and>\n\t\t</d:where>\n\t\t<d:orderby>\n\t\t\t<d:order>\n\t\t\t\t<d:prop>\n\t\t\t\t\t<d:getlastmodified/>\n\t\t\t\t</d:prop>\n\t\t\t\t<d:descending/>\n\t\t\t</d:order>\n\t\t</d:orderby>\n\t\t<d:limit>\n\t\t\t<d:nresults>100</d:nresults>\n\t\t\t<ns:firstresult>0</ns:firstresult>\n\t\t</d:limit>\n\t</d:basicsearch>\n</d:searchrequest>`\n}\n","/*\n * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { DAVResultResponseProps, FileStat, ResponseDataDetailed, WebDAVClient } from 'webdav'\nimport type { Node, NodeData } from '../node/index.ts'\n\nimport { getCurrentUser, getRequestToken, onRequestTokenUpdate } from '@nextcloud/auth'\nimport { generateRemoteUrl } from '@nextcloud/router'\nimport { getSharingToken, isPublicShare } from '@nextcloud/sharing/public'\nimport { createClient, getPatcher } from 'webdav'\nimport { File, Folder, NodeStatus } from '../node/index.ts'\nimport { parsePermissions } from './davPermissions.ts'\nimport { getFavoritesReport } from './davProperties.ts'\n\n/**\n * Nextcloud DAV result response\n */\ninterface ResponseProps extends DAVResultResponseProps {\n\tcreationdate: string\n\tpermissions: string\n\tmime: string\n\tfileid: number\n\tsize: number\n\t'owner-id': string | number\n}\n\n/**\n * Get the DAV root path for the current user or public share\n */\nexport function getRootPath(): string {\n\tif (isPublicShare()) {\n\t\treturn `/files/${getSharingToken()}`\n\t}\n\treturn `/files/${getCurrentUser()?.uid}`\n}\n\n/**\n * The DAV root path for the current user\n * This is a cached version of `getRemoteURL`\n */\nexport const defaultRootPath = getRootPath()\n\n/**\n * Get the DAV remote URL used as base URL for the WebDAV client\n * It also handles public shares\n */\nexport function getRemoteURL(): string {\n\tconst url = generateRemoteUrl('dav')\n\tif (isPublicShare()) {\n\t\treturn url.replace('remote.php', 'public.php')\n\t}\n\treturn url\n}\n\n/**\n * The DAV remote URL used as base URL for the WebDAV client\n * This is a cached version of `getRemoteURL`\n */\nexport const defaultRemoteURL = getRemoteURL()\n\n/**\n * Get a WebDAV client configured to include the Nextcloud request token\n *\n * @param remoteURL The DAV server remote URL\n * @param headers Optional additional headers to set for every request\n */\nexport function getClient(remoteURL = defaultRemoteURL, headers: Record<string, string> = {}) {\n\tconst client = createClient(remoteURL, { headers })\n\n\t/**\n\t * Set headers for DAV requests\n\t *\n\t * @param token CSRF token\n\t */\n\tfunction setHeaders(token: string | null) {\n\t\tclient.setHeaders({\n\t\t\t...headers,\n\t\t\t// Add this so the server knows it is an request from the browser\n\t\t\t'X-Requested-With': 'XMLHttpRequest',\n\t\t\t// Inject user auth\n\t\t\trequesttoken: token ?? '',\n\t\t})\n\t}\n\n\t// refresh headers when request token changes\n\tonRequestTokenUpdate(setHeaders)\n\tsetHeaders(getRequestToken())\n\n\t/**\n\t * Allow to override the METHOD to support dav REPORT\n\t *\n\t * @see https://github.com/perry-mitchell/webdav-client/blob/8d9694613c978ce7404e26a401c39a41f125f87f/source/request.ts\n\t */\n\tconst patcher = getPatcher()\n\t// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n\t// @ts-ignore\n\t// https://github.com/perry-mitchell/hot-patcher/issues/6\n\tpatcher.patch('fetch', (url: string, options: RequestInit): Promise<Response> => {\n\t\tconst headers = options.headers as Record<string, string>\n\t\tif (headers?.method) {\n\t\t\toptions.method = headers.method\n\t\t\tdelete headers.method\n\t\t}\n\t\treturn fetch(url, options)\n\t})\n\n\treturn client\n}\n\n/**\n * Use WebDAV to query for favorite Nodes\n *\n * @param options - Options for the favorite query\n * @param options.client - The WebDAV client to use for performing the request\n * @param options.path - Base path for the favorites, if unset all favorites are queried\n * @param options.davRoot - The root path for the DAV user (defaults to `defaultRootPath`)\n * @param options.signal - Optional abort signal to cancel the request\n *\n * @example\n * ```js\n * import { getFavoriteNodes } from '@nextcloud/files'\n *\n * // query favorites for the root\n * const favorites = await getFavoriteNodes()\n * ```\n * @example\n * ```js\n * // Advanced usage with custom client and path\n * import { getClient, defaultRootPath, getFavoriteNodes } from '@nextcloud/files'\n *\n * const client = getClient()\n * const controller = new AbortController()\n * const favoritesPromise = getFavoriteNodes({ client, path: '/some/folder', signal: controller.signal })\n * // you can abort the request if needed\n * controller.abort()\n * // or await the result\n * const favorites = await favoritesPromise\n * ```\n */\nexport async function getFavoriteNodes(options: { client?: WebDAVClient, path?: string, davRoot?: string, signal?: AbortSignal } = {}): Promise<Node[]> {\n\tconst client = options.client ?? getClient()\n\tconst path = options.path ?? '/'\n\tconst davRoot = options.davRoot ?? defaultRootPath\n\n\tconst contentsResponse = await client.getDirectoryContents(`${davRoot}${path}`, {\n\t\tsignal: options.signal,\n\t\tdetails: true,\n\t\tdata: getFavoritesReport(),\n\t\theaders: {\n\t\t\t// see getClient for patched webdav client\n\t\t\tmethod: 'REPORT',\n\t\t},\n\t\tincludeSelf: true,\n\t}) as ResponseDataDetailed<FileStat[]>\n\n\treturn contentsResponse.data\n\t\t.filter((node) => node.filename !== path) // exclude current dir\n\t\t.map((result) => resultToNode(result, davRoot))\n}\n\n/**\n * Convert DAV result `FileStat` to `Node`\n *\n * @param node The DAV result\n * @param filesRoot The DAV files root path\n * @param remoteURL The DAV server remote URL (same as on `getClient`)\n */\nexport function resultToNode(node: FileStat, filesRoot = defaultRootPath, remoteURL = defaultRemoteURL): Node {\n\tlet userId = getCurrentUser()?.uid\n\tif (isPublicShare()) {\n\t\tuserId = userId ?? 'anonymous'\n\t} else if (!userId) {\n\t\tthrow new Error('No user id found')\n\t}\n\n\tconst props = node.props as ResponseProps\n\tconst permissions = parsePermissions(props?.permissions)\n\tconst owner = String(props?.['owner-id'] || userId)\n\tconst id = props.fileid || 0\n\n\tconst mtime = new Date(Date.parse(node.lastmod))\n\tconst crtime = new Date(Date.parse(props.creationdate))\n\n\tconst nodeData: NodeData = {\n\t\tid,\n\t\tsource: `${remoteURL}${node.filename}`,\n\t\tmtime: !isNaN(mtime.getTime()) && mtime.getTime() !== 0 ? mtime : undefined,\n\t\tcrtime: !isNaN(crtime.getTime()) && crtime.getTime() !== 0 ? crtime : undefined,\n\t\tmime: node.mime || 'application/octet-stream',\n\t\t// Manually cast to work around for https://github.com/perry-mitchell/webdav-client/pull/380\n\t\tdisplayname: props.displayname !== undefined ? String(props.displayname) : undefined,\n\t\tsize: props?.size || Number.parseInt(props.getcontentlength || '0'),\n\t\t// The fileid is set to -1 for failed requests\n\t\tstatus: id < 0 ? NodeStatus.FAILED : undefined,\n\t\tpermissions,\n\t\towner,\n\t\troot: filesRoot,\n\t\tattributes: {\n\t\t\t...node,\n\t\t\t...props,\n\t\t\thasPreview: props?.['has-preview'],\n\t\t},\n\t}\n\n\tdelete nodeData.attributes?.props\n\n\treturn node.type === 'file' ? new File(nodeData) : new Folder(nodeData)\n}\n"],"names":["headers"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dav.mjs","sources":["../lib/dav/davPermissions.ts","../lib/dav/davProperties.ts","../lib/dav/dav.ts"],"sourcesContent":["/*!\n * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport { Permission } from '../permissions.ts'\n\n/**\n * Parse the WebDAV permission string to a permission enum\n *\n * @param permString - The DAV permission string\n */\nexport function parsePermissions(permString = ''): number {\n\tlet permissions = Permission.NONE\n\tif (!permString) {\n\t\treturn permissions\n\t}\n\n\tif (permString.includes('G')) {\n\t\tpermissions |= Permission.READ\n\t}\n\tif (permString.includes('W')) {\n\t\tpermissions |= Permission.WRITE\n\t}\n\tif (permString.includes('CK')) {\n\t\tpermissions |= Permission.CREATE\n\t}\n\tif (permString.includes('NV')) {\n\t\tpermissions |= Permission.UPDATE\n\t}\n\tif (permString.includes('D')) {\n\t\tpermissions |= Permission.DELETE\n\t}\n\tif (permString.includes('R')) {\n\t\tpermissions |= Permission.SHARE\n\t}\n\n\treturn permissions\n}\n","/**\n * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport { getCurrentUser } from '@nextcloud/auth'\nimport { scopedGlobals } from '../globalScope.ts'\nimport logger from '../utils/logger.ts'\n\nexport type DavProperty = { [key: string]: string }\n\nexport const defaultDavProperties = [\n\t'd:getcontentlength',\n\t'd:getcontenttype',\n\t'd:getetag',\n\t'd:getlastmodified',\n\t'd:creationdate',\n\t'd:displayname',\n\t'd:quota-available-bytes',\n\t'd:resourcetype',\n\t'nc:has-preview',\n\t'nc:is-encrypted',\n\t'nc:mount-type',\n\t'oc:comments-unread',\n\t'oc:favorite',\n\t'oc:fileid',\n\t'oc:owner-display-name',\n\t'oc:owner-id',\n\t'oc:permissions',\n\t'oc:size',\n]\n\nexport const defaultDavNamespaces = {\n\td: 'DAV:',\n\tnc: 'http://nextcloud.org/ns',\n\toc: 'http://owncloud.org/ns',\n\tocs: 'http://open-collaboration-services.org/ns',\n}\n\n/**\n * Register custom DAV properties\n *\n * Can be used if your app introduces custom DAV properties, so e.g. the files app can make use of it.\n *\n * @param prop The property\n * @param namespace The namespace of the property\n */\nexport function registerDavProperty(prop: string, namespace: DavProperty = { nc: 'http://nextcloud.org/ns' }): boolean {\n\tscopedGlobals.davNamespaces ??= { ...defaultDavNamespaces }\n\tscopedGlobals.davProperties ??= [...defaultDavProperties]\n\n\tconst namespaces = { ...scopedGlobals.davNamespaces, ...namespace }\n\n\t// Check duplicates\n\tif (scopedGlobals.davProperties.find((search) => search === prop)) {\n\t\tlogger.warn(`${prop} already registered`, { prop })\n\t\treturn false\n\t}\n\n\tif (prop.startsWith('<') || prop.split(':').length !== 2) {\n\t\tlogger.error(`${prop} is not valid. See example: 'oc:fileid'`, { prop })\n\t\treturn false\n\t}\n\n\tconst ns = prop.split(':')[0]\n\tif (!namespaces[ns]) {\n\t\tlogger.error(`${prop} namespace unknown`, { prop, namespaces })\n\t\treturn false\n\t}\n\n\tscopedGlobals.davProperties.push(prop)\n\tscopedGlobals.davNamespaces = namespaces\n\treturn true\n}\n\n/**\n * Get the registered dav properties\n */\nexport function getDavProperties(): string {\n\tscopedGlobals.davProperties ??= [...defaultDavProperties]\n\treturn scopedGlobals.davProperties.map((prop) => `<${prop} />`).join(' ')\n}\n\n/**\n * Get the registered dav namespaces\n */\nexport function getDavNameSpaces(): string {\n\tscopedGlobals.davNamespaces ??= { ...defaultDavNamespaces }\n\treturn Object.keys(scopedGlobals.davNamespaces)\n\t\t.map((ns) => `xmlns:${ns}=\"${scopedGlobals.davNamespaces?.[ns]}\"`)\n\t\t.join(' ')\n}\n\n/**\n * Get the default PROPFIND request body\n */\nexport function getDefaultPropfind(): string {\n\treturn `<?xml version=\"1.0\"?>\n\t\t<d:propfind ${getDavNameSpaces()}>\n\t\t\t<d:prop>\n\t\t\t\t${getDavProperties()}\n\t\t\t</d:prop>\n\t\t</d:propfind>`\n}\n\n/**\n * Get the REPORT body to filter for favorite nodes\n */\nexport function getFavoritesReport(): string {\n\treturn `<?xml version=\"1.0\"?>\n\t\t<oc:filter-files ${getDavNameSpaces()}>\n\t\t\t<d:prop>\n\t\t\t\t${getDavProperties()}\n\t\t\t</d:prop>\n\t\t\t<oc:filter-rules>\n\t\t\t\t<oc:favorite>1</oc:favorite>\n\t\t\t</oc:filter-rules>\n\t\t</oc:filter-files>`\n}\n\n/**\n * Get the SEARCH body to search for recently modified files\n *\n * @param lastModified Oldest timestamp to include (Unix timestamp)\n * @example\n * ```ts\n * // SEARCH for recent files need a different DAV endpoint\n * const client = davGetClient(generateRemoteUrl('dav'))\n * // Timestamp of last week\n * const lastWeek = Math.round(Date.now() / 1000) - (60 * 60 * 24 * 7)\n * const contentsResponse = await client.getDirectoryContents(path, {\n * details: true,\n * data: davGetRecentSearch(lastWeek),\n * headers: {\n * method: 'SEARCH',\n * 'Content-Type': 'application/xml; charset=utf-8',\n * },\n * deep: true,\n * }) as ResponseDataDetailed<FileStat[]>\n * ```\n */\nexport function getRecentSearch(lastModified: number): string {\n\treturn `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<d:searchrequest ${getDavNameSpaces()}\n\txmlns:ns=\"https://github.com/icewind1991/SearchDAV/ns\">\n\t<d:basicsearch>\n\t\t<d:select>\n\t\t\t<d:prop>\n\t\t\t\t${getDavProperties()}\n\t\t\t</d:prop>\n\t\t</d:select>\n\t\t<d:from>\n\t\t\t<d:scope>\n\t\t\t\t<d:href>/files/${getCurrentUser()?.uid}/</d:href>\n\t\t\t\t<d:depth>infinity</d:depth>\n\t\t\t</d:scope>\n\t\t</d:from>\n\t\t<d:where>\n\t\t\t<d:and>\n\t\t\t\t<d:or>\n\t\t\t\t\t<d:not>\n\t\t\t\t\t\t<d:eq>\n\t\t\t\t\t\t\t<d:prop>\n\t\t\t\t\t\t\t\t<d:getcontenttype/>\n\t\t\t\t\t\t\t</d:prop>\n\t\t\t\t\t\t\t<d:literal>httpd/unix-directory</d:literal>\n\t\t\t\t\t\t</d:eq>\n\t\t\t\t\t</d:not>\n\t\t\t\t\t<d:eq>\n\t\t\t\t\t\t<d:prop>\n\t\t\t\t\t\t\t<oc:size/>\n\t\t\t\t\t\t</d:prop>\n\t\t\t\t\t\t<d:literal>0</d:literal>\n\t\t\t\t\t</d:eq>\n\t\t\t\t</d:or>\n\t\t\t\t<d:gt>\n\t\t\t\t\t<d:prop>\n\t\t\t\t\t\t<d:getlastmodified/>\n\t\t\t\t\t</d:prop>\n\t\t\t\t\t<d:literal>${lastModified}</d:literal>\n\t\t\t\t</d:gt>\n\t\t\t</d:and>\n\t\t</d:where>\n\t\t<d:orderby>\n\t\t\t<d:order>\n\t\t\t\t<d:prop>\n\t\t\t\t\t<d:getlastmodified/>\n\t\t\t\t</d:prop>\n\t\t\t\t<d:descending/>\n\t\t\t</d:order>\n\t\t</d:orderby>\n\t\t<d:limit>\n\t\t\t<d:nresults>100</d:nresults>\n\t\t\t<ns:firstresult>0</ns:firstresult>\n\t\t</d:limit>\n\t</d:basicsearch>\n</d:searchrequest>`\n}\n","/*\n * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { DAVResultResponseProps, FileStat, ResponseDataDetailed, WebDAVClient } from 'webdav'\nimport type { Node, NodeData } from '../node/index.ts'\n\nimport { getCurrentUser, getRequestToken, onRequestTokenUpdate } from '@nextcloud/auth'\nimport { generateRemoteUrl } from '@nextcloud/router'\nimport { getSharingToken, isPublicShare } from '@nextcloud/sharing/public'\nimport { createClient, getPatcher } from 'webdav'\nimport { File, Folder, NodeStatus } from '../node/index.ts'\nimport { parsePermissions } from './davPermissions.ts'\nimport { getFavoritesReport } from './davProperties.ts'\n\n/**\n * Nextcloud DAV result response\n */\ninterface ResponseProps extends DAVResultResponseProps {\n\tcreationdate: string\n\tpermissions: string\n\tmime: string\n\tfileid: number\n\tsize: number\n\t'owner-id': string | number\n}\n\n/**\n * Get the DAV root path for the current user or public share\n */\nexport function getRootPath(): string {\n\tif (isPublicShare()) {\n\t\treturn `/files/${getSharingToken()}`\n\t}\n\treturn `/files/${getCurrentUser()?.uid}`\n}\n\n/**\n * The DAV root path for the current user\n * This is a cached version of `getRemoteURL`\n */\nexport const defaultRootPath = getRootPath()\n\n/**\n * Get the DAV remote URL used as base URL for the WebDAV client\n * It also handles public shares\n */\nexport function getRemoteURL(): string {\n\tconst url = generateRemoteUrl('dav')\n\tif (isPublicShare()) {\n\t\treturn url.replace('remote.php', 'public.php')\n\t}\n\treturn url\n}\n\n/**\n * The DAV remote URL used as base URL for the WebDAV client\n * This is a cached version of `getRemoteURL`\n */\nexport const defaultRemoteURL = getRemoteURL()\n\n/**\n * Get a WebDAV client configured to include the Nextcloud request token\n *\n * @param remoteURL The DAV server remote URL\n * @param headers Optional additional headers to set for every request\n */\nexport function getClient(remoteURL = defaultRemoteURL, headers: Record<string, string> = {}) {\n\tconst client = createClient(remoteURL, { headers })\n\n\t/**\n\t * Set headers for DAV requests\n\t *\n\t * @param token CSRF token\n\t */\n\tfunction setHeaders(token: string | null) {\n\t\tclient.setHeaders({\n\t\t\t...headers,\n\t\t\t// Add this so the server knows it is an request from the browser\n\t\t\t'X-Requested-With': 'XMLHttpRequest',\n\t\t\t// Inject user auth\n\t\t\trequesttoken: token ?? '',\n\t\t})\n\t}\n\n\t// refresh headers when request token changes\n\tonRequestTokenUpdate(setHeaders)\n\tsetHeaders(getRequestToken())\n\n\t/**\n\t * Allow to override the METHOD to support dav REPORT\n\t *\n\t * @see https://github.com/perry-mitchell/webdav-client/blob/8d9694613c978ce7404e26a401c39a41f125f87f/source/request.ts\n\t */\n\tconst patcher = getPatcher()\n\t// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n\t// @ts-ignore\n\t// https://github.com/perry-mitchell/hot-patcher/issues/6\n\tpatcher.patch('fetch', (url: string, options: RequestInit): Promise<Response> => {\n\t\tconst headers = options.headers as Record<string, string>\n\t\tif (headers?.method) {\n\t\t\toptions.method = headers.method\n\t\t\tdelete headers.method\n\t\t}\n\t\treturn fetch(url, options)\n\t})\n\n\treturn client\n}\n\n/**\n * Use WebDAV to query for favorite Nodes\n *\n * @param options - Options for the favorite query\n * @param options.client - The WebDAV client to use for performing the request\n * @param options.path - Base path for the favorites, if unset all favorites are queried\n * @param options.davRoot - The root path for the DAV user (defaults to `defaultRootPath`)\n * @param options.signal - Optional abort signal to cancel the request\n *\n * @example\n * ```js\n * import { getFavoriteNodes } from '@nextcloud/files'\n *\n * // query favorites for the root\n * const favorites = await getFavoriteNodes()\n * ```\n * @example\n * ```js\n * // Advanced usage with custom client and path\n * import { getClient, defaultRootPath, getFavoriteNodes } from '@nextcloud/files'\n *\n * const client = getClient()\n * const controller = new AbortController()\n * const favoritesPromise = getFavoriteNodes({ client, path: '/some/folder', signal: controller.signal })\n * // you can abort the request if needed\n * controller.abort()\n * // or await the result\n * const favorites = await favoritesPromise\n * ```\n */\nexport async function getFavoriteNodes(options: { client?: WebDAVClient, path?: string, davRoot?: string, signal?: AbortSignal } = {}): Promise<Node[]> {\n\tconst client = options.client ?? getClient()\n\tconst path = options.path ?? '/'\n\tconst davRoot = options.davRoot ?? defaultRootPath\n\n\tconst contentsResponse = await client.getDirectoryContents(`${davRoot}${path}`, {\n\t\tsignal: options.signal,\n\t\tdetails: true,\n\t\tdata: getFavoritesReport(),\n\t\theaders: {\n\t\t\t// see getClient for patched webdav client\n\t\t\tmethod: 'REPORT',\n\t\t},\n\t\tincludeSelf: true,\n\t}) as ResponseDataDetailed<FileStat[]>\n\n\treturn contentsResponse.data\n\t\t.filter((node) => node.filename !== path) // exclude current dir\n\t\t.map((result) => resultToNode(result, davRoot))\n}\n\n/**\n * Convert DAV result `FileStat` to `Node`\n *\n * @param node The DAV result\n * @param filesRoot The DAV files root path\n * @param remoteURL The DAV server remote URL (same as on `getClient`)\n */\nexport function resultToNode(node: FileStat, filesRoot = defaultRootPath, remoteURL = defaultRemoteURL): Node {\n\tlet userId = getCurrentUser()?.uid\n\tif (isPublicShare()) {\n\t\tuserId = userId ?? 'anonymous'\n\t} else if (!userId) {\n\t\tthrow new Error('No user id found')\n\t}\n\n\tconst props = node.props as ResponseProps\n\tconst permissions = parsePermissions(props?.permissions)\n\tconst owner = String(props?.['owner-id'] || userId)\n\tconst id = props.fileid || 0\n\n\tconst mtime = new Date(Date.parse(node.lastmod))\n\tconst crtime = new Date(Date.parse(props.creationdate))\n\n\tconst nodeData: NodeData = {\n\t\tid,\n\t\tsource: `${remoteURL}${node.filename}`,\n\t\tmtime: !isNaN(mtime.getTime()) && mtime.getTime() !== 0 ? mtime : undefined,\n\t\tcrtime: !isNaN(crtime.getTime()) && crtime.getTime() !== 0 ? crtime : undefined,\n\t\tmime: node.mime || 'application/octet-stream',\n\t\t// Manually cast to work around for https://github.com/perry-mitchell/webdav-client/pull/380\n\t\tdisplayname: props.displayname !== undefined ? String(props.displayname) : undefined,\n\t\tsize: props?.size || Number.parseInt(props.getcontentlength || '0'),\n\t\t// The fileid is set to -1 for failed requests\n\t\tstatus: id < 0 ? NodeStatus.FAILED : undefined,\n\t\tpermissions,\n\t\towner,\n\t\troot: filesRoot,\n\t\tattributes: {\n\t\t\t...node,\n\t\t\t...props,\n\t\t\thasPreview: props?.['has-preview'],\n\t\t},\n\t}\n\n\tdelete nodeData.attributes?.props\n\n\treturn node.type === 'file' ? new File(nodeData) : new Folder(nodeData)\n}\n"],"names":["headers"],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { IFileAction, IFileListAction } from './actions/index.ts';
|
|
2
|
+
import { DavProperty } from './dav/index.ts';
|
|
3
|
+
import { IFileListFilter, IFileListHeader, Navigation, NewMenu } from './index.ts';
|
|
4
|
+
import { FilesRegistry } from './registry.ts';
|
|
5
|
+
import { ISidebarTab } from './sidebar/index.ts';
|
|
6
|
+
import { ISidebarAction } from './sidebar/SidebarAction.ts';
|
|
7
|
+
interface InternalGlobalScope {
|
|
8
|
+
davNamespaces?: DavProperty;
|
|
9
|
+
davProperties?: string[];
|
|
10
|
+
newFileMenu?: NewMenu;
|
|
11
|
+
navigation?: Navigation;
|
|
12
|
+
registry?: FilesRegistry;
|
|
13
|
+
fileActions?: Map<string, IFileAction>;
|
|
14
|
+
fileListActions?: Map<string, IFileListAction>;
|
|
15
|
+
fileListFilters?: Map<string, IFileListFilter>;
|
|
16
|
+
fileListHeaders?: Map<string, IFileListHeader>;
|
|
17
|
+
filesSidebarActions?: Map<string, ISidebarAction>;
|
|
18
|
+
filesSidebarTabs?: Map<string, ISidebarTab>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Get the global scope for the files library.
|
|
22
|
+
* This is used to store global variables scoped to prevent breaking changes in the future.
|
|
23
|
+
*
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export declare const scopedGlobals: InternalGlobalScope;
|
|
27
|
+
export {};
|
package/dist/index.mjs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
+
import { s as scopedGlobals, l as logger, F as FileType } from "./chunks/folder-D9CBQngR.mjs";
|
|
2
|
+
import { a, b, N, c, P } from "./chunks/folder-D9CBQngR.mjs";
|
|
1
3
|
import { TypedEventTarget } from "typescript-event-target";
|
|
2
|
-
import { l as logger, F as FileType } from "./chunks/folder-C9vrC6sc.mjs";
|
|
3
|
-
import { a, b, N, c, P } from "./chunks/folder-C9vrC6sc.mjs";
|
|
4
4
|
import isSvg from "is-svg";
|
|
5
5
|
import { getCapabilities } from "@nextcloud/capabilities";
|
|
6
6
|
import { extname, basename } from "@nextcloud/paths";
|
|
7
7
|
import { getCanonicalLocale, getLanguage } from "@nextcloud/l10n";
|
|
8
|
-
class
|
|
8
|
+
class FilesRegistry extends TypedEventTarget {
|
|
9
9
|
}
|
|
10
10
|
function getRegistry() {
|
|
11
|
-
|
|
12
|
-
return
|
|
11
|
+
scopedGlobals.registry ??= new FilesRegistry();
|
|
12
|
+
return scopedGlobals.registry;
|
|
13
13
|
}
|
|
14
14
|
function getFilesRegistry() {
|
|
15
15
|
return getRegistry();
|
|
@@ -20,16 +20,19 @@ const DefaultType = Object.freeze({
|
|
|
20
20
|
});
|
|
21
21
|
function registerFileAction(action) {
|
|
22
22
|
validateAction$1(action);
|
|
23
|
-
|
|
24
|
-
if (
|
|
23
|
+
scopedGlobals.fileActions ??= /* @__PURE__ */ new Map();
|
|
24
|
+
if (scopedGlobals.fileActions.has(action.id)) {
|
|
25
25
|
logger.error(`FileAction ${action.id} already registered`, { action });
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
scopedGlobals.fileActions.set(action.id, action);
|
|
29
29
|
getRegistry().dispatchTypedEvent("register:action", new CustomEvent("register:action", { detail: action }));
|
|
30
30
|
}
|
|
31
31
|
function getFileActions() {
|
|
32
|
-
|
|
32
|
+
if (scopedGlobals.fileActions) {
|
|
33
|
+
return [...scopedGlobals.fileActions.values()];
|
|
34
|
+
}
|
|
35
|
+
return [];
|
|
33
36
|
}
|
|
34
37
|
function validateAction$1(action) {
|
|
35
38
|
if (!action.id || typeof action.id !== "string") {
|
|
@@ -85,16 +88,19 @@ function validateAction$1(action) {
|
|
|
85
88
|
}
|
|
86
89
|
function registerFileListAction(action) {
|
|
87
90
|
validateAction(action);
|
|
88
|
-
|
|
89
|
-
if (
|
|
91
|
+
scopedGlobals.fileListActions ??= /* @__PURE__ */ new Map();
|
|
92
|
+
if (scopedGlobals.fileListActions.has(action.id)) {
|
|
90
93
|
logger.error(`FileListAction with id "${action.id}" is already registered`, { action });
|
|
91
94
|
return;
|
|
92
95
|
}
|
|
93
|
-
|
|
96
|
+
scopedGlobals.fileListActions.set(action.id, action);
|
|
94
97
|
getRegistry().dispatchTypedEvent("register:listAction", new CustomEvent("register:listAction", { detail: action }));
|
|
95
98
|
}
|
|
96
99
|
function getFileListActions() {
|
|
97
|
-
|
|
100
|
+
if (scopedGlobals.fileListActions) {
|
|
101
|
+
return [...scopedGlobals.fileListActions.values()];
|
|
102
|
+
}
|
|
103
|
+
return [];
|
|
98
104
|
}
|
|
99
105
|
function validateAction(action) {
|
|
100
106
|
if (!action.id || typeof action.id !== "string") {
|
|
@@ -121,25 +127,24 @@ function validateAction(action) {
|
|
|
121
127
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
122
128
|
*/
|
|
123
129
|
function registerFileListFilter(filter) {
|
|
124
|
-
|
|
125
|
-
if (
|
|
130
|
+
scopedGlobals.fileListFilters ??= /* @__PURE__ */ new Map();
|
|
131
|
+
if (scopedGlobals.fileListFilters.has(filter.id)) {
|
|
126
132
|
throw new Error(`File list filter "${filter.id}" already registered`);
|
|
127
133
|
}
|
|
128
|
-
|
|
134
|
+
scopedGlobals.fileListFilters.set(filter.id, filter);
|
|
129
135
|
getRegistry().dispatchTypedEvent("register:listFilter", new CustomEvent("register:listFilter", { detail: filter }));
|
|
130
136
|
}
|
|
131
137
|
function unregisterFileListFilter(filterId) {
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
getRegistry().dispatchTypedEvent("unregister:listFilter", new CustomEvent("unregister:listFilter", { detail: filter }));
|
|
138
|
+
if (scopedGlobals.fileListFilters && scopedGlobals.fileListFilters.has(filterId)) {
|
|
139
|
+
scopedGlobals.fileListFilters.delete(filterId);
|
|
140
|
+
getRegistry().dispatchTypedEvent("unregister:listFilter", new CustomEvent("unregister:listFilter", { detail: filterId }));
|
|
136
141
|
}
|
|
137
142
|
}
|
|
138
143
|
function getFileListFilters() {
|
|
139
|
-
if (
|
|
140
|
-
return [];
|
|
144
|
+
if (scopedGlobals.fileListFilters) {
|
|
145
|
+
return [...scopedGlobals.fileListFilters.values()];
|
|
141
146
|
}
|
|
142
|
-
return [
|
|
147
|
+
return [];
|
|
143
148
|
}
|
|
144
149
|
/*!
|
|
145
150
|
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
|
@@ -165,17 +170,19 @@ class FileListFilter extends TypedEventTarget {
|
|
|
165
170
|
}
|
|
166
171
|
function registerFileListHeader(header) {
|
|
167
172
|
validateHeader(header);
|
|
168
|
-
|
|
169
|
-
if (
|
|
173
|
+
scopedGlobals.fileListHeaders ??= /* @__PURE__ */ new Map();
|
|
174
|
+
if (scopedGlobals.fileListHeaders.has(header.id)) {
|
|
170
175
|
logger.error(`Header ${header.id} already registered`, { header });
|
|
171
176
|
return;
|
|
172
177
|
}
|
|
173
|
-
|
|
178
|
+
scopedGlobals.fileListHeaders.set(header.id, header);
|
|
174
179
|
getRegistry().dispatchTypedEvent("register:listHeader", new CustomEvent("register:listHeader", { detail: header }));
|
|
175
180
|
}
|
|
176
181
|
function getFileListHeaders() {
|
|
177
|
-
|
|
178
|
-
|
|
182
|
+
if (!scopedGlobals.fileListHeaders) {
|
|
183
|
+
return [];
|
|
184
|
+
}
|
|
185
|
+
return [...scopedGlobals.fileListHeaders.values()];
|
|
179
186
|
}
|
|
180
187
|
function validateHeader(header) {
|
|
181
188
|
if (!header.id || !header.render || !header.updated) {
|
|
@@ -415,11 +422,8 @@ class Navigation extends TypedEventTarget {
|
|
|
415
422
|
}
|
|
416
423
|
}
|
|
417
424
|
function getNavigation() {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
logger.debug("Navigation service initialized");
|
|
421
|
-
}
|
|
422
|
-
return window._nc_navigation;
|
|
425
|
+
scopedGlobals.navigation ??= new Navigation();
|
|
426
|
+
return scopedGlobals.navigation;
|
|
423
427
|
}
|
|
424
428
|
const NewMenuEntryCategory = Object.freeze({
|
|
425
429
|
/**
|
|
@@ -489,11 +493,8 @@ class NewMenu {
|
|
|
489
493
|
}
|
|
490
494
|
}
|
|
491
495
|
function getNewFileMenu() {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
logger.debug("NewFileMenu initialized");
|
|
495
|
-
}
|
|
496
|
-
return window._nc_newfilemenu;
|
|
496
|
+
scopedGlobals.newFileMenu ??= new NewMenu();
|
|
497
|
+
return scopedGlobals.newFileMenu;
|
|
497
498
|
}
|
|
498
499
|
function addNewFileMenuEntry(entry) {
|
|
499
500
|
const newFileMenu = getNewFileMenu();
|
|
@@ -514,17 +515,17 @@ function getNewFileMenuEntries(context) {
|
|
|
514
515
|
}
|
|
515
516
|
function registerSidebarAction(action) {
|
|
516
517
|
validateSidebarAction(action);
|
|
517
|
-
|
|
518
|
-
if (
|
|
518
|
+
scopedGlobals.filesSidebarActions ??= /* @__PURE__ */ new Map();
|
|
519
|
+
if (scopedGlobals.filesSidebarActions.has(action.id)) {
|
|
519
520
|
logger.warn(`Sidebar action with id "${action.id}" already registered. Skipping.`);
|
|
520
521
|
return;
|
|
521
522
|
}
|
|
522
|
-
|
|
523
|
+
scopedGlobals.filesSidebarActions.set(action.id, action);
|
|
523
524
|
logger.debug(`New sidebar action with id "${action.id}" registered.`);
|
|
524
525
|
}
|
|
525
526
|
function getSidebarActions() {
|
|
526
|
-
if (
|
|
527
|
-
return [...
|
|
527
|
+
if (scopedGlobals.filesSidebarActions) {
|
|
528
|
+
return [...scopedGlobals.filesSidebarActions.values()];
|
|
528
529
|
}
|
|
529
530
|
return [];
|
|
530
531
|
}
|
|
@@ -550,17 +551,17 @@ function validateSidebarAction(action) {
|
|
|
550
551
|
}
|
|
551
552
|
function registerSidebarTab(tab) {
|
|
552
553
|
validateSidebarTab(tab);
|
|
553
|
-
|
|
554
|
-
if (
|
|
554
|
+
scopedGlobals.filesSidebarTabs ??= /* @__PURE__ */ new Map();
|
|
555
|
+
if (scopedGlobals.filesSidebarTabs.has(tab.id)) {
|
|
555
556
|
logger.warn(`Sidebar tab with id "${tab.id}" already registered. Skipping.`);
|
|
556
557
|
return;
|
|
557
558
|
}
|
|
558
|
-
|
|
559
|
+
scopedGlobals.filesSidebarTabs.set(tab.id, tab);
|
|
559
560
|
logger.debug(`New sidebar tab with id "${tab.id}" registered.`);
|
|
560
561
|
}
|
|
561
562
|
function getSidebarTabs() {
|
|
562
|
-
if (
|
|
563
|
-
return [...
|
|
563
|
+
if (scopedGlobals.filesSidebarTabs) {
|
|
564
|
+
return [...scopedGlobals.filesSidebarTabs.values()];
|
|
564
565
|
}
|
|
565
566
|
return [];
|
|
566
567
|
}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../lib/registry.ts","../lib/actions/fileAction.ts","../lib/actions/fileListAction.ts","../lib/filters/functions.ts","../lib/filters/listFilters.ts","../lib/headers/listHeaders.ts","../lib/utils/objectValidation.ts","../lib/navigation/column.ts","../lib/navigation/view.ts","../lib/navigation/navigation.ts","../lib/newMenu/NewMenu.ts","../lib/newMenu/functions.ts","../lib/sidebar/SidebarAction.ts","../lib/sidebar/SidebarTab.ts","../lib/sidebar/Sidebar.ts","../lib/utils/filename-validation.ts","../lib/utils/filename.ts","../lib/utils/fileSize.ts","../lib/utils/sorting.ts","../lib/utils/fileSorting.ts"],"sourcesContent":["/*\n * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { IFileAction, IFileListAction } from './actions/index.ts'\nimport type { IFileListFilter } from './filters/index.ts'\nimport type { IFileListHeader } from './headers/index.ts'\n\nimport { TypedEventTarget } from 'typescript-event-target'\n\ninterface FilesRegistryEvents {\n\t'register:action': CustomEvent<IFileAction>\n\t'register:listAction': CustomEvent<IFileListAction>\n\t'register:listFilter': CustomEvent<IFileListFilter>\n\t'unregister:listFilter': CustomEvent<IFileListFilter>\n\t'register:listHeader': CustomEvent<IFileListHeader>\n}\n\nexport class FilesRegistryV4 extends TypedEventTarget<FilesRegistryEvents> {}\n\nexport type PublicFilesRegistry = Pick<FilesRegistryV4, 'addEventListener' | 'removeEventListener'>\n\n/**\n * Get the global files registry\n *\n * @internal\n */\nexport function getRegistry() {\n\twindow._nc_files_registry_v4 ??= new FilesRegistryV4()\n\treturn window._nc_files_registry_v4\n}\n\n/**\n * Get the global files registry\n *\n * This allows to listen for new registrations of actions, filters, headers, etc.\n * Events are dispatched by the respective registration functions.\n */\nexport function getFilesRegistry(): PublicFilesRegistry {\n\treturn getRegistry()\n}\n","/**\n * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { ActionContext, ActionContextSingle } from '../types.ts'\n\nimport { getRegistry } from '../registry.ts'\nimport logger from '../utils/logger.ts'\n\nexport const DefaultType = Object.freeze({\n\tDEFAULT: 'default',\n\tHIDDEN: 'hidden',\n})\n\nexport type TDefaultType = typeof DefaultType[keyof typeof DefaultType]\n\nexport interface IHotkeyConfig {\n\t/**\n\t * Short, translated, description what this action is doing.\n\t * This will be used as the description next to the hotkey in the shortcuts overview.\n\t */\n\tdescription: string\n\n\t/**\n\t * The key to be pressed.\n\t *\n\t * @see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key\n\t */\n\tkey: string\n\n\t/**\n\t * If set then the callback is only called when the shift key is (not) pressed.\n\t * When left `undefined` a pressed shift key is ignored (callback is run with and without shift pressed).\n\t */\n\tshift?: boolean\n\n\t/**\n\t * Only execute the action if the control key is pressed.\n\t * On Mac devices the command key is used instead.\n\t */\n\tctrl?: true\n\n\t/**\n\t * Only execute the action if the alt key is pressed\n\t */\n\talt?: true\n}\n\nexport interface IFileAction {\n\t/** Unique ID */\n\tid: string\n\t/** Translatable string displayed in the menu */\n\tdisplayName: (context: ActionContext) => string\n\t/** Translatable title for of the action */\n\ttitle?: (context: ActionContext) => string\n\t/** Svg as inline string. <svg><path fill=\"...\" /></svg> */\n\ticonSvgInline: (context: ActionContext) => string\n\t/** Condition wether this action is shown or not */\n\tenabled?: (context: ActionContext) => boolean\n\n\t/**\n\t * Function executed on single file action\n\t *\n\t * @return true if the action was executed successfully,\n\t * false otherwise and null if the action is silent/undefined.\n\t * @throws {Error} If the action failed\n\t */\n\texec: (context: ActionContextSingle) => Promise<boolean | null>\n\t/**\n\t * Function executed on multiple files action\n\t *\n\t * @return true if the action was executed successfully,\n\t * false otherwise and null if the action is silent/undefined.\n\t * @throws {Error} If the action failed\n\t */\n\texecBatch?: (context: ActionContext) => Promise<(boolean | null)[]>\n\n\t/** This action order in the list */\n\torder?: number\n\n\t/**\n\t * Allows to define a hotkey which will trigger this action for the selected node.\n\t */\n\thotkey?: IHotkeyConfig\n\n\t/**\n\t * Set to true if this action is a destructive action, like \"delete\".\n\t * This will change the appearance in the action menu more prominent (e.g. red colored)\n\t */\n\tdestructive?: boolean\n\n\t/**\n\t * This action's parent id in the list.\n\t * If none found, will be displayed as a top-level action.\n\t */\n\tparent?: string\n\n\t/**\n\t * Make this action the default.\n\t *\n\t * If multiple actions are default, the first one will be used.\n\t * The other ones will be put as first entries in the actions menu iff `DefaultType.Hidden` is not used.\n\t *\n\t * A `DefaultType.Hidden` action will never be shown\n\t * in the actions menu even if another action takes\n\t * its place as default.\n\t *\n\t * @see DefaultType\n\t */\n\tdefault?: TDefaultType\n\t/**\n\t * If true, the renderInline function will be called\n\t */\n\tinline?: (context: ActionContextSingle) => boolean\n\t/**\n\t * If defined, the returned html element will be\n\t * appended before the actions menu.\n\t */\n\trenderInline?: (context: ActionContextSingle) => Promise<HTMLElement | null>\n}\n\n/**\n * Register a new file action.\n *\n * @param action - The file list action to register\n */\nexport function registerFileAction(action: IFileAction): void {\n\tvalidateAction(action)\n\n\twindow._nc_fileactions ??= []\n\tif (window._nc_fileactions.find((search) => search.id === action.id)) {\n\t\tlogger.error(`FileAction ${action.id} already registered`, { action })\n\t\treturn\n\t}\n\n\twindow._nc_fileactions.push(action)\n\tgetRegistry()\n\t\t.dispatchTypedEvent('register:action', new CustomEvent('register:action', { detail: action }))\n}\n\n/**\n * Get all registered file actions.\n */\nexport function getFileActions(): IFileAction[] {\n\treturn window._nc_fileactions ?? []\n}\n\n/**\n * Validate a file action.\n *\n * @param action - The action to validate\n */\nfunction validateAction(action: IFileAction) {\n\tif (!action.id || typeof action.id !== 'string') {\n\t\tthrow new Error('Invalid id')\n\t}\n\n\tif (!action.displayName || typeof action.displayName !== 'function') {\n\t\tthrow new Error('Invalid displayName function')\n\t}\n\n\tif ('title' in action && typeof action.title !== 'function') {\n\t\tthrow new Error('Invalid title function')\n\t}\n\n\tif (!action.iconSvgInline || typeof action.iconSvgInline !== 'function') {\n\t\tthrow new Error('Invalid iconSvgInline function')\n\t}\n\n\tif (!action.exec || typeof action.exec !== 'function') {\n\t\tthrow new Error('Invalid exec function')\n\t}\n\n\t// Optional properties --------------------------------------------\n\tif ('enabled' in action && typeof action.enabled !== 'function') {\n\t\tthrow new Error('Invalid enabled function')\n\t}\n\n\tif ('execBatch' in action && typeof action.execBatch !== 'function') {\n\t\tthrow new Error('Invalid execBatch function')\n\t}\n\n\tif ('order' in action && typeof action.order !== 'number') {\n\t\tthrow new Error('Invalid order')\n\t}\n\n\tif (action.destructive !== undefined && typeof action.destructive !== 'boolean') {\n\t\tthrow new Error('Invalid destructive flag')\n\t}\n\n\tif ('parent' in action && typeof action.parent !== 'string') {\n\t\tthrow new Error('Invalid parent')\n\t}\n\n\tif (action.default && !Object.values(DefaultType).includes(action.default)) {\n\t\tthrow new Error('Invalid default')\n\t}\n\n\tif ('inline' in action && typeof action.inline !== 'function') {\n\t\tthrow new Error('Invalid inline function')\n\t}\n\n\tif ('renderInline' in action && typeof action.renderInline !== 'function') {\n\t\tthrow new Error('Invalid renderInline function')\n\t}\n\n\tif ('hotkey' in action && action.hotkey !== undefined) {\n\t\tif (typeof action.hotkey !== 'object') {\n\t\t\tthrow new Error('Invalid hotkey configuration')\n\t\t}\n\n\t\tif (typeof action.hotkey.key !== 'string' || !action.hotkey.key) {\n\t\t\tthrow new Error('Missing or invalid hotkey key')\n\t\t}\n\n\t\tif (typeof action.hotkey.description !== 'string' || !action.hotkey.description) {\n\t\t\tthrow new Error('Missing or invalid hotkey description')\n\t\t}\n\t}\n}\n","/**\n * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { ViewActionContext } from '../types.ts'\n\nimport { getRegistry } from '../registry.ts'\nimport logger from '../utils/logger.ts'\n\nexport interface IFileListAction {\n\t/** Unique ID */\n\tid: string\n\n\t/** Translated name of the action */\n\tdisplayName: (context: ViewActionContext) => string\n\n\t/** Raw svg string */\n\ticonSvgInline?: (context: ViewActionContext) => string\n\n\t/** Sort order */\n\torder: number\n\n\t/**\n\t * Condition whether this action is shown or not\n\t */\n\tenabled?: (context: ViewActionContext) => boolean\n\n\t/**\n\t * Function executed on single file action\n\t *\n\t * @return true if the action was executed successfully,\n\t * false otherwise and null if the action is silent/undefined.\n\t * @throws {Error} If the action failed\n\t */\n\texec: (context: ViewActionContext) => Promise<boolean | null>\n}\n\n/**\n * Register a new file list action.\n *\n * @param action - The file list action to register\n */\nexport function registerFileListAction(action: IFileListAction) {\n\tvalidateAction(action)\n\n\twindow._nc_filelistactions ??= []\n\tif (window._nc_filelistactions.find((listAction) => listAction.id === action.id)) {\n\t\tlogger.error(`FileListAction with id \"${action.id}\" is already registered`, { action })\n\t\treturn\n\t}\n\n\twindow._nc_filelistactions.push(action)\n\tgetRegistry()\n\t\t.dispatchTypedEvent('register:listAction', new CustomEvent('register:listAction', { detail: action }))\n}\n\n/**\n * Get all currently registered file list actions.\n */\nexport function getFileListActions(): IFileListAction[] {\n\treturn [...(window._nc_filelistactions ?? [])]\n}\n\n/**\n * Validate a file list action.\n *\n * @param action - The action to validate\n */\nfunction validateAction(action: IFileListAction) {\n\tif (!action.id || typeof action.id !== 'string') {\n\t\tthrow new Error('Invalid id')\n\t}\n\n\tif (!action.displayName || typeof action.displayName !== 'function') {\n\t\tthrow new Error('Invalid displayName function')\n\t}\n\n\tif ('iconSvgInline' in action && typeof action.iconSvgInline !== 'function') {\n\t\tthrow new Error('Invalid iconSvgInline function')\n\t}\n\n\tif ('order' in action && typeof action.order !== 'number') {\n\t\tthrow new Error('Invalid order')\n\t}\n\n\tif ('enabled' in action && typeof action.enabled !== 'function') {\n\t\tthrow new Error('Invalid enabled function')\n\t}\n\n\tif (!action.exec || typeof action.exec !== 'function') {\n\t\tthrow new Error('Invalid exec function')\n\t}\n}\n","/*!\n * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { IFileListFilter } from './listFilters.ts'\n\nimport { getRegistry } from '../registry.ts'\n\n/**\n * Register a new filter on the file list\n *\n * This only must be called once to register the filter,\n * when the filter state changes you need to call `filterUpdated` on the filter instead.\n *\n * @param filter The filter to register on the file list\n */\nexport function registerFileListFilter(filter: IFileListFilter): void {\n\twindow._nc_filelist_filters ??= new Map<string, IFileListFilter>()\n\tif (window._nc_filelist_filters.has(filter.id)) {\n\t\tthrow new Error(`File list filter \"${filter.id}\" already registered`)\n\t}\n\n\twindow._nc_filelist_filters.set(filter.id, filter)\n\tgetRegistry()\n\t\t.dispatchTypedEvent('register:listFilter', new CustomEvent('register:listFilter', { detail: filter }))\n}\n\n/**\n * Remove a registered filter from the file list\n *\n * @param filterId The unique ID of the filter to remove\n */\nexport function unregisterFileListFilter(filterId: string): void {\n\tif (window._nc_filelist_filters && window._nc_filelist_filters.has(filterId)) {\n\t\tconst filter = window._nc_filelist_filters.get(filterId)!\n\t\twindow._nc_filelist_filters.delete(filterId)\n\t\tgetRegistry()\n\t\t\t.dispatchTypedEvent('unregister:listFilter', new CustomEvent('unregister:listFilter', { detail: filter }))\n\t}\n}\n\n/**\n * Get all registered file list filters\n */\nexport function getFileListFilters(): IFileListFilter[] {\n\tif (!window._nc_filelist_filters) {\n\t\treturn []\n\t}\n\treturn [...window._nc_filelist_filters.values()]\n}\n","/*!\n * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { INode } from '../node/index.ts'\n\nimport { TypedEventTarget } from 'typescript-event-target'\n\n/**\n * Active filters can provide one or more \"chips\" to show the currently active state.\n * Must at least provide a text representing the filters state and a callback to unset that state (disable this filter).\n */\nexport interface IFileListFilterChip {\n\t/**\n\t * Text of the chip\n\t */\n\ttext: string\n\n\t/**\n\t * Optional icon to be used on the chip (inline SVG as string)\n\t */\n\ticon?: string\n\n\t/**\n\t * Optional pass a user id to use a user avatar instead of an icon\n\t */\n\tuser?: string\n\n\t/**\n\t * Handler to be called on click\n\t */\n\tonclick(): void\n}\n\n/**\n * This event is emitted when the the filter value changed and the file list needs to be updated\n */\nexport interface FilterUpdateEvent extends CustomEvent<never> {\n\ttype: 'update:filter'\n}\n\n/**\n * This event is emitted when the the filter value changed and the file list needs to be updated\n */\nexport interface FilterUpdateChipsEvent extends CustomEvent<IFileListFilterChip[]> {\n\ttype: 'update:chips'\n}\n\ninterface IFileListFilterEvents {\n\t[name: string]: CustomEvent\n\t'update:filter': FilterUpdateEvent\n\t'update:chips': FilterUpdateChipsEvent\n}\n\nexport interface IFileListFilterBase extends TypedEventTarget<IFileListFilterEvents> {\n\n\t/**\n\t * Unique ID of this filter\n\t */\n\treadonly id: string\n\n\t/**\n\t * Order of the filter\n\t *\n\t * Use a low number to make this filter ordered in front.\n\t */\n\treadonly order: number\n\n\t/**\n\t * Filter function to decide if a node is shown.\n\t *\n\t * @param nodes Nodes to filter\n\t * @return Subset of the `nodes` parameter to show\n\t */\n\tfilter(nodes: INode[]): INode[]\n\n\t/**\n\t * Reset the filter to the initial state.\n\t *\n\t * Implementations should make sure,that if they provide chips they need to emit the `update:chips` event.\n\t * This is called by the files app.\n\t *\n\t * @since 3.10.0\n\t */\n\treset?(): void\n}\n\n/**\n * File list filter interface with UI component\n *\n * Filters with UI must provide additional information for rendering the filter component.\n * The ui will be rendered depending on the view port - either as inline actions or in a dropdown menu.\n */\nexport interface IFileListFilterWithUi extends IFileListFilterBase {\n\t/**\n\t * Display name of the filter\n\t */\n\treadonly displayName: string\n\n\t/**\n\t * Inline SVG icon as string for the filter\n\t */\n\treadonly iconSvgInline: string\n\n\t/**\n\t * Tag name of the web component to use as filter UI\n\t *\n\t * The web component must extend `HTMLElement` and accept a property `filter` of type `IFileListFilterWithUi`\n\t * (it will receive the filter instance itself).\n\t *\n\t * @see https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements\n\t */\n\treadonly tagName: string\n}\n\n/**\n * File list filter interface\n *\n * Filters can either be simple filters without UI or filters with a UI component.\n */\nexport type IFileListFilter = IFileListFilterBase | IFileListFilterWithUi\n\nexport class FileListFilter extends TypedEventTarget<IFileListFilterEvents> implements IFileListFilterBase {\n\tpublic id: string\n\tpublic order: number\n\n\tconstructor(id: string, order: number = 100) {\n\t\tsuper()\n\t\tthis.id = id\n\t\tthis.order = order\n\t}\n\n\tpublic filter(nodes: INode[]): INode[] {\n\t\tthrow new Error('Not implemented')\n\t\treturn nodes\n\t}\n\n\tprotected updateChips(chips: IFileListFilterChip[]) {\n\t\tthis.dispatchTypedEvent('update:chips', new CustomEvent('update:chips', { detail: chips }) as FilterUpdateChipsEvent)\n\t}\n\n\tprotected filterUpdated() {\n\t\tthis.dispatchTypedEvent('update:filter', new CustomEvent('update:filter') as FilterUpdateEvent)\n\t}\n}\n","/*\n * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { IView } from '../navigation/view.ts'\nimport type { IFolder } from '../node/folder.ts'\n\nimport { getRegistry } from '../registry.ts'\nimport logger from '../utils/logger.ts'\n\nexport interface IFileListHeader {\n\t/** Unique ID */\n\tid: string\n\t/** Order */\n\torder: number\n\t/** Condition wether this header is shown or not */\n\tenabled?: (folder: IFolder, view: IView) => boolean\n\t/** Executed when file list is initialized */\n\trender(el: HTMLElement, folder: IFolder, view: IView): void\n\t/** Executed when root folder changed */\n\tupdated(folder: IFolder, view: IView): void\n}\n\n/**\n * Register a new file list header.\n *\n * @param header - The header to register\n */\nexport function registerFileListHeader(header: IFileListHeader): void {\n\tvalidateHeader(header)\n\n\twindow._nc_filelistheader ??= []\n\tif (window._nc_filelistheader.find((search) => search.id === header.id)) {\n\t\tlogger.error(`Header ${header.id} already registered`, { header })\n\t\treturn\n\t}\n\n\twindow._nc_filelistheader.push(header)\n\tgetRegistry()\n\t\t.dispatchTypedEvent('register:listHeader', new CustomEvent('register:listHeader', { detail: header }))\n}\n\n/**\n * Get all currently registered file list headers.\n */\nexport function getFileListHeaders(): IFileListHeader[] {\n\twindow._nc_filelistheader ??= []\n\treturn [...window._nc_filelistheader]\n}\n\n/**\n * @param header - The header to validate\n */\nfunction validateHeader(header: IFileListHeader) {\n\tif (!header.id || !header.render || !header.updated) {\n\t\tthrow new Error('Invalid header: id, render and updated are required')\n\t}\n\n\tif (typeof header.id !== 'string') {\n\t\tthrow new Error('Invalid id property')\n\t}\n\n\tif (header.enabled !== undefined && typeof header.enabled !== 'function') {\n\t\tthrow new Error('Invalid enabled property')\n\t}\n\n\tif (header.render && typeof header.render !== 'function') {\n\t\tthrow new Error('Invalid render property')\n\t}\n\n\tif (header.updated && typeof header.updated !== 'function') {\n\t\tthrow new Error('Invalid updated property')\n\t}\n}\n","/*\n * SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\n/**\n * Check an optional property type\n *\n * @param obj - the object to check\n * @param property - the property name\n * @param type - the expected type\n * @throws {Error} if the property is defined and not of the expected type\n */\nexport function checkOptionalProperty<T extends object>(\n\tobj: T,\n\tproperty: Exclude<keyof T, symbol>,\n\ttype: 'array' | 'function' | 'string' | 'boolean' | 'number' | 'object',\n): void {\n\tif (typeof obj[property] !== 'undefined') {\n\t\tif (type === 'array') {\n\t\t\tif (!Array.isArray(obj[property])) {\n\t\t\t\tthrow new Error(`View ${property} must be an array`)\n\t\t\t}\n\t\t} else if (typeof obj[property] !== type) {\n\t\t\tthrow new Error(`View ${property} must be a ${type}`)\n\t\t} else if (type === 'object' && (obj[property] === null || Array.isArray(obj[property]))) {\n\t\t\tthrow new Error(`View ${property} must be an object`)\n\t\t}\n\t}\n}\n","/*\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { INode } from '../node/node.ts'\nimport type { IView } from './view.ts'\n\nimport { checkOptionalProperty } from '../utils/objectValidation.ts'\n\nexport interface IColumn {\n\t/** Unique column ID */\n\tid: string\n\t/** Translated column title */\n\ttitle: string\n\t/** The content of the cell. The element will be appended within */\n\trender: (node: INode, view: IView) => HTMLElement\n\t/** Function used to sort INodes between them */\n\tsort?: (nodeA: INode, nodeB: INode) => number\n\t/**\n\t * Custom summary of the column to display at the end of the list.\n\t * Will not be displayed if nothing is provided\n\t */\n\tsummary?: (node: INode[], view: IView) => string\n}\n\nexport class Column implements IColumn {\n\tprivate _column: IColumn\n\n\tconstructor(column: IColumn) {\n\t\tvalidateColumn(column)\n\t\tthis._column = column\n\t}\n\n\tget id() {\n\t\treturn this._column.id\n\t}\n\n\tget title() {\n\t\treturn this._column.title\n\t}\n\n\tget render() {\n\t\treturn this._column.render\n\t}\n\n\tget sort() {\n\t\treturn this._column.sort\n\t}\n\n\tget summary() {\n\t\treturn this._column.summary\n\t}\n}\n\n/**\n * Validate a column definition\n *\n * @param column - the column to check\n * @throws {Error} if the column is not valid\n */\nexport function validateColumn(column: IColumn): void {\n\tif (typeof column !== 'object' || column === null) {\n\t\tthrow new Error('View column must be an object')\n\t}\n\n\tif (!column.id || typeof column.id !== 'string') {\n\t\tthrow new Error('A column id is required')\n\t}\n\n\tif (!column.title || typeof column.title !== 'string') {\n\t\tthrow new Error('A column title is required')\n\t}\n\n\tif (!column.render || typeof column.render !== 'function') {\n\t\tthrow new Error('A render function is required')\n\t}\n\n\t// Optional properties\n\tcheckOptionalProperty(column, 'sort', 'function')\n\tcheckOptionalProperty(column, 'summary', 'function')\n}\n","/*\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { IFolder, INode } from '../node/index.ts'\nimport type { IColumn } from './column.ts'\n\nimport isSvg from 'is-svg'\nimport { checkOptionalProperty } from '../utils/objectValidation.ts'\nimport { validateColumn } from './column.ts'\n\nexport type ContentsWithRoot = {\n\tfolder: IFolder\n\tcontents: INode[]\n}\n\nexport interface IGetContentsOptions {\n\t/**\n\t * Abort signal to be able to cancel the request.\n\t *\n\t *@see https://developer.mozilla.org/en-US/docs/Web/API/AbortController\n\t */\n\tsignal: AbortSignal\n}\n\nexport interface IView {\n\t/** Unique view ID */\n\tid: string\n\t/** Translated view name */\n\tname: string\n\t/** Translated accessible description of the view */\n\tcaption?: string\n\n\t/** Translated title of the empty view */\n\temptyTitle?: string\n\t/** Translated description of the empty view */\n\temptyCaption?: string\n\t/**\n\t * Custom implementation of the empty view.\n\t * If set and no content is found for the current view,\n\t * then this method is called with the container element\n\t * where to render your empty view implementation.\n\t *\n\t * @param div - The container element to render into\n\t */\n\temptyView?: (div: HTMLDivElement) => void\n\n\t/**\n\t * Method return the content of the provided path.\n\t *\n\t * This method _must_ also return the current directory\n\t * information alongside with its content.\n\t *\n\t * An [abort signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) is provided\n\t * to be able to cancel the request if the user change directory.\n\t */\n\tgetContents(path: string, options: IGetContentsOptions): Promise<ContentsWithRoot>\n\n\t/**\n\t * If set then the view will be hidden from the navigation unless its the active view.\n\t */\n\thidden?: true\n\n\t/** The view icon as an inline svg */\n\ticon: string\n\n\t/**\n\t * The view order.\n\t * If not set will be natural sorted by view name.\n\t */\n\torder?: number\n\n\t/**\n\t * Custom params to give to the router on click\n\t * If defined, will be treated as a dummy view and\n\t * will just redirect and not fetch any contents.\n\t */\n\tparams?: Record<string, string>\n\n\t/**\n\t * This view column(s). Name and actions are\n\t * by default always included\n\t */\n\tcolumns?: IColumn[]\n\n\t/** The parent unique ID */\n\tparent?: string\n\t/** This view is sticky (sent at the bottom) */\n\tsticky?: boolean\n\n\t/**\n\t * This view has children and is expanded (by default)\n\t * or not. This will be overridden by user config.\n\t */\n\texpanded?: boolean\n\n\t/**\n\t * Will be used as default if the user\n\t * haven't customized their sorting column\n\t */\n\tdefaultSortKey?: string\n\n\t/**\n\t * Method called to load child views if any\n\t */\n\tloadChildViews?: (view: IView) => Promise<void>\n}\n\nexport class View implements IView {\n\tprivate _view: IView\n\n\tconstructor(view: IView) {\n\t\tvalidateView(view)\n\t\tthis._view = view\n\t}\n\n\tget id() {\n\t\treturn this._view.id\n\t}\n\n\tget name() {\n\t\treturn this._view.name\n\t}\n\n\tget caption() {\n\t\treturn this._view.caption\n\t}\n\n\tget emptyTitle() {\n\t\treturn this._view.emptyTitle\n\t}\n\n\tget emptyCaption() {\n\t\treturn this._view.emptyCaption\n\t}\n\n\tget getContents() {\n\t\treturn this._view.getContents\n\t}\n\n\tget hidden() {\n\t\treturn this._view.hidden\n\t}\n\n\tget icon() {\n\t\treturn this._view.icon\n\t}\n\n\tset icon(icon) {\n\t\tthis._view.icon = icon\n\t}\n\n\tget order() {\n\t\treturn this._view.order\n\t}\n\n\tset order(order) {\n\t\tthis._view.order = order\n\t}\n\n\tget params() {\n\t\treturn this._view.params\n\t}\n\n\tset params(params) {\n\t\tthis._view.params = params\n\t}\n\n\tget columns() {\n\t\treturn this._view.columns\n\t}\n\n\tget emptyView() {\n\t\treturn this._view.emptyView\n\t}\n\n\tget parent() {\n\t\treturn this._view.parent\n\t}\n\n\tget sticky() {\n\t\treturn this._view.sticky\n\t}\n\n\tget expanded() {\n\t\treturn this._view.expanded\n\t}\n\n\tset expanded(expanded: boolean | undefined) {\n\t\tthis._view.expanded = expanded\n\t}\n\n\tget defaultSortKey() {\n\t\treturn this._view.defaultSortKey\n\t}\n\n\tget loadChildViews() {\n\t\treturn this._view.loadChildViews\n\t}\n}\n\n/**\n * Validate a view interface to check all required properties are satisfied.\n *\n * @param view the view to check\n * @throws {Error} if the view is not valid\n */\nexport function validateView(view: IView) {\n\tif (!view.icon || typeof view.icon !== 'string' || !isSvg(view.icon)) {\n\t\tthrow new Error('View icon is required and must be a valid svg string')\n\t}\n\n\tif (!view.id || typeof view.id !== 'string') {\n\t\tthrow new Error('View id is required and must be a string')\n\t}\n\n\tif (!view.getContents || typeof view.getContents !== 'function') {\n\t\tthrow new Error('View getContents is required and must be a function')\n\t}\n\n\tif (!view.name || typeof view.name !== 'string') {\n\t\tthrow new Error('View name is required and must be a string')\n\t}\n\n\t// optional properties type checks\n\n\tcheckOptionalProperty(view, 'caption', 'string')\n\tcheckOptionalProperty(view, 'columns', 'array')\n\tcheckOptionalProperty(view, 'defaultSortKey', 'string')\n\tcheckOptionalProperty(view, 'emptyCaption', 'string')\n\tcheckOptionalProperty(view, 'emptyTitle', 'string')\n\tcheckOptionalProperty(view, 'emptyView', 'function')\n\tcheckOptionalProperty(view, 'expanded', 'boolean')\n\tcheckOptionalProperty(view, 'hidden', 'boolean')\n\tcheckOptionalProperty(view, 'loadChildViews', 'function')\n\tcheckOptionalProperty(view, 'order', 'number')\n\tcheckOptionalProperty(view, 'params', 'object')\n\tcheckOptionalProperty(view, 'parent', 'string')\n\tcheckOptionalProperty(view, 'sticky', 'boolean')\n\n\tif (view.columns) {\n\t\t// we cannot use `instanceof` here because if the Navigation and the Column class are loaded by different apps\n\t\t// (Navigation is set by files app and Column by a 3rd party app),\n\t\t// the `instanceof` check will fail even if the object has the correct shape because they are different classes in memory.\n\t\tview.columns.forEach(validateColumn)\n\t\tconst columnIds = view.columns.reduce((set, column) => set.add(column.id), new Set<string>())\n\t\tif (columnIds.size !== view.columns.length) {\n\t\t\tthrow new Error('View columns must have unique ids')\n\t\t}\n\t}\n}\n","/**\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { IView } from './view.ts'\n\nimport { TypedEventTarget } from 'typescript-event-target'\nimport logger from '../utils/logger.ts'\nimport { validateView } from './view.ts'\n\n/**\n * The event is emitted when the navigation view was updated.\n * It contains the new active view in the `detail` attribute.\n */\ninterface UpdateActiveViewEvent extends CustomEvent<IView | null> {\n\ttype: 'updateActive'\n}\n\n/**\n * This event is emitted when the list of registered views is changed\n */\ninterface UpdateViewsEvent extends CustomEvent<never> {\n\ttype: 'update'\n}\n\n/**\n * The files navigation manages the available and active views\n *\n * Custom views for the files app can be registered (examples are the favorites views or the shared-with-you view).\n * It is also possible to listen on changes of the registered views or when the current active view is changed.\n *\n * @example\n * ```js\n * const navigation = getNavigation()\n * navigation.addEventListener('update', () => {\n * // This will be called whenever a new view is registered or a view is removed\n * const viewNames = navigation.views.map((view) => view.name)\n * console.warn('Registered views changed', viewNames)\n * })\n * // Or you can react to changes of the current active view\n * navigation.addEventListener('updateActive', (event) => {\n * // This will be called whenever the active view changed\n * const newView = event.detail // you could also use `navigation.active`\n * console.warn('Active view changed to ' + newView.name)\n * })\n * ```\n */\nexport class Navigation extends TypedEventTarget<{ updateActive: UpdateActiveViewEvent, update: UpdateViewsEvent }> {\n\tprivate _views: IView[] = []\n\tprivate _currentView: IView | null = null\n\n\t/**\n\t * Register a new view on the navigation\n\t *\n\t * @param views The views to register\n\t * @throws {Error} if a view with the same id is already registered\n\t * @throws {Error} if the registered view is invalid\n\t */\n\tregister(...views: IView[]): void {\n\t\tfor (const view of views) {\n\t\t\tif (this._views.find((search) => search.id === view.id)) {\n\t\t\t\tthrow new Error(`IView id ${view.id} is already registered`)\n\t\t\t}\n\t\t\tvalidateView(view)\n\t\t}\n\n\t\tthis._views.push(...views)\n\t\tthis.dispatchTypedEvent('update', new CustomEvent<never>('update') as UpdateViewsEvent)\n\t}\n\n\t/**\n\t * Remove a registered view\n\t *\n\t * @param id The id of the view to remove\n\t */\n\tremove(id: string): void {\n\t\tconst index = this._views.findIndex((view) => view.id === id)\n\t\tif (index !== -1) {\n\t\t\tthis._views.splice(index, 1)\n\t\t\tthis.dispatchTypedEvent('update', new CustomEvent('update') as UpdateViewsEvent)\n\t\t}\n\t}\n\n\t/**\n\t * Set the currently active view\n\t *\n\t * @param id - The id of the view to set as active\n\t * @throws {Error} If no view with the given id was registered\n\t * @fires UpdateActiveViewEvent\n\t */\n\tsetActive(id: string | null): void {\n\t\tif (id === null) {\n\t\t\tthis._currentView = null\n\t\t} else {\n\t\t\tconst view = this._views.find(({ id: viewId }) => viewId === id)\n\t\t\tif (!view) {\n\t\t\t\tthrow new Error(`No view with ${id} registered`)\n\t\t\t}\n\t\t\tthis._currentView = view\n\t\t}\n\n\t\tconst event = new CustomEvent<IView | null>('updateActive', { detail: this._currentView })\n\t\tthis.dispatchTypedEvent('updateActive', event as UpdateActiveViewEvent)\n\t}\n\n\t/**\n\t * The currently active files view\n\t */\n\tget active(): IView | null {\n\t\treturn this._currentView\n\t}\n\n\t/**\n\t * All registered views\n\t */\n\tget views(): IView[] {\n\t\treturn this._views\n\t}\n}\n\n/**\n * Get the current files navigation\n */\nexport function getNavigation(): Navigation {\n\tif (typeof window._nc_navigation === 'undefined') {\n\t\twindow._nc_navigation = new Navigation()\n\t\tlogger.debug('Navigation service initialized')\n\t}\n\n\treturn window._nc_navigation\n}\n","/*\n * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { IFolder, INode } from '../node/index.ts'\n\nimport logger from '../utils/logger.ts'\n\nexport const NewMenuEntryCategory = Object.freeze({\n\t/**\n\t * For actions where the user is intended to upload from their device\n\t */\n\tUploadFromDevice: 0,\n\n\t/**\n\t * For actions that create new nodes on the server without uploading\n\t */\n\tCreateNew: 1,\n\n\t/**\n\t * For everything not matching the other categories\n\t */\n\tOther: 2,\n})\n\nexport type TNewMenuEntryCategory = typeof NewMenuEntryCategory[keyof typeof NewMenuEntryCategory]\n\nexport interface NewMenuEntry {\n\t/** Unique ID */\n\tid: string\n\n\t/**\n\t * Category to put this entry in\n\t * (supported since Nextcloud 30)\n\t *\n\t * @default NewMenuEntryCategory.CreateNew\n\t * @see NewMenuEntryCategory\n\t * @since 3.3.0\n\t */\n\tcategory?: TNewMenuEntryCategory\n\n\t/** Translatable string displayed in the menu */\n\tdisplayName: string\n\n\t/**\n\t * Condition wether this entry is shown or not\n\t *\n\t * @param context the creation context. Usually the current folder\n\t */\n\tenabled?: (context: IFolder) => boolean\n\n\t/**\n\t * Either iconSvgInline or iconClass must be defined\n\t * Svg as inline string. <svg><path fill=\"...\" /></svg>\n\t */\n\ticonSvgInline?: string\n\n\t/** Order of the entry in the menu */\n\torder?: number\n\n\t/**\n\t * Function to be run after creation\n\t *\n\t * @param context - The creation context. Usually the current folder\n\t * @param content - List of file/folders present in the context folder\n\t */\n\thandler: (context: IFolder, content: INode[]) => void\n}\n\nexport class NewMenu {\n\tprivate _entries: Array<NewMenuEntry> = []\n\n\tpublic registerEntry(entry: NewMenuEntry) {\n\t\tthis.validateEntry(entry)\n\t\tentry.category = entry.category ?? NewMenuEntryCategory.CreateNew\n\t\tthis._entries.push(entry)\n\t}\n\n\tpublic unregisterEntry(entry: NewMenuEntry | string) {\n\t\tconst entryIndex = typeof entry === 'string'\n\t\t\t? this.getEntryIndex(entry)\n\t\t\t: this.getEntryIndex(entry.id)\n\n\t\tif (entryIndex === -1) {\n\t\t\tlogger.warn('Entry not found, nothing removed', { entry, entries: this.getEntries() })\n\t\t\treturn\n\t\t}\n\n\t\tthis._entries.splice(entryIndex, 1)\n\t}\n\n\t/**\n\t * Get the list of registered entries\n\t *\n\t * @param context - The creation context. Usually the current folder\n\t */\n\tpublic getEntries(context?: IFolder): Array<NewMenuEntry> {\n\t\tif (context) {\n\t\t\treturn this._entries\n\t\t\t\t.filter((entry) => typeof entry.enabled === 'function' ? entry.enabled(context) : true)\n\t\t}\n\t\treturn this._entries\n\t}\n\n\tprivate getEntryIndex(id: string): number {\n\t\treturn this._entries.findIndex((entry) => entry.id === id)\n\t}\n\n\tprivate validateEntry(entry: NewMenuEntry) {\n\t\tif (!entry.id || !entry.displayName || !entry.iconSvgInline || !entry.handler) {\n\t\t\tthrow new Error('Invalid entry')\n\t\t}\n\n\t\tif (typeof entry.id !== 'string'\n\t\t\t|| typeof entry.displayName !== 'string') {\n\t\t\tthrow new Error('Invalid id or displayName property')\n\t\t}\n\n\t\tif (entry.iconSvgInline && typeof entry.iconSvgInline !== 'string') {\n\t\t\tthrow new Error('Invalid icon provided')\n\t\t}\n\n\t\tif (entry.enabled !== undefined && typeof entry.enabled !== 'function') {\n\t\t\tthrow new Error('Invalid enabled property')\n\t\t}\n\n\t\tif (typeof entry.handler !== 'function') {\n\t\t\tthrow new Error('Invalid handler property')\n\t\t}\n\n\t\tif ('order' in entry && typeof entry.order !== 'number') {\n\t\t\tthrow new Error('Invalid order property')\n\t\t}\n\n\t\tif (this.getEntryIndex(entry.id) !== -1) {\n\t\t\tthrow new Error('Duplicate entry')\n\t\t}\n\t}\n}\n","/*\n * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { IFolder } from '../node/index.ts'\nimport type { NewMenuEntry } from './NewMenu.ts'\n\nimport logger from '../utils/logger.ts'\nimport { NewMenu } from './NewMenu.ts'\n\n/**\n * Get the NewMenu instance used by the files app.\n */\nexport function getNewFileMenu(): NewMenu {\n\tif (typeof window._nc_newfilemenu === 'undefined') {\n\t\twindow._nc_newfilemenu = new NewMenu()\n\t\tlogger.debug('NewFileMenu initialized')\n\t}\n\treturn window._nc_newfilemenu\n}\n\n/**\n * Add a new menu entry to the upload manager menu\n *\n * @param entry - The new file menu entry\n */\nexport function addNewFileMenuEntry(entry: NewMenuEntry): void {\n\tconst newFileMenu = getNewFileMenu()\n\treturn newFileMenu.registerEntry(entry)\n}\n\n/**\n * Remove a previously registered entry from the upload menu\n *\n * @param entry - Entry or id of entry to remove\n */\nexport function removeNewFileMenuEntry(entry: NewMenuEntry | string) {\n\tconst newFileMenu = getNewFileMenu()\n\treturn newFileMenu.unregisterEntry(entry)\n}\n\n/**\n * Get the list of registered entries from the upload menu\n *\n * @param context - The current folder to get the available entries\n */\nexport function getNewFileMenuEntries(context?: IFolder): NewMenuEntry[] {\n\tconst newFileMenu = getNewFileMenu()\n\treturn newFileMenu.getEntries(context).sort((a: NewMenuEntry, b: NewMenuEntry) => {\n\t\t// If defined and different, sort by order\n\t\tif (a.order !== undefined\n\t\t\t&& b.order !== undefined\n\t\t\t&& a.order !== b.order) {\n\t\t\treturn a.order - b.order\n\t\t}\n\t\t// else sort by display name\n\t\treturn a.displayName.localeCompare(b.displayName, undefined, { numeric: true, sensitivity: 'base' })\n\t})\n}\n","/*\n * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { ISidebarContext } from './SidebarTab.ts'\n\nimport logger from '../utils/logger.ts'\n\n/**\n * Implementation of a custom sidebar tab within the files app.\n */\nexport interface ISidebarAction {\n\t/**\n\t * Unique id of the sidebar tab.\n\t * This has to conform to the HTML id attribute specification.\n\t */\n\tid: string\n\n\t/**\n\t * The order of this tab.\n\t * Use a low number to make this tab ordered in front.\n\t */\n\torder: number\n\n\t/**\n\t * The localized name of the sidebar tab.\n\t *\n\t * @param context - The current context of the files app\n\t */\n\tdisplayName(context: ISidebarContext): string\n\n\t/**\n\t * The icon, as SVG, of the sidebar tab.\n\t *\n\t * @param context - The current context of the files app\n\t */\n\ticonSvgInline(context: ISidebarContext): string\n\n\t/**\n\t * Callback to check if the sidebar tab should be shown for the selected node.\n\t *\n\t * @param context - The current context of the files app\n\t */\n\tenabled(context: ISidebarContext): boolean\n\n\t/**\n\t * Handle the sidebar action.\n\t *\n\t * @param context - The current context of the files app\n\t */\n\tonClick(context: ISidebarContext): void\n}\n\n/**\n * Register a new sidebar action.\n *\n * @param action - The sidebar action to register\n * @throws {Error} If the provided action is not a valid sidebar action and thus cannot be registered.\n */\nexport function registerSidebarAction(action: ISidebarAction): void {\n\tvalidateSidebarAction(action)\n\n\twindow._nc_files_sidebar_actions ??= new Map<string, ISidebarAction>()\n\tif (window._nc_files_sidebar_actions.has(action.id)) {\n\t\tlogger.warn(`Sidebar action with id \"${action.id}\" already registered. Skipping.`)\n\t\treturn\n\t}\n\twindow._nc_files_sidebar_actions.set(action.id, action)\n\tlogger.debug(`New sidebar action with id \"${action.id}\" registered.`)\n}\n\n/**\n * Get all currently registered sidebar actions.\n */\nexport function getSidebarActions(): ISidebarAction[] {\n\tif (window._nc_files_sidebar_actions) {\n\t\treturn [...window._nc_files_sidebar_actions.values()]\n\t}\n\treturn []\n}\n\n/**\n * Check if a given sidebar action object implements all necessary fields.\n *\n * @param action - The sidebar action to validate\n */\nfunction validateSidebarAction(action: ISidebarAction): void {\n\tif (typeof action !== 'object') {\n\t\tthrow new Error('Sidebar action is not an object')\n\t}\n\n\tif (!action.id || (typeof action.id !== 'string') || action.id !== CSS.escape(action.id)) {\n\t\tthrow new Error('Sidebar actions need to have an id conforming to the HTML id attribute specifications')\n\t}\n\n\tif (!action.displayName || typeof action.displayName !== 'function') {\n\t\tthrow new Error('Sidebar actions need to have a displayName function')\n\t}\n\n\tif (!action.iconSvgInline || typeof action.iconSvgInline !== 'function') {\n\t\tthrow new Error('Sidebar actions need to have a iconSvgInline function')\n\t}\n\n\tif (!action.enabled || typeof action.enabled !== 'function') {\n\t\tthrow new Error('Sidebar actions need to have an enabled function')\n\t}\n\n\tif (!action.onClick || typeof action.onClick !== 'function') {\n\t\tthrow new Error('Sidebar actions need to have an onClick function')\n\t}\n}\n","/*\n * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { IView } from '../navigation/view.ts'\nimport type { IFolder, INode } from '../node/index.ts'\n\nimport isSvg from 'is-svg'\nimport logger from '../utils/logger.ts'\n\nexport interface ISidebarContext {\n\t/**\n\t * The active node in the sidebar\n\t */\n\tnode: INode\n\n\t/**\n\t * The current open folder in the files app\n\t */\n\tfolder: IFolder\n\n\t/**\n\t * The currently active view\n\t */\n\tview: IView\n}\n\n/**\n * This component describes the custom web component that should be registered for a sidebar tab.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Web_components\n * @see https://vuejs.org/guide/extras/web-components#building-custom-elements-with-vue\n */\nexport interface SidebarTabComponent extends ISidebarContext {\n\t/**\n\t * The active state of the sidebar tab.\n\t * It will be set to true if this component is the currently active tab.\n\t */\n\tactive: boolean\n}\n\n/**\n * The instance type of a sidebar tab web component.\n */\nexport type SidebarTabComponentInstance = SidebarTabComponent & HTMLElement\n\n/**\n * Implementation of a custom sidebar tab within the files app.\n */\nexport interface ISidebarTab {\n\t/**\n\t * Unique id of the sidebar tab.\n\t * This has to conform to the HTML id attribute specification.\n\t */\n\tid: string\n\n\t/**\n\t * The localized name of the sidebar tab.\n\t */\n\tdisplayName: string\n\n\t/**\n\t * The icon, as SVG, of the sidebar tab.\n\t */\n\ticonSvgInline: string\n\n\t/**\n\t * The order of this tab.\n\t * Use a low number to make this tab ordered in front.\n\t */\n\torder: number\n\n\t/**\n\t * The tag name of the web component.\n\t *\n\t * The web component must be defined using this name with `CustomElementRegistry.define()`,\n\t * either on initialization or within the `onInit` callback (preferred).\n\t * When rendering the sidebar tab, the files app will wait for the component to be defined in the registry (`customElements.whenDefined()`).\n\t *\n\t * To avoid name clashes the name has to start with your appid (e.g. `your_app`).\n\t * So in addition with the web component naming rules a good name would be `your_app-files-sidebar-tab`.\n\t */\n\ttagName: string\n\n\t/**\n\t * Callback to check if the sidebar tab should be shown for the selected node.\n\t *\n\t * If not provided, the tab will always be shown.\n\t *\n\t * @param context - The current context of the files app\n\t */\n\tenabled?: (context: ISidebarContext) => boolean\n\n\t/**\n\t * Called when the sidebar tab is active and rendered the first time in the sidebar.\n\t * This should be used to register the web componen (`CustomElementRegistry.define()`).\n\t *\n\t * The sidebar itself will anyways wait for the component to be defined in the registry (`customElements.whenDefined()`).\n\t * But also will wait for the promise returned by this method to resolve before rendering the tab.\n\t */\n\tonInit?: () => Promise<void>\n}\n\n/**\n * Register a new sidebar tab for the files app.\n *\n * @param tab - The sidebar tab to register\n * @throws {Error} If the provided tab is not a valid sidebar tab and thus cannot be registered.\n */\nexport function registerSidebarTab(tab: ISidebarTab): void {\n\tvalidateSidebarTab(tab)\n\n\twindow._nc_files_sidebar_tabs ??= new Map<string, ISidebarTab>()\n\tif (window._nc_files_sidebar_tabs.has(tab.id)) {\n\t\tlogger.warn(`Sidebar tab with id \"${tab.id}\" already registered. Skipping.`)\n\t\treturn\n\t}\n\twindow._nc_files_sidebar_tabs.set(tab.id, tab)\n\tlogger.debug(`New sidebar tab with id \"${tab.id}\" registered.`)\n}\n\n/**\n * Get all currently registered sidebar tabs.\n */\nexport function getSidebarTabs(): ISidebarTab[] {\n\tif (window._nc_files_sidebar_tabs) {\n\t\treturn [...window._nc_files_sidebar_tabs.values()]\n\t}\n\treturn []\n}\n\n/**\n * Check if a given sidebar tab objects implements all necessary fields.\n *\n * @param tab - The sidebar tab to validate\n * @throws {Error} If the provided tab is not valid\n */\nfunction validateSidebarTab(tab: ISidebarTab): void {\n\tif (typeof tab !== 'object') {\n\t\tthrow new Error('Sidebar tab is not an object')\n\t}\n\n\tif (!tab.id || (typeof tab.id !== 'string') || tab.id !== CSS.escape(tab.id)) {\n\t\tthrow new Error('Sidebar tabs need to have an id conforming to the HTML id attribute specifications')\n\t}\n\n\tif (!tab.tagName || typeof tab.tagName !== 'string') {\n\t\tthrow new Error('Sidebar tabs need to have the tagName name set')\n\t}\n\n\tif (!tab.tagName.match(/^[a-z][a-z0-9-_]+$/)) {\n\t\tthrow new Error('Sidebar tab \"tagName\" is invalid')\n\t}\n\n\tif (!tab.displayName || typeof tab.displayName !== 'string') {\n\t\tthrow new Error('Sidebar tabs need to have a name set')\n\t}\n\n\tif (typeof tab.iconSvgInline !== 'string' || !isSvg(tab.iconSvgInline)) {\n\t\tthrow new Error('Sidebar tabs need to have an valid SVG icon')\n\t}\n\n\tif (typeof tab.order !== 'number') {\n\t\tthrow new Error('Sidebar tabs need to have a numeric order set')\n\t}\n\n\tif (tab.enabled && typeof tab.enabled !== 'function') {\n\t\tthrow new Error('Sidebar tab \"enabled\" is not a function')\n\t}\n\n\tif (tab.onInit && typeof tab.onInit !== 'function') {\n\t\tthrow new Error('Sidebar tab \"onInit\" is not a function')\n\t}\n}\n","/*\n * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { INode } from '../node/node.ts'\nimport type { ISidebarAction } from './SidebarAction.ts'\nimport type { ISidebarContext, ISidebarTab } from './SidebarTab.ts'\n\nimport { registerSidebarAction } from './SidebarAction.ts'\nimport { registerSidebarTab } from './SidebarTab.ts'\n\nexport interface ISidebar {\n\t/**\n\t * If the files sidebar can currently be accessed.\n\t * Registering tabs also works if the sidebar is currently not available.\n\t */\n\treadonly available: boolean\n\n\t/**\n\t * The current open state of the sidebar\n\t */\n\treadonly isOpen: boolean\n\n\t/**\n\t * The currently active sidebar tab id\n\t */\n\treadonly activeTab?: string\n\n\t/**\n\t * The currently opened node in the sidebar\n\t */\n\treadonly node?: INode\n\n\t/**\n\t * Open the sidebar for a specific node.\n\t *\n\t * When the sidebar is fully opened the `files:sidebar:opened` event is emitted,\n\t * see also `@nextcloud/event-bus`.\n\t *\n\t * @param node - The node to open the sidebar for\n\t * @param tab - The tab to open by default\n\t */\n\topen(node: INode, tab?: string): void\n\n\t/**\n\t * Close the sidebar.\n\t *\n\t * When the sidebar is fully closed the `files:sidebar:closed` event is emitted,\n\t * see also `@nextcloud/event-bus`.\n\t */\n\tclose(): void\n\n\t/**\n\t * Set the active sidebar tab\n\t *\n\t * @param tabId - The tab to set active\n\t */\n\tsetActiveTab(tabId: string): void\n\n\t/**\n\t * Register a new sidebar tab.\n\t * This should ideally be done on app initialization using Nextcloud init scripts.\n\t *\n\t * @param tab - The sidebar tab to register\n\t */\n\tregisterTab(tab: ISidebarTab): void\n\n\t/**\n\t * Get all registered sidebar tabs.\n\t * If a node is passed only the enabled tabs are retrieved.\n\t */\n\tgetTabs(context?: ISidebarContext): ISidebarTab[]\n\n\t/**\n\t * Get all registered sidebar actions.\n\t *\n\t * If a context is provided only the enabled actions are returned.\n\t *\n\t * @param context - The context\n\t */\n\tgetActions(context?: ISidebarContext): ISidebarAction[]\n\n\t/**\n\t * Register a new sidebar action.\n\t *\n\t * @param action - The action to register\n\t */\n\tregisterAction(action: ISidebarAction): void\n}\n\n/**\n * This is just a proxy allowing an arbitrary `@nextcloud/files` library version to access the defined interface of the sidebar.\n * By proxying this instead of providing the implementation here we ensure that if apps use different versions of the library,\n * we do not end up with version conflicts between them.\n *\n * If we add new properties they just will be available in new library versions.\n * If we decide to do a breaking change we can either add compatibility wrappers in the implementation in the files app.\n */\nclass SidebarProxy implements ISidebar {\n\tget #impl(): Omit<ISidebar, 'available' | 'registerTab' | 'registerAction'> | undefined {\n\t\treturn window.OCA?.Files?._sidebar?.()\n\t}\n\n\tget available(): boolean {\n\t\treturn !!this.#impl\n\t}\n\n\tget isOpen(): boolean {\n\t\treturn this.#impl?.isOpen ?? false\n\t}\n\n\tget activeTab(): string | undefined {\n\t\treturn this.#impl?.activeTab\n\t}\n\n\tget node(): INode | undefined {\n\t\treturn this.#impl?.node\n\t}\n\n\topen(node: INode, tab?: string): void {\n\t\tthis.#impl?.open(node, tab)\n\t}\n\n\tclose(): void {\n\t\tthis.#impl?.close()\n\t}\n\n\tsetActiveTab(tabId: string): void {\n\t\tthis.#impl?.setActiveTab(tabId)\n\t}\n\n\tregisterTab(tab: ISidebarTab): void {\n\t\tregisterSidebarTab(tab)\n\t}\n\n\tgetTabs(context?: ISidebarContext): ISidebarTab[] {\n\t\treturn this.#impl?.getTabs(context) ?? []\n\t}\n\n\tgetActions(context?: ISidebarContext): ISidebarAction[] {\n\t\treturn this.#impl?.getActions(context) ?? []\n\t}\n\n\tregisterAction(action: ISidebarAction): void {\n\t\tregisterSidebarAction(action)\n\t}\n}\n\n/**\n * Get a reference to the files sidebar.\n */\nexport function getSidebar(): ISidebar {\n\treturn new SidebarProxy()\n}\n","/**\n * SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later or LGPL-3.0-or-later\n */\n\nimport { getCapabilities } from '@nextcloud/capabilities'\n\ninterface NextcloudCapabilities extends Record<string, unknown> {\n\tfiles: {\n\t\tbigfilechunking: boolean\n\t\t// those are new in Nextcloud 30\n\t\tforbidden_filenames?: string[]\n\t\tforbidden_filename_basenames?: string[]\n\t\tforbidden_filename_characters?: string[]\n\t\tforbidden_filename_extensions?: string[]\n\t}\n}\n\nexport const InvalidFilenameErrorReason = Object.freeze({\n\tReservedName: 'reserved name',\n\tCharacter: 'character',\n\tExtension: 'extension',\n})\n\nexport type TInvalidFilenameErrorReason = typeof InvalidFilenameErrorReason[keyof typeof InvalidFilenameErrorReason]\n\ninterface InvalidFilenameErrorOptions {\n\t/**\n\t * The filename that was validated\n\t */\n\tfilename: string\n\n\t/**\n\t * Reason why the validation failed\n\t */\n\treason: TInvalidFilenameErrorReason\n\n\t/**\n\t * Part of the filename that caused this error\n\t */\n\tsegment: string\n}\n\nexport class InvalidFilenameError extends Error {\n\tpublic constructor(options: InvalidFilenameErrorOptions) {\n\t\tsuper(`Invalid ${options.reason} '${options.segment}' in filename '${options.filename}'`, { cause: options })\n\t}\n\n\t/**\n\t * The filename that was validated\n\t */\n\tpublic get filename() {\n\t\treturn (this.cause as InvalidFilenameErrorOptions).filename\n\t}\n\n\t/**\n\t * Reason why the validation failed\n\t */\n\tpublic get reason() {\n\t\treturn (this.cause as InvalidFilenameErrorOptions).reason\n\t}\n\n\t/**\n\t * Part of the filename that caused this error\n\t */\n\tpublic get segment() {\n\t\treturn (this.cause as InvalidFilenameErrorOptions).segment\n\t}\n}\n\n/**\n * Validate a given filename\n *\n * @param filename The filename to check\n * @throws {InvalidFilenameError}\n */\nexport function validateFilename(filename: string): void {\n\tconst capabilities = (getCapabilities() as NextcloudCapabilities).files\n\n\t// Handle forbidden characters\n\t// This needs to be done first as the other checks are case insensitive!\n\tconst forbiddenCharacters = capabilities.forbidden_filename_characters ?? ['/', '\\\\']\n\tfor (const character of forbiddenCharacters) {\n\t\tif (filename.includes(character)) {\n\t\t\tthrow new InvalidFilenameError({ segment: character, reason: InvalidFilenameErrorReason.Character, filename })\n\t\t}\n\t}\n\n\t// everything else is case insensitive (the capabilities are returned lowercase)\n\tfilename = filename.toLocaleLowerCase()\n\n\t// Handle forbidden filenames, on older Nextcloud versions without this capability it was hardcoded in the backend to '.htaccess'\n\tconst forbiddenFilenames = capabilities.forbidden_filenames ?? ['.htaccess']\n\tif (forbiddenFilenames.includes(filename)) {\n\t\tthrow new InvalidFilenameError({ filename, segment: filename, reason: InvalidFilenameErrorReason.ReservedName })\n\t}\n\n\t// Handle forbidden basenames\n\tconst endOfBasename = filename.indexOf('.', 1)\n\tconst basename = filename.substring(0, endOfBasename === -1 ? undefined : endOfBasename)\n\tconst forbiddenFilenameBasenames = capabilities.forbidden_filename_basenames ?? []\n\tif (forbiddenFilenameBasenames.includes(basename)) {\n\t\tthrow new InvalidFilenameError({ filename, segment: basename, reason: InvalidFilenameErrorReason.ReservedName })\n\t}\n\n\tconst forbiddenFilenameExtensions = capabilities.forbidden_filename_extensions ?? []\n\tfor (const extension of forbiddenFilenameExtensions) {\n\t\tif (filename.length > extension.length && filename.endsWith(extension)) {\n\t\t\tthrow new InvalidFilenameError({ segment: extension, reason: InvalidFilenameErrorReason.Extension, filename })\n\t\t}\n\t}\n}\n\n/**\n * Check the validity of a filename\n * This is a convenient wrapper for `checkFilenameValidity` to only return a boolean for the valid\n *\n * @param filename Filename to check validity\n */\nexport function isFilenameValid(filename: string): boolean {\n\ttry {\n\t\tvalidateFilename(filename)\n\t\treturn true\n\t} catch (error) {\n\t\tif (error instanceof InvalidFilenameError) {\n\t\t\treturn false\n\t\t}\n\t\tthrow error\n\t}\n}\n","/**\n * SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later or LGPL-3.0-or-later\n */\n\nimport { basename, extname } from '@nextcloud/paths'\n\ninterface UniqueNameOptions {\n\t/**\n\t * A function that takes an index and returns a suffix to add to the file name, defaults to '(index)'\n\t *\n\t * @param index The current index to add\n\t */\n\tsuffix?: (index: number) => string\n\t/**\n\t * Set to true to ignore the file extension when adding the suffix (when getting a unique directory name)\n\t */\n\tignoreFileExtension?: boolean\n}\n\n/**\n * Create an unique file name\n *\n * @param name The initial name to use\n * @param otherNames Other names that are already used\n * @param options Optional parameters for tuning the behavior\n * @return Either the initial name, if unique, or the name with the suffix so that the name is unique\n */\nexport function getUniqueName(\n\tname: string,\n\totherNames: string[],\n\toptions?: UniqueNameOptions,\n): string {\n\tconst opts = {\n\t\tsuffix: (n: number) => `(${n})`,\n\t\tignoreFileExtension: false,\n\t\t...options,\n\t}\n\n\tlet newName = name\n\tlet i = 1\n\twhile (otherNames.includes(newName)) {\n\t\tconst ext = opts.ignoreFileExtension ? '' : extname(name)\n\t\tconst base = basename(name, ext)\n\t\tnewName = `${base} ${opts.suffix(i++)}${ext}`\n\t}\n\treturn newName\n}\n","/**\n * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport { getCanonicalLocale } from '@nextcloud/l10n'\n\nconst humanList = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']\nconst humanListBinary = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB']\n\n/**\n * Format a file size in a human-like format. e.g. 42GB\n *\n * The default for Nextcloud is like Windows using binary sizes but showing decimal units,\n * meaning 1024 bytes will show as 1KB instead of 1KiB or like on Apple 1.02 KB\n *\n * @param size in bytes\n * @param skipSmallSizes avoid rendering tiny sizes and return '< 1 KB' instead\n * @param binaryPrefixes True if size binary prefixes like `KiB` should be used as per IEC 80000-13\n * @param base1000 Set to true to use base 1000 as per SI or used by Apple (default is base 1024 like Linux or Windows)\n */\nexport function formatFileSize(size: number | string, skipSmallSizes = false, binaryPrefixes = false, base1000 = false): string {\n\t// Binary prefixes only work with base 1024\n\tbinaryPrefixes = binaryPrefixes && !base1000\n\n\tif (typeof size === 'string') {\n\t\tsize = Number(size)\n\t}\n\n\t// Calculate Log with base 1024 or 1000: size = base ** order\n\tlet order = size > 0 ? Math.floor(Math.log(size) / Math.log(base1000 ? 1000 : 1024)) : 0\n\n\t// Stay in range of the byte sizes that are defined\n\torder = Math.min((binaryPrefixes ? humanListBinary.length : humanList.length) - 1, order)\n\n\tconst readableFormat = binaryPrefixes ? humanListBinary[order] : humanList[order]\n\tlet relativeSize = (size / Math.pow(base1000 ? 1000 : 1024, order)).toFixed(1)\n\n\tif (skipSmallSizes === true && order === 0) {\n\t\treturn (relativeSize !== '0.0' ? '< 1 ' : '0 ') + (binaryPrefixes ? humanListBinary[1] : humanList[1])\n\t}\n\n\tif (order < 2) {\n\t\trelativeSize = parseFloat(relativeSize).toFixed(0)\n\t} else {\n\t\trelativeSize = parseFloat(relativeSize).toLocaleString(getCanonicalLocale())\n\t}\n\n\treturn relativeSize + ' ' + readableFormat\n}\n\n/**\n * Returns a file size in bytes from a humanly readable string\n * Note: `b` and `B` are both parsed as bytes and not as bit or byte.\n *\n * @param value file size in human-readable format\n * @param forceBinary for backwards compatibility this allows values to be base 2 (so 2KB means 2048 bytes instead of 2000 bytes)\n * @return or null if string could not be parsed\n */\nexport function parseFileSize(value: string, forceBinary = false) {\n\ttry {\n\t\tvalue = `${value}`.toLocaleLowerCase().replaceAll(/\\s+/g, '').replaceAll(',', '.')\n\t} catch {\n\t\treturn null\n\t}\n\n\tconst match = value.match(/^([0-9]*(\\.[0-9]*)?)([kmgtp]?)(i?)b?$/)\n\t// ignore not found, missing pre- and decimal, empty number\n\tif (match === null || match[1] === '.' || match[1] === '') {\n\t\treturn null\n\t}\n\n\tconst bytesArray = {\n\t\t'': 0,\n\t\tk: 1,\n\t\tm: 2,\n\t\tg: 3,\n\t\tt: 4,\n\t\tp: 5,\n\t\te: 6,\n\t}\n\n\tconst decimalString = `${match[1]}`\n\tconst base = (match[4] === 'i' || forceBinary) ? 1024 : 1000\n\n\treturn Math.round(Number.parseFloat(decimalString) * (base ** bytesArray[match[3]]))\n}\n","/**\n * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport { getCanonicalLocale, getLanguage } from '@nextcloud/l10n'\n\ntype IdentifierFn<T> = (v: T) => unknown\nexport type SortingOrder = 'asc' | 'desc'\n\n/**\n * Helper to create string representation\n *\n * @param value Value to stringify\n */\nfunction stringify(value: unknown) {\n\t// The default representation of Date is not sortable because of the weekday names in front of it\n\tif (value instanceof Date) {\n\t\treturn value.toISOString()\n\t}\n\treturn String(value)\n}\n\n/**\n * Natural order a collection\n * You can define identifiers as callback functions, that get the element and return the value to sort.\n *\n * @param collection The collection to order\n * @param identifiers An array of identifiers to use, by default the identity of the element is used\n * @param orders Array of orders, by default all identifiers are sorted ascening\n */\nexport function orderBy<T>(collection: readonly T[], identifiers?: IdentifierFn<T>[], orders?: SortingOrder[]): T[] {\n\t// If not identifiers are set we use the identity of the value\n\tidentifiers = identifiers ?? [(value) => value]\n\t// By default sort the collection ascending\n\torders = orders ?? []\n\tconst sorting = identifiers.map((_, index) => (orders[index] ?? 'asc') === 'asc' ? 1 : -1)\n\n\tconst collator = Intl.Collator(\n\t\t[getLanguage(), getCanonicalLocale()],\n\t\t{\n\t\t\t// handle 10 as ten and not as one-zero\n\t\t\tnumeric: true,\n\t\t\tusage: 'sort',\n\t\t},\n\t)\n\n\treturn [...collection].sort((a, b) => {\n\t\tfor (const [index, identifier] of identifiers.entries()) {\n\t\t\t// Get the local compare of stringified value a and b\n\t\t\tconst value = collator.compare(stringify(identifier(a)), stringify(identifier(b)))\n\t\t\t// If they do not match return the order\n\t\t\tif (value !== 0) {\n\t\t\t\treturn value * sorting[index]\n\t\t\t}\n\t\t\t// If they match we need to continue with the next identifier\n\t\t}\n\t\t// If all are equal we need to return equality\n\t\treturn 0\n\t})\n}\n","/*\n * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { INode } from '../node/node.ts'\nimport type { SortingOrder } from './sorting.ts'\n\nimport { FileType } from '../node/fileType.ts'\nimport { orderBy } from './sorting.ts'\n\nexport const FilesSortingMode = Object.freeze({\n\tName: 'basename',\n\tModified: 'mtime',\n\tSize: 'size',\n})\n\nexport type TFilesSortingMode = typeof FilesSortingMode[keyof typeof FilesSortingMode]\n\nexport interface FilesSortingOptions {\n\t/**\n\t * They key to order the files by\n\t *\n\t * @default FilesSortingMode.Name\n\t */\n\tsortingMode?: TFilesSortingMode | string\n\n\t/**\n\t * @default 'asc'\n\t */\n\tsortingOrder?: SortingOrder\n\n\t/**\n\t * If set to true nodes marked as favorites are ordered on top of all other nodes\n\t *\n\t * @default false\n\t */\n\tsortFavoritesFirst?: boolean\n\n\t/**\n\t * If set to true folders are ordered on top of files\n\t *\n\t * @default false\n\t */\n\tsortFoldersFirst?: boolean\n}\n\n/**\n * Sort files and folders according to the sorting options\n *\n * @param nodes Nodes to sort\n * @param options Sorting options\n */\nexport function sortNodes(nodes: readonly INode[], options: FilesSortingOptions = {}): INode[] {\n\tconst sortingOptions = {\n\t\t// Default to sort by name\n\t\tsortingMode: FilesSortingMode.Name,\n\t\t// Default to sort ascending\n\t\tsortingOrder: 'asc' as const,\n\t\t...options,\n\t}\n\n\t/**\n\t * Get the basename without any extension if the current node is a file\n\t *\n\t * @param node - The node to get the basename of\n\t */\n\tfunction basename(node: INode): string {\n\t\tconst name = node.displayname || node.attributes?.displayname || node.basename || ''\n\t\tif (node.type === FileType.Folder) {\n\t\t\treturn name\n\t\t}\n\n\t\treturn name.lastIndexOf('.') > 0\n\t\t\t? name.slice(0, name.lastIndexOf('.'))\n\t\t\t: name\n\t}\n\n\tconst identifiers = [\n\t\t// 1: Sort favorites first if enabled\n\t\t...(sortingOptions.sortFavoritesFirst ? [(v: INode) => v.attributes?.favorite !== 1] : []),\n\t\t// 2: Sort folders first if sorting by name\n\t\t...(sortingOptions.sortFoldersFirst ? [(v: INode) => v.type !== 'folder'] : []),\n\t\t// 3: Use sorting mode if NOT basename (to be able to use display name too)\n\t\t...(sortingOptions.sortingMode !== FilesSortingMode.Name ? [(v: INode) => v[sortingOptions.sortingMode] ?? v.attributes[sortingOptions.sortingMode]] : []),\n\t\t// 4: Use display name if available, fallback to name\n\t\t(v: INode) => basename(v),\n\t\t// 5: Finally, use basename if all previous sorting methods failed\n\t\t(v: INode) => v.basename,\n\t]\n\tconst orders = [\n\t\t// (for 1): always sort favorites before normal files\n\t\t...(sortingOptions.sortFavoritesFirst ? ['asc'] : []),\n\t\t// (for 2): always sort folders before files\n\t\t...(sortingOptions.sortFoldersFirst ? ['asc'] : []),\n\t\t// (for 3): Reverse if sorting by mtime as mtime higher means edited more recent -> lower\n\t\t...(sortingOptions.sortingMode === FilesSortingMode.Modified ? [sortingOptions.sortingOrder === 'asc' ? 'desc' : 'asc'] : []),\n\t\t// (also for 3 so make sure not to conflict with 2 and 3)\n\t\t...(sortingOptions.sortingMode !== FilesSortingMode.Modified && sortingOptions.sortingMode !== FilesSortingMode.Name ? [sortingOptions.sortingOrder] : []),\n\t\t// for 4: use configured sorting direction\n\t\tsortingOptions.sortingOrder,\n\t\t// for 5: use configured sorting direction\n\t\tsortingOptions.sortingOrder,\n\t] as ('asc' | 'desc')[]\n\n\treturn orderBy(nodes, identifiers, orders)\n}\n"],"names":["validateAction","a","b","basename"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../lib/registry.ts","../lib/actions/fileAction.ts","../lib/actions/fileListAction.ts","../lib/filters/functions.ts","../lib/filters/listFilters.ts","../lib/headers/listHeaders.ts","../lib/utils/objectValidation.ts","../lib/navigation/column.ts","../lib/navigation/view.ts","../lib/navigation/navigation.ts","../lib/newMenu/NewMenu.ts","../lib/newMenu/functions.ts","../lib/sidebar/SidebarAction.ts","../lib/sidebar/SidebarTab.ts","../lib/sidebar/Sidebar.ts","../lib/utils/filename-validation.ts","../lib/utils/filename.ts","../lib/utils/fileSize.ts","../lib/utils/sorting.ts","../lib/utils/fileSorting.ts"],"sourcesContent":["/*\n * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { IFileAction, IFileListAction } from './actions/index.ts'\nimport type { IFileListFilter } from './filters/index.ts'\nimport type { IFileListHeader } from './headers/index.ts'\n\nimport { TypedEventTarget } from 'typescript-event-target'\nimport { scopedGlobals } from './globalScope.ts'\n\ninterface FilesRegistryEvents {\n\t'register:action': RegistrationEvent<IFileAction>\n\t'register:listAction': RegistrationEvent<IFileListAction>\n\t'register:listFilter': RegistrationEvent<IFileListFilter>\n\t'register:listHeader': RegistrationEvent<IFileListHeader>\n\t'unregister:listFilter': UnregisterEvent\n}\n\n/**\n * Custom event for registry events.\n * The detail is the registered item.\n */\nclass RegistrationEvent<T> extends CustomEvent<T> {}\n\n/**\n * Custom event for unregistering items from the registry.\n * The detail is the id of the unregistered item.\n */\nclass UnregisterEvent extends RegistrationEvent<string> {}\n\n/**\n * The registry for files app.\n * This is used to keep track of registered actions, filters, headers, etc. and to emit events when new items are registered.\n * Allowing to keep a reactive state of the registered items in the UI without being coupled to one specific reactivity framework.\n *\n * This is an internal implementation detail and should not be used directly.\n *\n * @internal\n * @see PublicFilesRegistry - for the public API to listen to registry events.\n */\nexport class FilesRegistry extends TypedEventTarget<FilesRegistryEvents> {}\n\n/**\n * The registry for files app.\n * This is used to keep track of registered actions, filters, headers, etc. and to emit events when new items are registered.\n * Allowing to keep a reactive state of the registered items in the UI without being coupled to one specific reactivity framework.\n */\nexport type PublicFilesRegistry = Pick<FilesRegistry, 'addEventListener' | 'removeEventListener'>\n\n/**\n * Get the global files registry.\n *\n * @internal\n */\nexport function getRegistry() {\n\tscopedGlobals.registry ??= new FilesRegistry()\n\treturn scopedGlobals.registry\n}\n\n/**\n * Get the global files registry.\n *\n * This allows to listen for new registrations of actions, filters, headers, etc.\n * Events are dispatched by the respective registration functions.\n */\nexport function getFilesRegistry(): PublicFilesRegistry {\n\treturn getRegistry()\n}\n","/**\n * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { ActionContext, ActionContextSingle } from '../types.ts'\n\nimport { scopedGlobals } from '../globalScope.ts'\nimport { getRegistry } from '../registry.ts'\nimport logger from '../utils/logger.ts'\n\nexport const DefaultType = Object.freeze({\n\tDEFAULT: 'default',\n\tHIDDEN: 'hidden',\n})\n\nexport type TDefaultType = typeof DefaultType[keyof typeof DefaultType]\n\nexport interface IHotkeyConfig {\n\t/**\n\t * Short, translated, description what this action is doing.\n\t * This will be used as the description next to the hotkey in the shortcuts overview.\n\t */\n\tdescription: string\n\n\t/**\n\t * The key to be pressed.\n\t *\n\t * @see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key\n\t */\n\tkey: string\n\n\t/**\n\t * If set then the callback is only called when the shift key is (not) pressed.\n\t * When left `undefined` a pressed shift key is ignored (callback is run with and without shift pressed).\n\t */\n\tshift?: boolean\n\n\t/**\n\t * Only execute the action if the control key is pressed.\n\t * On Mac devices the command key is used instead.\n\t */\n\tctrl?: true\n\n\t/**\n\t * Only execute the action if the alt key is pressed\n\t */\n\talt?: true\n}\n\nexport interface IFileAction {\n\t/** Unique ID */\n\tid: string\n\t/** Translatable string displayed in the menu */\n\tdisplayName: (context: ActionContext) => string\n\t/** Translatable title for of the action */\n\ttitle?: (context: ActionContext) => string\n\t/** Svg as inline string. <svg><path fill=\"...\" /></svg> */\n\ticonSvgInline: (context: ActionContext) => string\n\t/** Condition wether this action is shown or not */\n\tenabled?: (context: ActionContext) => boolean\n\n\t/**\n\t * Function executed on single file action\n\t *\n\t * @return true if the action was executed successfully,\n\t * false otherwise and null if the action is silent/undefined.\n\t * @throws {Error} If the action failed\n\t */\n\texec: (context: ActionContextSingle) => Promise<boolean | null>\n\t/**\n\t * Function executed on multiple files action\n\t *\n\t * @return true if the action was executed successfully,\n\t * false otherwise and null if the action is silent/undefined.\n\t * @throws {Error} If the action failed\n\t */\n\texecBatch?: (context: ActionContext) => Promise<(boolean | null)[]>\n\n\t/** This action order in the list */\n\torder?: number\n\n\t/**\n\t * Allows to define a hotkey which will trigger this action for the selected node.\n\t */\n\thotkey?: IHotkeyConfig\n\n\t/**\n\t * Set to true if this action is a destructive action, like \"delete\".\n\t * This will change the appearance in the action menu more prominent (e.g. red colored)\n\t */\n\tdestructive?: boolean\n\n\t/**\n\t * This action's parent id in the list.\n\t * If none found, will be displayed as a top-level action.\n\t */\n\tparent?: string\n\n\t/**\n\t * Make this action the default.\n\t *\n\t * If multiple actions are default, the first one will be used.\n\t * The other ones will be put as first entries in the actions menu iff `DefaultType.Hidden` is not used.\n\t *\n\t * A `DefaultType.Hidden` action will never be shown\n\t * in the actions menu even if another action takes\n\t * its place as default.\n\t *\n\t * @see DefaultType\n\t */\n\tdefault?: TDefaultType\n\t/**\n\t * If true, the renderInline function will be called\n\t */\n\tinline?: (context: ActionContextSingle) => boolean\n\t/**\n\t * If defined, the returned html element will be\n\t * appended before the actions menu.\n\t */\n\trenderInline?: (context: ActionContextSingle) => Promise<HTMLElement | null>\n}\n\n/**\n * Register a new file action.\n *\n * @param action - The file list action to register\n */\nexport function registerFileAction(action: IFileAction): void {\n\tvalidateAction(action)\n\n\tscopedGlobals.fileActions ??= new Map<string, IFileAction>()\n\tif (scopedGlobals.fileActions.has(action.id)) {\n\t\tlogger.error(`FileAction ${action.id} already registered`, { action })\n\t\treturn\n\t}\n\n\tscopedGlobals.fileActions.set(action.id, action)\n\tgetRegistry()\n\t\t.dispatchTypedEvent('register:action', new CustomEvent('register:action', { detail: action }))\n}\n\n/**\n * Get all registered file actions.\n */\nexport function getFileActions(): IFileAction[] {\n\tif (scopedGlobals.fileActions) {\n\t\treturn [...scopedGlobals.fileActions.values()]\n\t}\n\treturn []\n}\n\n/**\n * Validate a file action.\n *\n * @param action - The action to validate\n */\nfunction validateAction(action: IFileAction) {\n\tif (!action.id || typeof action.id !== 'string') {\n\t\tthrow new Error('Invalid id')\n\t}\n\n\tif (!action.displayName || typeof action.displayName !== 'function') {\n\t\tthrow new Error('Invalid displayName function')\n\t}\n\n\tif ('title' in action && typeof action.title !== 'function') {\n\t\tthrow new Error('Invalid title function')\n\t}\n\n\tif (!action.iconSvgInline || typeof action.iconSvgInline !== 'function') {\n\t\tthrow new Error('Invalid iconSvgInline function')\n\t}\n\n\tif (!action.exec || typeof action.exec !== 'function') {\n\t\tthrow new Error('Invalid exec function')\n\t}\n\n\t// Optional properties --------------------------------------------\n\tif ('enabled' in action && typeof action.enabled !== 'function') {\n\t\tthrow new Error('Invalid enabled function')\n\t}\n\n\tif ('execBatch' in action && typeof action.execBatch !== 'function') {\n\t\tthrow new Error('Invalid execBatch function')\n\t}\n\n\tif ('order' in action && typeof action.order !== 'number') {\n\t\tthrow new Error('Invalid order')\n\t}\n\n\tif (action.destructive !== undefined && typeof action.destructive !== 'boolean') {\n\t\tthrow new Error('Invalid destructive flag')\n\t}\n\n\tif ('parent' in action && typeof action.parent !== 'string') {\n\t\tthrow new Error('Invalid parent')\n\t}\n\n\tif (action.default && !Object.values(DefaultType).includes(action.default)) {\n\t\tthrow new Error('Invalid default')\n\t}\n\n\tif ('inline' in action && typeof action.inline !== 'function') {\n\t\tthrow new Error('Invalid inline function')\n\t}\n\n\tif ('renderInline' in action && typeof action.renderInline !== 'function') {\n\t\tthrow new Error('Invalid renderInline function')\n\t}\n\n\tif ('hotkey' in action && action.hotkey !== undefined) {\n\t\tif (typeof action.hotkey !== 'object') {\n\t\t\tthrow new Error('Invalid hotkey configuration')\n\t\t}\n\n\t\tif (typeof action.hotkey.key !== 'string' || !action.hotkey.key) {\n\t\t\tthrow new Error('Missing or invalid hotkey key')\n\t\t}\n\n\t\tif (typeof action.hotkey.description !== 'string' || !action.hotkey.description) {\n\t\t\tthrow new Error('Missing or invalid hotkey description')\n\t\t}\n\t}\n}\n","/**\n * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { ViewActionContext } from '../types.ts'\n\nimport { scopedGlobals } from '../globalScope.ts'\nimport { getRegistry } from '../registry.ts'\nimport logger from '../utils/logger.ts'\n\nexport interface IFileListAction {\n\t/** Unique ID */\n\tid: string\n\n\t/** Translated name of the action */\n\tdisplayName: (context: ViewActionContext) => string\n\n\t/** Raw svg string */\n\ticonSvgInline?: (context: ViewActionContext) => string\n\n\t/** Sort order */\n\torder: number\n\n\t/**\n\t * Condition whether this action is shown or not\n\t */\n\tenabled?: (context: ViewActionContext) => boolean\n\n\t/**\n\t * Function executed on single file action\n\t *\n\t * @return true if the action was executed successfully,\n\t * false otherwise and null if the action is silent/undefined.\n\t * @throws {Error} If the action failed\n\t */\n\texec: (context: ViewActionContext) => Promise<boolean | null>\n}\n\n/**\n * Register a new file list action.\n *\n * @param action - The file list action to register\n */\nexport function registerFileListAction(action: IFileListAction) {\n\tvalidateAction(action)\n\n\tscopedGlobals.fileListActions ??= new Map<string, IFileListAction>()\n\tif (scopedGlobals.fileListActions.has(action.id)) {\n\t\tlogger.error(`FileListAction with id \"${action.id}\" is already registered`, { action })\n\t\treturn\n\t}\n\n\tscopedGlobals.fileListActions.set(action.id, action)\n\tgetRegistry()\n\t\t.dispatchTypedEvent('register:listAction', new CustomEvent('register:listAction', { detail: action }))\n}\n\n/**\n * Get all currently registered file list actions.\n */\nexport function getFileListActions(): IFileListAction[] {\n\tif (scopedGlobals.fileListActions) {\n\t\treturn [...scopedGlobals.fileListActions.values()]\n\t}\n\treturn []\n}\n\n/**\n * Validate a file list action.\n *\n * @param action - The action to validate\n */\nfunction validateAction(action: IFileListAction) {\n\tif (!action.id || typeof action.id !== 'string') {\n\t\tthrow new Error('Invalid id')\n\t}\n\n\tif (!action.displayName || typeof action.displayName !== 'function') {\n\t\tthrow new Error('Invalid displayName function')\n\t}\n\n\tif ('iconSvgInline' in action && typeof action.iconSvgInline !== 'function') {\n\t\tthrow new Error('Invalid iconSvgInline function')\n\t}\n\n\tif ('order' in action && typeof action.order !== 'number') {\n\t\tthrow new Error('Invalid order')\n\t}\n\n\tif ('enabled' in action && typeof action.enabled !== 'function') {\n\t\tthrow new Error('Invalid enabled function')\n\t}\n\n\tif (!action.exec || typeof action.exec !== 'function') {\n\t\tthrow new Error('Invalid exec function')\n\t}\n}\n","/*!\n * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { IFileListFilter } from './listFilters.ts'\n\nimport { scopedGlobals } from '../globalScope.ts'\nimport { getRegistry } from '../registry.ts'\n\n/**\n * Register a new filter on the file list\n *\n * This only must be called once to register the filter,\n * when the filter state changes you need to call `filterUpdated` on the filter instead.\n *\n * @param filter The filter to register on the file list\n */\nexport function registerFileListFilter(filter: IFileListFilter): void {\n\tscopedGlobals.fileListFilters ??= new Map<string, IFileListFilter>()\n\tif (scopedGlobals.fileListFilters.has(filter.id)) {\n\t\tthrow new Error(`File list filter \"${filter.id}\" already registered`)\n\t}\n\n\tscopedGlobals.fileListFilters.set(filter.id, filter)\n\tgetRegistry()\n\t\t.dispatchTypedEvent('register:listFilter', new CustomEvent('register:listFilter', { detail: filter }))\n}\n\n/**\n * Remove a registered filter from the file list\n *\n * @param filterId The unique ID of the filter to remove\n */\nexport function unregisterFileListFilter(filterId: string): void {\n\tif (scopedGlobals.fileListFilters && scopedGlobals.fileListFilters.has(filterId)) {\n\t\tscopedGlobals.fileListFilters.delete(filterId)\n\t\tgetRegistry()\n\t\t\t.dispatchTypedEvent('unregister:listFilter', new CustomEvent('unregister:listFilter', { detail: filterId }))\n\t}\n}\n\n/**\n * Get all registered file list filters\n */\nexport function getFileListFilters(): IFileListFilter[] {\n\tif (scopedGlobals.fileListFilters) {\n\t\treturn [...scopedGlobals.fileListFilters.values()]\n\t}\n\treturn []\n}\n","/*!\n * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { INode } from '../node/index.ts'\n\nimport { TypedEventTarget } from 'typescript-event-target'\n\n/**\n * Active filters can provide one or more \"chips\" to show the currently active state.\n * Must at least provide a text representing the filters state and a callback to unset that state (disable this filter).\n */\nexport interface IFileListFilterChip {\n\t/**\n\t * Text of the chip\n\t */\n\ttext: string\n\n\t/**\n\t * Optional icon to be used on the chip (inline SVG as string)\n\t */\n\ticon?: string\n\n\t/**\n\t * Optional pass a user id to use a user avatar instead of an icon\n\t */\n\tuser?: string\n\n\t/**\n\t * Handler to be called on click\n\t */\n\tonclick(): void\n}\n\n/**\n * This event is emitted when the the filter value changed and the file list needs to be updated\n */\nexport interface FilterUpdateEvent extends CustomEvent<never> {\n\ttype: 'update:filter'\n}\n\n/**\n * This event is emitted when the the filter value changed and the file list needs to be updated\n */\nexport interface FilterUpdateChipsEvent extends CustomEvent<IFileListFilterChip[]> {\n\ttype: 'update:chips'\n}\n\ninterface IFileListFilterEvents {\n\t[name: string]: CustomEvent\n\t'update:filter': FilterUpdateEvent\n\t'update:chips': FilterUpdateChipsEvent\n}\n\nexport interface IFileListFilterBase extends TypedEventTarget<IFileListFilterEvents> {\n\n\t/**\n\t * Unique ID of this filter\n\t */\n\treadonly id: string\n\n\t/**\n\t * Order of the filter\n\t *\n\t * Use a low number to make this filter ordered in front.\n\t */\n\treadonly order: number\n\n\t/**\n\t * Filter function to decide if a node is shown.\n\t *\n\t * @param nodes Nodes to filter\n\t * @return Subset of the `nodes` parameter to show\n\t */\n\tfilter(nodes: INode[]): INode[]\n\n\t/**\n\t * Reset the filter to the initial state.\n\t *\n\t * Implementations should make sure,that if they provide chips they need to emit the `update:chips` event.\n\t * This is called by the files app.\n\t *\n\t * @since 3.10.0\n\t */\n\treset?(): void\n}\n\n/**\n * File list filter interface with UI component\n *\n * Filters with UI must provide additional information for rendering the filter component.\n * The ui will be rendered depending on the view port - either as inline actions or in a dropdown menu.\n */\nexport interface IFileListFilterWithUi extends IFileListFilterBase {\n\t/**\n\t * Display name of the filter\n\t */\n\treadonly displayName: string\n\n\t/**\n\t * Inline SVG icon as string for the filter\n\t */\n\treadonly iconSvgInline: string\n\n\t/**\n\t * Tag name of the web component to use as filter UI\n\t *\n\t * The web component must extend `HTMLElement` and accept a property `filter` of type `IFileListFilterWithUi`\n\t * (it will receive the filter instance itself).\n\t *\n\t * @see https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements\n\t */\n\treadonly tagName: string\n}\n\n/**\n * File list filter interface\n *\n * Filters can either be simple filters without UI or filters with a UI component.\n */\nexport type IFileListFilter = IFileListFilterBase | IFileListFilterWithUi\n\nexport class FileListFilter extends TypedEventTarget<IFileListFilterEvents> implements IFileListFilterBase {\n\tpublic id: string\n\tpublic order: number\n\n\tconstructor(id: string, order: number = 100) {\n\t\tsuper()\n\t\tthis.id = id\n\t\tthis.order = order\n\t}\n\n\tpublic filter(nodes: INode[]): INode[] {\n\t\tthrow new Error('Not implemented')\n\t\treturn nodes\n\t}\n\n\tprotected updateChips(chips: IFileListFilterChip[]) {\n\t\tthis.dispatchTypedEvent('update:chips', new CustomEvent('update:chips', { detail: chips }) as FilterUpdateChipsEvent)\n\t}\n\n\tprotected filterUpdated() {\n\t\tthis.dispatchTypedEvent('update:filter', new CustomEvent('update:filter') as FilterUpdateEvent)\n\t}\n}\n","/*\n * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { IView } from '../navigation/view.ts'\nimport type { IFolder } from '../node/folder.ts'\n\nimport { scopedGlobals } from '../globalScope.ts'\nimport { getRegistry } from '../registry.ts'\nimport logger from '../utils/logger.ts'\n\nexport interface IFileListHeader {\n\t/** Unique ID */\n\tid: string\n\t/** Order */\n\torder: number\n\t/** Condition wether this header is shown or not */\n\tenabled?: (folder: IFolder, view: IView) => boolean\n\t/** Executed when file list is initialized */\n\trender(el: HTMLElement, folder: IFolder, view: IView): void\n\t/** Executed when root folder changed */\n\tupdated(folder: IFolder, view: IView): void\n}\n\n/**\n * Register a new file list header.\n *\n * @param header - The header to register\n */\nexport function registerFileListHeader(header: IFileListHeader): void {\n\tvalidateHeader(header)\n\n\tscopedGlobals.fileListHeaders ??= new Map<string, IFileListHeader>()\n\tif (scopedGlobals.fileListHeaders.has(header.id)) {\n\t\tlogger.error(`Header ${header.id} already registered`, { header })\n\t\treturn\n\t}\n\n\tscopedGlobals.fileListHeaders.set(header.id, header)\n\tgetRegistry()\n\t\t.dispatchTypedEvent('register:listHeader', new CustomEvent('register:listHeader', { detail: header }))\n}\n\n/**\n * Get all currently registered file list headers.\n */\nexport function getFileListHeaders(): IFileListHeader[] {\n\tif (!scopedGlobals.fileListHeaders) {\n\t\treturn []\n\t}\n\treturn [...scopedGlobals.fileListHeaders.values()]\n}\n\n/**\n * @param header - The header to validate\n */\nfunction validateHeader(header: IFileListHeader) {\n\tif (!header.id || !header.render || !header.updated) {\n\t\tthrow new Error('Invalid header: id, render and updated are required')\n\t}\n\n\tif (typeof header.id !== 'string') {\n\t\tthrow new Error('Invalid id property')\n\t}\n\n\tif (header.enabled !== undefined && typeof header.enabled !== 'function') {\n\t\tthrow new Error('Invalid enabled property')\n\t}\n\n\tif (header.render && typeof header.render !== 'function') {\n\t\tthrow new Error('Invalid render property')\n\t}\n\n\tif (header.updated && typeof header.updated !== 'function') {\n\t\tthrow new Error('Invalid updated property')\n\t}\n}\n","/*\n * SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\n/**\n * Check an optional property type\n *\n * @param obj - the object to check\n * @param property - the property name\n * @param type - the expected type\n * @throws {Error} if the property is defined and not of the expected type\n */\nexport function checkOptionalProperty<T extends object>(\n\tobj: T,\n\tproperty: Exclude<keyof T, symbol>,\n\ttype: 'array' | 'function' | 'string' | 'boolean' | 'number' | 'object',\n): void {\n\tif (typeof obj[property] !== 'undefined') {\n\t\tif (type === 'array') {\n\t\t\tif (!Array.isArray(obj[property])) {\n\t\t\t\tthrow new Error(`View ${property} must be an array`)\n\t\t\t}\n\t\t} else if (typeof obj[property] !== type) {\n\t\t\tthrow new Error(`View ${property} must be a ${type}`)\n\t\t} else if (type === 'object' && (obj[property] === null || Array.isArray(obj[property]))) {\n\t\t\tthrow new Error(`View ${property} must be an object`)\n\t\t}\n\t}\n}\n","/*\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { INode } from '../node/node.ts'\nimport type { IView } from './view.ts'\n\nimport { checkOptionalProperty } from '../utils/objectValidation.ts'\n\nexport interface IColumn {\n\t/** Unique column ID */\n\tid: string\n\t/** Translated column title */\n\ttitle: string\n\t/** The content of the cell. The element will be appended within */\n\trender: (node: INode, view: IView) => HTMLElement\n\t/** Function used to sort INodes between them */\n\tsort?: (nodeA: INode, nodeB: INode) => number\n\t/**\n\t * Custom summary of the column to display at the end of the list.\n\t * Will not be displayed if nothing is provided\n\t */\n\tsummary?: (node: INode[], view: IView) => string\n}\n\nexport class Column implements IColumn {\n\tprivate _column: IColumn\n\n\tconstructor(column: IColumn) {\n\t\tvalidateColumn(column)\n\t\tthis._column = column\n\t}\n\n\tget id() {\n\t\treturn this._column.id\n\t}\n\n\tget title() {\n\t\treturn this._column.title\n\t}\n\n\tget render() {\n\t\treturn this._column.render\n\t}\n\n\tget sort() {\n\t\treturn this._column.sort\n\t}\n\n\tget summary() {\n\t\treturn this._column.summary\n\t}\n}\n\n/**\n * Validate a column definition\n *\n * @param column - the column to check\n * @throws {Error} if the column is not valid\n */\nexport function validateColumn(column: IColumn): void {\n\tif (typeof column !== 'object' || column === null) {\n\t\tthrow new Error('View column must be an object')\n\t}\n\n\tif (!column.id || typeof column.id !== 'string') {\n\t\tthrow new Error('A column id is required')\n\t}\n\n\tif (!column.title || typeof column.title !== 'string') {\n\t\tthrow new Error('A column title is required')\n\t}\n\n\tif (!column.render || typeof column.render !== 'function') {\n\t\tthrow new Error('A render function is required')\n\t}\n\n\t// Optional properties\n\tcheckOptionalProperty(column, 'sort', 'function')\n\tcheckOptionalProperty(column, 'summary', 'function')\n}\n","/*\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { IFolder, INode } from '../node/index.ts'\nimport type { IColumn } from './column.ts'\n\nimport isSvg from 'is-svg'\nimport { checkOptionalProperty } from '../utils/objectValidation.ts'\nimport { validateColumn } from './column.ts'\n\nexport type ContentsWithRoot = {\n\tfolder: IFolder\n\tcontents: INode[]\n}\n\nexport interface IGetContentsOptions {\n\t/**\n\t * Abort signal to be able to cancel the request.\n\t *\n\t *@see https://developer.mozilla.org/en-US/docs/Web/API/AbortController\n\t */\n\tsignal: AbortSignal\n}\n\nexport interface IView {\n\t/** Unique view ID */\n\tid: string\n\t/** Translated view name */\n\tname: string\n\t/** Translated accessible description of the view */\n\tcaption?: string\n\n\t/** Translated title of the empty view */\n\temptyTitle?: string\n\t/** Translated description of the empty view */\n\temptyCaption?: string\n\t/**\n\t * Custom implementation of the empty view.\n\t * If set and no content is found for the current view,\n\t * then this method is called with the container element\n\t * where to render your empty view implementation.\n\t *\n\t * @param div - The container element to render into\n\t */\n\temptyView?: (div: HTMLDivElement) => void\n\n\t/**\n\t * Method return the content of the provided path.\n\t *\n\t * This method _must_ also return the current directory\n\t * information alongside with its content.\n\t *\n\t * An [abort signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) is provided\n\t * to be able to cancel the request if the user change directory.\n\t */\n\tgetContents(path: string, options: IGetContentsOptions): Promise<ContentsWithRoot>\n\n\t/**\n\t * If set then the view will be hidden from the navigation unless its the active view.\n\t */\n\thidden?: true\n\n\t/** The view icon as an inline svg */\n\ticon: string\n\n\t/**\n\t * The view order.\n\t * If not set will be natural sorted by view name.\n\t */\n\torder?: number\n\n\t/**\n\t * Custom params to give to the router on click\n\t * If defined, will be treated as a dummy view and\n\t * will just redirect and not fetch any contents.\n\t */\n\tparams?: Record<string, string>\n\n\t/**\n\t * This view column(s). Name and actions are\n\t * by default always included\n\t */\n\tcolumns?: IColumn[]\n\n\t/** The parent unique ID */\n\tparent?: string\n\t/** This view is sticky (sent at the bottom) */\n\tsticky?: boolean\n\n\t/**\n\t * This view has children and is expanded (by default)\n\t * or not. This will be overridden by user config.\n\t */\n\texpanded?: boolean\n\n\t/**\n\t * Will be used as default if the user\n\t * haven't customized their sorting column\n\t */\n\tdefaultSortKey?: string\n\n\t/**\n\t * Method called to load child views if any\n\t */\n\tloadChildViews?: (view: IView) => Promise<void>\n}\n\nexport class View implements IView {\n\tprivate _view: IView\n\n\tconstructor(view: IView) {\n\t\tvalidateView(view)\n\t\tthis._view = view\n\t}\n\n\tget id() {\n\t\treturn this._view.id\n\t}\n\n\tget name() {\n\t\treturn this._view.name\n\t}\n\n\tget caption() {\n\t\treturn this._view.caption\n\t}\n\n\tget emptyTitle() {\n\t\treturn this._view.emptyTitle\n\t}\n\n\tget emptyCaption() {\n\t\treturn this._view.emptyCaption\n\t}\n\n\tget getContents() {\n\t\treturn this._view.getContents\n\t}\n\n\tget hidden() {\n\t\treturn this._view.hidden\n\t}\n\n\tget icon() {\n\t\treturn this._view.icon\n\t}\n\n\tset icon(icon) {\n\t\tthis._view.icon = icon\n\t}\n\n\tget order() {\n\t\treturn this._view.order\n\t}\n\n\tset order(order) {\n\t\tthis._view.order = order\n\t}\n\n\tget params() {\n\t\treturn this._view.params\n\t}\n\n\tset params(params) {\n\t\tthis._view.params = params\n\t}\n\n\tget columns() {\n\t\treturn this._view.columns\n\t}\n\n\tget emptyView() {\n\t\treturn this._view.emptyView\n\t}\n\n\tget parent() {\n\t\treturn this._view.parent\n\t}\n\n\tget sticky() {\n\t\treturn this._view.sticky\n\t}\n\n\tget expanded() {\n\t\treturn this._view.expanded\n\t}\n\n\tset expanded(expanded: boolean | undefined) {\n\t\tthis._view.expanded = expanded\n\t}\n\n\tget defaultSortKey() {\n\t\treturn this._view.defaultSortKey\n\t}\n\n\tget loadChildViews() {\n\t\treturn this._view.loadChildViews\n\t}\n}\n\n/**\n * Validate a view interface to check all required properties are satisfied.\n *\n * @param view the view to check\n * @throws {Error} if the view is not valid\n */\nexport function validateView(view: IView) {\n\tif (!view.icon || typeof view.icon !== 'string' || !isSvg(view.icon)) {\n\t\tthrow new Error('View icon is required and must be a valid svg string')\n\t}\n\n\tif (!view.id || typeof view.id !== 'string') {\n\t\tthrow new Error('View id is required and must be a string')\n\t}\n\n\tif (!view.getContents || typeof view.getContents !== 'function') {\n\t\tthrow new Error('View getContents is required and must be a function')\n\t}\n\n\tif (!view.name || typeof view.name !== 'string') {\n\t\tthrow new Error('View name is required and must be a string')\n\t}\n\n\t// optional properties type checks\n\n\tcheckOptionalProperty(view, 'caption', 'string')\n\tcheckOptionalProperty(view, 'columns', 'array')\n\tcheckOptionalProperty(view, 'defaultSortKey', 'string')\n\tcheckOptionalProperty(view, 'emptyCaption', 'string')\n\tcheckOptionalProperty(view, 'emptyTitle', 'string')\n\tcheckOptionalProperty(view, 'emptyView', 'function')\n\tcheckOptionalProperty(view, 'expanded', 'boolean')\n\tcheckOptionalProperty(view, 'hidden', 'boolean')\n\tcheckOptionalProperty(view, 'loadChildViews', 'function')\n\tcheckOptionalProperty(view, 'order', 'number')\n\tcheckOptionalProperty(view, 'params', 'object')\n\tcheckOptionalProperty(view, 'parent', 'string')\n\tcheckOptionalProperty(view, 'sticky', 'boolean')\n\n\tif (view.columns) {\n\t\t// we cannot use `instanceof` here because if the Navigation and the Column class are loaded by different apps\n\t\t// (Navigation is set by files app and Column by a 3rd party app),\n\t\t// the `instanceof` check will fail even if the object has the correct shape because they are different classes in memory.\n\t\tview.columns.forEach(validateColumn)\n\t\tconst columnIds = view.columns.reduce((set, column) => set.add(column.id), new Set<string>())\n\t\tif (columnIds.size !== view.columns.length) {\n\t\t\tthrow new Error('View columns must have unique ids')\n\t\t}\n\t}\n}\n","/**\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { IView } from './view.ts'\n\nimport { TypedEventTarget } from 'typescript-event-target'\nimport { scopedGlobals } from '../globalScope.ts'\nimport { validateView } from './view.ts'\n\n/**\n * The event is emitted when the navigation view was updated.\n * It contains the new active view in the `detail` attribute.\n */\ninterface UpdateActiveViewEvent extends CustomEvent<IView | null> {\n\ttype: 'updateActive'\n}\n\n/**\n * This event is emitted when the list of registered views is changed\n */\ninterface UpdateViewsEvent extends CustomEvent<never> {\n\ttype: 'update'\n}\n\n/**\n * The files navigation manages the available and active views\n *\n * Custom views for the files app can be registered (examples are the favorites views or the shared-with-you view).\n * It is also possible to listen on changes of the registered views or when the current active view is changed.\n *\n * @example\n * ```js\n * const navigation = getNavigation()\n * navigation.addEventListener('update', () => {\n * // This will be called whenever a new view is registered or a view is removed\n * const viewNames = navigation.views.map((view) => view.name)\n * console.warn('Registered views changed', viewNames)\n * })\n * // Or you can react to changes of the current active view\n * navigation.addEventListener('updateActive', (event) => {\n * // This will be called whenever the active view changed\n * const newView = event.detail // you could also use `navigation.active`\n * console.warn('Active view changed to ' + newView.name)\n * })\n * ```\n */\nexport class Navigation extends TypedEventTarget<{ updateActive: UpdateActiveViewEvent, update: UpdateViewsEvent }> {\n\tprivate _views: IView[] = []\n\tprivate _currentView: IView | null = null\n\n\t/**\n\t * Register a new view on the navigation\n\t *\n\t * @param views The views to register\n\t * @throws {Error} if a view with the same id is already registered\n\t * @throws {Error} if the registered view is invalid\n\t */\n\tregister(...views: IView[]): void {\n\t\tfor (const view of views) {\n\t\t\tif (this._views.find((search) => search.id === view.id)) {\n\t\t\t\tthrow new Error(`IView id ${view.id} is already registered`)\n\t\t\t}\n\t\t\tvalidateView(view)\n\t\t}\n\n\t\tthis._views.push(...views)\n\t\tthis.dispatchTypedEvent('update', new CustomEvent<never>('update') as UpdateViewsEvent)\n\t}\n\n\t/**\n\t * Remove a registered view\n\t *\n\t * @param id The id of the view to remove\n\t */\n\tremove(id: string): void {\n\t\tconst index = this._views.findIndex((view) => view.id === id)\n\t\tif (index !== -1) {\n\t\t\tthis._views.splice(index, 1)\n\t\t\tthis.dispatchTypedEvent('update', new CustomEvent('update') as UpdateViewsEvent)\n\t\t}\n\t}\n\n\t/**\n\t * Set the currently active view\n\t *\n\t * @param id - The id of the view to set as active\n\t * @throws {Error} If no view with the given id was registered\n\t * @fires UpdateActiveViewEvent\n\t */\n\tsetActive(id: string | null): void {\n\t\tif (id === null) {\n\t\t\tthis._currentView = null\n\t\t} else {\n\t\t\tconst view = this._views.find(({ id: viewId }) => viewId === id)\n\t\t\tif (!view) {\n\t\t\t\tthrow new Error(`No view with ${id} registered`)\n\t\t\t}\n\t\t\tthis._currentView = view\n\t\t}\n\n\t\tconst event = new CustomEvent<IView | null>('updateActive', { detail: this._currentView })\n\t\tthis.dispatchTypedEvent('updateActive', event as UpdateActiveViewEvent)\n\t}\n\n\t/**\n\t * The currently active files view\n\t */\n\tget active(): IView | null {\n\t\treturn this._currentView\n\t}\n\n\t/**\n\t * All registered views\n\t */\n\tget views(): IView[] {\n\t\treturn this._views\n\t}\n}\n\n/**\n * Get the current files navigation\n */\nexport function getNavigation(): Navigation {\n\tscopedGlobals.navigation ??= new Navigation()\n\treturn scopedGlobals.navigation\n}\n","/*\n * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { IFolder, INode } from '../node/index.ts'\n\nimport logger from '../utils/logger.ts'\n\nexport const NewMenuEntryCategory = Object.freeze({\n\t/**\n\t * For actions where the user is intended to upload from their device\n\t */\n\tUploadFromDevice: 0,\n\n\t/**\n\t * For actions that create new nodes on the server without uploading\n\t */\n\tCreateNew: 1,\n\n\t/**\n\t * For everything not matching the other categories\n\t */\n\tOther: 2,\n})\n\nexport type TNewMenuEntryCategory = typeof NewMenuEntryCategory[keyof typeof NewMenuEntryCategory]\n\nexport interface NewMenuEntry {\n\t/** Unique ID */\n\tid: string\n\n\t/**\n\t * Category to put this entry in\n\t * (supported since Nextcloud 30)\n\t *\n\t * @default NewMenuEntryCategory.CreateNew\n\t * @see NewMenuEntryCategory\n\t * @since 3.3.0\n\t */\n\tcategory?: TNewMenuEntryCategory\n\n\t/** Translatable string displayed in the menu */\n\tdisplayName: string\n\n\t/**\n\t * Condition wether this entry is shown or not\n\t *\n\t * @param context the creation context. Usually the current folder\n\t */\n\tenabled?: (context: IFolder) => boolean\n\n\t/**\n\t * Either iconSvgInline or iconClass must be defined\n\t * Svg as inline string. <svg><path fill=\"...\" /></svg>\n\t */\n\ticonSvgInline?: string\n\n\t/** Order of the entry in the menu */\n\torder?: number\n\n\t/**\n\t * Function to be run after creation\n\t *\n\t * @param context - The creation context. Usually the current folder\n\t * @param content - List of file/folders present in the context folder\n\t */\n\thandler: (context: IFolder, content: INode[]) => void\n}\n\nexport class NewMenu {\n\tprivate _entries: Array<NewMenuEntry> = []\n\n\tpublic registerEntry(entry: NewMenuEntry) {\n\t\tthis.validateEntry(entry)\n\t\tentry.category = entry.category ?? NewMenuEntryCategory.CreateNew\n\t\tthis._entries.push(entry)\n\t}\n\n\tpublic unregisterEntry(entry: NewMenuEntry | string) {\n\t\tconst entryIndex = typeof entry === 'string'\n\t\t\t? this.getEntryIndex(entry)\n\t\t\t: this.getEntryIndex(entry.id)\n\n\t\tif (entryIndex === -1) {\n\t\t\tlogger.warn('Entry not found, nothing removed', { entry, entries: this.getEntries() })\n\t\t\treturn\n\t\t}\n\n\t\tthis._entries.splice(entryIndex, 1)\n\t}\n\n\t/**\n\t * Get the list of registered entries\n\t *\n\t * @param context - The creation context. Usually the current folder\n\t */\n\tpublic getEntries(context?: IFolder): Array<NewMenuEntry> {\n\t\tif (context) {\n\t\t\treturn this._entries\n\t\t\t\t.filter((entry) => typeof entry.enabled === 'function' ? entry.enabled(context) : true)\n\t\t}\n\t\treturn this._entries\n\t}\n\n\tprivate getEntryIndex(id: string): number {\n\t\treturn this._entries.findIndex((entry) => entry.id === id)\n\t}\n\n\tprivate validateEntry(entry: NewMenuEntry) {\n\t\tif (!entry.id || !entry.displayName || !entry.iconSvgInline || !entry.handler) {\n\t\t\tthrow new Error('Invalid entry')\n\t\t}\n\n\t\tif (typeof entry.id !== 'string'\n\t\t\t|| typeof entry.displayName !== 'string') {\n\t\t\tthrow new Error('Invalid id or displayName property')\n\t\t}\n\n\t\tif (entry.iconSvgInline && typeof entry.iconSvgInline !== 'string') {\n\t\t\tthrow new Error('Invalid icon provided')\n\t\t}\n\n\t\tif (entry.enabled !== undefined && typeof entry.enabled !== 'function') {\n\t\t\tthrow new Error('Invalid enabled property')\n\t\t}\n\n\t\tif (typeof entry.handler !== 'function') {\n\t\t\tthrow new Error('Invalid handler property')\n\t\t}\n\n\t\tif ('order' in entry && typeof entry.order !== 'number') {\n\t\t\tthrow new Error('Invalid order property')\n\t\t}\n\n\t\tif (this.getEntryIndex(entry.id) !== -1) {\n\t\t\tthrow new Error('Duplicate entry')\n\t\t}\n\t}\n}\n","/*\n * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { IFolder } from '../node/index.ts'\nimport type { NewMenuEntry } from './NewMenu.ts'\n\nimport { scopedGlobals } from '../globalScope.ts'\nimport { NewMenu } from './NewMenu.ts'\n\n/**\n * Get the NewMenu instance used by the files app.\n */\nexport function getNewFileMenu(): NewMenu {\n\tscopedGlobals.newFileMenu ??= new NewMenu()\n\treturn scopedGlobals.newFileMenu\n}\n\n/**\n * Add a new menu entry to the upload manager menu\n *\n * @param entry - The new file menu entry\n */\nexport function addNewFileMenuEntry(entry: NewMenuEntry): void {\n\tconst newFileMenu = getNewFileMenu()\n\treturn newFileMenu.registerEntry(entry)\n}\n\n/**\n * Remove a previously registered entry from the upload menu\n *\n * @param entry - Entry or id of entry to remove\n */\nexport function removeNewFileMenuEntry(entry: NewMenuEntry | string) {\n\tconst newFileMenu = getNewFileMenu()\n\treturn newFileMenu.unregisterEntry(entry)\n}\n\n/**\n * Get the list of registered entries from the upload menu\n *\n * @param context - The current folder to get the available entries\n */\nexport function getNewFileMenuEntries(context?: IFolder): NewMenuEntry[] {\n\tconst newFileMenu = getNewFileMenu()\n\treturn newFileMenu.getEntries(context).sort((a: NewMenuEntry, b: NewMenuEntry) => {\n\t\t// If defined and different, sort by order\n\t\tif (a.order !== undefined\n\t\t\t&& b.order !== undefined\n\t\t\t&& a.order !== b.order) {\n\t\t\treturn a.order - b.order\n\t\t}\n\t\t// else sort by display name\n\t\treturn a.displayName.localeCompare(b.displayName, undefined, { numeric: true, sensitivity: 'base' })\n\t})\n}\n","/*\n * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { ISidebarContext } from './SidebarTab.ts'\n\nimport { scopedGlobals } from '../globalScope.ts'\nimport logger from '../utils/logger.ts'\n\n/**\n * Implementation of a custom sidebar tab within the files app.\n */\nexport interface ISidebarAction {\n\t/**\n\t * Unique id of the sidebar tab.\n\t * This has to conform to the HTML id attribute specification.\n\t */\n\tid: string\n\n\t/**\n\t * The order of this tab.\n\t * Use a low number to make this tab ordered in front.\n\t */\n\torder: number\n\n\t/**\n\t * The localized name of the sidebar tab.\n\t *\n\t * @param context - The current context of the files app\n\t */\n\tdisplayName(context: ISidebarContext): string\n\n\t/**\n\t * The icon, as SVG, of the sidebar tab.\n\t *\n\t * @param context - The current context of the files app\n\t */\n\ticonSvgInline(context: ISidebarContext): string\n\n\t/**\n\t * Callback to check if the sidebar tab should be shown for the selected node.\n\t *\n\t * @param context - The current context of the files app\n\t */\n\tenabled(context: ISidebarContext): boolean\n\n\t/**\n\t * Handle the sidebar action.\n\t *\n\t * @param context - The current context of the files app\n\t */\n\tonClick(context: ISidebarContext): void\n}\n\n/**\n * Register a new sidebar action.\n *\n * @param action - The sidebar action to register\n * @throws {Error} If the provided action is not a valid sidebar action and thus cannot be registered.\n */\nexport function registerSidebarAction(action: ISidebarAction): void {\n\tvalidateSidebarAction(action)\n\n\tscopedGlobals.filesSidebarActions ??= new Map<string, ISidebarAction>()\n\tif (scopedGlobals.filesSidebarActions.has(action.id)) {\n\t\tlogger.warn(`Sidebar action with id \"${action.id}\" already registered. Skipping.`)\n\t\treturn\n\t}\n\tscopedGlobals.filesSidebarActions.set(action.id, action)\n\tlogger.debug(`New sidebar action with id \"${action.id}\" registered.`)\n}\n\n/**\n * Get all currently registered sidebar actions.\n */\nexport function getSidebarActions(): ISidebarAction[] {\n\tif (scopedGlobals.filesSidebarActions) {\n\t\treturn [...scopedGlobals.filesSidebarActions.values()]\n\t}\n\treturn []\n}\n\n/**\n * Check if a given sidebar action object implements all necessary fields.\n *\n * @param action - The sidebar action to validate\n */\nfunction validateSidebarAction(action: ISidebarAction): void {\n\tif (typeof action !== 'object') {\n\t\tthrow new Error('Sidebar action is not an object')\n\t}\n\n\tif (!action.id || (typeof action.id !== 'string') || action.id !== CSS.escape(action.id)) {\n\t\tthrow new Error('Sidebar actions need to have an id conforming to the HTML id attribute specifications')\n\t}\n\n\tif (!action.displayName || typeof action.displayName !== 'function') {\n\t\tthrow new Error('Sidebar actions need to have a displayName function')\n\t}\n\n\tif (!action.iconSvgInline || typeof action.iconSvgInline !== 'function') {\n\t\tthrow new Error('Sidebar actions need to have a iconSvgInline function')\n\t}\n\n\tif (!action.enabled || typeof action.enabled !== 'function') {\n\t\tthrow new Error('Sidebar actions need to have an enabled function')\n\t}\n\n\tif (!action.onClick || typeof action.onClick !== 'function') {\n\t\tthrow new Error('Sidebar actions need to have an onClick function')\n\t}\n}\n","/*\n * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { IView } from '../navigation/view.ts'\nimport type { IFolder, INode } from '../node/index.ts'\n\nimport isSvg from 'is-svg'\nimport { scopedGlobals } from '../globalScope.ts'\nimport logger from '../utils/logger.ts'\n\nexport interface ISidebarContext {\n\t/**\n\t * The active node in the sidebar\n\t */\n\tnode: INode\n\n\t/**\n\t * The current open folder in the files app\n\t */\n\tfolder: IFolder\n\n\t/**\n\t * The currently active view\n\t */\n\tview: IView\n}\n\n/**\n * This component describes the custom web component that should be registered for a sidebar tab.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Web_components\n * @see https://vuejs.org/guide/extras/web-components#building-custom-elements-with-vue\n */\nexport interface SidebarTabComponent extends ISidebarContext {\n\t/**\n\t * The active state of the sidebar tab.\n\t * It will be set to true if this component is the currently active tab.\n\t */\n\tactive: boolean\n}\n\n/**\n * The instance type of a sidebar tab web component.\n */\nexport type SidebarTabComponentInstance = SidebarTabComponent & HTMLElement\n\n/**\n * Implementation of a custom sidebar tab within the files app.\n */\nexport interface ISidebarTab {\n\t/**\n\t * Unique id of the sidebar tab.\n\t * This has to conform to the HTML id attribute specification.\n\t */\n\tid: string\n\n\t/**\n\t * The localized name of the sidebar tab.\n\t */\n\tdisplayName: string\n\n\t/**\n\t * The icon, as SVG, of the sidebar tab.\n\t */\n\ticonSvgInline: string\n\n\t/**\n\t * The order of this tab.\n\t * Use a low number to make this tab ordered in front.\n\t */\n\torder: number\n\n\t/**\n\t * The tag name of the web component.\n\t *\n\t * The web component must be defined using this name with `CustomElementRegistry.define()`,\n\t * either on initialization or within the `onInit` callback (preferred).\n\t * When rendering the sidebar tab, the files app will wait for the component to be defined in the registry (`customElements.whenDefined()`).\n\t *\n\t * To avoid name clashes the name has to start with your appid (e.g. `your_app`).\n\t * So in addition with the web component naming rules a good name would be `your_app-files-sidebar-tab`.\n\t */\n\ttagName: string\n\n\t/**\n\t * Callback to check if the sidebar tab should be shown for the selected node.\n\t *\n\t * If not provided, the tab will always be shown.\n\t *\n\t * @param context - The current context of the files app\n\t */\n\tenabled?: (context: ISidebarContext) => boolean\n\n\t/**\n\t * Called when the sidebar tab is active and rendered the first time in the sidebar.\n\t * This should be used to register the web componen (`CustomElementRegistry.define()`).\n\t *\n\t * The sidebar itself will anyways wait for the component to be defined in the registry (`customElements.whenDefined()`).\n\t * But also will wait for the promise returned by this method to resolve before rendering the tab.\n\t */\n\tonInit?: () => Promise<void>\n}\n\n/**\n * Register a new sidebar tab for the files app.\n *\n * @param tab - The sidebar tab to register\n * @throws {Error} If the provided tab is not a valid sidebar tab and thus cannot be registered.\n */\nexport function registerSidebarTab(tab: ISidebarTab): void {\n\tvalidateSidebarTab(tab)\n\n\tscopedGlobals.filesSidebarTabs ??= new Map<string, ISidebarTab>()\n\tif (scopedGlobals.filesSidebarTabs.has(tab.id)) {\n\t\tlogger.warn(`Sidebar tab with id \"${tab.id}\" already registered. Skipping.`)\n\t\treturn\n\t}\n\tscopedGlobals.filesSidebarTabs.set(tab.id, tab)\n\tlogger.debug(`New sidebar tab with id \"${tab.id}\" registered.`)\n}\n\n/**\n * Get all currently registered sidebar tabs.\n */\nexport function getSidebarTabs(): ISidebarTab[] {\n\tif (scopedGlobals.filesSidebarTabs) {\n\t\treturn [...scopedGlobals.filesSidebarTabs.values()]\n\t}\n\treturn []\n}\n\n/**\n * Check if a given sidebar tab objects implements all necessary fields.\n *\n * @param tab - The sidebar tab to validate\n * @throws {Error} If the provided tab is not valid\n */\nfunction validateSidebarTab(tab: ISidebarTab): void {\n\tif (typeof tab !== 'object') {\n\t\tthrow new Error('Sidebar tab is not an object')\n\t}\n\n\tif (!tab.id || (typeof tab.id !== 'string') || tab.id !== CSS.escape(tab.id)) {\n\t\tthrow new Error('Sidebar tabs need to have an id conforming to the HTML id attribute specifications')\n\t}\n\n\tif (!tab.tagName || typeof tab.tagName !== 'string') {\n\t\tthrow new Error('Sidebar tabs need to have the tagName name set')\n\t}\n\n\tif (!tab.tagName.match(/^[a-z][a-z0-9-_]+$/)) {\n\t\tthrow new Error('Sidebar tab \"tagName\" is invalid')\n\t}\n\n\tif (!tab.displayName || typeof tab.displayName !== 'string') {\n\t\tthrow new Error('Sidebar tabs need to have a name set')\n\t}\n\n\tif (typeof tab.iconSvgInline !== 'string' || !isSvg(tab.iconSvgInline)) {\n\t\tthrow new Error('Sidebar tabs need to have an valid SVG icon')\n\t}\n\n\tif (typeof tab.order !== 'number') {\n\t\tthrow new Error('Sidebar tabs need to have a numeric order set')\n\t}\n\n\tif (tab.enabled && typeof tab.enabled !== 'function') {\n\t\tthrow new Error('Sidebar tab \"enabled\" is not a function')\n\t}\n\n\tif (tab.onInit && typeof tab.onInit !== 'function') {\n\t\tthrow new Error('Sidebar tab \"onInit\" is not a function')\n\t}\n}\n","/*\n * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { INode } from '../node/node.ts'\nimport type { ISidebarAction } from './SidebarAction.ts'\nimport type { ISidebarContext, ISidebarTab } from './SidebarTab.ts'\n\nimport { registerSidebarAction } from './SidebarAction.ts'\nimport { registerSidebarTab } from './SidebarTab.ts'\n\nexport interface ISidebar {\n\t/**\n\t * If the files sidebar can currently be accessed.\n\t * Registering tabs also works if the sidebar is currently not available.\n\t */\n\treadonly available: boolean\n\n\t/**\n\t * The current open state of the sidebar\n\t */\n\treadonly isOpen: boolean\n\n\t/**\n\t * The currently active sidebar tab id\n\t */\n\treadonly activeTab?: string\n\n\t/**\n\t * The currently opened node in the sidebar\n\t */\n\treadonly node?: INode\n\n\t/**\n\t * Open the sidebar for a specific node.\n\t *\n\t * When the sidebar is fully opened the `files:sidebar:opened` event is emitted,\n\t * see also `@nextcloud/event-bus`.\n\t *\n\t * @param node - The node to open the sidebar for\n\t * @param tab - The tab to open by default\n\t */\n\topen(node: INode, tab?: string): void\n\n\t/**\n\t * Close the sidebar.\n\t *\n\t * When the sidebar is fully closed the `files:sidebar:closed` event is emitted,\n\t * see also `@nextcloud/event-bus`.\n\t */\n\tclose(): void\n\n\t/**\n\t * Set the active sidebar tab\n\t *\n\t * @param tabId - The tab to set active\n\t */\n\tsetActiveTab(tabId: string): void\n\n\t/**\n\t * Register a new sidebar tab.\n\t * This should ideally be done on app initialization using Nextcloud init scripts.\n\t *\n\t * @param tab - The sidebar tab to register\n\t */\n\tregisterTab(tab: ISidebarTab): void\n\n\t/**\n\t * Get all registered sidebar tabs.\n\t * If a node is passed only the enabled tabs are retrieved.\n\t */\n\tgetTabs(context?: ISidebarContext): ISidebarTab[]\n\n\t/**\n\t * Get all registered sidebar actions.\n\t *\n\t * If a context is provided only the enabled actions are returned.\n\t *\n\t * @param context - The context\n\t */\n\tgetActions(context?: ISidebarContext): ISidebarAction[]\n\n\t/**\n\t * Register a new sidebar action.\n\t *\n\t * @param action - The action to register\n\t */\n\tregisterAction(action: ISidebarAction): void\n}\n\n/**\n * This is just a proxy allowing an arbitrary `@nextcloud/files` library version to access the defined interface of the sidebar.\n * By proxying this instead of providing the implementation here we ensure that if apps use different versions of the library,\n * we do not end up with version conflicts between them.\n *\n * If we add new properties they just will be available in new library versions.\n * If we decide to do a breaking change we can either add compatibility wrappers in the implementation in the files app.\n */\nclass SidebarProxy implements ISidebar {\n\tget #impl(): Omit<ISidebar, 'available' | 'registerTab' | 'registerAction'> | undefined {\n\t\treturn window.OCA?.Files?._sidebar?.()\n\t}\n\n\tget available(): boolean {\n\t\treturn !!this.#impl\n\t}\n\n\tget isOpen(): boolean {\n\t\treturn this.#impl?.isOpen ?? false\n\t}\n\n\tget activeTab(): string | undefined {\n\t\treturn this.#impl?.activeTab\n\t}\n\n\tget node(): INode | undefined {\n\t\treturn this.#impl?.node\n\t}\n\n\topen(node: INode, tab?: string): void {\n\t\tthis.#impl?.open(node, tab)\n\t}\n\n\tclose(): void {\n\t\tthis.#impl?.close()\n\t}\n\n\tsetActiveTab(tabId: string): void {\n\t\tthis.#impl?.setActiveTab(tabId)\n\t}\n\n\tregisterTab(tab: ISidebarTab): void {\n\t\tregisterSidebarTab(tab)\n\t}\n\n\tgetTabs(context?: ISidebarContext): ISidebarTab[] {\n\t\treturn this.#impl?.getTabs(context) ?? []\n\t}\n\n\tgetActions(context?: ISidebarContext): ISidebarAction[] {\n\t\treturn this.#impl?.getActions(context) ?? []\n\t}\n\n\tregisterAction(action: ISidebarAction): void {\n\t\tregisterSidebarAction(action)\n\t}\n}\n\n/**\n * Get a reference to the files sidebar.\n */\nexport function getSidebar(): ISidebar {\n\treturn new SidebarProxy()\n}\n","/**\n * SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later or LGPL-3.0-or-later\n */\n\nimport { getCapabilities } from '@nextcloud/capabilities'\n\ninterface NextcloudCapabilities extends Record<string, unknown> {\n\tfiles: {\n\t\tbigfilechunking: boolean\n\t\t// those are new in Nextcloud 30\n\t\tforbidden_filenames?: string[]\n\t\tforbidden_filename_basenames?: string[]\n\t\tforbidden_filename_characters?: string[]\n\t\tforbidden_filename_extensions?: string[]\n\t}\n}\n\nexport const InvalidFilenameErrorReason = Object.freeze({\n\tReservedName: 'reserved name',\n\tCharacter: 'character',\n\tExtension: 'extension',\n})\n\nexport type TInvalidFilenameErrorReason = typeof InvalidFilenameErrorReason[keyof typeof InvalidFilenameErrorReason]\n\ninterface InvalidFilenameErrorOptions {\n\t/**\n\t * The filename that was validated\n\t */\n\tfilename: string\n\n\t/**\n\t * Reason why the validation failed\n\t */\n\treason: TInvalidFilenameErrorReason\n\n\t/**\n\t * Part of the filename that caused this error\n\t */\n\tsegment: string\n}\n\nexport class InvalidFilenameError extends Error {\n\tpublic constructor(options: InvalidFilenameErrorOptions) {\n\t\tsuper(`Invalid ${options.reason} '${options.segment}' in filename '${options.filename}'`, { cause: options })\n\t}\n\n\t/**\n\t * The filename that was validated\n\t */\n\tpublic get filename() {\n\t\treturn (this.cause as InvalidFilenameErrorOptions).filename\n\t}\n\n\t/**\n\t * Reason why the validation failed\n\t */\n\tpublic get reason() {\n\t\treturn (this.cause as InvalidFilenameErrorOptions).reason\n\t}\n\n\t/**\n\t * Part of the filename that caused this error\n\t */\n\tpublic get segment() {\n\t\treturn (this.cause as InvalidFilenameErrorOptions).segment\n\t}\n}\n\n/**\n * Validate a given filename\n *\n * @param filename The filename to check\n * @throws {InvalidFilenameError}\n */\nexport function validateFilename(filename: string): void {\n\tconst capabilities = (getCapabilities() as NextcloudCapabilities).files\n\n\t// Handle forbidden characters\n\t// This needs to be done first as the other checks are case insensitive!\n\tconst forbiddenCharacters = capabilities.forbidden_filename_characters ?? ['/', '\\\\']\n\tfor (const character of forbiddenCharacters) {\n\t\tif (filename.includes(character)) {\n\t\t\tthrow new InvalidFilenameError({ segment: character, reason: InvalidFilenameErrorReason.Character, filename })\n\t\t}\n\t}\n\n\t// everything else is case insensitive (the capabilities are returned lowercase)\n\tfilename = filename.toLocaleLowerCase()\n\n\t// Handle forbidden filenames, on older Nextcloud versions without this capability it was hardcoded in the backend to '.htaccess'\n\tconst forbiddenFilenames = capabilities.forbidden_filenames ?? ['.htaccess']\n\tif (forbiddenFilenames.includes(filename)) {\n\t\tthrow new InvalidFilenameError({ filename, segment: filename, reason: InvalidFilenameErrorReason.ReservedName })\n\t}\n\n\t// Handle forbidden basenames\n\tconst endOfBasename = filename.indexOf('.', 1)\n\tconst basename = filename.substring(0, endOfBasename === -1 ? undefined : endOfBasename)\n\tconst forbiddenFilenameBasenames = capabilities.forbidden_filename_basenames ?? []\n\tif (forbiddenFilenameBasenames.includes(basename)) {\n\t\tthrow new InvalidFilenameError({ filename, segment: basename, reason: InvalidFilenameErrorReason.ReservedName })\n\t}\n\n\tconst forbiddenFilenameExtensions = capabilities.forbidden_filename_extensions ?? []\n\tfor (const extension of forbiddenFilenameExtensions) {\n\t\tif (filename.length > extension.length && filename.endsWith(extension)) {\n\t\t\tthrow new InvalidFilenameError({ segment: extension, reason: InvalidFilenameErrorReason.Extension, filename })\n\t\t}\n\t}\n}\n\n/**\n * Check the validity of a filename\n * This is a convenient wrapper for `checkFilenameValidity` to only return a boolean for the valid\n *\n * @param filename Filename to check validity\n */\nexport function isFilenameValid(filename: string): boolean {\n\ttry {\n\t\tvalidateFilename(filename)\n\t\treturn true\n\t} catch (error) {\n\t\tif (error instanceof InvalidFilenameError) {\n\t\t\treturn false\n\t\t}\n\t\tthrow error\n\t}\n}\n","/**\n * SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later or LGPL-3.0-or-later\n */\n\nimport { basename, extname } from '@nextcloud/paths'\n\ninterface UniqueNameOptions {\n\t/**\n\t * A function that takes an index and returns a suffix to add to the file name, defaults to '(index)'\n\t *\n\t * @param index The current index to add\n\t */\n\tsuffix?: (index: number) => string\n\t/**\n\t * Set to true to ignore the file extension when adding the suffix (when getting a unique directory name)\n\t */\n\tignoreFileExtension?: boolean\n}\n\n/**\n * Create an unique file name\n *\n * @param name The initial name to use\n * @param otherNames Other names that are already used\n * @param options Optional parameters for tuning the behavior\n * @return Either the initial name, if unique, or the name with the suffix so that the name is unique\n */\nexport function getUniqueName(\n\tname: string,\n\totherNames: string[],\n\toptions?: UniqueNameOptions,\n): string {\n\tconst opts = {\n\t\tsuffix: (n: number) => `(${n})`,\n\t\tignoreFileExtension: false,\n\t\t...options,\n\t}\n\n\tlet newName = name\n\tlet i = 1\n\twhile (otherNames.includes(newName)) {\n\t\tconst ext = opts.ignoreFileExtension ? '' : extname(name)\n\t\tconst base = basename(name, ext)\n\t\tnewName = `${base} ${opts.suffix(i++)}${ext}`\n\t}\n\treturn newName\n}\n","/**\n * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport { getCanonicalLocale } from '@nextcloud/l10n'\n\nconst humanList = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']\nconst humanListBinary = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB']\n\n/**\n * Format a file size in a human-like format. e.g. 42GB\n *\n * The default for Nextcloud is like Windows using binary sizes but showing decimal units,\n * meaning 1024 bytes will show as 1KB instead of 1KiB or like on Apple 1.02 KB\n *\n * @param size in bytes\n * @param skipSmallSizes avoid rendering tiny sizes and return '< 1 KB' instead\n * @param binaryPrefixes True if size binary prefixes like `KiB` should be used as per IEC 80000-13\n * @param base1000 Set to true to use base 1000 as per SI or used by Apple (default is base 1024 like Linux or Windows)\n */\nexport function formatFileSize(size: number | string, skipSmallSizes = false, binaryPrefixes = false, base1000 = false): string {\n\t// Binary prefixes only work with base 1024\n\tbinaryPrefixes = binaryPrefixes && !base1000\n\n\tif (typeof size === 'string') {\n\t\tsize = Number(size)\n\t}\n\n\t// Calculate Log with base 1024 or 1000: size = base ** order\n\tlet order = size > 0 ? Math.floor(Math.log(size) / Math.log(base1000 ? 1000 : 1024)) : 0\n\n\t// Stay in range of the byte sizes that are defined\n\torder = Math.min((binaryPrefixes ? humanListBinary.length : humanList.length) - 1, order)\n\n\tconst readableFormat = binaryPrefixes ? humanListBinary[order] : humanList[order]\n\tlet relativeSize = (size / Math.pow(base1000 ? 1000 : 1024, order)).toFixed(1)\n\n\tif (skipSmallSizes === true && order === 0) {\n\t\treturn (relativeSize !== '0.0' ? '< 1 ' : '0 ') + (binaryPrefixes ? humanListBinary[1] : humanList[1])\n\t}\n\n\tif (order < 2) {\n\t\trelativeSize = parseFloat(relativeSize).toFixed(0)\n\t} else {\n\t\trelativeSize = parseFloat(relativeSize).toLocaleString(getCanonicalLocale())\n\t}\n\n\treturn relativeSize + ' ' + readableFormat\n}\n\n/**\n * Returns a file size in bytes from a humanly readable string\n * Note: `b` and `B` are both parsed as bytes and not as bit or byte.\n *\n * @param value file size in human-readable format\n * @param forceBinary for backwards compatibility this allows values to be base 2 (so 2KB means 2048 bytes instead of 2000 bytes)\n * @return or null if string could not be parsed\n */\nexport function parseFileSize(value: string, forceBinary = false) {\n\ttry {\n\t\tvalue = `${value}`.toLocaleLowerCase().replaceAll(/\\s+/g, '').replaceAll(',', '.')\n\t} catch {\n\t\treturn null\n\t}\n\n\tconst match = value.match(/^([0-9]*(\\.[0-9]*)?)([kmgtp]?)(i?)b?$/)\n\t// ignore not found, missing pre- and decimal, empty number\n\tif (match === null || match[1] === '.' || match[1] === '') {\n\t\treturn null\n\t}\n\n\tconst bytesArray = {\n\t\t'': 0,\n\t\tk: 1,\n\t\tm: 2,\n\t\tg: 3,\n\t\tt: 4,\n\t\tp: 5,\n\t\te: 6,\n\t}\n\n\tconst decimalString = `${match[1]}`\n\tconst base = (match[4] === 'i' || forceBinary) ? 1024 : 1000\n\n\treturn Math.round(Number.parseFloat(decimalString) * (base ** bytesArray[match[3]]))\n}\n","/**\n * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport { getCanonicalLocale, getLanguage } from '@nextcloud/l10n'\n\ntype IdentifierFn<T> = (v: T) => unknown\nexport type SortingOrder = 'asc' | 'desc'\n\n/**\n * Helper to create string representation\n *\n * @param value Value to stringify\n */\nfunction stringify(value: unknown) {\n\t// The default representation of Date is not sortable because of the weekday names in front of it\n\tif (value instanceof Date) {\n\t\treturn value.toISOString()\n\t}\n\treturn String(value)\n}\n\n/**\n * Natural order a collection\n * You can define identifiers as callback functions, that get the element and return the value to sort.\n *\n * @param collection The collection to order\n * @param identifiers An array of identifiers to use, by default the identity of the element is used\n * @param orders Array of orders, by default all identifiers are sorted ascening\n */\nexport function orderBy<T>(collection: readonly T[], identifiers?: IdentifierFn<T>[], orders?: SortingOrder[]): T[] {\n\t// If not identifiers are set we use the identity of the value\n\tidentifiers = identifiers ?? [(value) => value]\n\t// By default sort the collection ascending\n\torders = orders ?? []\n\tconst sorting = identifiers.map((_, index) => (orders[index] ?? 'asc') === 'asc' ? 1 : -1)\n\n\tconst collator = Intl.Collator(\n\t\t[getLanguage(), getCanonicalLocale()],\n\t\t{\n\t\t\t// handle 10 as ten and not as one-zero\n\t\t\tnumeric: true,\n\t\t\tusage: 'sort',\n\t\t},\n\t)\n\n\treturn [...collection].sort((a, b) => {\n\t\tfor (const [index, identifier] of identifiers.entries()) {\n\t\t\t// Get the local compare of stringified value a and b\n\t\t\tconst value = collator.compare(stringify(identifier(a)), stringify(identifier(b)))\n\t\t\t// If they do not match return the order\n\t\t\tif (value !== 0) {\n\t\t\t\treturn value * sorting[index]\n\t\t\t}\n\t\t\t// If they match we need to continue with the next identifier\n\t\t}\n\t\t// If all are equal we need to return equality\n\t\treturn 0\n\t})\n}\n","/*\n * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { INode } from '../node/node.ts'\nimport type { SortingOrder } from './sorting.ts'\n\nimport { FileType } from '../node/fileType.ts'\nimport { orderBy } from './sorting.ts'\n\nexport const FilesSortingMode = Object.freeze({\n\tName: 'basename',\n\tModified: 'mtime',\n\tSize: 'size',\n})\n\nexport type TFilesSortingMode = typeof FilesSortingMode[keyof typeof FilesSortingMode]\n\nexport interface FilesSortingOptions {\n\t/**\n\t * They key to order the files by\n\t *\n\t * @default FilesSortingMode.Name\n\t */\n\tsortingMode?: TFilesSortingMode | string\n\n\t/**\n\t * @default 'asc'\n\t */\n\tsortingOrder?: SortingOrder\n\n\t/**\n\t * If set to true nodes marked as favorites are ordered on top of all other nodes\n\t *\n\t * @default false\n\t */\n\tsortFavoritesFirst?: boolean\n\n\t/**\n\t * If set to true folders are ordered on top of files\n\t *\n\t * @default false\n\t */\n\tsortFoldersFirst?: boolean\n}\n\n/**\n * Sort files and folders according to the sorting options\n *\n * @param nodes Nodes to sort\n * @param options Sorting options\n */\nexport function sortNodes(nodes: readonly INode[], options: FilesSortingOptions = {}): INode[] {\n\tconst sortingOptions = {\n\t\t// Default to sort by name\n\t\tsortingMode: FilesSortingMode.Name,\n\t\t// Default to sort ascending\n\t\tsortingOrder: 'asc' as const,\n\t\t...options,\n\t}\n\n\t/**\n\t * Get the basename without any extension if the current node is a file\n\t *\n\t * @param node - The node to get the basename of\n\t */\n\tfunction basename(node: INode): string {\n\t\tconst name = node.displayname || node.attributes?.displayname || node.basename || ''\n\t\tif (node.type === FileType.Folder) {\n\t\t\treturn name\n\t\t}\n\n\t\treturn name.lastIndexOf('.') > 0\n\t\t\t? name.slice(0, name.lastIndexOf('.'))\n\t\t\t: name\n\t}\n\n\tconst identifiers = [\n\t\t// 1: Sort favorites first if enabled\n\t\t...(sortingOptions.sortFavoritesFirst ? [(v: INode) => v.attributes?.favorite !== 1] : []),\n\t\t// 2: Sort folders first if sorting by name\n\t\t...(sortingOptions.sortFoldersFirst ? [(v: INode) => v.type !== 'folder'] : []),\n\t\t// 3: Use sorting mode if NOT basename (to be able to use display name too)\n\t\t...(sortingOptions.sortingMode !== FilesSortingMode.Name ? [(v: INode) => v[sortingOptions.sortingMode] ?? v.attributes[sortingOptions.sortingMode]] : []),\n\t\t// 4: Use display name if available, fallback to name\n\t\t(v: INode) => basename(v),\n\t\t// 5: Finally, use basename if all previous sorting methods failed\n\t\t(v: INode) => v.basename,\n\t]\n\tconst orders = [\n\t\t// (for 1): always sort favorites before normal files\n\t\t...(sortingOptions.sortFavoritesFirst ? ['asc'] : []),\n\t\t// (for 2): always sort folders before files\n\t\t...(sortingOptions.sortFoldersFirst ? ['asc'] : []),\n\t\t// (for 3): Reverse if sorting by mtime as mtime higher means edited more recent -> lower\n\t\t...(sortingOptions.sortingMode === FilesSortingMode.Modified ? [sortingOptions.sortingOrder === 'asc' ? 'desc' : 'asc'] : []),\n\t\t// (also for 3 so make sure not to conflict with 2 and 3)\n\t\t...(sortingOptions.sortingMode !== FilesSortingMode.Modified && sortingOptions.sortingMode !== FilesSortingMode.Name ? [sortingOptions.sortingOrder] : []),\n\t\t// for 4: use configured sorting direction\n\t\tsortingOptions.sortingOrder,\n\t\t// for 5: use configured sorting direction\n\t\tsortingOptions.sortingOrder,\n\t] as ('asc' | 'desc')[]\n\n\treturn orderBy(nodes, identifiers, orders)\n}\n"],"names":["validateAction","a","b","basename"],"mappings":"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"}
|
package/dist/registry.d.ts
CHANGED
|
@@ -3,23 +3,50 @@ import { IFileListFilter } from './filters/index.ts';
|
|
|
3
3
|
import { IFileListHeader } from './headers/index.ts';
|
|
4
4
|
import { TypedEventTarget } from 'typescript-event-target';
|
|
5
5
|
interface FilesRegistryEvents {
|
|
6
|
-
'register:action':
|
|
7
|
-
'register:listAction':
|
|
8
|
-
'register:listFilter':
|
|
9
|
-
'
|
|
10
|
-
'
|
|
6
|
+
'register:action': RegistrationEvent<IFileAction>;
|
|
7
|
+
'register:listAction': RegistrationEvent<IFileListAction>;
|
|
8
|
+
'register:listFilter': RegistrationEvent<IFileListFilter>;
|
|
9
|
+
'register:listHeader': RegistrationEvent<IFileListHeader>;
|
|
10
|
+
'unregister:listFilter': UnregisterEvent;
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Custom event for registry events.
|
|
14
|
+
* The detail is the registered item.
|
|
15
|
+
*/
|
|
16
|
+
declare class RegistrationEvent<T> extends CustomEvent<T> {
|
|
13
17
|
}
|
|
14
|
-
export type PublicFilesRegistry = Pick<FilesRegistryV4, 'addEventListener' | 'removeEventListener'>;
|
|
15
18
|
/**
|
|
16
|
-
*
|
|
19
|
+
* Custom event for unregistering items from the registry.
|
|
20
|
+
* The detail is the id of the unregistered item.
|
|
21
|
+
*/
|
|
22
|
+
declare class UnregisterEvent extends RegistrationEvent<string> {
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The registry for files app.
|
|
26
|
+
* This is used to keep track of registered actions, filters, headers, etc. and to emit events when new items are registered.
|
|
27
|
+
* Allowing to keep a reactive state of the registered items in the UI without being coupled to one specific reactivity framework.
|
|
28
|
+
*
|
|
29
|
+
* This is an internal implementation detail and should not be used directly.
|
|
30
|
+
*
|
|
31
|
+
* @internal
|
|
32
|
+
* @see PublicFilesRegistry - for the public API to listen to registry events.
|
|
33
|
+
*/
|
|
34
|
+
export declare class FilesRegistry extends TypedEventTarget<FilesRegistryEvents> {
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The registry for files app.
|
|
38
|
+
* This is used to keep track of registered actions, filters, headers, etc. and to emit events when new items are registered.
|
|
39
|
+
* Allowing to keep a reactive state of the registered items in the UI without being coupled to one specific reactivity framework.
|
|
40
|
+
*/
|
|
41
|
+
export type PublicFilesRegistry = Pick<FilesRegistry, 'addEventListener' | 'removeEventListener'>;
|
|
42
|
+
/**
|
|
43
|
+
* Get the global files registry.
|
|
17
44
|
*
|
|
18
45
|
* @internal
|
|
19
46
|
*/
|
|
20
|
-
export declare function getRegistry():
|
|
47
|
+
export declare function getRegistry(): FilesRegistry;
|
|
21
48
|
/**
|
|
22
|
-
* Get the global files registry
|
|
49
|
+
* Get the global files registry.
|
|
23
50
|
*
|
|
24
51
|
* This allows to listen for new registrations of actions, filters, headers, etc.
|
|
25
52
|
* Events are dispatched by the respective registration functions.
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"folder-C9vrC6sc.mjs","sources":["../../lib/utils/logger.ts","../../lib/node/fileType.ts","../../lib/permissions.ts","../../lib/node/nodeData.ts","../../lib/node/node.ts","../../lib/node/file.ts","../../lib/node/folder.ts"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\nimport { getLoggerBuilder } from '@nextcloud/logger'\n\nexport default getLoggerBuilder()\n\t.setApp('@nextcloud/files')\n\t.detectUser()\n\t.build()\n","/**\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nexport const FileType = Object.freeze({\n\tFolder: 'folder',\n\tFile: 'file',\n})\n\nexport type IFileType = typeof FileType[keyof typeof FileType]\n","/**\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\n/**\n * Node permissions\n */\nexport const Permission = Object.freeze({\n\t/**\n\t * No permissions granted\n\t */\n\tNONE: 0,\n\t/**\n\t * Can read the file content\n\t */\n\tREAD: 1,\n\t/**\n\t * Can modify the file itself (move, rename, etc)\n\t */\n\tUPDATE: 2,\n\t/**\n\t * Can create new files/folders inside a folder\n\t */\n\tCREATE: 4,\n\t/**\n\t * Can change the file content\n\t */\n\tWRITE: 4,\n\t/**\n\t * Can delete the node\n\t */\n\tDELETE: 8,\n\t/**\n\t * Can share the node\n\t */\n\tSHARE: 16,\n\t/**\n\t * All permissions are granted\n\t */\n\tALL: 31,\n})\n","/**\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport { join } from '@nextcloud/paths'\nimport { Permission } from '../permissions.ts'\n\nexport const NodeStatus = Object.freeze({\n\t/** This is a new node and it doesn't exists on the filesystem yet */\n\tNEW: 'new',\n\t/** This node has failed and is unavailable */\n\tFAILED: 'failed',\n\t/** This node is currently loading or have an operation in progress */\n\tLOADING: 'loading',\n\t/** This node is locked and cannot be modified */\n\tLOCKED: 'locked',\n})\n\nexport type INodeStatus = typeof NodeStatus[keyof typeof NodeStatus]\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport interface Attribute { [key: string]: any }\n\nexport interface NodeData {\n\t/**\n\t * URL to the resource.\n\t * e.g. https://cloud.domain.com/remote.php/dav/files/emma/Photos/picture.jpg\n\t * or https://domain.com/Photos/picture.jpg\n\t */\n\tsource: string\n\n\t/**\n\t * The absolute root of the home relative to the service.\n\t * It is highly recommended to provide that information.\n\t * e.g. /files/emma\n\t */\n\troot: string\n\n\t/** Unique ID */\n\tid?: number\n\n\t/** Last modified time */\n\tmtime?: Date\n\n\t/** Creation time */\n\tcrtime?: Date\n\n\t/** The mime type Optional for folders only */\n\tmime?: string\n\n\t/** The node size type */\n\tsize?: number\n\n\t/**\n\t * The node permissions.\n\t *\n\t * A binary mask of `Permission` values.\n\t *\n\t * @see Permission\n\t */\n\tpermissions?: number\n\n\t/** The owner UID of this node */\n\towner: string | null\n\n\t/** Optional the displayname of this node */\n\tdisplayname?: string\n\n\t/** The node attributes */\n\tattributes?: Attribute\n\n\t/** The node status */\n\tstatus?: INodeStatus\n}\n\n/**\n * Check if a node source is from a specific DAV service\n *\n * @param source The source to check\n * @param davService Pattern to check if source is DAV resource\n */\nexport function isDavResource(source: string, davService: RegExp): boolean {\n\treturn source.match(davService) !== null\n}\n\n/**\n * Validate Node construct data\n *\n * @param data The node data\n * @param davService Pattern to check if source is DAV ressource\n */\nexport function validateData(data: NodeData, davService: RegExp) {\n\tif (data.id && typeof data.id !== 'number') {\n\t\tthrow new Error('Invalid id type of value')\n\t}\n\n\tif (!data.source) {\n\t\tthrow new Error('Missing mandatory source')\n\t}\n\n\ttry {\n\t\tnew URL(data.source)\n\t} catch {\n\t\tthrow new Error('Invalid source format, source must be a valid URL')\n\t}\n\n\tif (!data.source.startsWith('http')) {\n\t\tthrow new Error('Invalid source format, only http(s) is supported')\n\t}\n\n\tif (!data.root) {\n\t\tthrow new Error('Missing mandatory root')\n\t}\n\n\tif (typeof data.root !== 'string') {\n\t\tthrow new Error('Invalid root type')\n\t}\n\n\tif (!data.root.startsWith('/')) {\n\t\tthrow new Error('Root must start with a leading slash')\n\t}\n\n\tif (!data.source.includes(data.root)) {\n\t\tthrow new Error('Root must be part of the source')\n\t}\n\n\tif (isDavResource(data.source, davService)) {\n\t\tconst service = data.source.match(davService)![0]\n\t\tif (!data.source.includes(join(service, data.root))) {\n\t\t\tthrow new Error('The root must be relative to the service. e.g /files/emma')\n\t\t}\n\t}\n\n\tif (data.displayname && typeof data.displayname !== 'string') {\n\t\tthrow new Error('Invalid displayname type')\n\t}\n\n\tif (data.mtime && !(data.mtime instanceof Date)) {\n\t\tthrow new Error('Invalid mtime type')\n\t}\n\n\tif (data.crtime && !(data.crtime instanceof Date)) {\n\t\tthrow new Error('Invalid crtime type')\n\t}\n\n\tif (!data.mime || typeof data.mime !== 'string'\n\t\t|| !data.mime.match(/^[-\\w.]+\\/[-+\\w.]+$/gi)) {\n\t\tthrow new Error('Missing or invalid mandatory mime')\n\t}\n\n\t// Allow size to be 0\n\tif ('size' in data && typeof data.size !== 'number' && data.size !== undefined) {\n\t\tthrow new Error('Invalid size type')\n\t}\n\n\t// Allow permissions to be 0\n\tif ('permissions' in data\n\t\t&& data.permissions !== undefined\n\t\t&& !(typeof data.permissions === 'number'\n\t\t\t&& data.permissions >= Permission.NONE\n\t\t\t&& data.permissions <= Permission.ALL\n\t\t)) {\n\t\tthrow new Error('Invalid permissions')\n\t}\n\n\tif (data.owner\n\t\t&& data.owner !== null\n\t\t&& typeof data.owner !== 'string') {\n\t\tthrow new Error('Invalid owner type')\n\t}\n\n\tif (data.attributes && typeof data.attributes !== 'object') {\n\t\tthrow new Error('Invalid attributes type')\n\t}\n\n\tif (data.status && !Object.values(NodeStatus).includes(data.status)) {\n\t\tthrow new Error('Status must be a valid NodeStatus')\n\t}\n}\n\n/**\n * In case we try to create a node from deserialized data,\n * we need to fix date types.\n *\n * @param data The internal node data\n */\nexport function fixDates(data: NodeData) {\n\tif (data.mtime && typeof data.mtime === 'string') {\n\t\tif (!isNaN(Date.parse(data.mtime))\n\t\t\t&& JSON.stringify(new Date(data.mtime)) === JSON.stringify(data.mtime)) {\n\t\t\tdata.mtime = new Date(data.mtime)\n\t\t}\n\t}\n\n\tif (data.crtime && typeof data.crtime === 'string') {\n\t\tif (!isNaN(Date.parse(data.crtime))\n\t\t\t&& JSON.stringify(new Date(data.crtime)) === JSON.stringify(data.crtime)) {\n\t\t\tdata.crtime = new Date(data.crtime)\n\t\t}\n\t}\n}\n\n/**\n * Fix a RegExp pattern from string or RegExp to RegExp\n *\n * @param pattern The pattern as string or RegExp\n */\nexport function fixRegExp(pattern: string | RegExp): RegExp {\n\tif (pattern instanceof RegExp) {\n\t\treturn pattern\n\t}\n\n\t// Extract the pattern and flags if it's a string\n\t// Pulled from https://www.npmjs.com/package/regex-parser\n\tconst matches = pattern.match(/(\\/?)(.+)\\1([a-z]*)/i)\n\n\t// If there's no match, throw an error\n\tif (!matches) {\n\t\tthrow new Error('Invalid regular expression format.')\n\t}\n\n\t// Filter valid flags: 'g', 'i', 'm', 's', 'u', and 'y'\n\tconst validFlags = Array.from(new Set(matches[3]))\n\t\t.filter((flag) => 'gimsuy'.includes(flag))\n\t\t.join('')\n\n\t// Create the regular expression\n\treturn new RegExp(matches[2], validFlags)\n}\n","/**\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { IFileType } from './fileType.ts'\nimport type { Attribute, INodeStatus, NodeData } from './nodeData.ts'\n\nimport { basename, dirname, encodePath, extname } from '@nextcloud/paths'\nimport { Permission } from '../permissions.ts'\nimport { fixDates, fixRegExp, isDavResource, validateData } from './nodeData.ts'\n\nexport type NodeConstructorData = [NodeData, RegExp?]\n\nexport abstract class Node {\n\tprivate _attributes: Attribute\n\n\tprotected _data: NodeData\n\tprotected _knownDavService = /(remote|public)\\.php\\/(web)?dav/i\n\n\tprivate readonlyAttributes = Object.entries(Object.getOwnPropertyDescriptors(Node.prototype))\n\t\t.filter((e) => typeof e[1].get === 'function' && e[0] !== '__proto__')\n\t\t.map((e) => e[0])\n\n\tprivate handler = {\n\t\tset: (target: Attribute, prop: string, value: unknown): boolean => {\n\t\t\tif (this.readonlyAttributes.includes(prop)) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Apply original changes\n\t\t\treturn Reflect.set(target, prop, value)\n\t\t},\n\t\tdeleteProperty: (target: Attribute, prop: string): boolean => {\n\t\t\tif (this.readonlyAttributes.includes(prop)) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Apply original changes\n\t\t\treturn Reflect.deleteProperty(target, prop)\n\t\t},\n\t} as ProxyHandler<Attribute>\n\n\tprotected constructor(...[data, davService]: NodeConstructorData) {\n\t\tif (!data.mime) {\n\t\t\tdata.mime = 'application/octet-stream'\n\t\t}\n\n\t\t// Fix primitive types if needed\n\t\tfixDates(data)\n\t\tdavService = fixRegExp(davService || this._knownDavService)\n\n\t\t// Validate data\n\t\tvalidateData(data, davService)\n\n\t\tthis._data = {\n\t\t\t...data,\n\t\t\tattributes: {},\n\t\t}\n\n\t\t// Proxy the attributes to update the mtime on change\n\t\tthis._attributes = new Proxy(this._data.attributes!, this.handler)\n\n\t\t// Update attributes, this sanitizes the attributes to only contain valid attributes\n\t\tthis.update(data.attributes ?? {})\n\n\t\tif (davService) {\n\t\t\tthis._knownDavService = davService\n\t\t}\n\t}\n\n\t/**\n\t * Get the source url to this object\n\t * There is no setter as the source is not meant to be changed manually.\n\t * You can use the rename or move method to change the source.\n\t */\n\tget source(): string {\n\t\t// strip any ending slash\n\t\treturn this._data.source.replace(/\\/$/i, '')\n\t}\n\n\t/**\n\t * Get the encoded source url to this object for requests purposes\n\t */\n\tget encodedSource(): string {\n\t\tconst { origin } = new URL(this.source)\n\t\treturn origin + encodePath(this.source.slice(origin.length))\n\t}\n\n\t/**\n\t * Get this object name\n\t * There is no setter as the source is not meant to be changed manually.\n\t * You can use the rename or move method to change the source.\n\t */\n\tget basename(): string {\n\t\treturn basename(this.source)\n\t}\n\n\t/**\n\t * The nodes displayname\n\t * By default the display name and the `basename` are identical,\n\t * but it is possible to have a different name. This happens\n\t * on the files app for example for shared folders.\n\t */\n\tget displayname(): string {\n\t\treturn this._data.displayname || this.basename\n\t}\n\n\t/**\n\t * Set the displayname\n\t */\n\tset displayname(displayname: string) {\n\t\tvalidateData({ ...this._data, displayname }, this._knownDavService)\n\t\tthis._data.displayname = displayname\n\t}\n\n\t/**\n\t * Get this object's extension\n\t * There is no setter as the source is not meant to be changed manually.\n\t * You can use the rename or move method to change the source.\n\t */\n\tget extension(): string | null {\n\t\treturn extname(this.source)\n\t}\n\n\t/**\n\t * Get the directory path leading to this object\n\t * Will use the relative path to root if available\n\t *\n\t * There is no setter as the source is not meant to be changed manually.\n\t * You can use the rename or move method to change the source.\n\t */\n\tget dirname(): string {\n\t\treturn dirname(this.path)\n\t}\n\n\t/**\n\t * Is it a file or a folder ?\n\t */\n\tabstract get type(): IFileType\n\n\t/**\n\t * Get the file mime\n\t */\n\tget mime(): string {\n\t\treturn this._data.mime || 'application/octet-stream'\n\t}\n\n\t/**\n\t * Set the file mime\n\t * Removing the mime type will set it to `application/octet-stream`\n\t */\n\tset mime(mime: string | undefined) {\n\t\tmime ??= 'application/octet-stream'\n\n\t\tvalidateData({ ...this._data, mime }, this._knownDavService)\n\t\tthis._data.mime = mime\n\t}\n\n\t/**\n\t * Get the file modification time\n\t */\n\tget mtime(): Date | undefined {\n\t\treturn this._data.mtime\n\t}\n\n\t/**\n\t * Set the file modification time\n\t */\n\tset mtime(mtime: Date | undefined) {\n\t\tvalidateData({ ...this._data, mtime }, this._knownDavService)\n\t\tthis._data.mtime = mtime\n\t}\n\n\t/**\n\t * Get the file creation time\n\t * There is no setter as the creation time is not meant to be changed\n\t */\n\tget crtime(): Date | undefined {\n\t\treturn this._data.crtime\n\t}\n\n\t/**\n\t * Get the file size\n\t */\n\tget size(): number | undefined {\n\t\treturn this._data.size\n\t}\n\n\t/**\n\t * Set the file size\n\t */\n\tset size(size: number | undefined) {\n\t\tvalidateData({ ...this._data, size }, this._knownDavService)\n\t\tthis.updateMtime()\n\t\tthis._data.size = size\n\t}\n\n\t/**\n\t * Get the file attribute\n\t * This contains all additional attributes not provided by the Node class\n\t */\n\tget attributes(): Attribute {\n\t\treturn this._attributes\n\t}\n\n\t/**\n\t * Get the file permissions\n\t */\n\tget permissions(): number {\n\t\t// If this is not a dav resource, we can only read it\n\t\tif (this.owner === null && !this.isDavResource) {\n\t\t\treturn Permission.READ\n\t\t}\n\n\t\t// If the permissions are not defined, we have none\n\t\treturn this._data.permissions !== undefined\n\t\t\t? this._data.permissions\n\t\t\t: Permission.NONE\n\t}\n\n\t/**\n\t * Set the file permissions\n\t */\n\tset permissions(permissions: number) {\n\t\tvalidateData({ ...this._data, permissions }, this._knownDavService)\n\t\tthis.updateMtime()\n\t\tthis._data.permissions = permissions\n\t}\n\n\t/**\n\t * Get the file owner\n\t * There is no setter as the owner is not meant to be changed\n\t */\n\tget owner(): string | null {\n\t\t// Remote resources have no owner\n\t\tif (!this.isDavResource) {\n\t\t\treturn null\n\t\t}\n\t\treturn this._data.owner\n\t}\n\n\t/**\n\t * Is this a dav-related resource ?\n\t */\n\tget isDavResource(): boolean {\n\t\treturn isDavResource(this.source, this._knownDavService)\n\t}\n\n\t/**\n\t * Get the dav root of this object\n\t * There is no setter as the root is not meant to be changed\n\t */\n\tget root(): string {\n\t\treturn this._data.root.replace(/^(.+)\\/$/, '$1')\n\t}\n\n\t/**\n\t * Get the absolute path of this object relative to the root\n\t */\n\tget path(): string {\n\t\t// Extract the path part from the source URL\n\t\t// e.g. https://cloud.domain.com/remote.php/dav/files/username/Path/To/File.txt\n\t\tconst idx = this.source.indexOf('://')\n\t\tconst protocol = this.source.slice(0, idx) // e.g. https\n\t\tconst remainder = this.source.slice(idx + 3) // e.g. cloud.domain.com/remote.php/dav/files/username/Path/To/File.txt\n\n\t\tconst slashIndex = remainder.indexOf('/')\n\t\tconst host = remainder.slice(0, slashIndex) // e.g. cloud.domain.com\n\t\tconst rawPath = remainder.slice(slashIndex) // e.g. /remote.php/dav/files/username/Path/To/File.txt\n\n\t\t// Rebuild a safe URL with encoded path\n\t\tconst safeUrl = `${protocol}://${host}${encodePath(rawPath)}`\n\t\tconst url = new URL(safeUrl)\n\n\t\tlet source = decodeURIComponent(url.pathname)\n\n\t\tif (this.isDavResource) {\n\t\t\t// ensure we only work on the real path in case root is not distinct\n\t\t\tsource = source.split(this._knownDavService).pop()!\n\t\t}\n\t\t// Using replace would remove all part matching root\n\t\tconst firstMatch = source.indexOf(this.root)\n\t\t// Ensure we do not remove the leading slash\n\t\tconst root = this.root.replace(/\\/$/, '')\n\t\treturn source.slice(firstMatch + root.length) || '/'\n\t}\n\n\t/**\n\t * Get the node id if defined.\n\t * There is no setter as the fileid is not meant to be changed\n\t */\n\tget fileid(): number | undefined {\n\t\treturn this._data?.id\n\t}\n\n\t/**\n\t * Get the node status.\n\t */\n\tget status(): INodeStatus | undefined {\n\t\treturn this._data?.status\n\t}\n\n\t/**\n\t * Set the node status.\n\t */\n\tset status(status: INodeStatus | undefined) {\n\t\tvalidateData({ ...this._data, status }, this._knownDavService)\n\t\tthis._data.status = status\n\t}\n\n\t/**\n\t * Move the node to a new destination\n\t *\n\t * @param destination the new source.\n\t * e.g. https://cloud.domain.com/remote.php/dav/files/emma/Photos/picture.jpg\n\t */\n\tmove(destination: string) {\n\t\tvalidateData({ ...this._data, source: destination }, this._knownDavService)\n\t\tconst oldBasename = this.basename\n\n\t\tthis._data.source = destination\n\t\t// Check if the displayname and the (old) basename were the same\n\t\t// meaning no special displayname was set but just a fallback to the basename by Nextcloud's WebDAV server\n\t\tif (this.displayname === oldBasename\n\t\t\t&& this.basename !== oldBasename) {\n\t\t\t// We have to assume that the displayname was not set but just a copy of the basename\n\t\t\t// this can not be guaranteed, so to be sure users should better refetch the node\n\t\t\tthis.displayname = this.basename\n\t\t}\n\t}\n\n\t/**\n\t * Rename the node\n\t * This aliases the move method for easier usage\n\t *\n\t * @param basename The new name of the node\n\t */\n\trename(basename: string) {\n\t\tif (basename.includes('/')) {\n\t\t\tthrow new Error('Invalid basename')\n\t\t}\n\t\tthis.move(dirname(this.source) + '/' + basename)\n\t}\n\n\t/**\n\t * Update the mtime if exists\n\t */\n\tupdateMtime() {\n\t\tif (this._data.mtime) {\n\t\t\tthis._data.mtime = new Date()\n\t\t}\n\t}\n\n\t/**\n\t * Update the attributes of the node\n\t * Warning, updating attributes will NOT automatically update the mtime.\n\t *\n\t * @param attributes The new attributes to update on the Node attributes\n\t */\n\tupdate(attributes: Attribute) {\n\t\tfor (const [name, value] of Object.entries(attributes)) {\n\t\t\ttry {\n\t\t\t\tif (value === undefined) {\n\t\t\t\t\tdelete this.attributes[name]\n\t\t\t\t} else {\n\t\t\t\t\tthis.attributes[name] = value\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\t// Ignore readonly attributes\n\t\t\t\tif (e instanceof TypeError) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\t// Throw all other exceptions\n\t\t\t\tthrow e\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Returns a clone of the node\n\t */\n\tclone(): this {\n\t\t// @ts-expect-error -- this class is abstract and cannot be instantiated directly but all its children can\n\t\treturn new this.constructor(structuredClone(this._data), this._knownDavService)\n\t}\n\n\t/**\n\t * JSON representation of the node\n\t */\n\ttoJSON(): string {\n\t\treturn JSON.stringify([structuredClone(this._data), this._knownDavService.toString()])\n\t}\n}\n\n/**\n * Interface of the node class\n */\nexport type INode = Pick<Node, keyof Node>\n","/**\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { NodeConstructorData } from './node.ts'\n\nimport { FileType } from './fileType.ts'\nimport { Node } from './node.ts'\n\nexport class File extends Node {\n\tpublic constructor(...[data, davService]: NodeConstructorData) {\n\t\tsuper(data, davService)\n\t}\n\n\tget type(): typeof FileType.File {\n\t\treturn FileType.File\n\t}\n}\n\n/**\n * Interface of the File class\n */\nexport type IFile = Pick<File, keyof File>\n","/**\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { NodeConstructorData } from './node.ts'\n\nimport { FileType } from './fileType.ts'\nimport { Node } from './node.ts'\n\nexport class Folder extends Node {\n\tconstructor(...[data, davService]: NodeConstructorData) {\n\t\t// enforcing mimes\n\t\tsuper({\n\t\t\t...data,\n\t\t\tmime: 'httpd/unix-directory',\n\t\t}, davService)\n\t}\n\n\tget type(): typeof FileType.Folder {\n\t\treturn FileType.Folder\n\t}\n\n\tget extension(): null {\n\t\treturn null\n\t}\n\n\tget mime(): 'httpd/unix-directory' {\n\t\treturn 'httpd/unix-directory'\n\t}\n}\n\n/**\n * Interface of the folder class\n */\nexport type IFolder = Pick<Folder, keyof Folder>\n"],"names":["basename"],"mappings":"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"}
|