migrated more code to ts

This commit is contained in:
arabcoders 2025-07-25 19:41:17 +03:00
parent 4e1a1e65ed
commit dcaeb7acc8
9 changed files with 249 additions and 263 deletions

View file

@ -198,7 +198,7 @@
</label> </label>
<div class="columns is-multiline is-mobile"> <div class="columns is-multiline is-mobile">
<template v-for="_, key in form.request.headers" :key="key"> <template v-for="_, key in form.request.headers" :key="key">
<div class="column is-5"> <div class="column is-5" v-if="form.request.headers[key]">
<div class="field"> <div class="field">
<div class="control has-icons-left"> <div class="control has-icons-left">
<input type="text" class="input" v-model="form.request.headers[key].key" <input type="text" class="input" v-model="form.request.headers[key].key"
@ -211,7 +211,7 @@
<span>The header key to send with the notification.</span> <span>The header key to send with the notification.</span>
</span> </span>
</div> </div>
<div class="column is-6"> <div class="column is-6" v-if="form.request.headers[key]">
<div class="field"> <div class="field">
<div class="control has-icons-left"> <div class="control has-icons-left">
<input type="text" class="input" v-model="form.request.headers[key].value" <input type="text" class="input" v-model="form.request.headers[key].value"
@ -267,11 +267,14 @@
</main> </main>
</template> </template>
<script setup> <script setup lang="ts">
import { useStorage } from '@vueuse/core' import { useStorage } from '@vueuse/core'
import type { notification, notificationImport } from '~/types/notification'
const emitter = defineEmits(['cancel', 'submit'])
const toast = useNotification()
const box = useConfirm()
const emitter = defineEmits(['cancel', 'submit']);
const toast = useNotification();
const props = defineProps({ const props = defineProps({
reference: { reference: {
type: String, type: String,
@ -279,11 +282,11 @@ const props = defineProps({
default: null, default: null,
}, },
allowedEvents: { allowedEvents: {
type: Array, type: Array as () => string[],
required: true, required: true,
}, },
item: { item: {
type: Object, type: Object as () => notification,
required: true, required: true,
}, },
addInProgress: { addInProgress: {
@ -293,71 +296,75 @@ const props = defineProps({
}, },
}) })
const form = reactive(props.item); const form = reactive<notification>({ ...props.item })
const requestMethods = ['POST', 'PUT']; const requestMethods = ['POST', 'PUT']
const requestType = ['json', 'form']; const requestType = ['json', 'form']
const showImport = useStorage('showImport', false); const showImport = useStorage('showImport', false)
const import_string = ref(''); const import_string = ref('')
const box = useConfirm()
onMounted(() => { onMounted(() => {
if (!form.request.data_key) { if (!form.request.data_key) {
form.request.data_key = 'data'; form.request.data_key = 'data'
} }
}); })
const checkInfo = async () => { const checkInfo = async () => {
let required; let required: string[]
if (!isApprise.value) { if (!isApprise.value) {
required = ['name', 'request.url', 'request.method', 'request.type', 'request.data_key']; required = ['name', 'request.url', 'request.method', 'request.type', 'request.data_key']
} else { } else {
required = ['name', 'request.url']; required = ['name', 'request.url']
} }
for (const key of required) { for (const key of required) {
if (key.includes('.')) { if (key.includes('.')) {
const [parent, child] = key.split('.'); const [parent, child] = key.split('.') as [keyof typeof form, string]
if (!form[parent][child]) { const parentObj = form[parent] as Record<string, any> | undefined
toast.error(`The field ${parent}.${child} is required.`);
return; if (!parentObj || !parentObj[child]) {
toast.error(`The field ${parent}.${child} is required.`)
return
}
} else {
const value = (form as Record<string, any>)[key]
if (!value) {
toast.error(`The field ${key} is required.`)
return
} }
} else if (!form[key]) {
toast.error(`The field ${key} is required.`);
return;
} }
} }
if (!isApprise.value) { if (!isApprise.value) {
try { try {
new URL(form.request.url); new URL(form.request.url)
} catch (e) { } catch (_) {
toast.error('Invalid URL'); toast.error('Invalid URL')
return; return
} }
} }
let headers = [] const headers = []
for (const header of form.request.headers) { for (const header of form.request.headers) {
if (!header.key || !header.value) { if (!header.key || !header.value) {
continue continue
} }
headers.push({ key: String(header.key).trim(), value: String(header.value).trim() }) headers.push({ key: String(header.key).trim(), value: String(header.value).trim() })
} }
form.request.headers = headers
form.request.headers = headers; emitter('submit', { reference: toRaw(props.reference), item: toRaw(form) })
emitter('submit', { reference: toRaw(props.reference), item: toRaw(form) });
} }
const importItem = async () => { const importItem = async () => {
let val = import_string.value.trim() const val = import_string.value.trim()
if (!val) { if (!val) {
toast.error('The import string is required.') toast.error('The import string is required.')
return return
} }
try { try {
const item = decode(val) const item = decode(val) as notificationImport
if ('notification' !== item._type) { if ('notification' !== item._type) {
toast.error(`Invalid import string. Expected type 'notification', got '${item._type}'.`) toast.error(`Invalid import string. Expected type 'notification', got '${item._type}'.`)
@ -365,7 +372,7 @@ const importItem = async () => {
return return
} }
if (form.target) { if (form.name || form.request?.url) {
if (false === box.confirm('Overwrite the current form fields?', true)) { if (false === box.confirm('Overwrite the current form fields?', true)) {
return return
} }
@ -375,24 +382,25 @@ const importItem = async () => {
form.name = item.name form.name = item.name
} }
if (item.url) { if (!form.request) {
form.url = item.url form.request = {} as any
} }
if (item.request) { if (item.request) {
form.request = item.request form.request = item.request
} }
if (item.data_key) { if (item.request?.data_key) {
form.data_key = item.data_key form.request.data_key = item.request.data_key
} }
if (item.on) { if (item.on) {
form.on = item.on form.on = item.on
} }
import_string.value = '' import_string.value = ''
} catch (e) { } catch (e: any) {
console.error(e) console.error(e)
toast.error(`Failed to import task. ${e.message}`) toast.error(`Failed to import task. ${e.message}`)
} }

View file

@ -1,3 +1,46 @@
<style scoped>
.notification-item {
border-left: 4px solid transparent;
padding-left: 0.75rem;
border-bottom: 1px solid #f5f5f5;
}
.notification-info {
border-color: var(--bulma-info);
}
.notification-success {
border-color: var(--bulma-primary);
}
.notification-warning {
border-color: var(--bulma-warning);
}
.notification-error {
border-color: var(--bulma-danger);
}
.notification-list {
max-height: 300px;
overflow-y: auto;
}
.notification-message {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
max-width: 280px;
}
.notification-message.expanded {
white-space: normal;
word-break: break-word;
max-width: 100%;
}
</style>
<template> <template>
<div class="navbar-item has-dropdown is-hoverable"> <div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link"> <a class="navbar-link">
@ -73,64 +116,21 @@
</div> </div>
</template> </template>
<script setup> <script setup lang="ts">
import moment from 'moment' import moment from 'moment'
const store = useNotificationStore() const store = useNotificationStore()
const copiedId = ref(null)
const expandedId = ref(null)
const toggleExpand = id => expandedId.value = expandedId.value === id ? null : id const copiedId = ref<string | null>(null)
const expandedId = ref<string | null>(null)
const copy_text = (id, text) => { const toggleExpand = (id: string) => expandedId.value = expandedId.value === id ? null : id
const copy_text = (id: string, text: string): void => {
copiedId.value = id copiedId.value = id
copyText(text, false, false) copyText(text, false, false)
setTimeout(() => { setTimeout(() => {
if (copiedId.value === id) { if (copiedId.value === id) copiedId.value = null
copiedId.value = null
}
}, 2000) }, 2000)
} }
</script> </script>
<style scoped>
.notification-item {
border-left: 4px solid transparent;
padding-left: 0.75rem;
border-bottom: 1px solid #f5f5f5;
}
.notification-info {
border-color: var(--bulma-info);
}
.notification-success {
border-color: var(--bulma-primary);
}
.notification-warning {
border-color: var(--bulma-warning);
}
.notification-error {
border-color: var(--bulma-danger);
}
.notification-list {
max-height: 300px;
overflow-y: auto;
}
.notification-message {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
max-width: 280px;
}
.notification-message.expanded {
white-space: normal;
word-break: break-word;
max-width: 100%;
}
</style>

View file

@ -230,102 +230,74 @@
</main> </main>
</template> </template>
<script setup> <script setup lang="ts">
import { useStorage } from '@vueuse/core' import { useStorage } from '@vueuse/core'
import type { Preset, PresetImport } from '~/types/presets'
const emitter = defineEmits(['cancel', 'submit']); const emitter = defineEmits<{
(event: 'cancel'): void
(event: 'submit', payload: { reference: string | null, preset: Preset }): void
}>()
const props = defineProps({ const props = defineProps<{
reference: { reference?: string | null
type: String, preset: Preset
required: false, addInProgress?: boolean
default: null, presets?: Preset[]
}, }>()
preset: {
type: Object,
required: true,
},
addInProgress: {
type: Boolean,
required: false,
default: false,
},
presets: {
type: Array,
required: false,
default: () => [],
},
})
const config = useConfigStore() const config = useConfigStore()
const toast = useNotification() const toast = useNotification()
const form = reactive(JSON.parse(JSON.stringify(props.preset))) const form = reactive<Preset>(JSON.parse(JSON.stringify(props.preset)))
const import_string = ref('') const import_string = ref<string>('')
const showImport = useStorage('showImport', false) const showImport = useStorage<boolean>('showImport', false)
const box = useConfirm() const selected_preset = ref<string>('')
const selected_preset = ref('')
onMounted(() => { const checkInfo = async (): Promise<void> => {
if (props.preset?.cli && '' !== props.preset?.cli) {
return
}
if (props.preset?.args && (typeof props.preset.args === 'object')) {
form.args = JSON.stringify(props.preset.args, null, 2)
}
if (props.preset?.postprocessors && (typeof props.preset.postprocessors === 'object')) {
form.postprocessors = JSON.stringify(props.preset.postprocessors, null, 2)
}
})
const checkInfo = async () => {
for (const key of ['name']) { for (const key of ['name']) {
if (!form[key]) { if (!form[key as keyof Preset]) {
toast.error(`The ${key} field is required.`); toast.error(`The ${key} field is required.`)
return return
} }
} }
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) {
return return
} }
form.cli = form.cli.trim() form.cli = form.cli.trim()
} }
let copy = JSON.parse(JSON.stringify(form)); const copy: Preset = JSON.parse(JSON.stringify(form))
let usedName = false
const name = String(form.name).trim().toLowerCase()
let usedName = false; props.presets?.forEach(p => {
let name = String(form.name).trim().toLowerCase();
props.presets.forEach(p => {
if (p.id === props.reference) { if (p.id === props.reference) {
return; return
} }
if (String(p.name).toLowerCase() === name) { if (String(p.name).toLowerCase() === name) {
usedName = true; usedName = true
} }
}); })
if (true === usedName) { if (usedName) {
toast.error('The preset name is already in use.'); toast.error('The preset name is already in use.')
return; return
} }
for (const key in copy) { for (const key in copy) {
if (typeof copy[key] !== 'string') { const val = copy[key as keyof Preset]
continue if ('string' === typeof val) {
(copy as any)[key] = val.trim()
} }
copy[key] = copy[key].trim()
} }
emitter('submit', { reference: toRaw(props.reference), preset: toRaw(copy) }); emitter('submit', { reference: toRaw(props.reference ?? null), preset: toRaw(copy) })
} }
const convertOptions = async args => { const convertOptions = async (args: string): Promise<Record<string, any> | null> => {
try { try {
const response = await convertCliOptions(args) const response = await convertCliOptions(args)
@ -337,23 +309,22 @@ const convertOptions = async args => {
form.folder = response.download_path form.folder = response.download_path
} }
return response.opts return response.opts as Record<string, any>
} catch (e) { } catch (e: any) {
toast.error(e.message) toast.error(e.message)
return null
} }
return null;
} }
const importItem = async () => { const importItem = async (): Promise<void> => {
let val = import_string.value.trim() const val = import_string.value.trim()
if (!val) { if (!val) {
toast.error('The import string is required.') toast.error('The import string is required.')
return return
} }
try { try {
const item = decode(val) const item = decode(val) as PresetImport
if (!item?._type || 'preset' !== item._type) { if (!item?._type || 'preset' !== item._type) {
toast.error(`Invalid import string. Expected type 'preset', got '${item._type ?? 'unknown'}'.`) toast.error(`Invalid import string. Expected type 'preset', got '${item._type ?? 'unknown'}'.`)
@ -368,16 +339,6 @@ const importItem = async () => {
form.cli = item.cli form.cli = item.cli
} }
// -- backwards compatibility for old presets.
if (item.format) {
if (!item?.cli) {
form.cli = `--format '${item.format}'`
} else {
form.cli = `--format '${item.format}'\n${form.cli}`
}
form.cli = form.cli.trim()
}
if (item.template) { if (item.template) {
form.template = item.template form.template = item.template
} }
@ -392,15 +353,15 @@ const importItem = async () => {
import_string.value = '' import_string.value = ''
showImport.value = false showImport.value = false
} catch (e) { } catch (e: any) {
console.error(e) console.error(e)
toast.error(`Failed to parse. ${e.message}`) toast.error(`Failed to parse. ${e.message}`)
} }
} }
const filter_presets = (flag = true) => config.presets.filter(item => item.default === flag) const filter_presets = (flag = true): Preset[] => config.presets.filter(item => item.default === flag)
const import_existing_preset = async () => { const import_existing_preset = async (): Promise<void> => {
if (!selected_preset.value) { if (!selected_preset.value) {
return return
} }

View file

@ -288,52 +288,45 @@
</main> </main>
</template> </template>
<script setup> <script lang="ts" setup>
import 'assets/css/bulma-switch.css' import 'assets/css/bulma-switch.css'
import { useStorage } from '@vueuse/core' import { useStorage } from '@vueuse/core'
import { CronExpressionParser } from 'cron-parser' import { CronExpressionParser } from 'cron-parser'
import type { exported_task, task_item } from '~/types/tasks'
const props = defineProps({ const props = defineProps<{
reference: { reference?: string | null | undefined
type: String, task: task_item
required: false, addInProgress?: boolean
default: null, }>()
},
task: {
type: Object,
required: true,
},
addInProgress: {
type: Boolean,
required: false,
default: false,
},
})
const emitter = defineEmits(['cancel', 'submit']) const emitter = defineEmits<{
(e: 'cancel'): void
(e: 'submit', payload: { reference: string | null | undefined, task: task_item }): void
}>()
const toast = useNotification() const toast = useNotification()
const config = useConfigStore() const config = useConfigStore()
const box = useConfirm() const box = useConfirm()
const showImport = useStorage('showImport', false) const showImport = useStorage('showImport', false)
const import_string = ref('') const import_string = ref<string>('')
const CHANNEL_REGEX = /^https?:\/\/(?:www\.)?youtube\.com\/(?:(?:channel\/(?<channelId>UC[0-9A-Za-z_-]{22}))|(?:c\/(?<customName>[A-Za-z0-9_-]+))|(?:user\/(?<userName>[A-Za-z0-9_-]+))|(?:@(?<handle>[A-Za-z0-9_-]+)))\/?$/; const CHANNEL_REGEX = /^https?:\/\/(?:www\.)?youtube\.com\/(?:(?:channel\/(?<channelId>UC[0-9A-Za-z_-]{22}))|(?:c\/(?<customName>[A-Za-z0-9_-]+))|(?:user\/(?<userName>[A-Za-z0-9_-]+))|(?:@(?<handle>[A-Za-z0-9_-]+)))\/?$/
const form = reactive(props.task) const form = reactive<task_item>({ ...props.task })
onMounted(() => { onMounted(() => {
if (!props.task?.preset || '' === props.task.preset) { if (!props.task?.preset || '' === props.task.preset) {
form.preset = toRaw(config.app.default_preset) form.preset = toRaw(config.app.default_preset)
} }
if (typeof form.auto_start === 'undefined' || form.auto_start === null) { if (typeof form.auto_start === 'undefined' || null === form.auto_start) {
form.auto_start = true form.auto_start = true
} }
}) })
const checkInfo = async () => { const checkInfo = async (): Promise<void> => {
const required = ['name', 'url'] const required = ['name', 'url'] as const
for (const key of required) { for (const key of required) {
if (!form[key]) { if (!form[key]) {
toast.error(`The ${key} field is required.`) toast.error(`The ${key} field is required.`)
@ -344,7 +337,7 @@ const checkInfo = async () => {
if (form.timer) { if (form.timer) {
try { try {
CronExpressionParser.parse(form.timer) CronExpressionParser.parse(form.timer)
} catch (e) { } catch (e: any) {
console.error(e) console.error(e)
toast.error(`Invalid CRON expression. ${e.message}`) toast.error(`Invalid CRON expression. ${e.message}`)
return return
@ -353,31 +346,29 @@ const checkInfo = async () => {
try { try {
new URL(form.url) new URL(form.url)
} catch (e) { } catch {
toast.error('Invalid URL') toast.error('Invalid URL')
return return
} }
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) return
return form.cli = form.cli.trim()
}
form.cli = form.cli.trim(" ")
} }
emitter('submit', { reference: toRaw(props.reference), task: toRaw(form) }) emitter('submit', { reference: toRaw(props.reference), task: toRaw(form) })
} }
const importItem = async () => { const importItem = async (): Promise<void> => {
let val = import_string.value.trim() const val = import_string.value.trim()
if (!val) { if (!val) {
toast.error('The import string is required.') toast.error('The import string is required.')
return return
} }
try { try {
const item = decode(val) const item = decode(val) as exported_task
if ('task' !== item._type) { if ('task' !== item._type) {
toast.error(`Invalid import string. Expected type 'task', got '${item._type}'.`) toast.error(`Invalid import string. Expected type 'task', got '${item._type}'.`)
@ -391,34 +382,15 @@ const importItem = async () => {
} }
} }
if (item.name) { form.name = item.name ?? form.name
form.name = item.name form.url = item.url ?? form.url
} form.template = item.template ?? form.template
form.timer = item.timer ?? form.timer
if (item.url) { form.folder = item.folder ?? form.folder
form.url = item.url form.cli = item.cli ?? form.cli
} form.auto_start = item.auto_start ?? true
if (item.template) {
form.template = item.template
}
if (item.timer) {
form.timer = item.timer
}
if (item.folder) {
form.folder = item.folder
}
if (item.cli) {
form.cli = item.cli
}
form.auto_start = item?.auto_start ?? true
if (item.preset) { if (item.preset) {
// -- check if the preset exists in config.presets
const preset = config.presets.find(p => p.name === item.preset) const preset = config.presets.find(p => p.name === item.preset)
if (!preset) { if (!preset) {
toast.warning(`Preset '${item.preset}' not found. Preset will be set to default.`) toast.warning(`Preset '${item.preset}' not found. Preset will be set to default.`)
@ -429,13 +401,13 @@ const importItem = async () => {
} }
import_string.value = '' import_string.value = ''
} catch (e) { } catch (e: any) {
console.error(e) console.error(e)
toast.error(`Failed to import string. ${e.message}`) toast.error(`Failed to import string. ${e.message}`)
} }
} }
const convertOptions = async args => { const convertOptions = async (args: string): Promise<Record<string, any> | null> => {
try { try {
const response = await convertCliOptions(args) const response = await convertCliOptions(args)
@ -447,51 +419,42 @@ const convertOptions = async args => {
form.folder = response.download_path form.folder = response.download_path
} }
return response.opts return response.opts as Record<string, any>
} catch (e) { } catch (e: any) {
toast.error(e.message) toast.error(e.message)
} }
return null return null
} }
const hasFormatInConfig = computed(() => { const hasFormatInConfig = computed<boolean>(() => !!form.cli && /(?<!\S)(-f|--format)(=|\s)(\S+)/.test(form.cli))
if (!form?.cli) {
return false
}
return /(?<!\S)(-f|--format)(=|\s)(\S+)/.test(form.cli)
})
const filter_presets = (flag = true) => config.presets.filter(item => item.default === flag) const filter_presets = (flag = true) => config.presets.filter(item => item.default === flag)
const get_download_folder = () => { const get_download_folder = (): string => {
if (form.preset && !hasFormatInConfig.value) { if (form.preset && false === hasFormatInConfig.value) {
const preset = config.presets.find(p => p.name === form.preset) const preset = config.presets.find(p => p.name === form.preset)
if (preset && preset.folder) { if (preset?.folder) {
return preset.folder.replace(config.app.download_path, '') return preset.folder.replace(config.app.download_path, '')
} }
} }
return '/' return '/'
} }
const get_output_template = () => { const get_output_template = (): string => {
if (form.preset && !hasFormatInConfig.value) { if (form.preset && false === hasFormatInConfig.value) {
const preset = config.presets.find(p => p.name === form.preset) const preset = config.presets.find(p => p.name === form.preset)
if (preset && preset.template) { if (preset?.template) {
return preset.template return preset.template
} }
} }
return config.app.output_template || '%(title)s.%(ext)s' return config.app.output_template || '%(title)s.%(ext)s'
} }
function is_yt_handle(url) { const is_yt_handle = (url: string): boolean => {
let m = url.match(CHANNEL_REGEX); const m = url.match(CHANNEL_REGEX)
if (m?.groups) { if (m?.groups) {
if (m.groups?.channelId) { return !m.groups.channelId
return false
}
return true
} }
return false return false
} }

View file

@ -28,6 +28,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useStorage } from '@vueuse/core' import { useStorage } from '@vueuse/core'
import Hls from 'hls.js' import Hls from 'hls.js'
import type { StoreItem } from '~/types/store'
type video_track_element = { type video_track_element = {
file: string, file: string,
@ -58,7 +59,7 @@ const toast = useNotification()
const props = defineProps({ const props = defineProps({
item: { item: {
type: Object, type: Object as () => StoreItem,
default: () => ({}), default: () => ({}),
} }
}) })
@ -122,8 +123,8 @@ onMounted(async () => {
if (props.item.extras?.thumbnail) { if (props.item.extras?.thumbnail) {
thumbnail.value = '/api/thumbnail?url=' + encodePath(props.item.extras.thumbnail) thumbnail.value = '/api/thumbnail?url=' + encodePath(props.item.extras.thumbnail)
} else { } else {
if (response?.sidecar?.image && response.sidecar.image.length > 0) { if (response.sidecar?.image?.[0]?.file) {
thumbnail.value = makeDownload(config, { "filename": response.sidecar.image[0]['file'] }) thumbnail.value = makeDownload(config, { "filename": response.sidecar.image[0].file })
} }
} }

26
ui/app/types/notification.d.ts vendored Normal file
View file

@ -0,0 +1,26 @@
type notificationRequestHeaderItem = {
key: string;
value: string;
};
type notificationRequest = {
data_key: string;
headers: notificationRequestHeaderItem[];
method: string;
type: string;
url: string;
};
type notification = {
id?: string;
name: string;
request: notificationRequest;
on: string[];
};
type notificationImport = notification & {
_type: 'notification';
_version: string;
};
export type { notificationRequestHeaderItem, notification, notificationRequest, notificationImport };

17
ui/app/types/presets.d.ts vendored Normal file
View file

@ -0,0 +1,17 @@
type Preset = {
id?: string
name: string
cli: string
cookies: string
default: boolean
description: string
folder: string
template: string
}
type PresetImport = Preset & {
_type: 'preset'
_version: string
}
export type { Preset, PresetImport }

View file

@ -54,6 +54,11 @@ export type StoreItem = {
thumbnail?: string thumbnail?: string
/** The uploader of the item if available */ /** The uploader of the item if available */
uploader?: string uploader?: string
/** Uploader name if available */
is_audio?: boolean
/** If the item has audio stream */
is_video?: boolean
/** If the item has video stream */
} }
/** The item temporary filename */ /** The item temporary filename */
tmpfilename?: string | null tmpfilename?: string | null

View file

@ -1,4 +1,5 @@
import type { convert_args_response } from "~/types/responses"; import type { convert_args_response } from "~/types/responses";
import type { StoreItem } from "~/types/store";
const runtimeConfig = useRuntimeConfig() const runtimeConfig = useRuntimeConfig()
const toast = useNotification() const toast = useNotification()
@ -437,7 +438,7 @@ const getQueryParams = (url: string = window.location.search): Record<string, st
* @param base - The base endpoint type (default: 'api/download'). * @param base - The base endpoint type (default: 'api/download').
* @returns The fully constructed download URI. * @returns The fully constructed download URI.
*/ */
const makeDownload = (config: any, item: { folder?: string; filename: string }, base: string = 'api/download'): string => { const makeDownload = (config: any, item: StoreItem | { folder?: string; filename: string }, base: string = 'api/download'): string => {
let baseDir = 'api/player/m3u8/video/' let baseDir = 'api/player/m3u8/video/'
if ('m3u8' !== base) { if ('m3u8' !== base) {
baseDir = `${base}/` baseDir = `${base}/`
@ -448,6 +449,10 @@ const makeDownload = (config: any, item: { folder?: string; filename: string },
baseDir += item.folder + '/' baseDir += item.folder + '/'
} }
if (!item.filename) {
return ''
}
const url = `/${sTrim(baseDir, '/')}${encodePath(item.filename)}` const url = `/${sTrim(baseDir, '/')}${encodePath(item.filename)}`
return uri('m3u8' === base ? `${url}.m3u8` : url) return uri('m3u8' === base ? `${url}.m3u8` : url)
} }