Refactor: improve UX to prevent accidental space as download path

This commit is contained in:
arabcoders 2025-12-29 17:11:07 +03:00
parent 450b140561
commit 0dccfad5c8
8 changed files with 134 additions and 95 deletions

View file

@ -9,7 +9,7 @@
<span class="icon"><i class="fa-solid fa-link" /></span> <span class="icon"><i class="fa-solid fa-link" /></span>
<span class="has-tooltip" v-tooltip="'Use Shift+Enter to switch to multiline input mode.'"> <span class="has-tooltip" v-tooltip="'Use Shift+Enter to switch to multiline input mode.'">
URLs separated by newlines or <span class="is-bold is-lowercase">{{ getSeparatorsName(separator) URLs separated by newlines or <span class="is-bold is-lowercase">{{ getSeparatorsName(separator)
}}</span> }}</span>
</span> </span>
</label> </label>
<div class="field is-grouped"> <div class="field is-grouped">
@ -33,11 +33,14 @@
</div> </div>
<div class="column is-4-tablet is-12-mobile"> <div class="column is-4-tablet is-12-mobile">
<label class="label" for="preset">
<span class="icon"><i class="fa-solid fa-sliders" /></span>
<span>Preset</span>
</label>
<div class="field has-addons"> <div class="field has-addons">
<div class="control" @click="show_description = !show_description"> <div class="control" @click="show_description = !show_description">
<label class="button is-static"> <label class="button is-static">
<span class="icon"><i class="fa-solid fa-sliders" /></span> <span class="icon"><i class="fa-solid fa-sliders" /></span>
<span>Preset</span>
</label> </label>
</div> </div>
<div class="control is-expanded"> <div class="control is-expanded">
@ -62,22 +65,25 @@
</div> </div>
<div class="column is-6-tablet is-12-mobile"> <div class="column is-6-tablet is-12-mobile">
<div class="field has-addons" v-tooltip="'Folder relative to ' + config.app.download_path"> <label class="label" for="folder">
<div class="control"> <span class="icon"><i class="fa-solid fa-save" /></span>
<span>Download path</span>
</label>
<div class="field has-addons">
<div class="control" v-tooltip="`Download path relative to: ${config.app.download_path}`">
<label class="button is-static"> <label class="button is-static">
<span class="icon"><i class="fa-solid fa-folder" /></span> <span>{{ shortPath(config.app.download_path) }}</span>
<span>Save in</span>
</label> </label>
</div> </div>
<div class="control is-expanded"> <div class="control is-expanded">
<input type="text" class="input is-fullwidth" id="path" v-model="form.folder" <input type="text" class="input is-fullwidth" id="folder" v-model="form.folder"
:placeholder="getDefault('folder', '/')" :disabled="!socket.isConnected || addInProgress" :placeholder="getDefault('folder', '/')" :disabled="!socket.isConnected || addInProgress"
list="folders"> list="folders">
</div> </div>
</div> </div>
</div> </div>
<div class="column"> <div class="column is-align-content-end">
<button type="button" class="button is-info" @click="showAdvanced = !showAdvanced" <button type="button" class="button is-info" @click="showAdvanced = !showAdvanced"
:disabled="!socket.isConnected"> :disabled="!socket.isConnected">
<span class="icon"><i class="fa-solid fa-cog" /></span> <span class="icon"><i class="fa-solid fa-cog" /></span>
@ -161,7 +167,7 @@
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>
<span>Use the <NuxtLink target="_blank" <span>Use the <NuxtLink target="_blank"
to="https://github.com/yt-dlp/yt-dlp/wiki/FAQ#how-do-i-pass-cookies-to-yt-dlp"> to="https://github.com/yt-dlp/yt-dlp/wiki/FAQ#how-do-i-pass-cookies-to-yt-dlp">
Recommended addon</NuxtLink> by yt-dlp to export cookies. <span class="has-text-danger">The Recommended addon</NuxtLink> to export cookies. <span class="has-text-danger">The
cookies MUST be in Netscape HTTP Cookie format.</span> cookies MUST be in Netscape HTTP Cookie format.</span>
</span> </span>
</span> </span>
@ -401,12 +407,6 @@ const splitUrls = (urlString: string): Array<string> => {
} }
const addDownload = async () => { const addDownload = async () => {
if (' ' === form.value?.folder) {
toast.warning('The download folder contain only spaces. Resetting to default.')
form.value.folder = ''
await nextTick()
}
if (form.value.folder) { if (form.value.folder) {
form.value.folder = form.value.folder.trim() form.value.folder = form.value.folder.trim()
} }
@ -578,14 +578,9 @@ onMounted(async () => {
form.value.preset = config.app.default_preset form.value.preset = config.app.default_preset
} }
if (' ' === form.value?.folder) { if (form.value?.folder) {
toast.warning('The download folder contain only spaces. Resetting to default.')
form.value.folder = ''
await nextTick()
}
if (form.value.folder) {
form.value.folder = form.value.folder.trim() form.value.folder = form.value.folder.trim()
await nextTick()
} }
if (config.isLoaded() && form.value?.preset && !config.presets.some(p => p.name === form.value.preset)) { if (config.isLoaded() && form.value?.preset && !config.presets.some(p => p.name === form.value.preset)) {
@ -620,6 +615,11 @@ const runCliCommand = async (): Promise<void> => {
return return
} }
if (form.value?.folder) {
form.value.folder = form.value.folder.trim()
await nextTick()
}
const { status } = await dialog.confirmDialog({ const { status } = await dialog.confirmDialog({
title: 'Run CLI Command', title: 'Run CLI Command',
message: `This will generate a yt-dlp command and run it in the console. Continue?`, message: `This will generate a yt-dlp command and run it in the console. Continue?`,
@ -697,11 +697,13 @@ const testDownloadOptions = async (): Promise<void> => {
return return
} }
let form_cli = (form.value?.cli || '').trim() if (form.value?.folder) {
if (' ' === form.value?.folder) { form.value.folder = form.value.folder.trim()
form.value.folder = '' await nextTick()
} }
let form_cli = (form.value?.cli || '').trim()
if (dlFields.value && Object.keys(dlFields.value).length > 0) { if (dlFields.value && Object.keys(dlFields.value).length > 0) {
const joined = [] const joined = []
for (const [key, value] of Object.entries(dlFields.value)) { for (const [key, value] of Object.entries(dlFields.value)) {

View file

@ -1,7 +1,7 @@
<template> <template>
<main class="columns mt-2 is-multiline"> <main class="columns mt-2 is-multiline">
<div class="column is-12"> <div class="column is-12">
<form autocomplete="off" id="presetForm" @submit.prevent="checkInfo()"> <form autocomplete="off" id="presetForm" @submit.prevent="checkInfo">
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<div class="card-header-title is-text-overflow is-block"> <div class="card-header-title is-text-overflow is-block">
@ -104,28 +104,32 @@
</div> </div>
<div class="column is-6-tablet is-12-mobile"> <div class="column is-6-tablet is-12-mobile">
<div class="field"> <label class="label is-inline" for="folder">
<label class="label is-inline" for="folder"> <span class="icon"><i class="fa-solid fa-save" /></span>
<span class="icon"><i class="fa-solid fa-folder" /></span> download path
Default Download path </label>
</label> <div class="field has-addons">
<div class="control"> <div class="control" v-tooltip="`Full Path: ${config.app.download_path}`">
<span class="button is-static">
<span>{{ shortPath(config.app.download_path) }}</span>
</span>
</div>
<div class="control is-expanded">
<input type="text" class="input" id="folder" placeholder="Leave empty to use default download path" <input type="text" class="input" id="folder" placeholder="Leave empty to use default download path"
v-model="form.folder" :disabled="addInProgress" list="folders"> v-model="form.folder" :disabled="addInProgress" list="folders">
</div> </div>
<span class="help is-bold">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span>Use this folder if non is given with URL. Leave empty to use default download path. Default
download path <code>{{ config.app.download_path }}</code>.</span>
</span>
</div> </div>
<span class="help is-bold">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span>Use this defined path if non are given with the URL.</span>
</span>
</div> </div>
<div class="column is-6-tablet is-12-mobile"> <div class="column is-6-tablet is-12-mobile">
<div class="field"> <div class="field">
<label class="label is-inline" for="output_template"> <label class="label is-inline" for="output_template">
<span class="icon"><i class="fa-solid fa-file" /></span> <span class="icon"><i class="fa-solid fa-file" /></span>
Default Output template Output template
</label> </label>
<div class="control"> <div class="control">
<input type="text" class="input" id="output_template" :disabled="addInProgress" <input type="text" class="input" id="output_template" :disabled="addInProgress"
@ -133,10 +137,7 @@
</div> </div>
<span class="help is-bold"> <span class="help is-bold">
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>
<span>Use this output template if non are given with URL. if not set, it will defaults to <span>Use this output template if non are given with URL.</span>
<code>{{ config.app.output_template }}</code>. For more information visit <NuxtLink
href="https://github.com/yt-dlp/yt-dlp#output-template" target="_blank">this page</NuxtLink>.
</span>
</span> </span>
</div> </div>
</div> </div>
@ -277,6 +278,11 @@ const checkInfo = async (): Promise<void> => {
} }
} }
if (form.folder) {
form.folder = form.folder.trim()
await nextTick()
}
if (form.cli && '' !== form.cli) { if (form.cli && '' !== form.cli) {
const options = await convertOptions(form.cli) const options = await convertOptions(form.cli)
if (null === options) { if (null === options) {

View file

@ -191,8 +191,8 @@
<span class="help"> <span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>
<span class="is-bold"> <span class="is-bold">
The CRON timer expression to use for this task. If not set, the task will be disabled. For more The CRON timer expression to use for this task. If not set, the task runner will be disabled. For
information on CRON expressions, see <NuxtLink to="https://crontab.guru/" target="_blank"> more information on CRON expressions, see <NuxtLink to="https://crontab.guru/" target="_blank">
crontab.guru</NuxtLink>. crontab.guru</NuxtLink>.
</span> </span>
</span> </span>
@ -200,21 +200,27 @@
</div> </div>
<div class="column is-6-tablet is-12-mobile"> <div class="column is-6-tablet is-12-mobile">
<div class="field"> <label class="label is-inline" for="folder">
<label class="label is-inline" for="folder"> <span class="icon"><i class="fa-solid fa-save" /></span>
<span class="icon"><i class="fa-solid fa-folder" /></span> Download path
Save in </label>
</label> <div class="field has-addons">
<div class="control"> <div class="control" v-tooltip="`Full Path: ${config.app.download_path}`">
<span class="button is-static">
<span>{{ shortPath(config.app.download_path) }}</span>
</span>
</div>
<div class="control is-expanded">
<input type="text" class="input" id="folder" :placeholder="getDefault('folder', '/')" <input type="text" class="input" id="folder" :placeholder="getDefault('folder', '/')"
v-model="form.folder" :disabled="addInProgress" list="folders"> v-model="form.folder" :disabled="addInProgress" list="folders">
</div> </div>
<span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span class="is-bold">All folders are sub-folders of
<code>{{ config.app.download_path }}</code>.</span>
</span>
</div> </div>
<span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span class="is-bold">
Path relative to the download path, leave empty to use preset or default download path.
</span>
</span>
</div> </div>
<div class="column is-6-tablet is-12-mobile"> <div class="column is-6-tablet is-12-mobile">
@ -230,7 +236,9 @@
</div> </div>
<span class="help"> <span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>
<span class="is-bold">The template to use for the output file name.</span> <span class="is-bold">
The template to use for the output file name. Leave empty to use preset or default template.
</span>
</span> </span>
</div> </div>
</div> </div>
@ -251,7 +259,8 @@
<span class="help"> <span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>
<span class="is-bold">Some URLs have special handlers to monitor for new content. Like YouTube <span class="is-bold">Some URLs have special handlers to monitor for new content. Like YouTube
channels/playlists.</span> channels/playlists. <span class="has-text-danger">Handlers run regardless of task timer.</span>
</span>
</span> </span>
</div> </div>
</div> </div>
@ -324,6 +333,10 @@
<Message class="is-info" :newStyle="true"> <Message class="is-info" :newStyle="true">
<span> <span>
<ul> <ul>
<li><strong>Tasks:</strong> requires <code>--download-archive</code> in
<code>Command options for yt-dlp</code> can be set via presets or manually. Default presets already
include this option.
</li>
<li><strong>YouTube RSS:</strong> Use <code>channel_id</code> or <code>playlist_id</code> URLs. Other link <li><strong>YouTube RSS:</strong> Use <code>channel_id</code> or <code>playlist_id</code> URLs. Other link
types (custom names, handles, user profiles) are not supported. types (custom names, handles, user profiles) are not supported.
</li> </li>
@ -334,9 +347,6 @@
<li><strong>RSS Monitoring Basics:</strong> Runs hourly independently. Timer controls scheduled downloads to <li><strong>RSS Monitoring Basics:</strong> Runs hourly independently. Timer controls scheduled downloads to
yt-dlp. Disable <code>Enable Handler</code> to disable RSS monitoring. yt-dlp. Disable <code>Enable Handler</code> to disable RSS monitoring.
</li> </li>
<li><strong>Archive Requirement:</strong> RSS monitoring requires <code>--download-archive</code> in
<code>Command options for yt-dlp</code> (set in task or preset).
</li>
</ul> </ul>
</span> </span>
</Message> </Message>
@ -420,6 +430,11 @@ const checkInfo = async (): Promise<void> => {
} }
} }
if (form.folder) {
form.folder = form.folder.trim()
await nextTick()
}
if (form.timer) { if (form.timer) {
try { try {
CronExpressionParser.parse(form.timer) CronExpressionParser.parse(form.timer)

View file

@ -129,7 +129,7 @@
<p :class="{ 'is-text-overflow': !isExpanded(item.id, 'folder'), 'is-clickable': true }" <p :class="{ 'is-text-overflow': !isExpanded(item.id, 'folder'), 'is-clickable': true }"
v-if="item.folder" @click="toggleExpand(item.id, 'folder')" v-if="item.folder" @click="toggleExpand(item.id, 'folder')"
:title="!isExpanded(item.id, 'folder') ? 'Click to expand' : 'Click to collapse'"> :title="!isExpanded(item.id, 'folder') ? 'Click to expand' : 'Click to collapse'">
<span class="icon"><i class="fa-solid fa-folder" /></span> <span class="icon"><i class="fa-solid fa-save" /></span>
<span>{{ calcPath(item.folder) }}</span> <span>{{ calcPath(item.folder) }}</span>
</p> </p>
<p :class="{ 'is-text-overflow': !isExpanded(item.id, 'template'), 'is-clickable': true }" <p :class="{ 'is-text-overflow': !isExpanded(item.id, 'template'), 'is-clickable': true }"

View file

@ -332,7 +332,7 @@
<span v-else class="has-text-danger">No timer or handler</span> <span v-else class="has-text-danger">No timer or handler</span>
</p> </p>
<p class="is-text-overflow" v-if="item.folder"> <p class="is-text-overflow" v-if="item.folder">
<span class="icon"><i class="fa-solid fa-folder" /></span> <span class="icon"><i class="fa-solid fa-save" /></span>
<span>{{ calcPath(item.folder) }}</span> <span>{{ calcPath(item.folder) }}</span>
</p> </p>
<p class="is-text-overflow" v-if="item.template"> <p class="is-text-overflow" v-if="item.template">

View file

@ -703,11 +703,26 @@ const stripPath = (base_path: string, real_path: string): string => {
return real_path.replace(base_path, '').replace(/^\//, '') return real_path.replace(base_path, '').replace(/^\//, '')
} }
const shortPath = (path: string, prefix: string = '...'): string => {
if (typeof path !== 'string') {
return path;
}
const hasTrailingSlash = /\/$/.test(path);
const clean = path.replace(/\/+$/, '');
const parts = clean.split('/').filter(Boolean);
if (parts.length <= 1) {
return path;
}
return `${prefix}/${parts.at(-1)}${hasTrailingSlash ? '/' : ''}`;
}
export { export {
separators, convertCliOptions, getSeparatorsName, iTrim, eTrim, sTrim, ucFirst, separators, convertCliOptions, getSeparatorsName, iTrim, eTrim, sTrim, ucFirst,
getValue, ag, ag_set, awaitElement, r, copyText, dEvent, makePagination, encodePath, getValue, ag, ag_set, awaitElement, r, copyText, dEvent, makePagination, encodePath,
request, removeANSIColors, dec2hex, makeId, basename, dirname, getQueryParams, request, removeANSIColors, dec2hex, makeId, basename, dirname, getQueryParams,
makeDownload, formatBytes, has_data, toggleClass, cleanObject, uri, formatTime, makeDownload, formatBytes, has_data, toggleClass, cleanObject, uri, formatTime,
sleep, awaiter, encode, decode, disableOpacity, enableOpacity, stripPath sleep, awaiter, encode, decode, disableOpacity, enableOpacity, stripPath, shortPath
} }

View file

@ -34,7 +34,7 @@
"moment": "^2.30.1", "moment": "^2.30.1",
"nuxt": "^4.2.2", "nuxt": "^4.2.2",
"pinia": "^3.0.4", "pinia": "^3.0.4",
"socket.io-client": "^4.8.2", "socket.io-client": "^4.8.3",
"vue": "^3.5.26", "vue": "^3.5.26",
"vue-router": "^4.6.4", "vue-router": "^4.6.4",
"vue-toastification": "2.0.0-rc.5" "vue-toastification": "2.0.0-rc.5"

View file

@ -57,8 +57,8 @@ importers:
specifier: ^3.0.4 specifier: ^3.0.4
version: 3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3)) version: 3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))
socket.io-client: socket.io-client:
specifier: ^4.8.2 specifier: ^4.8.3
version: 4.8.2 version: 4.8.3
vue: vue:
specifier: ^3.5.26 specifier: ^3.5.26
version: 3.5.26(typescript@5.9.3) version: 3.5.26(typescript@5.9.3)
@ -1267,8 +1267,8 @@ packages:
'@rolldown/pluginutils@1.0.0-beta.53': '@rolldown/pluginutils@1.0.0-beta.53':
resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==} resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==}
'@rolldown/pluginutils@1.0.0-beta.9-commit.d91dfb5': '@rolldown/pluginutils@1.0.0-beta.57':
resolution: {integrity: sha512-8sExkWRK+zVybw3+2/kBkYBFeLnEUWz1fT7BLHplpzmtqkOfTbAQ9gkt4pzwGIIZmg4Qn5US5ACjUBenrhezwQ==} resolution: {integrity: sha512-aQNelgx14tGA+n2tNSa9x6/jeoCL9fkDeCei7nOKnHx0fEFRRMu5ReiITo+zZD5TzWDGGRjbSYCs93IfRIyTuQ==}
'@rollup/plugin-alias@5.1.1': '@rollup/plugin-alias@5.1.1':
resolution: {integrity: sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ==} resolution: {integrity: sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ==}
@ -1577,8 +1577,8 @@ packages:
resolution: {integrity: sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==} resolution: {integrity: sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@unhead/vue@2.0.19': '@unhead/vue@2.1.1':
resolution: {integrity: sha512-7BYjHfOaoZ9+ARJkT10Q2TjnTUqDXmMpfakIAsD/hXiuff1oqWg1xeXT5+MomhNcC15HbiABpbbBmITLSHxdKg==} resolution: {integrity: sha512-WYa8ORhfv7lWDSoNpkMKhbW1Dbsux/3HqMcVkZS3xZ2/c/VrcChLj+IMadpCd1WNR0srITfRJhBYZ1i9hON5Qw==}
peerDependencies: peerDependencies:
vue: '>=3.5.18' vue: '>=3.5.18'
@ -1690,11 +1690,11 @@ packages:
engines: {node: '>=18'} engines: {node: '>=18'}
hasBin: true hasBin: true
'@vitejs/plugin-vue-jsx@5.1.2': '@vitejs/plugin-vue-jsx@5.1.3':
resolution: {integrity: sha512-3a2BOryRjG/Iih87x87YXz5c8nw27eSlHytvSKYfp8ZIsp5+FgFQoKeA7k2PnqWpjJrv6AoVTMnvmuKUXb771A==} resolution: {integrity: sha512-I6Zr8cYVr5WHMW5gNOP09DNqW9rgO8RX73Wa6Czgq/0ndpTfJM4vfDChfOT1+3KtdrNqilNBtNlFwVeB02ZzGw==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
peerDependencies: peerDependencies:
vite: ^5.0.0 || ^6.0.0 || ^7.0.0 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
vue: ^3.0.0 vue: ^3.0.0
'@vitejs/plugin-vue@6.0.3': '@vitejs/plugin-vue@6.0.3':
@ -1877,8 +1877,8 @@ packages:
ajv@6.12.6: ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
alien-signals@3.1.1: alien-signals@3.1.2:
resolution: {integrity: sha512-ogkIWbVrLwKtHY6oOAXaYkAxP+cTH7V5FZ5+Tm4NZFd8VDZ6uNMDrfzqctTZ42eTMCSR3ne3otpcxmqSnFfPYA==} resolution: {integrity: sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==}
ansi-regex@5.0.1: ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
@ -3831,8 +3831,8 @@ packages:
serialize-javascript@6.0.2: serialize-javascript@6.0.2:
resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
seroval@1.4.1: seroval@1.4.2:
resolution: {integrity: sha512-9GOc+8T6LN4aByLN75uRvMbrwY5RDBW6lSlknsY4LEa9ZmWcxKcRe1G/Q3HZXjltxMHTrStnvrwAICxZrhldtg==} resolution: {integrity: sha512-N3HEHRCZYn3cQbsC4B5ldj9j+tHdf4JZoYPlcI4rRYu0Xy4qN8MQf1Z08EibzB0WpgRG5BGK08FTrmM66eSzKQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
serve-placeholder@2.0.2: serve-placeholder@2.0.2:
@ -3881,8 +3881,8 @@ packages:
smob@1.5.0: smob@1.5.0:
resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==}
socket.io-client@4.8.2: socket.io-client@4.8.3:
resolution: {integrity: sha512-4MY14EMsyEPFA6lM01XIYepRdV8P6dUir2hxAlAysOYcbNAy5QNHYgIHOcQ1KYM7wTcKnKEW/ZRoIxRinWRXvA==} resolution: {integrity: sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
socket.io-parser@4.2.5: socket.io-parser@4.2.5:
@ -4130,8 +4130,8 @@ packages:
unenv@2.0.0-rc.24: unenv@2.0.0-rc.24:
resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==}
unhead@2.0.19: unhead@2.1.1:
resolution: {integrity: sha512-gEEjkV11Aj+rBnY6wnRfsFtF2RxKOLaPN4i+Gx3UhBxnszvV6ApSNZbGk7WKyy/lErQ6ekPN63qdFL7sa1leow==} resolution: {integrity: sha512-NOt8n2KybAOxSLfNXegAVai4SGU8bPKqWnqCzNAvnRH2i8mW+0bbFjN/L75LBgCSTiOjJSpANe5w2V34Grr7Cw==}
unicorn-magic@0.3.0: unicorn-magic@0.3.0:
resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==}
@ -4456,6 +4456,7 @@ packages:
whatwg-encoding@3.1.1: whatwg-encoding@3.1.1:
resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
whatwg-mimetype@4.0.0: whatwg-mimetype@4.0.0:
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
@ -5421,7 +5422,7 @@ snapshots:
dependencies: dependencies:
'@nuxt/devalue': 2.0.2 '@nuxt/devalue': 2.0.2
'@nuxt/kit': 4.2.2(magicast@0.5.1) '@nuxt/kit': 4.2.2(magicast@0.5.1)
'@unhead/vue': 2.0.19(vue@3.5.26(typescript@5.9.3)) '@unhead/vue': 2.1.1(vue@3.5.26(typescript@5.9.3))
'@vue/shared': 3.5.26 '@vue/shared': 3.5.26
consola: 3.4.2 consola: 3.4.2
defu: 6.1.4 defu: 6.1.4
@ -5511,7 +5512,7 @@ snapshots:
'@nuxt/kit': 4.2.2(magicast@0.5.1) '@nuxt/kit': 4.2.2(magicast@0.5.1)
'@rollup/plugin-replace': 6.0.3(rollup@4.54.0) '@rollup/plugin-replace': 6.0.3(rollup@4.54.0)
'@vitejs/plugin-vue': 6.0.3(vite@7.3.0(@types/node@22.18.1)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)) '@vitejs/plugin-vue': 6.0.3(vite@7.3.0(@types/node@22.18.1)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))
'@vitejs/plugin-vue-jsx': 5.1.2(vite@7.3.0(@types/node@22.18.1)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)) '@vitejs/plugin-vue-jsx': 5.1.3(vite@7.3.0(@types/node@22.18.1)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))
autoprefixer: 10.4.23(postcss@8.5.6) autoprefixer: 10.4.23(postcss@8.5.6)
consola: 3.4.2 consola: 3.4.2
cssnano: 7.1.2(postcss@8.5.6) cssnano: 7.1.2(postcss@8.5.6)
@ -5531,7 +5532,7 @@ snapshots:
pkg-types: 2.3.0 pkg-types: 2.3.0
postcss: 8.5.6 postcss: 8.5.6
rollup-plugin-visualizer: 6.0.5(rollup@4.54.0) rollup-plugin-visualizer: 6.0.5(rollup@4.54.0)
seroval: 1.4.1 seroval: 1.4.2
std-env: 3.10.0 std-env: 3.10.0
ufo: 1.6.1 ufo: 1.6.1
unenv: 2.0.0-rc.24 unenv: 2.0.0-rc.24
@ -5799,7 +5800,7 @@ snapshots:
'@rolldown/pluginutils@1.0.0-beta.53': {} '@rolldown/pluginutils@1.0.0-beta.53': {}
'@rolldown/pluginutils@1.0.0-beta.9-commit.d91dfb5': {} '@rolldown/pluginutils@1.0.0-beta.57': {}
'@rollup/plugin-alias@5.1.1(rollup@4.54.0)': '@rollup/plugin-alias@5.1.1(rollup@4.54.0)':
optionalDependencies: optionalDependencies:
@ -6072,10 +6073,10 @@ snapshots:
'@typescript-eslint/types': 8.50.1 '@typescript-eslint/types': 8.50.1
eslint-visitor-keys: 4.2.1 eslint-visitor-keys: 4.2.1
'@unhead/vue@2.0.19(vue@3.5.26(typescript@5.9.3))': '@unhead/vue@2.1.1(vue@3.5.26(typescript@5.9.3))':
dependencies: dependencies:
hookable: 5.5.3 hookable: 5.5.3
unhead: 2.0.19 unhead: 2.1.1
vue: 3.5.26(typescript@5.9.3) vue: 3.5.26(typescript@5.9.3)
'@unrs/resolver-binding-android-arm-eabi@1.11.1': '@unrs/resolver-binding-android-arm-eabi@1.11.1':
@ -6156,12 +6157,12 @@ snapshots:
- rollup - rollup
- supports-color - supports-color
'@vitejs/plugin-vue-jsx@5.1.2(vite@7.3.0(@types/node@22.18.1)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))': '@vitejs/plugin-vue-jsx@5.1.3(vite@7.3.0(@types/node@22.18.1)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))':
dependencies: dependencies:
'@babel/core': 7.28.5 '@babel/core': 7.28.5
'@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5)
'@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5) '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5)
'@rolldown/pluginutils': 1.0.0-beta.9-commit.d91dfb5 '@rolldown/pluginutils': 1.0.0-beta.57
'@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.28.5) '@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.28.5)
vite: 7.3.0(@types/node@22.18.1)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2) vite: 7.3.0(@types/node@22.18.1)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2)
vue: 3.5.26(typescript@5.9.3) vue: 3.5.26(typescript@5.9.3)
@ -6345,7 +6346,7 @@ snapshots:
'@volar/language-core': 2.4.27 '@volar/language-core': 2.4.27
'@vue/compiler-dom': 3.5.26 '@vue/compiler-dom': 3.5.26
'@vue/shared': 3.5.26 '@vue/shared': 3.5.26
alien-signals: 3.1.1 alien-signals: 3.1.2
muggle-string: 0.4.1 muggle-string: 0.4.1
path-browserify: 1.0.1 path-browserify: 1.0.1
picomatch: 4.0.3 picomatch: 4.0.3
@ -6427,7 +6428,7 @@ snapshots:
json-schema-traverse: 0.4.1 json-schema-traverse: 0.4.1
uri-js: 4.4.1 uri-js: 4.4.1
alien-signals@3.1.1: {} alien-signals@3.1.2: {}
ansi-regex@5.0.1: {} ansi-regex@5.0.1: {}
@ -7947,7 +7948,7 @@ snapshots:
'@nuxt/schema': 4.2.2 '@nuxt/schema': 4.2.2
'@nuxt/telemetry': 2.6.6(magicast@0.5.1) '@nuxt/telemetry': 2.6.6(magicast@0.5.1)
'@nuxt/vite-builder': 4.2.2(@types/node@22.18.1)(eslint@9.39.2(jiti@2.6.1))(magicast@0.5.1)(nuxt@4.2.2(@parcel/watcher@2.5.1)(@types/node@22.18.1)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.8.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.54.0)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.0(@types/node@22.18.1)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@3.2.1(typescript@5.9.3))(yaml@2.8.2))(optionator@0.9.4)(rollup@4.54.0)(terser@5.44.1)(typescript@5.9.3)(vue-tsc@3.2.1(typescript@5.9.3))(vue@3.5.26(typescript@5.9.3))(yaml@2.8.2) '@nuxt/vite-builder': 4.2.2(@types/node@22.18.1)(eslint@9.39.2(jiti@2.6.1))(magicast@0.5.1)(nuxt@4.2.2(@parcel/watcher@2.5.1)(@types/node@22.18.1)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.8.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.54.0)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.0(@types/node@22.18.1)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@3.2.1(typescript@5.9.3))(yaml@2.8.2))(optionator@0.9.4)(rollup@4.54.0)(terser@5.44.1)(typescript@5.9.3)(vue-tsc@3.2.1(typescript@5.9.3))(vue@3.5.26(typescript@5.9.3))(yaml@2.8.2)
'@unhead/vue': 2.0.19(vue@3.5.26(typescript@5.9.3)) '@unhead/vue': 2.1.1(vue@3.5.26(typescript@5.9.3))
'@vue/shared': 3.5.26 '@vue/shared': 3.5.26
c12: 3.3.3(magicast@0.5.1) c12: 3.3.3(magicast@0.5.1)
chokidar: 5.0.0 chokidar: 5.0.0
@ -8628,7 +8629,7 @@ snapshots:
dependencies: dependencies:
randombytes: 2.1.0 randombytes: 2.1.0
seroval@1.4.1: {} seroval@1.4.2: {}
serve-placeholder@2.0.2: serve-placeholder@2.0.2:
dependencies: dependencies:
@ -8677,7 +8678,7 @@ snapshots:
smob@1.5.0: {} smob@1.5.0: {}
socket.io-client@4.8.2: socket.io-client@4.8.3:
dependencies: dependencies:
'@socket.io/component-emitter': 3.1.2 '@socket.io/component-emitter': 3.1.2
debug: 4.4.3 debug: 4.4.3
@ -8928,7 +8929,7 @@ snapshots:
dependencies: dependencies:
pathe: 2.0.3 pathe: 2.0.3
unhead@2.0.19: unhead@2.1.1:
dependencies: dependencies:
hookable: 5.5.3 hookable: 5.5.3