refactor(files_trashbin): migrate app to Vue 3

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
pull/56439/head
Ferdinand Thiessen 2025-11-14 14:58:49 +07:00
parent f2f9cb7664
commit 0059d17ae3
No known key found for this signature in database
GPG Key ID: 45FAE7268762B400
18 changed files with 108 additions and 102 deletions

@ -2,6 +2,7 @@
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { Node, View } from '@nextcloud/files'
import CloseSvg from '@mdi/svg/svg/close.svg?raw'
@ -11,10 +12,13 @@ import { FileAction, Permission } from '@nextcloud/files'
import { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'
import PQueue from 'p-queue'
import { TRASHBIN_VIEW_ID } from '../../../files_trashbin/src/files_views/trashbinView.ts'
import logger from '../logger.ts'
import { askConfirmation, canDisconnectOnly, canUnshareOnly, deleteNode, displayName, shouldAskForConfirmation } from './deleteUtils.ts'
// TODO: once the files app is migrated to the new frontend use the import instead:
// import { TRASHBIN_VIEW_ID } from '../../../files_trashbin/src/files_views/trashbinView.ts'
const TRASHBIN_VIEW_ID = 'trashbin'
const queue = new PQueue({ concurrency: 5 })
export const ACTION_DELETE = 'delete'

@ -11,7 +11,10 @@ import { ShareType } from '@nextcloud/sharing'
import { isPublicShare } from '@nextcloud/sharing/public'
import Vue from 'vue'
import FileListFilterAccount from '../components/FileListFilterAccount.vue'
import { TRASHBIN_VIEW_ID } from '../../../files_trashbin/src/files_views/trashbinView.ts'
// once files_sharing is migrated to the new frontend use the import instead:
// import { TRASHBIN_VIEW_ID } from '../../../files_trashbin/src/files_views/trashbinView.ts'
const TRASHBIN_VIEW_ID = 'trashbin'
export interface IAccountData {
uid: string

@ -7,16 +7,23 @@ import * as ncEventBus from '@nextcloud/event-bus'
import { Folder } from '@nextcloud/files'
import isSvg from 'is-svg'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { PERMISSION_ALL, PERMISSION_NONE } from '../../../../core/src/OC/constants.js'
import { trashbinView } from '../files_views/trashbinView.ts'
import { restoreAction } from './restoreAction.ts'
// TODO: once core is migrated to the new frontend use the import instead:
// import { PERMISSION_ALL, PERMISSION_NONE } from '../../../../core/src/OC/constants.js'
export const PERMISSION_NONE = 0
export const PERMISSION_ALL = 31
const axiosMock = vi.hoisted(() => ({
request: vi.fn(),
}))
vi.mock('@nextcloud/axios', () => ({ default: axiosMock }))
vi.mock('@nextcloud/axios', async (origial) => ({ ...(await origial()), default: axiosMock }))
vi.mock('@nextcloud/auth')
const errorSpy = vi.spyOn(window.console, 'error').mockImplementation(() => {})
beforeEach(() => errorSpy.mockClear())
describe('files_trashbin: file actions - restore action', () => {
it('has id set', () => {
expect(restoreAction.id).toBe('restore')
@ -99,9 +106,9 @@ describe('files_trashbin: file actions - restore action', () => {
expect(await restoreAction.exec(node, trashbinView, '/')).toBe(true)
expect(axiosMock.request).toBeCalled()
expect(axiosMock.request.mock.calls[0][0].method).toBe('MOVE')
expect(axiosMock.request.mock.calls[0][0].url).toBe(node.encodedSource)
expect(axiosMock.request.mock.calls[0][0].headers.destination).toContain('/restore/')
expect(axiosMock.request.mock.calls[0]![0].method).toBe('MOVE')
expect(axiosMock.request.mock.calls[0]![0].url).toBe(node.encodedSource)
expect(axiosMock.request.mock.calls[0]![0].headers.destination).toContain('/restore/')
})
it('deletes node from current view after successfull request', async () => {
@ -115,7 +122,7 @@ describe('files_trashbin: file actions - restore action', () => {
expect(emitSpy).toBeCalledWith('files:node:deleted', node)
})
it('does not delete node from view if reuest failed', async () => {
it('does not delete node from view if request failed', async () => {
const node = new Folder({ owner: 'test', source: 'https://example.com/remote.php/dav/trashbin/test/folder', root: '/trashbin/test/', permissions: PERMISSION_ALL })
axiosMock.request.mockImplementationOnce(() => {
@ -126,6 +133,7 @@ describe('files_trashbin: file actions - restore action', () => {
expect(await restoreAction.exec(node, trashbinView, '/')).toBe(false)
expect(axiosMock.request).toBeCalled()
expect(emitSpy).not.toBeCalled()
expect(errorSpy).toBeCalled()
})
it('batch: only returns success if all requests worked', async () => {
@ -143,6 +151,7 @@ describe('files_trashbin: file actions - restore action', () => {
})
expect(await restoreAction.execBatch!([node, node], trashbinView, '/')).toStrictEqual([false, true])
expect(axiosMock.request).toBeCalledTimes(2)
expect(errorSpy).toBeCalled()
})
})
})

@ -1,20 +1,21 @@
import type { Node, View } from '@nextcloud/files'
import svgHistory from '@mdi/svg/svg/history.svg?raw'
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { Node, View } from '@nextcloud/files'
import svgHistory from '@mdi/svg/svg/history.svg?raw'
import { getCurrentUser } from '@nextcloud/auth'
import axios from '@nextcloud/axios'
import axios, { isAxiosError } from '@nextcloud/axios'
import { showError } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
import { FileAction, Permission } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import { encodePath } from '@nextcloud/paths'
import { generateRemoteUrl } from '@nextcloud/router'
import logger from '../../../files/src/logger.ts'
import { TRASHBIN_VIEW_ID } from '../files_views/trashbinView.ts'
import { logger } from '../logger.ts'
export const restoreAction = new FileAction({
id: 'restore',
@ -54,7 +55,7 @@ export const restoreAction = new FileAction({
emit('files:node:deleted', node)
return true
} catch (error) {
if (error.response?.status === 507) {
if (isAxiosError(error) && error.response?.status === 507) {
showError(t('files_trashbin', 'Not enough free space to restore the file/folder'))
}
logger.error('Failed to restore node', { error, node })

@ -126,7 +126,7 @@ describe('files_trashbin: file list actions - empty trashbin', () => {
dialogBuilder.build.mockImplementationOnce(() => ({
show: async () => {
const buttons = dialogBuilder.setButtons.mock.calls[0][0]
const buttons = dialogBuilder.setButtons.mock.calls[0]![0]
const cancel = buttons.find(({ label }) => label === 'Cancel')
await cancel.callback()
},
@ -142,7 +142,7 @@ describe('files_trashbin: file list actions - empty trashbin', () => {
dialogBuilder.build.mockImplementationOnce(() => ({
show: async () => {
const buttons = dialogBuilder.setButtons.mock.calls[0][0]
const buttons = dialogBuilder.setButtons.mock.calls[0]![0]
const cancel = buttons.find(({ label }) => label === 'Empty deleted files')
await cancel.callback()
},
@ -160,7 +160,7 @@ describe('files_trashbin: file list actions - empty trashbin', () => {
dialogBuilder.build.mockImplementationOnce(() => ({
show: async () => {
const buttons = dialogBuilder.setButtons.mock.calls[0][0]
const buttons = dialogBuilder.setButtons.mock.calls[0]![0]
const cancel = buttons.find(({ label }) => label === 'Empty deleted files')
await cancel.callback()
},

@ -180,14 +180,14 @@ describe('files_trashbin: file list columns', () => {
const node = new File({ owner: 'test', source: 'https://example.com/remote.php/dav/files/test/a.txt', mime: 'text/plain', attributes: { 'trashbin-deleted-by-id': 'user-id' } })
const el: HTMLElement = deletedBy.render(node, trashbinView)
expect(el).toBeInstanceOf(HTMLElement)
expect(el.textContent).toMatch(/\suser-id\s/)
expect(el.textContent.trim()).toBe('user-id')
})
it('renders a node with deleting user display name', () => {
const node = new File({ owner: 'test', source: 'https://example.com/remote.php/dav/files/test/a.txt', mime: 'text/plain', attributes: { 'trashbin-deleted-by-display-name': 'user-name', 'trashbin-deleted-by-id': 'user-id' } })
const el: HTMLElement = deletedBy.render(node, trashbinView)
expect(el).toBeInstanceOf(HTMLElement)
expect(el.textContent).toMatch(/\suser-name\s/)
expect(el.textContent.trim()).toBe('user-name')
})
it('renders a node even when information is missing', () => {

@ -9,7 +9,7 @@ import { getCurrentUser } from '@nextcloud/auth'
import { Column } from '@nextcloud/files'
import { formatRelativeTime, getCanonicalLocale, getLanguage, t } from '@nextcloud/l10n'
import { dirname } from '@nextcloud/paths'
import Vue from 'vue'
import { createApp } from 'vue'
import NcUserBubble from '@nextcloud/vue/components/NcUserBubble'
export const originalLocation = new Column({
@ -40,14 +40,13 @@ export const deletedBy = new Column({
return span
}
const UserBubble = Vue.extend(NcUserBubble)
const propsData = {
const el = document.createElement('div')
createApp(NcUserBubble, {
size: 32,
user: userId ?? undefined,
displayName: displayName ?? userId,
}
const userBubble = new UserBubble({ propsData }).$mount().$el
return userBubble as HTMLElement
}).mount(el)
return el
},
sort(nodeA, nodeB) {
const deletedByA = parseDeletedBy(nodeA)

@ -1,8 +1,9 @@
import isSvg from 'is-svg'
/**
/*!
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import isSvg from 'is-svg'
import { describe, expect, it } from 'vitest'
import { getContents } from '../services/trashbin.ts'
import { deleted, deletedBy, originalLocation } from './columns.ts'

@ -1,8 +1,9 @@
import svgDelete from '@mdi/svg/svg/trash-can-outline.svg?raw'
/**
/*!
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import svgDelete from '@mdi/svg/svg/trash-can-outline.svg?raw'
import { View } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import { getContents } from '../services/trashbin.ts'

@ -13,8 +13,8 @@ describe('files_trashbin: logger', () => {
logger.error('<message>')
expect(consoleSpy).toBeCalledTimes(1)
expect(consoleSpy.mock.calls[0][0]).toContain('<message>')
expect(consoleSpy.mock.calls[0][0]).toContain('files_trashbin')
expect(consoleSpy.mock.calls[0][1].app).toBe('files_trashbin')
expect(consoleSpy.mock.calls[0]![0]).toContain('<message>')
expect(consoleSpy.mock.calls[0]![0]).toContain('files_trashbin')
expect(consoleSpy.mock.calls[0]![1].app).toBe('files_trashbin')
})
})

@ -4,9 +4,8 @@
*/
import { getCurrentUser } from '@nextcloud/auth'
import { davGetClient } from '@nextcloud/files'
import { getClient } from '@nextcloud/files/dav'
// init webdav client
export const rootPath = `/trashbin/${getCurrentUser()?.uid}/trash`
export const client = davGetClient()
export const client = getClient()

@ -1,11 +1,12 @@
import type { ContentsWithRoot, File, Folder } from '@nextcloud/files'
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { ContentsWithRoot, Folder, Node } from '@nextcloud/files'
import type { FileStat, ResponseDataDetailed } from 'webdav'
import { davResultToNode, getDavNameSpaces, getDavProperties } from '@nextcloud/files'
import { resultToNode as davResultToNode, getDavNameSpaces, getDavProperties } from '@nextcloud/files/dav'
import { generateUrl } from '@nextcloud/router'
import { client, rootPath } from './client.ts'
@ -22,18 +23,21 @@ const data = `<?xml version="1.0"?>
</d:propfind>`
/**
* Converts a WebDAV file stat to a File or Folder
* This will fix the preview URL attribute for trashbin items
*
* @param stat
* @param stat - The file stat object from WebDAV response
*/
function resultToNode(stat: FileStat): File | Folder {
function resultToNode(stat: FileStat): Node {
const node = davResultToNode(stat, rootPath)
node.attributes.previewUrl = generateUrl('/apps/files_trashbin/preview?fileId={fileid}&x=32&y=32', { fileid: node.fileid })
return node
}
/**
* Get the contents of a trashbin folder
*
* @param path
* @param path - The path of the trashbin folder to get contents from
*/
export async function getContents(path = '/'): Promise<ContentsWithRoot> {
const contentsResponse = await client.getDirectoryContents(`${rootPath}${path}`, {

@ -0,0 +1,9 @@
/*!
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
declare module '*?raw' {
const content: string
export default content
}

@ -2,6 +2,7 @@
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
.files-list__row-trashbin-original-location {
width: 150px !important;
width: 150px !important;
}

@ -57,9 +57,6 @@ module.exports = {
'personal-settings': path.join(__dirname, 'apps/files_sharing/src', 'personal-settings.js'),
'public-nickname-handler': path.join(__dirname, 'apps/files_sharing/src', 'public-nickname-handler.ts'),
},
files_trashbin: {
init: path.join(__dirname, 'apps/files_trashbin/src', 'files-init.ts'),
},
oauth2: {
oauth2: path.join(__dirname, 'apps/oauth2/src', 'main.js'),
},

@ -7,6 +7,9 @@ import { createAppConfig } from '@nextcloud/vite-config'
import { resolve } from 'node:path'
const modules = {
files_trashbin: {
init: resolve(import.meta.dirname, 'apps/files_trashbin/src', 'files-init.ts'),
},
files_versions: {
'sidebar-tab': resolve(import.meta.dirname, 'apps/files_versions/src', 'sidebar_tab.ts'),
},

87
package-lock.json generated

@ -213,7 +213,6 @@
"integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6.9.0"
}
@ -256,7 +255,6 @@
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"license": "ISC",
"peer": true,
"bin": {
"semver": "bin/semver.js"
}
@ -267,7 +265,6 @@
"integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/parser": "^7.28.3",
"@babel/types": "^7.28.2",
@ -285,7 +282,6 @@
"integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/compat-data": "^7.27.2",
"@babel/helper-validator-option": "^7.27.1",
@ -303,7 +299,6 @@
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"license": "ISC",
"peer": true,
"bin": {
"semver": "bin/semver.js"
}
@ -314,7 +309,6 @@
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6.9.0"
}
@ -325,7 +319,6 @@
"integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/traverse": "^7.27.1",
"@babel/types": "^7.27.1"
@ -340,7 +333,6 @@
"integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/helper-module-imports": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1",
@ -386,7 +378,6 @@
"integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6.9.0"
}
@ -397,7 +388,6 @@
"integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/template": "^7.27.2",
"@babel/types": "^7.28.4"
@ -451,7 +441,6 @@
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/parser": "^7.27.2",
@ -467,7 +456,6 @@
"integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.3",
@ -565,6 +553,7 @@
"integrity": "sha512-h0Un1ieD+HUrzBH6dJXhod3ifSghk5Hw/2Y4/KHBziPlZecrFyE9YOTPU6eOs0V9pYl8gOs86fkr/KN8lUX39A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@keyv/serialize": "^1.1.1"
}
@ -717,6 +706,7 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
},
@ -763,6 +753,7 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
}
@ -1891,7 +1882,6 @@
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
@ -2356,6 +2346,7 @@
"resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-3.4.0.tgz",
"integrity": "sha512-K4UBSl0Ou6sXXLxyjuhktRf2FyTCjyvHxJyBLmS2z3YEYcRkpf8ib3XneRwEQIEpzBPQjul2/ZdFlt7umd8Gaw==",
"license": "GPL-3.0-or-later",
"peer": true,
"dependencies": {
"@nextcloud/router": "^3.0.1",
"@nextcloud/typings": "^1.9.1",
@ -2514,6 +2505,7 @@
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-9.1.0.tgz",
"integrity": "sha512-iXXLQ6Ps0pH1VQ9fmUTvUKUaR+ForwXdcegasTgrgkfBpomdm4VTrqTU910o/A99Cs6C+ChgyLEVuFPq/W9Lcw==",
"peer": true,
"dependencies": {
"@ckpack/vue-color": "^1.6.0",
"@floating-ui/dom": "^1.7.4",
@ -3379,6 +3371,7 @@
"integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"json-schema-traverse": "^1.0.0",
@ -3545,6 +3538,7 @@
"integrity": "sha512-IeZF+8H0ns6prg4VrkhgL+yrvDXWDH2cKchrbh80ejG9dQgZWp10epHMbgRuQvgchLII/lfh6Xn3lu6+6L86Hw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.0",
"@typescript-eslint/types": "^8.46.1",
@ -3843,6 +3837,7 @@
"integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~7.16.0"
}
@ -3992,6 +3987,7 @@
"integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.46.2",
"@typescript-eslint/types": "8.46.2",
@ -4451,6 +4447,7 @@
"version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.24.tgz",
"integrity": "sha512-8EG5YPRgmTB+YxYBM3VXy8zHD9SWHUJLIGPhDovo3Z8VOgvP+O7UP5vl0J4BBPWYD9vxtBabzW1EuEZ+Cqs14g==",
"peer": true,
"dependencies": {
"@babel/parser": "^7.28.5",
"@vue/compiler-core": "3.5.24",
@ -4733,6 +4730,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@ -5523,6 +5521,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.9",
"caniuse-lite": "^1.0.30001746",
@ -6209,8 +6208,7 @@
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/copy-anything": {
"version": "4.0.5",
@ -6477,6 +6475,7 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@cypress/request": "^3.0.9",
"@cypress/xvfb": "^1.2.4",
@ -7194,7 +7193,6 @@
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
@ -7210,7 +7208,6 @@
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
"license": "BSD-2-Clause",
"peer": true,
"engines": {
"node": ">=0.12"
},
@ -7241,8 +7238,7 @@
"url": "https://github.com/sponsors/fb55"
}
],
"license": "BSD-2-Clause",
"peer": true
"license": "BSD-2-Clause"
},
"node_modules/domhandler": {
"version": "5.0.3",
@ -7250,7 +7246,6 @@
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"dev": true,
"license": "BSD-2-Clause",
"peer": true,
"dependencies": {
"domelementtype": "^2.3.0"
},
@ -7276,7 +7271,6 @@
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
"dev": true,
"license": "BSD-2-Clause",
"peer": true,
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
@ -7461,6 +7455,7 @@
"integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"ansi-colors": "^4.1.1",
"strip-ansi": "^6.0.1"
@ -7678,6 +7673,7 @@
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz",
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
"dev": true,
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@ -8722,7 +8718,6 @@
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6.9.0"
}
@ -9303,7 +9298,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
@ -9317,7 +9311,6 @@
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
"license": "BSD-2-Clause",
"peer": true,
"engines": {
"node": ">=0.12"
},
@ -10493,7 +10486,6 @@
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"jsesc": "bin/jsesc"
},
@ -10655,7 +10647,6 @@
"integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
"license": "MPL-2.0",
"optional": true,
"peer": true,
"dependencies": {
"detect-libc": "^2.0.3"
},
@ -10692,7 +10683,6 @@
"os": [
"android"
],
"peer": true,
"engines": {
"node": ">= 12.0.0"
},
@ -10713,7 +10703,6 @@
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">= 12.0.0"
},
@ -10734,7 +10723,6 @@
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">= 12.0.0"
},
@ -10755,7 +10743,6 @@
"os": [
"freebsd"
],
"peer": true,
"engines": {
"node": ">= 12.0.0"
},
@ -10776,7 +10763,6 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">= 12.0.0"
},
@ -10797,7 +10783,6 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">= 12.0.0"
},
@ -10818,7 +10803,6 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">= 12.0.0"
},
@ -10839,7 +10823,6 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">= 12.0.0"
},
@ -10860,7 +10843,6 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">= 12.0.0"
},
@ -10881,7 +10863,6 @@
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">= 12.0.0"
},
@ -10902,7 +10883,6 @@
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">= 12.0.0"
},
@ -10917,7 +10897,6 @@
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"license": "Apache-2.0",
"optional": true,
"peer": true,
"engines": {
"node": ">=8"
}
@ -11145,7 +11124,6 @@
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"dev": true,
"license": "ISC",
"peer": true,
"dependencies": {
"yallist": "^3.0.2"
}
@ -12998,6 +12976,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@ -13013,7 +12992,6 @@
"integrity": "sha512-5mMeb1TgLWoRKxZ0Xh9RZDfwUUIqRrcxO2uXO+Ezl1N5lqpCiSU5Gk6+1kZediBfBHFtPCdopr2UZ2SgUsKcgQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"htmlparser2": "^8.0.0",
"js-tokens": "^9.0.0",
@ -13029,16 +13007,14 @@
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
"integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/postcss-media-query-parser": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz",
"integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/postcss-resolve-nested-selector": {
"version": "0.1.6",
@ -13053,7 +13029,6 @@
"integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12.0"
},
@ -13085,7 +13060,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12.0"
},
@ -13858,6 +13832,7 @@
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz",
"integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/estree": "1.0.8"
},
@ -14066,6 +14041,7 @@
"resolved": "https://registry.npmjs.org/sass/-/sass-1.93.3.tgz",
"integrity": "sha512-elOcIZRTM76dvxNAjqYrucTSI0teAF/L2Lv0s6f6b7FOwcwIuA357bIE871580AjHJuSvLIRUosgV+lIWx6Rgg==",
"devOptional": true,
"peer": true,
"dependencies": {
"chokidar": "^4.0.0",
"immutable": "^5.0.2",
@ -15025,6 +15001,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-tokenizer": "^3.0.4",
@ -15078,7 +15055,6 @@
"integrity": "sha512-IZv4IVESjKLumUGi+HWeb7skgO6/g4VMuAYrJdlqQFndgbj6WJAXPhaysvBiXefX79upBdQVumgYcdd17gCpjQ==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": "^12 || >=14"
},
@ -15106,7 +15082,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18.12.0"
},
@ -15168,7 +15143,6 @@
"integrity": "sha512-UJUfBFIvXfly8WKIgmqfmkGKPilKB4L5j38JfsDd+OCg2GBdU0vGUV08Uw82tsRZzd4TbsUURVVNGeOhJVF7pA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"css-tree": "^3.0.1",
"is-plain-object": "^5.0.0",
@ -15191,16 +15165,14 @@
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.36.0.tgz",
"integrity": "sha512-A+9jP+IUmuQsNdsLdcg6Yt7voiMF/D4K83ew0OpJtpu+l34ef7LaohWV0Rc6KNvzw6ZDizkqfyB5JznZnzuKQA==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/stylelint-scss/node_modules/mdn-data": {
"version": "2.24.0",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.24.0.tgz",
"integrity": "sha512-i97fklrJl03tL1tdRVw0ZfLLvuDsdb6wxL+TrJ+PKkCbLrp2PCu2+OYdCKychIUm19nSM/35S6qz7pJpnXttoA==",
"dev": true,
"license": "CC0-1.0",
"peer": true
"license": "CC0-1.0"
},
"node_modules/stylelint-scss/node_modules/postcss-selector-parser": {
"version": "7.1.0",
@ -15208,7 +15180,6 @@
"integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@ -15325,6 +15296,7 @@
"integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@ -16077,6 +16049,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@ -16445,6 +16418,7 @@
"version": "7.1.12",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz",
"integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
@ -16596,6 +16570,7 @@
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/chai": "^5.2.2",
"@vitest/expect": "3.2.4",
@ -16680,6 +16655,7 @@
"version": "3.5.24",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.24.tgz",
"integrity": "sha512-uTHDOpVQTMjcGgrqFPSb8iO2m1DUvo+WbGqoXQz8Y1CeBYQ0FXf2z1gLRaBtHjlRz7zZUBHxjVB5VTLzYkvftg==",
"peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.24",
"@vue/compiler-sfc": "3.5.24",
@ -17193,8 +17169,7 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true,
"license": "ISC",
"peer": true
"license": "ISC"
},
"node_modules/yargs": {
"version": "17.7.2",