@nextcloud/files 4.0.0-beta.8 → 4.0.0-beta.9
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/actions/fileAction.d.ts +29 -14
- package/dist/actions/fileListAction.d.ts +12 -3
- package/dist/actions/index.d.ts +2 -2
- package/dist/chunks/{folder-Bf-tAYWu.mjs → folder-CeyZUHai.mjs} +57 -31
- package/dist/chunks/folder-CeyZUHai.mjs.map +1 -0
- package/dist/dav/dav.d.ts +2 -2
- package/dist/dav/davPermissions.d.ts +6 -2
- package/dist/dav/davProperties.d.ts +10 -6
- package/dist/dav/index.d.ts +4 -3
- package/dist/dav.mjs +31 -24
- package/dist/dav.mjs.map +1 -1
- package/dist/fileListFilters.d.ts +36 -8
- package/dist/fileListHeaders.d.ts +11 -3
- package/dist/index.mjs +56 -59
- package/dist/index.mjs.map +1 -1
- package/dist/navigation/index.d.ts +3 -3
- package/dist/navigation/navigation.d.ts +5 -2
- package/dist/navigation/view.d.ts +2 -3
- package/dist/newMenu/NewMenu.d.ts +12 -7
- package/dist/node/file.d.ts +3 -3
- package/dist/node/fileType.d.ts +5 -4
- package/dist/node/folder.d.ts +3 -3
- package/dist/node/node.d.ts +15 -15
- package/dist/node/nodeData.d.ts +13 -8
- package/dist/permissions.d.ts +34 -9
- package/dist/sidebar/SidebarAction.d.ts +1 -1
- package/dist/sidebar/SidebarTab.d.ts +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/utils/fileSize.d.ts +3 -3
- package/dist/utils/fileSorting.d.ts +11 -6
- package/dist/utils/filename-validation.d.ts +10 -7
- package/dist/utils/filename.d.ts +3 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/sorting.d.ts +4 -0
- package/package.json +5 -5
- package/dist/chunks/folder-Bf-tAYWu.mjs.map +0 -1
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 */\nimport { Permission } from '../permissions'\n\n/**\n * Parse the WebDAV permission string to a permission enum\n *\n * @param permString The DAV permission string\n */\nexport const parsePermissions = function(permString = ''): number {\n\tlet permissions = Permission.NONE\n\n\tif (!permString) { return permissions }\n\n\tif (permString.includes('C') || permString.includes('K')) { permissions |= Permission.CREATE }\n\n\tif (permString.includes('G')) { permissions |= Permission.READ }\n\n\tif (permString.includes('W') || permString.includes('N') || permString.includes('V')) { permissions |= Permission.UPDATE }\n\n\tif (permString.includes('D')) { permissions |= Permission.DELETE }\n\n\tif (permString.includes('R')) { permissions |= Permission.SHARE }\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 */\nimport { getCurrentUser } from '@nextcloud/auth'\nimport logger from '../utils/logger'\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 const registerDavProperty = function(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 const getDavProperties = function(): 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 const getDavNameSpaces = function(): 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 const getDefaultPropfind = function(): 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 const getFavoritesReport = function(): 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 const getRecentSearch = function(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 { getSharingToken, isPublicShare } from '@nextcloud/sharing/public'\nimport { generateRemoteUrl } from '@nextcloud/router'\nimport { createClient, getPatcher } from 'webdav'\nimport { parsePermissions } from './davPermissions.ts'\nimport { getFavoritesReport } from './davProperties.ts'\nimport { File, Folder, NodeStatus } from '../node/index.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 const getClient = function(remoteURL = defaultRemoteURL, headers: Record<string, string> = {}) {\n\tconst client = createClient(remoteURL, { headers })\n\n\t/**\n\t * Set headers for DAV requests\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 const resultToNode = function(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 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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"}
|
|
@@ -39,7 +39,7 @@ interface IFileListFilterEvents {
|
|
|
39
39
|
'update:filter': FilterUpdateEvent;
|
|
40
40
|
'update:chips': FilterUpdateChipsEvent;
|
|
41
41
|
}
|
|
42
|
-
export interface
|
|
42
|
+
export interface IFileListFilterBase extends TypedEventTarget<IFileListFilterEvents> {
|
|
43
43
|
/**
|
|
44
44
|
* Unique ID of this filter
|
|
45
45
|
*/
|
|
@@ -57,21 +57,48 @@ export interface IFileListFilter extends TypedEventTarget<IFileListFilterEvents>
|
|
|
57
57
|
* @return Subset of the `nodes` parameter to show
|
|
58
58
|
*/
|
|
59
59
|
filter(nodes: INode[]): INode[];
|
|
60
|
-
/**
|
|
61
|
-
* If the filter needs a visual element for settings it can provide a function to mount it.
|
|
62
|
-
* @param el The DOM element to mount to
|
|
63
|
-
*/
|
|
64
|
-
mount?(el: HTMLElement): void;
|
|
65
60
|
/**
|
|
66
61
|
* Reset the filter to the initial state.
|
|
67
|
-
*
|
|
62
|
+
*
|
|
68
63
|
* Implementations should make sure,that if they provide chips they need to emit the `update:chips` event.
|
|
64
|
+
* This is called by the files app.
|
|
69
65
|
*
|
|
70
66
|
* @since 3.10.0
|
|
71
67
|
*/
|
|
72
68
|
reset?(): void;
|
|
73
69
|
}
|
|
74
|
-
|
|
70
|
+
/**
|
|
71
|
+
* File list filter interface with UI component
|
|
72
|
+
*
|
|
73
|
+
* Filters with UI must provide additional information for rendering the filter component.
|
|
74
|
+
* The ui will be rendered depending on the view port - either as inline actions or in a dropdown menu.
|
|
75
|
+
*/
|
|
76
|
+
export interface IFileListFilterWithUi extends IFileListFilterBase {
|
|
77
|
+
/**
|
|
78
|
+
* Display name of the filter
|
|
79
|
+
*/
|
|
80
|
+
readonly displayName: string;
|
|
81
|
+
/**
|
|
82
|
+
* Inline SVG icon as string for the filter
|
|
83
|
+
*/
|
|
84
|
+
readonly iconSvgInline: string;
|
|
85
|
+
/**
|
|
86
|
+
* Tag name of the web component to use as filter UI
|
|
87
|
+
*
|
|
88
|
+
* The web component must extend `HTMLElement` and accept a property `filter` of type `IFileListFilterWithUi`
|
|
89
|
+
* (it will receive the filter instance itself).
|
|
90
|
+
*
|
|
91
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements
|
|
92
|
+
*/
|
|
93
|
+
readonly tagName: string;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* File list filter interface
|
|
97
|
+
*
|
|
98
|
+
* Filters can either be simple filters without UI or filters with a UI component.
|
|
99
|
+
*/
|
|
100
|
+
export type IFileListFilter = IFileListFilterBase | IFileListFilterWithUi;
|
|
101
|
+
export declare class FileListFilter extends TypedEventTarget<IFileListFilterEvents> implements IFileListFilterBase {
|
|
75
102
|
id: string;
|
|
76
103
|
order: number;
|
|
77
104
|
constructor(id: string, order?: number);
|
|
@@ -90,6 +117,7 @@ export declare class FileListFilter extends TypedEventTarget<IFileListFilterEven
|
|
|
90
117
|
export declare function registerFileListFilter(filter: IFileListFilter): void;
|
|
91
118
|
/**
|
|
92
119
|
* Remove a registered filter from the file list
|
|
120
|
+
*
|
|
93
121
|
* @param filterId The unique ID of the filter to remove
|
|
94
122
|
*/
|
|
95
123
|
export declare function unregisterFileListFilter(filterId: string): void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { IFolder } from './node/folder.ts';
|
|
2
1
|
import { IView } from './navigation/view.ts';
|
|
2
|
+
import { IFolder } from './node/folder.ts';
|
|
3
3
|
export interface HeaderData {
|
|
4
4
|
/** Unique ID */
|
|
5
5
|
id: string;
|
|
@@ -22,5 +22,13 @@ export declare class Header {
|
|
|
22
22
|
get updated(): (folder: IFolder, view: IView) => any;
|
|
23
23
|
private validateHeader;
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Register a new file list header.
|
|
27
|
+
*
|
|
28
|
+
* @param header - The header to register
|
|
29
|
+
*/
|
|
30
|
+
export declare function registerFileListHeaders(header: Header): void;
|
|
31
|
+
/**
|
|
32
|
+
* Get all currently registered file list headers.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getFileListHeaders(): Header[];
|
package/dist/index.mjs
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import { l as logger, F as FileType } from "./chunks/folder-
|
|
2
|
-
import { a, b, N, c, P } from "./chunks/folder-
|
|
1
|
+
import { l as logger, F as FileType } from "./chunks/folder-CeyZUHai.mjs";
|
|
2
|
+
import { a, b, N, c, P } from "./chunks/folder-CeyZUHai.mjs";
|
|
3
3
|
import { TypedEventTarget } from "typescript-event-target";
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
})(DefaultType || {});
|
|
8
|
+
const DefaultType = Object.freeze({
|
|
9
|
+
DEFAULT: "default",
|
|
10
|
+
HIDDEN: "hidden"
|
|
11
|
+
});
|
|
13
12
|
class FileAction {
|
|
14
13
|
_action;
|
|
15
14
|
constructor(action) {
|
|
@@ -111,7 +110,7 @@ class FileAction {
|
|
|
111
110
|
}
|
|
112
111
|
}
|
|
113
112
|
}
|
|
114
|
-
|
|
113
|
+
function registerFileAction(action) {
|
|
115
114
|
if (typeof window._nc_fileactions === "undefined") {
|
|
116
115
|
window._nc_fileactions = [];
|
|
117
116
|
logger.debug("FileActions initialized");
|
|
@@ -121,14 +120,14 @@ const registerFileAction = function(action) {
|
|
|
121
120
|
return;
|
|
122
121
|
}
|
|
123
122
|
window._nc_fileactions.push(action);
|
|
124
|
-
}
|
|
125
|
-
|
|
123
|
+
}
|
|
124
|
+
function getFileActions() {
|
|
126
125
|
if (typeof window._nc_fileactions === "undefined") {
|
|
127
126
|
window._nc_fileactions = [];
|
|
128
127
|
logger.debug("FileActions initialized");
|
|
129
128
|
}
|
|
130
129
|
return window._nc_fileactions;
|
|
131
|
-
}
|
|
130
|
+
}
|
|
132
131
|
class FileListAction {
|
|
133
132
|
_action;
|
|
134
133
|
constructor(action) {
|
|
@@ -174,7 +173,7 @@ class FileListAction {
|
|
|
174
173
|
}
|
|
175
174
|
}
|
|
176
175
|
}
|
|
177
|
-
|
|
176
|
+
function registerFileListAction(action) {
|
|
178
177
|
if (typeof window._nc_filelistactions === "undefined") {
|
|
179
178
|
window._nc_filelistactions = [];
|
|
180
179
|
}
|
|
@@ -183,13 +182,13 @@ const registerFileListAction = (action) => {
|
|
|
183
182
|
return;
|
|
184
183
|
}
|
|
185
184
|
window._nc_filelistactions.push(action);
|
|
186
|
-
}
|
|
187
|
-
|
|
185
|
+
}
|
|
186
|
+
function getFileListActions() {
|
|
188
187
|
if (typeof window._nc_filelistactions === "undefined") {
|
|
189
188
|
window._nc_filelistactions = [];
|
|
190
189
|
}
|
|
191
190
|
return window._nc_filelistactions;
|
|
192
|
-
}
|
|
191
|
+
}
|
|
193
192
|
function getDefaultExportFromCjs(x) {
|
|
194
193
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
|
|
195
194
|
}
|
|
@@ -878,7 +877,7 @@ class Header {
|
|
|
878
877
|
}
|
|
879
878
|
}
|
|
880
879
|
}
|
|
881
|
-
|
|
880
|
+
function registerFileListHeaders(header) {
|
|
882
881
|
if (typeof window._nc_filelistheader === "undefined") {
|
|
883
882
|
window._nc_filelistheader = [];
|
|
884
883
|
logger.debug("FileListHeaders initialized");
|
|
@@ -888,14 +887,14 @@ const registerFileListHeaders = function(header) {
|
|
|
888
887
|
return;
|
|
889
888
|
}
|
|
890
889
|
window._nc_filelistheader.push(header);
|
|
891
|
-
}
|
|
892
|
-
|
|
890
|
+
}
|
|
891
|
+
function getFileListHeaders() {
|
|
893
892
|
if (typeof window._nc_filelistheader === "undefined") {
|
|
894
893
|
window._nc_filelistheader = [];
|
|
895
894
|
logger.debug("FileListHeaders initialized");
|
|
896
895
|
}
|
|
897
896
|
return window._nc_filelistheader;
|
|
898
|
-
}
|
|
897
|
+
}
|
|
899
898
|
function checkOptionalProperty(obj, property, type) {
|
|
900
899
|
if (typeof obj[property] !== "undefined") {
|
|
901
900
|
if (type === "array") {
|
|
@@ -1056,6 +1055,7 @@ class Navigation extends TypedEventTarget {
|
|
|
1056
1055
|
_currentView = null;
|
|
1057
1056
|
/**
|
|
1058
1057
|
* Register a new view on the navigation
|
|
1058
|
+
*
|
|
1059
1059
|
* @param view The view to register
|
|
1060
1060
|
* @throws {Error} if a view with the same id is already registered
|
|
1061
1061
|
* @throws {Error} if the registered view is invalid
|
|
@@ -1070,6 +1070,7 @@ class Navigation extends TypedEventTarget {
|
|
|
1070
1070
|
}
|
|
1071
1071
|
/**
|
|
1072
1072
|
* Remove a registered view
|
|
1073
|
+
*
|
|
1073
1074
|
* @param id The id of the view to remove
|
|
1074
1075
|
*/
|
|
1075
1076
|
remove(id) {
|
|
@@ -1112,24 +1113,32 @@ class Navigation extends TypedEventTarget {
|
|
|
1112
1113
|
return this._views;
|
|
1113
1114
|
}
|
|
1114
1115
|
}
|
|
1115
|
-
|
|
1116
|
+
function getNavigation() {
|
|
1116
1117
|
if (typeof window._nc_navigation === "undefined") {
|
|
1117
1118
|
window._nc_navigation = new Navigation();
|
|
1118
1119
|
logger.debug("Navigation service initialized");
|
|
1119
1120
|
}
|
|
1120
1121
|
return window._nc_navigation;
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1122
|
+
}
|
|
1123
|
+
const NewMenuEntryCategory = Object.freeze({
|
|
1124
|
+
/**
|
|
1125
|
+
* For actions where the user is intended to upload from their device
|
|
1126
|
+
*/
|
|
1127
|
+
UploadFromDevice: 0,
|
|
1128
|
+
/**
|
|
1129
|
+
* For actions that create new nodes on the server without uploading
|
|
1130
|
+
*/
|
|
1131
|
+
CreateNew: 1,
|
|
1132
|
+
/**
|
|
1133
|
+
* For everything not matching the other categories
|
|
1134
|
+
*/
|
|
1135
|
+
Other: 2
|
|
1136
|
+
});
|
|
1128
1137
|
class NewMenu {
|
|
1129
1138
|
_entries = [];
|
|
1130
1139
|
registerEntry(entry) {
|
|
1131
1140
|
this.validateEntry(entry);
|
|
1132
|
-
entry.category = entry.category ??
|
|
1141
|
+
entry.category = entry.category ?? NewMenuEntryCategory.CreateNew;
|
|
1133
1142
|
this._entries.push(entry);
|
|
1134
1143
|
}
|
|
1135
1144
|
unregisterEntry(entry) {
|
|
@@ -1321,12 +1330,11 @@ class SidebarProxy {
|
|
|
1321
1330
|
function getSidebar() {
|
|
1322
1331
|
return new SidebarProxy();
|
|
1323
1332
|
}
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
})(InvalidFilenameErrorReason || {});
|
|
1333
|
+
const InvalidFilenameErrorReason = Object.freeze({
|
|
1334
|
+
ReservedName: "reserved name",
|
|
1335
|
+
Character: "character",
|
|
1336
|
+
Extension: "extension"
|
|
1337
|
+
});
|
|
1330
1338
|
class InvalidFilenameError extends Error {
|
|
1331
1339
|
constructor(options) {
|
|
1332
1340
|
super(`Invalid ${options.reason} '${options.segment}' in filename '${options.filename}'`, { cause: options });
|
|
@@ -1355,34 +1363,24 @@ function validateFilename(filename) {
|
|
|
1355
1363
|
const forbiddenCharacters = capabilities.forbidden_filename_characters ?? ["/", "\\"];
|
|
1356
1364
|
for (const character of forbiddenCharacters) {
|
|
1357
1365
|
if (filename.includes(character)) {
|
|
1358
|
-
throw new InvalidFilenameError({ segment: character, reason:
|
|
1366
|
+
throw new InvalidFilenameError({ segment: character, reason: InvalidFilenameErrorReason.Character, filename });
|
|
1359
1367
|
}
|
|
1360
1368
|
}
|
|
1361
1369
|
filename = filename.toLocaleLowerCase();
|
|
1362
1370
|
const forbiddenFilenames = capabilities.forbidden_filenames ?? [".htaccess"];
|
|
1363
1371
|
if (forbiddenFilenames.includes(filename)) {
|
|
1364
|
-
throw new InvalidFilenameError({
|
|
1365
|
-
filename,
|
|
1366
|
-
segment: filename,
|
|
1367
|
-
reason: "reserved name"
|
|
1368
|
-
/* ReservedName */
|
|
1369
|
-
});
|
|
1372
|
+
throw new InvalidFilenameError({ filename, segment: filename, reason: InvalidFilenameErrorReason.ReservedName });
|
|
1370
1373
|
}
|
|
1371
1374
|
const endOfBasename = filename.indexOf(".", 1);
|
|
1372
1375
|
const basename2 = filename.substring(0, endOfBasename === -1 ? void 0 : endOfBasename);
|
|
1373
1376
|
const forbiddenFilenameBasenames = capabilities.forbidden_filename_basenames ?? [];
|
|
1374
1377
|
if (forbiddenFilenameBasenames.includes(basename2)) {
|
|
1375
|
-
throw new InvalidFilenameError({
|
|
1376
|
-
filename,
|
|
1377
|
-
segment: basename2,
|
|
1378
|
-
reason: "reserved name"
|
|
1379
|
-
/* ReservedName */
|
|
1380
|
-
});
|
|
1378
|
+
throw new InvalidFilenameError({ filename, segment: basename2, reason: InvalidFilenameErrorReason.ReservedName });
|
|
1381
1379
|
}
|
|
1382
1380
|
const forbiddenFilenameExtensions = capabilities.forbidden_filename_extensions ?? [];
|
|
1383
1381
|
for (const extension of forbiddenFilenameExtensions) {
|
|
1384
1382
|
if (filename.length > extension.length && filename.endsWith(extension)) {
|
|
1385
|
-
throw new InvalidFilenameError({ segment: extension, reason:
|
|
1383
|
+
throw new InvalidFilenameError({ segment: extension, reason: InvalidFilenameErrorReason.Extension, filename });
|
|
1386
1384
|
}
|
|
1387
1385
|
}
|
|
1388
1386
|
}
|
|
@@ -1436,7 +1434,7 @@ function formatFileSize(size, skipSmallSizes = false, binaryPrefixes = false, ba
|
|
|
1436
1434
|
function parseFileSize(value, forceBinary = false) {
|
|
1437
1435
|
try {
|
|
1438
1436
|
value = `${value}`.toLocaleLowerCase().replaceAll(/\s+/g, "").replaceAll(",", ".");
|
|
1439
|
-
} catch
|
|
1437
|
+
} catch {
|
|
1440
1438
|
return null;
|
|
1441
1439
|
}
|
|
1442
1440
|
const match = value.match(/^([0-9]*(\.[0-9]*)?)([kmgtp]?)(i?)b?$/);
|
|
@@ -1484,16 +1482,15 @@ function orderBy(collection, identifiers2, orders) {
|
|
|
1484
1482
|
return 0;
|
|
1485
1483
|
});
|
|
1486
1484
|
}
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
})(FilesSortingMode || {});
|
|
1485
|
+
const FilesSortingMode = Object.freeze({
|
|
1486
|
+
Name: "basename",
|
|
1487
|
+
Modified: "mtime",
|
|
1488
|
+
Size: "size"
|
|
1489
|
+
});
|
|
1493
1490
|
function sortNodes(nodes, options = {}) {
|
|
1494
1491
|
const sortingOptions = {
|
|
1495
1492
|
// Default to sort by name
|
|
1496
|
-
sortingMode:
|
|
1493
|
+
sortingMode: FilesSortingMode.Name,
|
|
1497
1494
|
// Default to sort ascending
|
|
1498
1495
|
sortingOrder: "asc",
|
|
1499
1496
|
...options
|
|
@@ -1511,7 +1508,7 @@ function sortNodes(nodes, options = {}) {
|
|
|
1511
1508
|
// 2: Sort folders first if sorting by name
|
|
1512
1509
|
...sortingOptions.sortFoldersFirst ? [(v) => v.type !== "folder"] : [],
|
|
1513
1510
|
// 3: Use sorting mode if NOT basename (to be able to use display name too)
|
|
1514
|
-
...sortingOptions.sortingMode !==
|
|
1511
|
+
...sortingOptions.sortingMode !== FilesSortingMode.Name ? [(v) => v[sortingOptions.sortingMode] ?? v.attributes[sortingOptions.sortingMode]] : [],
|
|
1515
1512
|
// 4: Use display name if available, fallback to name
|
|
1516
1513
|
(v) => basename2(v),
|
|
1517
1514
|
// 5: Finally, use basename if all previous sorting methods failed
|
|
@@ -1523,9 +1520,9 @@ function sortNodes(nodes, options = {}) {
|
|
|
1523
1520
|
// (for 2): always sort folders before files
|
|
1524
1521
|
...sortingOptions.sortFoldersFirst ? ["asc"] : [],
|
|
1525
1522
|
// (for 3): Reverse if sorting by mtime as mtime higher means edited more recent -> lower
|
|
1526
|
-
...sortingOptions.sortingMode ===
|
|
1523
|
+
...sortingOptions.sortingMode === FilesSortingMode.Modified ? [sortingOptions.sortingOrder === "asc" ? "desc" : "asc"] : [],
|
|
1527
1524
|
// (also for 3 so make sure not to conflict with 2 and 3)
|
|
1528
|
-
...sortingOptions.sortingMode !==
|
|
1525
|
+
...sortingOptions.sortingMode !== FilesSortingMode.Modified && sortingOptions.sortingMode !== FilesSortingMode.Name ? [sortingOptions.sortingOrder] : [],
|
|
1529
1526
|
// for 4: use configured sorting direction
|
|
1530
1527
|
sortingOptions.sortingOrder,
|
|
1531
1528
|
// for 5: use configured sorting direction
|