mirror of
https://github.com/nextcloud/server.git
synced 2026-05-19 08:25:56 -04:00
refactor(appstore): migrate app bundles view to Vue 3
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
parent
a4d8b3be43
commit
26ef27bfb1
11 changed files with 291 additions and 32 deletions
|
|
@ -15,7 +15,15 @@ import { APPSTORE_CATEGORY_NAMES } from './constants.ts'
|
|||
|
||||
const route = useRoute()
|
||||
|
||||
const currentCategory = computed(() => [route.params.category].flat()[0] ?? 'discover')
|
||||
const currentCategory = computed(() => {
|
||||
if (route.params.category) {
|
||||
return [route.params.category].flat()[0]!
|
||||
}
|
||||
if (route.name === 'apps-bundles') {
|
||||
return 'bundles'
|
||||
}
|
||||
return 'discover'
|
||||
})
|
||||
const heading = computed(() => APPSTORE_CATEGORY_NAMES[currentCategory.value] ?? currentCategory.value)
|
||||
const pageTitle = computed(() => `${heading.value} - ${t('appstore', 'App store')}`)
|
||||
|
||||
|
|
|
|||
6
apps/appstore/src/apps.d.ts
vendored
6
apps/appstore/src/apps.d.ts
vendored
|
|
@ -138,3 +138,9 @@ export interface IAppstoreExApp extends IAppstoreApp {
|
|||
error?: string
|
||||
releases: IAppstoreExAppRelease[]
|
||||
}
|
||||
|
||||
export interface IAppBundle {
|
||||
id: string
|
||||
name: string
|
||||
appIdentifiers: readonly string[]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,29 +6,40 @@
|
|||
import type { MaybeRefOrGetter } from 'vue'
|
||||
import type { IAppstoreApp, IAppstoreExApp } from '../apps.d.ts'
|
||||
|
||||
import { mdiCheck, mdiClose, mdiDownload, mdiTrashCanOutline, mdiUpdate } from '@mdi/js'
|
||||
import { mdiAlertCircleCheckOutline, mdiCheck, mdiClose, mdiDownload, mdiTrashCanOutline, mdiUpdate } from '@mdi/js'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { computed, toValue } from 'vue'
|
||||
import { useAppsStore } from '../store/apps.ts'
|
||||
import { useUpdatesStore } from '../store/updates.ts'
|
||||
import { canDisable, canEnable, canForceEnable, canInstall, canUninstall, canUpdate } from '../utils/appStatus.ts'
|
||||
import { canDisable, canEnable, canInstall, canUninstall, canUpdate, needForceEnable } from '../utils/appStatus.ts'
|
||||
|
||||
type AppAction = {
|
||||
id: string
|
||||
icon: string
|
||||
label: (app: IAppstoreApp | IAppstoreExApp) => string
|
||||
callback: (app: IAppstoreApp | IAppstoreExApp) => Promise<void>
|
||||
variant?: 'primary' | 'error' | 'warning'
|
||||
inline?: boolean
|
||||
}
|
||||
|
||||
const AppAction = Object.freeze({
|
||||
INSTALL: {
|
||||
id: 'install',
|
||||
icon: mdiDownload,
|
||||
variant: 'primary',
|
||||
label: (app: IAppstoreApp | IAppstoreExApp) => {
|
||||
if (app.app_api) {
|
||||
return t('appstore', 'Deploy and enable')
|
||||
}
|
||||
return t('appstore', 'Download and enable')
|
||||
if (app.needsDownload) {
|
||||
return t('appstore', 'Download and enable')
|
||||
}
|
||||
return t('appstore', 'Install and enable')
|
||||
},
|
||||
async callback(app: IAppstoreApp | IAppstoreExApp) {
|
||||
const store = useAppsStore()
|
||||
await store.enableApp(app.id)
|
||||
},
|
||||
} as const,
|
||||
} as AppAction,
|
||||
ENABLE: {
|
||||
id: 'enable',
|
||||
icon: mdiCheck,
|
||||
|
|
@ -38,37 +49,38 @@ const AppAction = Object.freeze({
|
|||
const store = useAppsStore()
|
||||
await store.enableApp(app.id)
|
||||
},
|
||||
} as const,
|
||||
} as AppAction,
|
||||
FORCE_ENABLE: {
|
||||
id: 'force-enable',
|
||||
icon: mdiCheck,
|
||||
variant: 'primary',
|
||||
icon: mdiAlertCircleCheckOutline,
|
||||
inline: false,
|
||||
label: () => t('appstore', 'Force enable'),
|
||||
variant: 'warning',
|
||||
async callback(app: IAppstoreApp | IAppstoreExApp) {
|
||||
const store = useAppsStore()
|
||||
await store.forceEnableApp(app.id)
|
||||
},
|
||||
} as const,
|
||||
} as AppAction,
|
||||
DISABLE: {
|
||||
id: 'disable',
|
||||
icon: mdiClose,
|
||||
variant: 'tertiary',
|
||||
label: () => t('appstore', 'Disable'),
|
||||
async callback(app: IAppstoreApp | IAppstoreExApp) {
|
||||
const store = useAppsStore()
|
||||
await store.disableApp(app.id)
|
||||
},
|
||||
} as const,
|
||||
} as AppAction,
|
||||
REMOVE: {
|
||||
id: 'remove',
|
||||
icon: mdiTrashCanOutline,
|
||||
variant: 'error',
|
||||
inline: false,
|
||||
label: () => t('appstore', 'Remove'),
|
||||
async callback(app: IAppstoreApp | IAppstoreExApp) {
|
||||
const store = useAppsStore()
|
||||
await store.uninstallApp(app.id)
|
||||
},
|
||||
} as const,
|
||||
} as AppAction,
|
||||
UPDATE: {
|
||||
id: 'update',
|
||||
icon: mdiUpdate,
|
||||
|
|
@ -78,7 +90,7 @@ const AppAction = Object.freeze({
|
|||
const store = useUpdatesStore()
|
||||
await store.updateApp(app.id)
|
||||
},
|
||||
} as const,
|
||||
} as AppAction,
|
||||
})
|
||||
|
||||
/**
|
||||
|
|
@ -97,12 +109,12 @@ export function useActions(app: MaybeRefOrGetter<IAppstoreApp | IAppstoreExApp>)
|
|||
actions.push(AppAction.DISABLE)
|
||||
}
|
||||
|
||||
if (canInstall(toValue(app))) {
|
||||
if (needForceEnable(toValue(app))) {
|
||||
actions.push(AppAction.FORCE_ENABLE)
|
||||
} else if (canInstall(toValue(app))) {
|
||||
actions.push(AppAction.INSTALL)
|
||||
} else if (canEnable(toValue(app))) {
|
||||
actions.push(AppAction.ENABLE)
|
||||
} else if (canForceEnable(toValue(app))) {
|
||||
actions.push(AppAction.FORCE_ENABLE)
|
||||
}
|
||||
|
||||
if (canUninstall(toValue(app))) {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ const appstoreEnabled = loadState<boolean>('appstore', 'appstoreEnabled', true)
|
|||
// Dynamic loading
|
||||
const AppstoreDiscover = defineAsyncComponent(() => import('../views/AppstoreDiscover.vue'))
|
||||
const AppstoreManage = defineAsyncComponent(() => import('../views/AppstoreManage.vue'))
|
||||
const AppstoreBundles = defineAsyncComponent(() => import('../views/AppstoreBundles.vue'))
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
|
|
@ -32,6 +33,11 @@ const routes: RouteRecordRaw[] = [
|
|||
name: 'apps-discover',
|
||||
component: AppstoreDiscover,
|
||||
},
|
||||
{
|
||||
path: 'bundles/:id?',
|
||||
name: 'apps-bundles',
|
||||
component: AppstoreBundles,
|
||||
},
|
||||
{
|
||||
path: ':category(installed|enabled|disabled|updates)/:id?',
|
||||
name: 'apps-manage',
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import type { IAppstoreApp, IAppstoreCategory } from '../apps.d.ts'
|
|||
import axios from '@nextcloud/axios'
|
||||
import { addPasswordConfirmationInterceptors, PwdConfirmationMode } from '@nextcloud/password-confirmation'
|
||||
import { generateOcsUrl } from '@nextcloud/router'
|
||||
import PQueue from 'p-queue'
|
||||
import { APPSTORE_CATEGORY_ICONS } from '../constants.ts'
|
||||
|
||||
addPasswordConfirmationInterceptors(axios)
|
||||
|
|
@ -21,8 +22,11 @@ const Url = Object.freeze({
|
|||
disable: `${BASE_URL}/apps/disable`,
|
||||
uninstall: `${BASE_URL}/apps/uninstall`,
|
||||
update: `${BASE_URL}/apps/update`,
|
||||
bundleEnable: `${BASE_URL}/bundles/enable`,
|
||||
})
|
||||
|
||||
const queue = new PQueue({ concurrency: 1 })
|
||||
|
||||
/**
|
||||
* Enable an app by its app id
|
||||
*
|
||||
|
|
@ -30,7 +34,9 @@ const Url = Object.freeze({
|
|||
* @param force - Whether to force enable the app
|
||||
*/
|
||||
export async function enableApp(appId: string, force = false) {
|
||||
await axios.post(Url.enable, { appId, force: force || undefined }, { confirmPassword: PwdConfirmationMode.Strict })
|
||||
return queue.add(async () => {
|
||||
await axios.post(Url.enable, { appId, force: force || undefined }, { confirmPassword: PwdConfirmationMode.Strict })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -39,7 +45,9 @@ export async function enableApp(appId: string, force = false) {
|
|||
* @param appId - The app to disable
|
||||
*/
|
||||
export async function disableApp(appId: string) {
|
||||
await axios.post(Url.disable, { appId }, { confirmPassword: PwdConfirmationMode.Lax })
|
||||
return queue.add(async () => {
|
||||
await axios.post(Url.disable, { appId }, { confirmPassword: PwdConfirmationMode.Lax })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -48,7 +56,9 @@ export async function disableApp(appId: string) {
|
|||
* @param appId - The app id to update
|
||||
*/
|
||||
export async function updateApp(appId: string) {
|
||||
await axios.post(Url.update, { appId }, { confirmPassword: PwdConfirmationMode.Strict })
|
||||
return queue.add(async () => {
|
||||
await axios.post(Url.update, { appId }, { confirmPassword: PwdConfirmationMode.Strict })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -57,7 +67,9 @@ export async function updateApp(appId: string) {
|
|||
* @param appId - The app to uninstall
|
||||
*/
|
||||
export async function uninstallApp(appId: string) {
|
||||
await axios.post(Url.uninstall, { appId }, { confirmPassword: PwdConfirmationMode.Lax })
|
||||
return queue.add(async () => {
|
||||
await axios.post(Url.uninstall, { appId }, { confirmPassword: PwdConfirmationMode.Strict })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -78,3 +90,14 @@ export async function getCategories() {
|
|||
}
|
||||
return data.ocs.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable an app bundle by its id
|
||||
*
|
||||
* @param bundleId - The id of the bundle to enable
|
||||
*/
|
||||
export async function enableBundle(bundleId: string) {
|
||||
return queue.add(async () => {
|
||||
await axios.post(Url.bundleEnable, { bundleId }, { confirmPassword: PwdConfirmationMode.Strict })
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { IAppstoreApp, IAppstoreCategory, IAppstoreExApp } from '../apps.d.ts'
|
||||
import type { IAppBundle, IAppstoreApp, IAppstoreCategory, IAppstoreExApp } from '../apps.d.ts'
|
||||
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, readonly, ref } from 'vue'
|
||||
import * as api from '../service/api.ts'
|
||||
import { rebuildNavigation } from '../service/rebuild-navigation.ts'
|
||||
import { canDisable, canInstall, canUninstall, needForceEnable } from '../utils/appStatus.ts'
|
||||
|
|
@ -26,6 +27,11 @@ export const useAppsStore = defineStore('apps', () => {
|
|||
* All app categories available in the appstore
|
||||
*/
|
||||
const categories = ref<IAppstoreCategory[]>([])
|
||||
/**
|
||||
* All app bundles available in the appstore
|
||||
*/
|
||||
const bundles = readonly(loadState<IAppBundle[]>('appstore', 'appstoreBundles'))
|
||||
|
||||
/**
|
||||
* Loading state of the store
|
||||
*/
|
||||
|
|
@ -175,6 +181,38 @@ export const useAppsStore = defineStore('apps', () => {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable a whole bundle of apps by its id
|
||||
*
|
||||
* @param bundleId - The id of the bundle to enable
|
||||
*/
|
||||
async function enableBundle(bundleId: string) {
|
||||
const bundle = bundles.find((b) => b.id === bundleId)
|
||||
if (!bundle) {
|
||||
throw new Error(`Bundle with id ${bundleId} not found`)
|
||||
}
|
||||
|
||||
try {
|
||||
for (const appId of bundle.appIdentifiers) {
|
||||
const app = getAppById(appId)!
|
||||
app.loading = true
|
||||
}
|
||||
await api.enableBundle(bundle.id)
|
||||
for (const appId of bundle.appIdentifiers) {
|
||||
const app = getAppById(appId)!
|
||||
app.active = true
|
||||
app.installed = true
|
||||
app.removable = true
|
||||
await rebuildNavigation()
|
||||
}
|
||||
} finally {
|
||||
for (const appId of bundle.appIdentifiers) {
|
||||
const app = getAppById(appId)!
|
||||
app.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the app categories from the backend
|
||||
*/
|
||||
|
|
@ -211,6 +249,7 @@ export const useAppsStore = defineStore('apps', () => {
|
|||
|
||||
return {
|
||||
apps,
|
||||
bundles,
|
||||
categories,
|
||||
isLoadingApps,
|
||||
isLoadingCategories,
|
||||
|
|
@ -218,6 +257,7 @@ export const useAppsStore = defineStore('apps', () => {
|
|||
disableApp,
|
||||
enableApp,
|
||||
uninstallApp,
|
||||
enableBundle,
|
||||
|
||||
getAppById,
|
||||
getAppsByCategory,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,20 @@ import type { IAppstoreApp, IAppstoreExApp } from '../apps.d.ts'
|
|||
* @param app - The app to check if installable
|
||||
*/
|
||||
export function canInstall(app: IAppstoreApp | IAppstoreExApp) {
|
||||
return !app.installed && (!app.missingDependencies || app.missingDependencies.length === 0)
|
||||
if (app.installed || app.internal) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (app.missingDependencies === undefined || app.missingDependencies.length === 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (!app.isCompatible && app.missingDependencies.length === 1) {
|
||||
// incompatible so can be installed but has to be force-enabled
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -41,6 +54,15 @@ export function canForceEnable(app: IAppstoreApp | IAppstoreExApp) {
|
|||
return !app.active && (app.installed || canInstall(app))
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an app needs to be force-enabled
|
||||
*
|
||||
* @param app - The app to check
|
||||
*/
|
||||
export function needForceEnable(app: IAppstoreApp | IAppstoreExApp) {
|
||||
return !app.active && !app.isCompatible
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an app can be disabled.
|
||||
*
|
||||
|
|
|
|||
112
apps/appstore/src/views/AppstoreBundles.vue
Normal file
112
apps/appstore/src/views/AppstoreBundles.vue
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { IAppBundle, IAppstoreApp } from '../apps.d.ts'
|
||||
|
||||
import { mdiDownloadMultiple } from '@mdi/js'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { computed } from 'vue'
|
||||
import NcButton from '@nextcloud/vue/components/NcButton'
|
||||
import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
|
||||
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
|
||||
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
|
||||
import AppTable from '../components/AppTable/AppTable.vue'
|
||||
import { useAppsStore } from '../store/apps.ts'
|
||||
import { canEnable } from '../utils/appStatus.ts'
|
||||
|
||||
const store = useAppsStore()
|
||||
|
||||
const appBundles = computed(() => store.bundles.map((bundle) => ({
|
||||
...bundle,
|
||||
apps: bundle.appIdentifiers
|
||||
.map((id) => store.apps.find((app) => app.id === id))
|
||||
.filter(Boolean) as IAppstoreApp[],
|
||||
isEnabling: false,
|
||||
})))
|
||||
|
||||
/**
|
||||
* Check if a bundle can be enabled
|
||||
*
|
||||
* @param bundle - The bundle to check
|
||||
*/
|
||||
function canEnableBundle(bundle: IAppBundle): boolean {
|
||||
return bundle.appIdentifiers.every((id) => {
|
||||
const app = store.apps.find((app) => app.id === id)
|
||||
return app && (app.active || canEnable(app))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a bundle is enabled
|
||||
*
|
||||
* @param bundle - The bundle to check
|
||||
*/
|
||||
function isBundleEnabled(bundle: IAppBundle): boolean {
|
||||
return bundle.appIdentifiers.every((id) => {
|
||||
const app = store.apps.find((app) => app.id === id)
|
||||
return app && app.active
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable all apps in a bundle
|
||||
*
|
||||
* @param bundle - The bundle to enable all apps
|
||||
*/
|
||||
async function enableAll(bundle: typeof appBundles.value[number]) {
|
||||
bundle.isEnabling = true
|
||||
await store.enableBundle(bundle.id)
|
||||
bundle.isEnabling = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Apps list -->
|
||||
<NcEmptyContent
|
||||
v-if="store.isLoadingApps"
|
||||
:name="t('appstore', 'Loading app list')">
|
||||
<template #icon>
|
||||
<NcLoadingIcon :size="64" />
|
||||
</template>
|
||||
</NcEmptyContent>
|
||||
|
||||
<template v-else>
|
||||
<section v-for="bundle of appBundles" :key="bundle.id">
|
||||
<div :class="$style.appstoreBundles__header">
|
||||
<h3>{{ bundle.name }}</h3>
|
||||
<NcButton
|
||||
v-if="!isBundleEnabled(bundle)"
|
||||
:disabled="!canEnableBundle(bundle)"
|
||||
variant="primary"
|
||||
@click="enableAll(bundle)">
|
||||
<template #icon>
|
||||
<NcIconSvgWrapper :path="mdiDownloadMultiple" />
|
||||
</template>
|
||||
{{ t('appstore', 'Download and enable all') }}
|
||||
</NcButton>
|
||||
</div>
|
||||
|
||||
<AppTable
|
||||
:class="$style.appstoreBundles__appTable"
|
||||
:apps="bundle.apps" />
|
||||
</section>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style module>
|
||||
.appstoreBundles__header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: var(--default-clickable-area);
|
||||
padding-inline: var(--default-grid-baseline);
|
||||
}
|
||||
|
||||
.appstoreBundles__appTable:last-of-type {
|
||||
margin-bottom: var(--body-container-margin);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -6,9 +6,11 @@
|
|||
<script setup lang="ts">
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import NcAppNavigation from '@nextcloud/vue/components/NcAppNavigation'
|
||||
import NcAppNavigationItem from '@nextcloud/vue/components/NcAppNavigationItem'
|
||||
import NcAppNavigationSearch from '@nextcloud/vue/components/NcAppNavigationSearch'
|
||||
import NcAppNavigationSpacer from '@nextcloud/vue/components/NcAppNavigationSpacer'
|
||||
import NcCounterBubble from '@nextcloud/vue/components/NcCounterBubble'
|
||||
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
|
||||
|
|
@ -24,6 +26,27 @@ const updateStore = useUpdatesStore()
|
|||
const categories = computed(() => store.categories)
|
||||
const categoriesLoading = computed(() => store.isLoadingCategories)
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const search = ref('')
|
||||
watch(search, (newValue, oldValue) => {
|
||||
if (newValue.trim() === oldValue.trim()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (router.currentRoute.value.name === 'apps-search') {
|
||||
router.replace({
|
||||
name: 'apps-search',
|
||||
query: { q: newValue },
|
||||
})
|
||||
return
|
||||
}
|
||||
router.push({
|
||||
name: 'apps-search',
|
||||
query: { q: newValue },
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Check if the current instance has a support subscription from the Nextcloud GmbH
|
||||
*
|
||||
|
|
@ -35,6 +58,11 @@ const isSubscribed = computed(() => store.apps.find(({ level }) => level === 300
|
|||
<template>
|
||||
<!-- Categories & filters -->
|
||||
<NcAppNavigation :aria-label="t('appstore', 'Appstore categories')">
|
||||
<template #search>
|
||||
<NcAppNavigationSearch
|
||||
v-model="search"
|
||||
:label="t('appstore', 'Search apps…')" />
|
||||
</template>
|
||||
<template #list>
|
||||
<NcAppNavigationItem
|
||||
v-if="appstoreEnabled"
|
||||
|
|
|
|||
15
package-lock.json
generated
15
package-lock.json
generated
|
|
@ -32,6 +32,7 @@
|
|||
"color": "^5.0.3",
|
||||
"debounce": "^3.0.0",
|
||||
"marked": "^17.0.1",
|
||||
"p-queue": "^9.2.0",
|
||||
"pinia": "^3.0.4",
|
||||
"sortablejs": "^1.15.7",
|
||||
"vue": "^3.5.33",
|
||||
|
|
@ -8535,9 +8536,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
|
||||
"integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/events": {
|
||||
|
|
@ -12972,12 +12973,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/p-queue": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.1.0.tgz",
|
||||
"integrity": "sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw==",
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.2.0.tgz",
|
||||
"integrity": "sha512-dWgLE8AH0HjQ9fe74pUkKkvzzYT18Inp4zra3lKHnnwqGvcfcUBrvF2EAVX+envufDNBOzpPq/IBUONDbI7+3g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eventemitter3": "^5.0.1",
|
||||
"eventemitter3": "^5.0.4",
|
||||
"p-timeout": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@
|
|||
"color": "^5.0.3",
|
||||
"debounce": "^3.0.0",
|
||||
"marked": "^17.0.1",
|
||||
"p-queue": "^9.2.0",
|
||||
"pinia": "^3.0.4",
|
||||
"sortablejs": "^1.15.7",
|
||||
"vue": "^3.5.33",
|
||||
|
|
|
|||
Loading…
Reference in a new issue