diff --git a/README.md b/README.md index ef8d3b1..52c49c3 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,8 @@ npm i -S @nextcloud/sharing ## Usage -There are two entry points provided: +There are three entry points provided: - The main entry point `@nextcloud/sharing` provides general utils for file sharing - The _public_ entry point `@nextcloud/sharing/public` provides utils for handling public file shares +- The _ui_ entry point `@nextcloud/sharing/ui` provides API bindings to interact with the files sharing interface in the files app. diff --git a/REUSE.toml b/REUSE.toml index f374a26..b3e6a8f 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -6,7 +6,7 @@ SPDX-PackageSupplier = "Nextcloud GmbH " SPDX-PackageDownloadLocation = "https://github.com/nextcloud-libraries/nextcloud-sharing" [[annotations]] -path = ["package.json", "package-lock.json", "tsconfig.json"] +path = ["package.json", "package-lock.json", "**/tsconfig.json"] precedence = "aggregate" SPDX-FileCopyrightText = "2021 Nextcloud GmbH and Nextcloud contributors" SPDX-License-Identifier = "GPL-3.0-or-later" diff --git a/lib/index.ts b/lib/index.ts index 930a2b9..c9f35ca 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -3,46 +3,5 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -/** @deprecated will be removed with the next version use `ShareType` instead */ -export enum Type { - SHARE_TYPE_USER = 0, - SHARE_TYPE_GROUP = 1, - SHARE_TYPE_LINK = 3, - SHARE_TYPE_EMAIL = 4, - SHARE_TYPE_REMOTE = 6, - SHARE_TYPE_CIRCLE = 7, - SHARE_TYPE_GUEST = 8, - SHARE_TYPE_REMOTE_GROUP = 9, - SHARE_TYPE_ROOM = 10, - SHARE_TYPE_DECK = 12, - /** - * @since 26.0.0 - */ - SHARE_TYPE_FEDERATED_GROUP = 14, -} - -export enum ShareType { - User = 0, - Group = 1, - Link = 3, - Email = 4, - Remote = 6, - /** - * Was called `Circle` before Nextcloud 29 - */ - Team = 7, - Guest = 8, - RemoteGroup = 9, - Room = 10, - Deck = 12, - /** - * @since 26.0.0 - */ - FederatedGroup = 14, - /** - * Third party share types - * - * @since 25.0.0 - */ - ScienceMesh = 15, -} +export type * from './share/index.ts' +export { ShareType } from './share/index.ts' diff --git a/lib/share/Share.ts b/lib/share/Share.ts new file mode 100644 index 0000000..67b6ab1 --- /dev/null +++ b/lib/share/Share.ts @@ -0,0 +1,158 @@ +/*! + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import type { ShareType } from './ShareType.ts' + +export type ShareAttribute = { + value: boolean + key: string + scope: string +} + +export interface IGenericShare { + /** + * The share id + */ + id: number + + /** + * The share type + */ + type: ShareType + + /** + * The generic share attributes + */ + attributes: ShareAttribute[] + + /** + * The share creation timestamp (in seconds since UNIX epoch) + */ + createdTime: number + + /** + * The share permissions for the share receiver. + * + * A bitmask of the share permissions like `SharePermission.Read | SharePermission.Write`. + * These are not the permissions for the current user but for the share recipient (shareWith). + */ + permissions: number + + /** + * Whether the current user can edit this share. + * Only relevant for existing shares. + */ + readonly canEdit: boolean + + /** + * Whether the current user can delete this share. + * Only relevant for existing shares. + */ + readonly canDelete: boolean + + /** + * The uid of the share owner + */ + owner: string + + /** + * The displayname of the share owner + */ + ownerDisplayname: string + + /** + * Get the share with entity id + */ + shareWith: string + + /** + * The displayname of the entity this was shared with + */ + shareWithDisplayname: string + + /** + * In case of multiple shares with the same entity this is the unique displayname. + */ + shareWithDisplaynameUnique: string + + /** + * The uid of the owner of the shared file or folder. + */ + fileOwner: string + + /** + * The displayname of the owner of the shared file or folder. + */ + fileOwnerDisplayname: string + + /** + * Hide the download functionalities for the shared nodes. + * This obfuscates the download buttons for such shares. + */ + hideDownload: boolean + + /** + * Have a mail been sent to the share recipient + */ + mailSend: boolean + + /** + * The share note + */ + note?: string + + /** + * The share label + */ + label?: string + + /** + * The share expiration date in "YYYY-MM-DD HH:mm" format + */ + expireDate?: `${number}-${number}-${number} ${number}:${number}` +} + +export interface ILinkShare extends IGenericShare { + /** + * @inheritdoc + */ + type: ShareType.Link | ShareType.Email + + /** + * The share token + */ + token: string + + /** + * The share password + */ + password: string + + /** + * Password expiration time as "YYYY-MM-DD HH:mm" format + */ + passwordExpirationTime: `${number}-${number}-${number} ${number}:${number}` + + /** + * Password should be sent to recipient using Nextcloud Talk + */ + sendPasswordByTalk: boolean +} + +export interface IRemoteShare extends IGenericShare { + /** + * @inheritdoc + */ + type: ShareType.Remote | ShareType.RemoteGroup + + /** + * Is the share from a trusted remote server + */ + isTrustedServer: boolean +} + +export type IInternalShare = IGenericShare + +export type IShare = ILinkShare | IInternalShare | IRemoteShare diff --git a/lib/share/ShareType.ts b/lib/share/ShareType.ts new file mode 100644 index 0000000..c67096a --- /dev/null +++ b/lib/share/ShareType.ts @@ -0,0 +1,30 @@ +/*! + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +export enum ShareType { + User = 0, + Group = 1, + Link = 3, + Email = 4, + Remote = 6, + /** + * Was called `Circle` before Nextcloud 29 + */ + Team = 7, + Guest = 8, + RemoteGroup = 9, + Room = 10, + Deck = 12, + /** + * @since 26.0.0 + */ + FederatedGroup = 14, + /** + * Third party share types + * + * @since 25.0.0 + */ + ScienceMesh = 15, +} diff --git a/lib/share/index.ts b/lib/share/index.ts new file mode 100644 index 0000000..ae60ae1 --- /dev/null +++ b/lib/share/index.ts @@ -0,0 +1,8 @@ +/*! + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +export type * from './Share.ts' + +export { ShareType } from './ShareType.ts' diff --git a/lib/ui/index.ts b/lib/ui/index.ts new file mode 100644 index 0000000..0d361d6 --- /dev/null +++ b/lib/ui/index.ts @@ -0,0 +1,10 @@ +/*! + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +export type * from './sidebar-action.ts' +export type * from './sidebar-section.ts' + +export * from './sidebar-action.ts' +export * from './sidebar-section.ts' diff --git a/lib/ui/sidebar-action.ts b/lib/ui/sidebar-action.ts new file mode 100644 index 0000000..d718e37 --- /dev/null +++ b/lib/ui/sidebar-action.ts @@ -0,0 +1,158 @@ +/*! + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import type { INode } from '@nextcloud/files' +import type { IShare } from '../index.ts' + +import isSvg from 'is-svg' + +export interface ISidebarAction { + /** + * Unique identifier for the action + */ + id: string + + /** + * The registered identifier of the custom web component to be used. + * + * The custom elements identifier must be prefixed with your apps namespace like `oca_myapp-sharing_action`. + * Also the component must implement following properties: + * - `node`: The shared node as `INode` from `@nextcloud/files`. + * - `share`: The share model if the share already exists as object of type `IShare` (potentially undefined). + * - `onSave`: A registration method to register a on-save callback which will be called when the share is saved `(callback: () => Promise) => void` + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/Web_components + * @see https://vuejs.org/guide/extras/web-components#building-custom-elements-with-vue + */ + element: string + + /** + * Order of the sidebar actions. + * A higher value means the action will be displayed first. + */ + order: number + + /** + * Check if the action is enabled for a specific share type + * + * @param share - The share + * @param node - The node that share is about + */ + enabled(share: IShare, node: INode): boolean +} + +export interface ISidebarInlineAction { + /** + * Unique identifier for the action + */ + id: string + + /** + * Check if the action is enabled for a specific share type + * + * @param share - The share + * @param node - The node that share is about + */ + enabled(share: IShare, node: INode): boolean + + /** + * The callback handler when the user selected this action. + * + * @param share - The share + * @param node - The shared node + */ + exec(share: IShare, node: INode): void | Promise + + /** + * The inline svg icon to use. + */ + iconSvg: string + + /** + * The localized label of this action + * + * @param share - The current share (might not be created yet) + * @param node - The shared node + */ + label(share: IShare, node: INode): string + + /** + * Order of the sidebar actions. + * A higher value means the action will be displayed first. + */ + order: number +} + +/** + * Register a new sidebar action + * + * @param action - The action to register + */ +export function registerSidebarAction(action: ISidebarAction): void { + if (!action.id) { + throw new Error('Sidebar actions must have an id') + } + if (!action.element || !action.element.startsWith('oca_') || !window.customElements.get(action.element)) { + throw new Error('Sidebar actions must provide a registered custom web component identifier') + } + if (typeof action.order !== 'number') { + throw new Error('Sidebar actions must have the order property') + } + if (typeof action.enabled !== 'function') { + throw new Error('Sidebar actions must implement the "enabled" method') + } + window._nc_files_sharing_sidebar_actions ??= new Map() + + if (window._nc_files_sharing_sidebar_actions.has(action.id)) { + throw new Error(`Sidebar action with id "${action.id}" is already registered`) + } + window._nc_files_sharing_sidebar_actions.set(action.id, action) +} + +/** + * Register a new sidebar action + * + * @param action - The action to register + */ +export function registerSidebarInlineAction(action: ISidebarInlineAction): void { + if (!action.id) { + throw new Error('Sidebar actions must have an id') + } + if (typeof action.order !== 'number') { + throw new Error('Sidebar actions must have the "order" property') + } + if (typeof action.iconSvg !== 'string' || !isSvg(action.iconSvg)) { + throw new Error('Sidebar actions must have the "iconSvg" property') + } + if (typeof action.label !== 'function') { + throw new Error('Sidebar actions must implement the "label" method') + } + if (typeof action.exec !== 'function') { + throw new Error('Sidebar actions must implement the "exec" method') + } + if (typeof action.enabled !== 'function') { + throw new Error('Sidebar actions must implement the "enabled" method') + } + window._nc_files_sharing_sidebar_inline_actions ??= new Map() + + if (window._nc_files_sharing_sidebar_inline_actions.has(action.id)) { + throw new Error(`Sidebar action with id "${action.id}" is already registered`) + } + window._nc_files_sharing_sidebar_inline_actions.set(action.id, action) +} + +/** + * Get all registered sidebar actions + */ +export function getSidebarActions(): ISidebarAction[] { + return [...(window._nc_files_sharing_sidebar_actions?.values() ?? [])] +} + +/** + * Get all registered sidebar inline actions + */ +export function getSidebarInlineActions(): ISidebarInlineAction[] { + return [...(window._nc_files_sharing_sidebar_inline_actions?.values() ?? [])] +} diff --git a/lib/ui/sidebar-section.ts b/lib/ui/sidebar-section.ts new file mode 100644 index 0000000..b0599c3 --- /dev/null +++ b/lib/ui/sidebar-section.ts @@ -0,0 +1,70 @@ +/*! + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import type { INode } from '@nextcloud/files' + +export interface ISidebarSection { + /** + * Unique identifier for the section + */ + id: string + + /** + * The registered identifier of the custom web component to be used. + * + * The custom elements identifier must be prefixed with your apps namespace like `oca_myapp-sharing_section`. + * Also the component must at least have a `node` property which must accept the current active node as `INode` from `@nextcloud/files`. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/Web_components + * @see https://vuejs.org/guide/extras/web-components#building-custom-elements-with-vue + */ + element: string + + /** + * Order of the sidebar sections. + * A higher value means the section will be displayed first. + */ + order: number + + /** + * Check if the section is enabled for the current active node + * + * @param node - The node to check + */ + enabled(node: INode): boolean +} + +/** + * Register a new sidebar section inside the files sharing sidebar tab. + * + * @param section - The section to register + */ +export function registerSidebarSection(section: ISidebarSection): void { + if (!section.id) { + throw new Error('Sidebar sections must have an id') + } + if (!section.element || !section.element.startsWith('oca_') || !window.customElements.get(section.element)) { + throw new Error('Sidebar sections must provide a registered custom web component identifier') + } + if (typeof section.order !== 'number') { + throw new Error('Sidebar sections must have the order property') + } + if (typeof section.enabled !== 'function') { + throw new Error('Sidebar sections must implement the enabled method') + } + + window._nc_files_sharing_sidebar_sections ??= new Map() + if (window._nc_files_sharing_sidebar_sections.has(section.id)) { + throw new Error(`Sidebar section with id "${section.id}" is already registered`) + } + window._nc_files_sharing_sidebar_sections.set(section.id, section) +} + +/** + * Get all registered sidebar sections for the files sharing sidebar tab. + */ +export function getSidebarSections(): ISidebarSection[] { + return [...(window._nc_files_sharing_sidebar_sections?.values() ?? [])] +} diff --git a/lib/window.d.ts b/lib/window.d.ts new file mode 100644 index 0000000..36955a6 --- /dev/null +++ b/lib/window.d.ts @@ -0,0 +1,14 @@ +/*! + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import type { ISidebarAction, ISidebarInlineAction, ISidebarSection } from './ui/index.ts' + +declare global { + interface Window { + _nc_files_sharing_sidebar_actions?: Map + _nc_files_sharing_sidebar_inline_actions?: Map + _nc_files_sharing_sidebar_sections?: Map + } +} diff --git a/package-lock.json b/package-lock.json index 5bcf898..5cfc277 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,12 @@ "version": "0.2.5", "license": "GPL-3.0-or-later", "dependencies": { - "@nextcloud/initial-state": "^3.0.0" + "@nextcloud/initial-state": "^3.0.0", + "is-svg": "^6.1.0" }, "devDependencies": { - "@nextcloud/browserslist-config": "^3.0.1", "@nextcloud/eslint-config": "^9.0.0-rc.5", + "@nextcloud/files": "^3.12.0", "@types/node": "^24.3.0", "@vitest/coverage-v8": "^3.2.4", "eslint": "^9.29.0", @@ -24,6 +25,9 @@ }, "engines": { "node": "^20.0.0 || ^22.0.0 || ^24.0.0" + }, + "optionalDependencies": { + "@nextcloud/files": "^3.12.0" } }, "node_modules/@ampproject/remapping": { @@ -100,6 +104,16 @@ "node": ">=18" } }, + "node_modules/@buttercup/fetch": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@buttercup/fetch/-/fetch-0.2.1.tgz", + "integrity": "sha512-sCgECOx8wiqY8NN1xN22BqqKzXYIG2AicNLlakOAI4f0WgyLVUbAigMf8CZhBtJxdudTcB1gD5lciqi44jwJvg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "node-fetch": "^3.3.0" + } + }, "node_modules/@es-joy/jsdoccomment": { "version": "0.52.0", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.52.0.tgz", @@ -778,6 +792,16 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@file-type/xml": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@file-type/xml/-/xml-0.4.4.tgz", + "integrity": "sha512-NhCyXoHlVZ8TqM476hyzwGJ24+D5IPSaZhmrPj7qXnEVb3q6jrFzA3mM9TBpknKSI9EuQeGTKRg2DXGUwvBBoQ==", + "license": "MIT", + "dependencies": { + "sax": "^1.4.1", + "strtok3": "^10.3.4" + } + }, "node_modules/@gerrit0/mini-shiki": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.12.0.tgz", @@ -949,10 +973,52 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@nextcloud/browserslist-config": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@nextcloud/browserslist-config/-/browserslist-config-3.0.1.tgz", - "integrity": "sha512-GZTxL5fsDgmFoot/qnRurjHCuHjSfOg+A6t4+P2TySXua2Q1Ex0lecZYlSnRuOR/W5BGOZ06ITTA/hbkSh1Ypg==", + "node_modules/@nextcloud/auth": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@nextcloud/auth/-/auth-2.5.2.tgz", + "integrity": "sha512-1dr+Xhvg2QtsFC23KFXJSkU524EVgI/+WFBGTZ8tFa+9hmcZ3CqZIHjtXm3KxUvezwAY5023Ml0n2vpdYkpBXQ==", + "dev": true, + "license": "GPL-3.0-or-later", + "dependencies": { + "@nextcloud/browser-storage": "^0.4.0", + "@nextcloud/event-bus": "^3.3.2" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || ^24.0.0" + } + }, + "node_modules/@nextcloud/browser-storage": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@nextcloud/browser-storage/-/browser-storage-0.4.0.tgz", + "integrity": "sha512-D6XxznxCYmJ3oBCC3p0JB6GZJ2RZ9dgbB1UqtTePXrIvHUMBAeF/YkiGKYxLAVZCZb+NSNZXgAYHm/3LnIUbDg==", + "dev": true, + "license": "GPL-3.0-or-later", + "dependencies": { + "core-js": "3.37.0" + }, + "engines": { + "node": "^20.0.0", + "npm": "^10.0.0" + } + }, + "node_modules/@nextcloud/capabilities": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@nextcloud/capabilities/-/capabilities-1.2.0.tgz", + "integrity": "sha512-L1NQtOfHWzkfj0Ple1MEJt6HmOHWAi3y4qs+OnwSWexqJT0DtXTVPyRxi7ADyITwRxS5H9R/HMl6USAj4Nr1nQ==", + "dev": true, + "license": "GPL-3.0-or-later", + "dependencies": { + "@nextcloud/initial-state": "^2.1.0" + }, + "engines": { + "node": "^20.0.0", + "npm": "^10.0.0" + } + }, + "node_modules/@nextcloud/capabilities/node_modules/@nextcloud/initial-state": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@nextcloud/initial-state/-/initial-state-2.2.0.tgz", + "integrity": "sha512-cDW98L5KGGgpS8pzd+05304/p80cyu8U2xSDQGa+kGPTpUFmCbv2qnO5WrwwGTauyjYijCal2bmw82VddSH+Pg==", "dev": true, "license": "GPL-3.0-or-later", "engines": { @@ -987,6 +1053,44 @@ "eslint": ">=9" } }, + "node_modules/@nextcloud/event-bus": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@nextcloud/event-bus/-/event-bus-3.3.2.tgz", + "integrity": "sha512-1Qfs6i7Tz2qd1A33NpBQOt810ydHIRjhyXMFwSEkYX2yUI80lAk/sWO8HIB2Fqp+iffhyviPPcQYoytMDRyDNw==", + "dev": true, + "license": "GPL-3.0-or-later", + "dependencies": { + "@types/semver": "^7.5.8", + "semver": "^7.6.3" + }, + "engines": { + "node": "^20.0.0", + "npm": "^10.0.0" + } + }, + "node_modules/@nextcloud/files": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@nextcloud/files/-/files-3.12.0.tgz", + "integrity": "sha512-LVZklgooZzBj2jkbPRZO4jnnvW5+RvOn7wN5weyOZltF6i2wVMbg1Y/Czl2pi/UNMjUm5ENqc0j7FgxMBo8bwA==", + "dev": true, + "license": "AGPL-3.0-or-later", + "dependencies": { + "@nextcloud/auth": "^2.5.1", + "@nextcloud/capabilities": "^1.2.0", + "@nextcloud/l10n": "^3.3.0", + "@nextcloud/logger": "^3.0.2", + "@nextcloud/paths": "^2.2.1", + "@nextcloud/router": "^3.0.1", + "@nextcloud/sharing": "^0.2.4", + "cancelable-promise": "^4.3.1", + "is-svg": "^6.0.0", + "typescript-event-target": "^1.1.1", + "webdav": "^5.8.0" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || ^24.0.0" + } + }, "node_modules/@nextcloud/initial-state": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@nextcloud/initial-state/-/initial-state-3.0.0.tgz", @@ -996,6 +1100,100 @@ "node": "^20.0.0 || ^22.0.0 || ^24.0.0" } }, + "node_modules/@nextcloud/l10n": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-3.4.0.tgz", + "integrity": "sha512-K4UBSl0Ou6sXXLxyjuhktRf2FyTCjyvHxJyBLmS2z3YEYcRkpf8ib3XneRwEQIEpzBPQjul2/ZdFlt7umd8Gaw==", + "dev": true, + "license": "GPL-3.0-or-later", + "dependencies": { + "@nextcloud/router": "^3.0.1", + "@nextcloud/typings": "^1.9.1", + "@types/escape-html": "^1.0.4", + "dompurify": "^3.2.6", + "escape-html": "^1.0.3" + }, + "engines": { + "node": "^20 || ^22 || ^24" + } + }, + "node_modules/@nextcloud/logger": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@nextcloud/logger/-/logger-3.0.2.tgz", + "integrity": "sha512-wByt0R0/6QC44RBpaJr1MWghjjOxk/pRbACHo/ZWWKht1qYbJRHB4GtEi+35KEIHY07ZpqxiDk6dIRuN7sXYWQ==", + "dev": true, + "license": "GPL-3.0-or-later", + "dependencies": { + "@nextcloud/auth": "^2.3.0" + }, + "engines": { + "node": "^20.0.0", + "npm": "^10.0.0" + } + }, + "node_modules/@nextcloud/paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@nextcloud/paths/-/paths-2.2.1.tgz", + "integrity": "sha512-M3ShLjrxR7B48eKThLMoqbxTqTKyQXcwf9TgeXQGbCIhiHoXU6as5j8l5qNv/uZlANokVdowpuWHBi3b2+YNNA==", + "dev": true, + "license": "GPL-3.0-or-later", + "engines": { + "node": "^20.0.0", + "npm": "^10.0.0" + } + }, + "node_modules/@nextcloud/router": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@nextcloud/router/-/router-3.0.1.tgz", + "integrity": "sha512-Ci/uD3x8OKHdxSqXL6gRJ+mGJOEXjeiHjj7hqsZqVTsT7kOrCjDf0/J8z5RyLlokKZ0IpSe+hGxgi3YB7Gpw3Q==", + "dev": true, + "license": "GPL-3.0-or-later", + "dependencies": { + "@nextcloud/typings": "^1.7.0" + }, + "engines": { + "node": "^20.0.0", + "npm": "^10.0.0" + } + }, + "node_modules/@nextcloud/sharing": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@nextcloud/sharing/-/sharing-0.2.5.tgz", + "integrity": "sha512-B3K5Dq9b5dexDA5n3AAuCF69Huwhrpw0J72fsVXV4KpPdImjhVPlExAv5o70AoXa+OqN4Rwn6gqJw+3ED892zg==", + "dev": true, + "license": "GPL-3.0-or-later", + "dependencies": { + "@nextcloud/initial-state": "^2.2.0" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || ^24.0.0" + } + }, + "node_modules/@nextcloud/sharing/node_modules/@nextcloud/initial-state": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@nextcloud/initial-state/-/initial-state-2.2.0.tgz", + "integrity": "sha512-cDW98L5KGGgpS8pzd+05304/p80cyu8U2xSDQGa+kGPTpUFmCbv2qnO5WrwwGTauyjYijCal2bmw82VddSH+Pg==", + "dev": true, + "license": "GPL-3.0-or-later", + "engines": { + "node": "^20.0.0", + "npm": "^10.0.0" + } + }, + "node_modules/@nextcloud/typings": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@nextcloud/typings/-/typings-1.9.1.tgz", + "integrity": "sha512-i0l/L5gKW8EACbXHVxXM6wn3sUhY2qmnL2OijppzU4dENC7/hqySMQDer7/+cJbNSNG7uHF/Z+9JmHtDfRfuGg==", + "dev": true, + "license": "GPL-3.0-or-later", + "dependencies": { + "@types/jquery": "3.5.16" + }, + "engines": { + "node": "^20.0.0", + "npm": "^10.0.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1719,6 +1917,12 @@ "eslint": ">=9.0.0" } }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" + }, "node_modules/@types/chai": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", @@ -1736,6 +1940,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/escape-html": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/escape-html/-/escape-html-1.0.4.tgz", + "integrity": "sha512-qZ72SFTgUAZ5a7Tj6kf2SHLetiH5S6f8G5frB2SPQ3EyF02kxdyBFf4Tz4banE3xCgGnKgWLt//a6VuYHKYJTg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1753,6 +1964,16 @@ "@types/unist": "*" } }, + "node_modules/@types/jquery": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.16.tgz", + "integrity": "sha512-bsI7y4ZgeMkmpG9OM710RRzDFp+w4P1RGiIt30C1mSBT+ExCleeh4HObwgArnDFELmRrOpXgSYN9VF1hj+f1lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sizzle": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1770,6 +1991,28 @@ "undici-types": "~7.10.0" } }, + "node_modules/@types/semver": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", + "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/sizzle": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.10.tgz", + "integrity": "sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -2300,6 +2543,13 @@ "dev": true, "license": "MIT" }, + "node_modules/base-64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", + "dev": true, + "license": "MIT" + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -2330,6 +2580,13 @@ "node": ">=8" } }, + "node_modules/byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/byte-length/-/byte-length-1.0.2.tgz", + "integrity": "sha512-ovBpjmsgd/teRmgcPh23d4gJvxDoXtAzEL9xTfMU8Yc2kqCDb7L9jAG0XHl1nzuGl+h3ebCIF1i62UFyA9V/2Q==", + "dev": true, + "license": "MIT" + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -2350,6 +2607,13 @@ "node": ">=6" } }, + "node_modules/cancelable-promise": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/cancelable-promise/-/cancelable-promise-4.3.1.tgz", + "integrity": "sha512-A/8PwLk/T7IJDfUdQ68NR24QHa8rIlnN/stiJEBo6dmVUkD4K14LswG0w3VwdeK/o7qOwRUR1k2MhK5Rpy2m7A==", + "dev": true, + "license": "MIT" + }, "node_modules/chai": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", @@ -2384,6 +2648,16 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, "node_modules/check-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", @@ -2449,6 +2723,18 @@ "dev": true, "license": "MIT" }, + "node_modules/core-js": { + "version": "3.37.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.0.tgz", + "integrity": "sha512-fu5vHevQ8ZG4og+LXug8ulUtVxjOcEYvifJr7L5Bfq9GOztVqsKd9/59hUk2ZSbCrS3BqUr3EpaYGIYzq7g3Ug==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2464,6 +2750,16 @@ "node": ">= 8" } }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -2477,6 +2773,16 @@ "node": ">=4" } }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -2550,6 +2856,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/dompurify": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", + "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", + "dev": true, + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -2625,6 +2941,13 @@ "@esbuild/win32-x64": "0.25.5" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3038,6 +3361,30 @@ } } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -3119,6 +3466,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3240,6 +3600,13 @@ "node": ">=8" } }, + "node_modules/hot-patcher": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hot-patcher/-/hot-patcher-2.0.1.tgz", + "integrity": "sha512-ECg1JFG0YzehicQaogenlcs2qg6WsXQsxtnbr1i696u5tLUjtJdQAh0u2g0Q5YV45f263Ta1GnUJsc8WIfJf4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -3293,6 +3660,13 @@ "node": ">=0.8.19" } }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true, + "license": "MIT" + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3349,6 +3723,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-svg": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-6.1.0.tgz", + "integrity": "sha512-i7YPdvYuSCYcaLQrKwt8cvKTlwHcdA6Hp8N9SO3Q5jIzo8x6kH3N47W0BvPP7NdxVBmIHx7X9DK36czYYW7lHg==", + "license": "MIT", + "dependencies": { + "@file-type/xml": "^0.4.3" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3487,6 +3876,13 @@ "json-buffer": "3.0.1" } }, + "node_modules/layerr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/layerr/-/layerr-3.0.0.tgz", + "integrity": "sha512-tv754Ki2dXpPVApOrjTyRo4/QegVb9eVFq4mjqp4+NM5NaX7syQvN5BBNfV/ZpAHCEHV24XdUVrBAoka4jt3pA==", + "dev": true, + "license": "MIT" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -3611,6 +4007,18 @@ "markdown-it": "bin/markdown-it.mjs" } }, + "node_modules/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, "node_modules/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", @@ -3724,6 +4132,13 @@ "node": ">=18" } }, + "node_modules/nested-property": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nested-property/-/nested-property-4.0.0.tgz", + "integrity": "sha512-yFehXNWRs4cM0+dz7QxCd06hTbWbSkV0ISsqBfkntU6TOY4Qm3Q88fRRLOddkGh2Qq6dZvnKVAahfhjcUvLnyA==", + "dev": true, + "license": "MIT" + }, "node_modules/node-addon-api": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", @@ -3733,6 +4148,46 @@ "optional": true, "peer": true }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -3853,6 +4308,13 @@ "node": ">=8" } }, + "node_modules/path-posix": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/path-posix/-/path-posix-1.0.0.tgz", + "integrity": "sha512-1gJ0WpNIiYcQydgg3Ed8KzvIqTsDpNwq+cjBCssvBtuTWjEqY1AW+i+OepiEMqDCzyro9B2sLAe4RBPajMYFiA==", + "dev": true, + "license": "ISC" + }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", @@ -3980,6 +4442,13 @@ "node": ">=6" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4017,6 +4486,13 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -4132,6 +4608,12 @@ "@parcel/watcher": "^2.4.1" } }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "license": "ISC" + }, "node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -4409,6 +4891,22 @@ ], "license": "MIT" }, + "node_modules/strtok3": { + "version": "10.3.4", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", + "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -4599,6 +5097,13 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/typescript-event-target": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/typescript-event-target/-/typescript-event-target-1.1.1.tgz", + "integrity": "sha512-dFSOFBKV6uwaloBCCUhxlD3Pr/P1a/tJdcmPrTXCHlEFD3faj0mztjcGn6VBAhQ0/Bdy8K3VWrrqwbt/ffsYsg==", + "dev": true, + "license": "MIT" + }, "node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", @@ -4623,6 +5128,27 @@ "punycode": "^2.1.0" } }, + "node_modules/url-join": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", + "integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4826,6 +5352,87 @@ "eslint": "^8.57.0 || ^9.0.0" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webdav": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webdav/-/webdav-5.8.0.tgz", + "integrity": "sha512-iuFG7NamJ41Oshg4930iQgfIpRrUiatPWIekeznYgEf2EOraTRcDPTjy7gIOMtkdpKTaqPk1E68NO5PAGtJahA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@buttercup/fetch": "^0.2.1", + "base-64": "^1.0.0", + "byte-length": "^1.0.2", + "entities": "^6.0.0", + "fast-xml-parser": "^4.5.1", + "hot-patcher": "^2.0.1", + "layerr": "^3.0.0", + "md5": "^2.3.0", + "minimatch": "^9.0.5", + "nested-property": "^4.0.0", + "node-fetch": "^3.3.2", + "path-posix": "^1.0.0", + "url-join": "^5.0.0", + "url-parse": "^1.5.10" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/webdav/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/webdav/node_modules/fast-xml-parser": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", + "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.1.1" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/webdav/node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/whatwg-mimetype": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", diff --git a/package.json b/package.json index 2255a18..897166e 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,15 @@ "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.js" + "default": "./dist/index.js" }, "./public": { "types": "./dist/public.d.ts", - "import": "./dist/public.js" + "default": "./dist/public.js" + }, + "./ui": { + "types": "./dist/ui/index.d.ts", + "default": "./dist/ui/index.js" } }, "main": "dist/index.js", @@ -29,7 +33,7 @@ "scripts": { "build": "npm run build:cleanup && npm run build:source", "build:cleanup": "tsc --build --clean", - "build:doc": "typedoc --out dist/doc lib/index.ts lib/publicShare.ts && touch dist/doc/.nojekyll", + "build:doc": "typedoc --out dist/doc lib/index.ts lib/publicShare.ts lib/ui/index.ts && touch dist/doc/.nojekyll", "build:source": "tsc", "dev": "vite --mode development build", "lint": "eslint", @@ -40,15 +44,13 @@ "ts:check": "tsc --noEmit", "watch": "vite --mode development build --watch" }, - "browserslist": [ - "extends @nextcloud/browserslist-config" - ], "dependencies": { - "@nextcloud/initial-state": "^3.0.0" + "@nextcloud/initial-state": "^3.0.0", + "is-svg": "^6.1.0" }, "devDependencies": { - "@nextcloud/browserslist-config": "^3.0.1", "@nextcloud/eslint-config": "^9.0.0-rc.5", + "@nextcloud/files": "^3.12.0", "@types/node": "^24.3.0", "@vitest/coverage-v8": "^3.2.4", "eslint": "^9.29.0", @@ -57,6 +59,9 @@ "typescript": "^5.9.2", "vitest": "^3.2.4" }, + "optionalDependencies": { + "@nextcloud/files": "^3.12.0" + }, "engines": { "node": "^20.0.0 || ^22.0.0 || ^24.0.0" }, diff --git a/lib/index.spec.ts b/tests/index.spec.ts similarity index 92% rename from lib/index.spec.ts rename to tests/index.spec.ts index 792128a..6266f22 100644 --- a/lib/index.spec.ts +++ b/tests/index.spec.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: CC0-1.0 */ import { expect, test } from 'vitest' -import { ShareType } from '.' +import { ShareType } from '../lib/index.ts' test('ShareType', () => { for (const type of Object.values(ShareType)) { diff --git a/lib/publicShare.spec.ts b/tests/publicShare.spec.ts similarity index 97% rename from lib/publicShare.spec.ts rename to tests/publicShare.spec.ts index 75e6bfa..edd1573 100644 --- a/lib/publicShare.spec.ts +++ b/tests/publicShare.spec.ts @@ -60,7 +60,7 @@ describe('getSharingToken', () => { }) const getSharingToken = async () => { - const { getSharingToken: sharingToken } = await import('./public.ts') + const { getSharingToken: sharingToken } = await import('../lib/public.ts') return sharingToken() } diff --git a/tests/tsconfig.json b/tests/tsconfig.json new file mode 100644 index 0000000..60b965b --- /dev/null +++ b/tests/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../tsconfig.json", + "include": ["../lib/**/*.ts", "./**/*.ts"], + "exclude": [], + "compilerOptions": { + "rootDir": ".." + } +} diff --git a/tests/ui/sidebar-action.spec.ts b/tests/ui/sidebar-action.spec.ts new file mode 100644 index 0000000..3a72566 --- /dev/null +++ b/tests/ui/sidebar-action.spec.ts @@ -0,0 +1,83 @@ +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-3.0-or-later + * @vitest-environment happy-dom + */ + +import type { ISidebarAction } from '../../lib/ui/index.ts' + +import { beforeEach, describe, expect, test, vi } from 'vitest' +import { getSidebarActions, registerSidebarAction } from '../../lib/ui/index.ts' + +describe('sidebar action', () => { + const elementsSpy = vi.spyOn(window.customElements, 'get') + + beforeEach(() => { + vi.resetAllMocks() + elementsSpy.mockImplementation((id: string) => id.startsWith('oca_myapp') ? class extends HTMLElement {} : undefined) + delete window._nc_files_sharing_sidebar_actions + }) + + test('register action', async () => { + registerSidebarAction(createAction()) + expect(window._nc_files_sharing_sidebar_actions).toHaveLength(1) + expect(elementsSpy).toHaveBeenCalledOnce() + }) + + test('register multiple actions', async () => { + registerSidebarAction(createAction()) + registerSidebarAction({ ...createAction(), id: 'test-2' }) + expect(window._nc_files_sharing_sidebar_actions).toHaveLength(2) + expect(elementsSpy).toHaveBeenCalledTimes(2) + }) + + test('get registered actions', async () => { + registerSidebarAction(createAction()) + registerSidebarAction({ ...createAction(), id: 'test-2' }) + expect(getSidebarActions()).toHaveLength(2) + expect(getSidebarActions().map(({ id }) => id)).toEqual(['test', 'test-2']) + }) + + test('register same action twice', async () => { + registerSidebarAction(createAction()) + expect(() => registerSidebarAction(createAction())).toThrowErrorMatchingInlineSnapshot('[Error: Sidebar action with id "test" is already registered]') + }) + + test('register action with missing id', async () => { + expect(() => registerSidebarAction({ + ...createAction(), + id: '', + })).toThrowErrorMatchingInlineSnapshot('[Error: Sidebar actions must have an id]') + }) + + test('register action with missing enabled', async () => { + const action = createAction() + // @ts-expect-error mocking for tests + delete action.enabled + + expect(() => registerSidebarAction(action)).toThrowErrorMatchingInlineSnapshot('[Error: Sidebar actions must implement the "enabled" method]') + }) + + test('register action with invalid element', async () => { + const action = createAction() + action.element = 'invalid-element' + + expect(() => registerSidebarAction(action)).toThrowErrorMatchingInlineSnapshot('[Error: Sidebar actions must provide a registered custom web component identifier]') + }) + + test('register action with unregistered element', async () => { + const action = createAction() + action.element = 'oca_non_existent_element' + + expect(() => registerSidebarAction(action)).toThrowErrorMatchingInlineSnapshot('[Error: Sidebar actions must provide a registered custom web component identifier]') + }) +}) + +function createAction(): ISidebarAction { + return { + id: 'test', + order: 0, + element: 'oca_myapp-sharing_action', + enabled: vi.fn(), + } +} diff --git a/tests/ui/sidebar-inline-action.spec.ts b/tests/ui/sidebar-inline-action.spec.ts new file mode 100644 index 0000000..6cf4332 --- /dev/null +++ b/tests/ui/sidebar-inline-action.spec.ts @@ -0,0 +1,78 @@ +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-3.0-or-later + * @vitest-environment happy-dom + */ + +import type { ISidebarInlineAction } from '../../lib/ui/index.ts' + +import { beforeEach, describe, expect, test, vi } from 'vitest' +import { getSidebarInlineActions, registerSidebarInlineAction } from '../../lib/ui/index.ts' + +describe('sidebar inline action', () => { + beforeEach(() => { + delete window._nc_files_sharing_sidebar_inline_actions + }) + + test('register action', async () => { + registerSidebarInlineAction(createAction()) + expect(window._nc_files_sharing_sidebar_inline_actions).toHaveLength(1) + }) + + test('register multiple actions', async () => { + registerSidebarInlineAction(createAction()) + registerSidebarInlineAction({ ...createAction(), id: 'test-2' }) + expect(window._nc_files_sharing_sidebar_inline_actions).toHaveLength(2) + }) + + test('get registered actions', async () => { + registerSidebarInlineAction(createAction()) + registerSidebarInlineAction({ ...createAction(), id: 'test-2' }) + expect(getSidebarInlineActions()).toHaveLength(2) + expect(getSidebarInlineActions().map(({ id }) => id)).toEqual(['test', 'test-2']) + }) + + test('register same action twice', async () => { + registerSidebarInlineAction(createAction()) + expect(() => registerSidebarInlineAction(createAction())).toThrowErrorMatchingInlineSnapshot('[Error: Sidebar action with id "test" is already registered]') + }) + + test('register action with missing id', async () => { + expect(() => registerSidebarInlineAction({ + ...createAction(), + id: '', + })).toThrowErrorMatchingInlineSnapshot('[Error: Sidebar actions must have an id]') + }) + + test.for` + method + -- + ${'enabled'} + ${'label'} + ${'exec'} + `('register action with missing $method', async ({ method }) => { + const action = createAction() + // @ts-expect-error mocking for tests + delete action[method] + + expect(() => registerSidebarInlineAction(action)).toThrowError() + }) + + test('register action with invalid icon', async () => { + expect(() => registerSidebarInlineAction({ + ...createAction(), + iconSvg: '
', + })).toThrowErrorMatchingInlineSnapshot('[Error: Sidebar actions must have the "iconSvg" property]') + }) +}) + +function createAction(): ISidebarInlineAction { + return { + id: 'test', + order: 0, + iconSvg: '', + enabled: vi.fn(), + label: vi.fn(), + exec: vi.fn(), + } +} diff --git a/tests/ui/sidebar-section.spec.ts b/tests/ui/sidebar-section.spec.ts new file mode 100644 index 0000000..ba90c58 --- /dev/null +++ b/tests/ui/sidebar-section.spec.ts @@ -0,0 +1,83 @@ +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-3.0-or-later + * @vitest-environment happy-dom + */ + +import type { ISidebarSection } from '../../lib/ui/index.ts' + +import { beforeEach, describe, expect, test, vi } from 'vitest' +import { getSidebarSections, registerSidebarSection } from '../../lib/ui/index.ts' + +describe('sidebar section', () => { + const elementsSpy = vi.spyOn(window.customElements, 'get') + + beforeEach(() => { + vi.resetAllMocks() + elementsSpy.mockImplementation((id: string) => id.startsWith('oca_myapp') ? class extends HTMLElement {} : undefined) + delete window._nc_files_sharing_sidebar_sections + }) + + test('register section', async () => { + registerSidebarSection(createSection()) + expect(window._nc_files_sharing_sidebar_sections).toHaveLength(1) + expect(elementsSpy).toHaveBeenCalledOnce() + }) + + test('register multiple sections', async () => { + registerSidebarSection(createSection()) + registerSidebarSection({ ...createSection(), id: 'test-2' }) + expect(window._nc_files_sharing_sidebar_sections).toHaveLength(2) + expect(elementsSpy).toHaveBeenCalledTimes(2) + }) + + test('get registered sections', async () => { + registerSidebarSection(createSection()) + registerSidebarSection({ ...createSection(), id: 'test-2' }) + expect(getSidebarSections()).toHaveLength(2) + expect(getSidebarSections().map(({ id }) => id)).toEqual(['test', 'test-2']) + }) + + test('register same section twice', async () => { + registerSidebarSection(createSection()) + expect(() => registerSidebarSection(createSection())).toThrowErrorMatchingInlineSnapshot('[Error: Sidebar section with id "test" is already registered]') + }) + + test('register section with missing id', async () => { + expect(() => registerSidebarSection({ + ...createSection(), + id: '', + })).toThrowErrorMatchingInlineSnapshot('[Error: Sidebar sections must have an id]') + }) + + test('register section with missing enabled', async () => { + const section = createSection() + // @ts-expect-error mocking for tests + delete section.enabled + + expect(() => registerSidebarSection(section)).toThrowErrorMatchingInlineSnapshot('[Error: Sidebar sections must implement the enabled method]') + }) + + test('register section with invalid element', async () => { + const section = createSection() + section.element = 'invalid-element' + + expect(() => registerSidebarSection(section)).toThrowErrorMatchingInlineSnapshot('[Error: Sidebar sections must provide a registered custom web component identifier]') + }) + + test('register section with unregistered element', async () => { + const section = createSection() + section.element = 'oca_non_existent_element' + + expect(() => registerSidebarSection(section)).toThrowErrorMatchingInlineSnapshot('[Error: Sidebar sections must provide a registered custom web component identifier]') + }) +}) + +function createSection(): ISidebarSection { + return { + id: 'test', + order: 0, + element: 'oca_myapp-sharing_section', + enabled: vi.fn(), + } +} diff --git a/tsconfig.json b/tsconfig.json index 6f69eff..deb0819 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,9 +3,9 @@ "exclude": ["lib/**/*.spec.ts"], "compilerOptions": { "rootDir": "lib", - "target": "ESNext", - "module": "nodenext", - "moduleResolution": "nodenext", + "target": "esnext", + "module": "esnext", + "moduleResolution": "bundler", "declaration": true, "outDir": "./dist", "strict": true,