diff --git a/ui/app/components/History.vue b/ui/app/components/History.vue index 16f5bbf4..97136389 100644 --- a/ui/app/components/History.vue +++ b/ui/app/components/History.vue @@ -16,7 +16,7 @@ {{ selectedElms.length }} - +
- +
- - - - - + + + + + @@ -85,8 +85,8 @@ -
@@ -63,11 +63,11 @@ /> TitleStatusCreatedSize/StartsActionsTitleStatusCreatedSize/StartsActions
-
+
+
{{ item.error }}

{{ item.msg }}

@@ -244,7 +244,7 @@ @click="() => void removeItem(item)" /> - +
-
+
@@ -440,7 +440,7 @@
-
+
- +

{{ item.error }}

{{ item.msg }}

@@ -652,6 +652,7 @@ const masterSelectAll = ref(false); const embed_url = ref(''); const video_item = ref(null); const loadMoreTrigger = ref(null); +const expandedMessages = reactive>>({}); const paginationInfo = computed(() => stateStore.getPagination()); @@ -1277,10 +1278,45 @@ const downloadSelected = async () => { } }; -const toggle_class = (e: Event) => - ['is-text-overflow', 'is-word-break'].forEach((c) => - (e.currentTarget as HTMLElement).classList.toggle(c), - ); +const toggleMessage = (itemId: string, field: 'error' | 'msg', view: 'list' | 'card') => { + const key = `${itemId}:${view}`; + + if (!expandedMessages[key]) { + expandedMessages[key] = new Set(); + } + + if (expandedMessages[key].has(field)) { + expandedMessages[key].delete(field); + return; + } + + expandedMessages[key].add(field); +}; + +const isMessageExpanded = (itemId: string, field: 'error' | 'msg', view: 'list' | 'card') => + expandedMessages[`${itemId}:${view}`]?.has(field) ?? false; + +const messageClass = ( + itemId: string, + field: 'error' | 'msg', + view: 'list' | 'card', + spacingClass = '', +) => { + const expanded = isMessageExpanded(itemId, field, view); + const base = ['cursor-pointer', 'text-sm', 'text-error']; + + if (spacingClass) { + base.push(spacingClass); + } + + if ('card' === view) { + base.push(expanded ? 'whitespace-pre-wrap break-words' : 'line-clamp-2 break-words'); + return base; + } + + base.push(expanded ? 'whitespace-pre-wrap break-words' : 'block max-w-full truncate'); + return base; +}; const removeFromArchiveDialog = async (item: StoreItem): Promise => { const options = [ diff --git a/ui/app/components/NewDownload.vue b/ui/app/components/NewDownload.vue index 9f48a2f7..42078657 100644 --- a/ui/app/components/NewDownload.vue +++ b/ui/app/components/NewDownload.vue @@ -375,7 +375,7 @@
- + - + - +
-
+
@@ -417,7 +417,7 @@
-
+
- + diff --git a/ui/app/composables/useDocs.ts b/ui/app/composables/useDocs.ts index 9c7a7705..9678bd7f 100644 --- a/ui/app/composables/useDocs.ts +++ b/ui/app/composables/useDocs.ts @@ -27,7 +27,7 @@ const DOCS_ENTRIES: DocsEntry[] = [ { id: 'faq', title: 'FAQ', - description: 'Answers for setup details, task handlers, and common issues.', + description: 'Frequently asked questions about the project and troubleshooting.', file: 'FAQ.md', route: '/docs/faq', slug: ['faq'], @@ -77,6 +77,7 @@ const getDocsNavigationEntries = () => DOCS_ENTRIES.map((entry) => ({ id: entry.id, label: entry.navLabel, + description: entry.description, icon: entry.icon, to: entry.route, })); diff --git a/ui/app/layouts/default.vue b/ui/app/layouts/default.vue index 345e701e..7adad258 100644 --- a/ui/app/layouts/default.vue +++ b/ui/app/layouts/default.vue @@ -139,6 +139,16 @@ @@ -480,6 +490,7 @@ import type { version_check } from '~/types'; type NavEntry = { id: string; label: string; + description?: string; icon: string; to?: string; children?: NavEntry[]; @@ -544,21 +555,65 @@ const makeNavigationItem = (item: NavEntry): NavigationMenuItem => ({ const docsNavigationEntries = getDocsNavigationEntries(); const allNavItems = computed(() => [ - { id: 'downloads', label: 'Downloads', icon: 'i-lucide-download', to: '/' }, - { id: 'files', label: 'Files', icon: 'i-lucide-folder-tree', to: '/browser' }, - { id: 'presets', label: 'Presets', icon: 'i-lucide-sliders-horizontal', to: '/presets' }, - { id: 'custom-fields', label: 'Custom Fields', icon: 'i-lucide-braces', to: '/dl_fields' }, - { id: 'conditions', label: 'Conditions', icon: 'i-lucide-filter', to: '/conditions' }, - { id: 'notifications', label: 'Notifications', icon: 'i-lucide-bell', to: '/notifications' }, + { + id: 'downloads', + label: 'Downloads', + description: 'Queued and completed downloads list.', + icon: 'i-lucide-download', + to: '/', + }, + { + id: 'files', + label: 'Files', + description: 'Browse downloaded files.', + icon: 'i-lucide-folder-tree', + to: '/browser', + }, + { + id: 'presets', + label: 'Presets', + description: + 'Presets are pre-defined command options for yt-dlp that you want to apply to given download.', + icon: 'i-lucide-sliders-horizontal', + to: '/presets', + }, + { + id: 'custom-fields', + label: 'Custom Fields', + description: 'Custom fields allow you to add new fields to the download form.', + icon: 'i-lucide-braces', + to: '/dl_fields', + }, + { + id: 'conditions', + label: 'Conditions', + description: 'Run yt-dlp custom match filter on returned info and apply options.', + icon: 'i-lucide-filter', + to: '/conditions', + }, + { + id: 'notifications', + label: 'Notifications', + description: 'Send notifications to your webhooks based on specified events or presets.', + icon: 'i-lucide-bell', + to: '/notifications', + }, { id: 'tasks', label: 'Tasks', icon: 'i-lucide-list-todo', children: [ - { id: 'tasks-list', label: 'Tasks', icon: 'i-lucide-list-todo', to: '/tasks' }, + { + id: 'tasks-list', + label: 'Tasks', + description: 'Queue playlist/channels for automatic download at specified intervals.', + icon: 'i-lucide-list-todo', + to: '/tasks', + }, { id: 'task-definitions', label: 'Task Definitions', + description: 'Create definitions to turn any website into a downloadable feed of links.', icon: 'i-lucide-workflow', to: '/task_definitions', }, @@ -570,10 +625,26 @@ const allNavItems = computed(() => [ icon: 'i-lucide-wrench', children: [ ...(config.app?.file_logging - ? [{ id: 'logs', label: 'Logs', icon: 'i-lucide-file-text', to: '/logs' }] + ? [ + { + id: 'logs', + label: 'Logs', + description: 'Scroll near the top to load older logs.', + icon: 'i-lucide-file-text', + to: '/logs', + }, + ] : []), ...(config.app.console_enabled - ? [{ id: 'console', label: 'Console', icon: 'i-lucide-terminal', to: '/console' }] + ? [ + { + id: 'console', + label: 'Console', + description: 'Run yt-dlp commands directly in a non-interactive session.', + icon: 'i-lucide-terminal', + to: '/console', + }, + ] : []), ], }, @@ -583,7 +654,14 @@ const allNavItems = computed(() => [ icon: 'i-lucide-book-open', children: [ ...docsNavigationEntries, - { id: 'changelog', label: 'Changelog', icon: 'i-lucide-list', to: '/changelog' }, + { + id: 'changelog', + label: 'Changelog', + description: + 'Latest project changes, loaded remotely when available and falling back to the bundled changelog file.', + icon: 'i-lucide-list', + to: '/changelog', + }, ], }, ]); @@ -674,6 +752,7 @@ const routeSearchGroups = computed(() => [ ? [ { label: item.label, + description: item.description, icon: item.icon, suffix: item.to, onSelect: () => handleRouteSelect(item), @@ -683,6 +762,7 @@ const routeSearchGroups = computed(() => [ const children = (item.children || []).map((child) => ({ label: child.label, + description: child.description, icon: child.icon, suffix: child.to || '', onSelect: () => handleRouteSelect(child), @@ -698,18 +778,30 @@ const routeSearchGroups = computed(() => [ config.paused ? { label: 'Resume Downloads', - description: 'Resume the global download queue so pending items can start again.', + description: 'Resume globally paused downloads.', icon: 'i-lucide-play', onSelect: () => void resumeDownloads(), } : { label: 'Pause Downloads', - description: 'Pause pending downloads without stopping items already in progress.', + description: 'Globally pause all non-active downloads.', icon: 'i-lucide-pause', onSelect: () => void pauseDownloads(), }, ], }, + { + id: 'preferences', + label: 'Preferences', + items: [ + { + label: 'WebUI Settings', + description: 'Adjust interface behavior and download defaults.', + icon: 'i-lucide-settings-2', + onSelect: () => void openSettings(), + }, + ], + }, ]); const closeRouteSearch = async (): Promise => { @@ -723,19 +815,6 @@ const closeRouteSearch = async (): Promise => { const pauseDownloads = async (): Promise => { await closeRouteSearch(); - - const { status } = await confirmDialog({ - title: 'Pause Downloads', - confirmText: 'Pause', - cancelText: 'Cancel', - confirmColor: 'warning', - message: 'Are you sure you want to pause all non-active downloads?', - }); - - if (!status) { - return; - } - await request('/api/system/pause', { method: 'POST' }); }; @@ -744,6 +823,11 @@ const resumeDownloads = async (): Promise => { await request('/api/system/resume', { method: 'POST' }); }; +const openSettings = async (): Promise => { + await closeRouteSearch(); + show_settings.value = true; +}; + const syncShellModeClass = () => { const html = document.documentElement; html.classList.toggle('simple-mode', simpleMode.value); diff --git a/ui/app/pages/browser/[...slug].vue b/ui/app/pages/browser/[...slug].vue index 14b8d109..dd8d0e07 100644 --- a/ui/app/pages/browser/[...slug].vue +++ b/ui/app/pages/browser/[...slug].vue @@ -84,7 +84,7 @@ {{ display_style === 'list' ? 'List' : 'Grid' }} - + -
+
@@ -386,7 +386,7 @@
-
+
{{ cond.name }} -
+
-
+
{{ field.name }}
-
+
@@ -227,7 +227,7 @@
-
+
-
+
-
+
-
+
-
+
-
+
- + Delete - +
-
+
-
+
- +