2026-03-02 18:16:04 +00:00
|
|
|
|
import { ref } from 'vue'
|
2026-03-02 19:57:44 +00:00
|
|
|
|
import type { Film, Song, Podcast } from '@aiui/core/types/content'
|
2026-03-02 16:48:17 +00:00
|
|
|
|
import { mockFilms } from '@/mocks/films'
|
2026-03-02 18:16:04 +00:00
|
|
|
|
import { mockSongs } from '@/mocks/songs'
|
2026-03-02 19:57:44 +00:00
|
|
|
|
import { mockPodcasts } from '@/mocks/podcasts'
|
2026-03-02 18:16:04 +00:00
|
|
|
|
import { generatePosterFallback } from '@/composables/useImageFallback'
|
2026-03-02 16:48:17 +00:00
|
|
|
|
|
|
|
|
|
|
const panelOpen = ref(false)
|
|
|
|
|
|
const panelFilms = ref<Film[]>([])
|
2026-03-02 18:16:04 +00:00
|
|
|
|
const panelSongs = ref<Song[]>([])
|
2026-03-02 19:57:44 +00:00
|
|
|
|
const panelPodcasts = ref<Podcast[]>([])
|
2026-03-02 16:48:17 +00:00
|
|
|
|
const selectedFilm = ref<Film | null>(null)
|
2026-03-02 18:16:04 +00:00
|
|
|
|
const selectedSong = ref<Song | null>(null)
|
2026-03-02 19:57:44 +00:00
|
|
|
|
const selectedPodcast = ref<Podcast | null>(null)
|
2026-03-02 16:48:17 +00:00
|
|
|
|
const panelTitle = ref('Recommended Films')
|
2026-03-02 19:57:44 +00:00
|
|
|
|
const contentType = ref<'film' | 'song' | 'podcast'>('film')
|
2026-03-02 16:48:17 +00:00
|
|
|
|
|
2026-03-02 16:49:47 +00:00
|
|
|
|
const FILM_TAG_RE = /\[\[film:(f?\d+)\]\]/gi
|
2026-03-02 18:16:04 +00:00
|
|
|
|
const FILM_EXT_RE = /\[\[film_ext:([^|]+)\|(\d{4})\|([^\]]+)\]\]/gi
|
|
|
|
|
|
const SONG_TAG_RE = /\[\[song:(s?\d+)\]\]/gi
|
|
|
|
|
|
const SONG_EXT_RE = /\[\[song_ext:([^|]+)\|([^|]+)(?:\|(\d{4}))?\]\]/gi
|
2026-03-02 19:57:44 +00:00
|
|
|
|
const PODCAST_TAG_RE = /\[\[podcast:(p?\d+)\]\]/gi
|
|
|
|
|
|
const PODCAST_EXT_RE = /\[\[podcast_ext:([^|]+)\|([^|]+)(?:\|(\d{4}))?\]\]/gi
|
2026-03-02 16:48:17 +00:00
|
|
|
|
|
|
|
|
|
|
export function useContentPanel() {
|
2026-03-02 16:49:47 +00:00
|
|
|
|
function normalizeFilmId(raw: string): string {
|
|
|
|
|
|
return raw.startsWith('f') ? raw : `f${raw}`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 16:48:17 +00:00
|
|
|
|
function extractFilmIds(text: string): string[] {
|
|
|
|
|
|
const ids: string[] = []
|
|
|
|
|
|
let match: RegExpExecArray | null
|
2026-03-02 16:49:47 +00:00
|
|
|
|
const re = new RegExp(FILM_TAG_RE.source, 'gi')
|
2026-03-02 16:48:17 +00:00
|
|
|
|
while ((match = re.exec(text)) !== null) {
|
2026-03-02 16:49:47 +00:00
|
|
|
|
const id = normalizeFilmId(match[1])
|
|
|
|
|
|
if (!ids.includes(id)) ids.push(id)
|
2026-03-02 16:48:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
return ids
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolveFilms(ids: string[]): Film[] {
|
|
|
|
|
|
return ids
|
|
|
|
|
|
.map((id) => mockFilms.find((f) => f.id === id))
|
|
|
|
|
|
.filter((f): f is Film => !!f)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 18:16:04 +00:00
|
|
|
|
function extractExternalFilms(text: string): Film[] {
|
|
|
|
|
|
const films: Film[] = []
|
|
|
|
|
|
const seen = new Set<string>()
|
|
|
|
|
|
let match: RegExpExecArray | null
|
|
|
|
|
|
const re = new RegExp(FILM_EXT_RE.source, 'gi')
|
|
|
|
|
|
while ((match = re.exec(text)) !== null) {
|
|
|
|
|
|
const title = match[1].trim()
|
|
|
|
|
|
const year = parseInt(match[2], 10)
|
|
|
|
|
|
const director = match[3].trim()
|
|
|
|
|
|
const key = `${title.toLowerCase()}|${year}`
|
|
|
|
|
|
if (seen.has(key)) continue
|
|
|
|
|
|
seen.add(key)
|
|
|
|
|
|
films.push({
|
|
|
|
|
|
id: `ext-${key.replace(/\W/g, '-')}`,
|
|
|
|
|
|
title,
|
|
|
|
|
|
year,
|
|
|
|
|
|
posterUrl: generatePosterFallback(title, year),
|
|
|
|
|
|
synopsis: '',
|
|
|
|
|
|
genres: [],
|
|
|
|
|
|
rating: 0,
|
|
|
|
|
|
runtime: 0,
|
|
|
|
|
|
director,
|
|
|
|
|
|
cast: [],
|
|
|
|
|
|
sources: [],
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
return films
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function extractAllFilms(text: string): Film[] {
|
|
|
|
|
|
const libraryFilms = resolveFilms(extractFilmIds(text))
|
|
|
|
|
|
const externalFilms = extractExternalFilms(text)
|
|
|
|
|
|
return [...libraryFilms, ...externalFilms]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function normalizeSongId(raw: string): string {
|
|
|
|
|
|
return raw.startsWith('s') ? raw : `s${raw}`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function extractSongIds(text: string): string[] {
|
|
|
|
|
|
const ids: string[] = []
|
|
|
|
|
|
let match: RegExpExecArray | null
|
|
|
|
|
|
const re = new RegExp(SONG_TAG_RE.source, 'gi')
|
|
|
|
|
|
while ((match = re.exec(text)) !== null) {
|
|
|
|
|
|
const id = normalizeSongId(match[1])
|
|
|
|
|
|
if (!ids.includes(id)) ids.push(id)
|
|
|
|
|
|
}
|
|
|
|
|
|
return ids
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolveSongs(ids: string[]): Song[] {
|
|
|
|
|
|
return ids
|
|
|
|
|
|
.map((id) => mockSongs.find((s) => s.id === id))
|
|
|
|
|
|
.filter((s): s is Song => !!s)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function extractExternalSongs(text: string): Song[] {
|
|
|
|
|
|
const songs: Song[] = []
|
|
|
|
|
|
const seen = new Set<string>()
|
|
|
|
|
|
let match: RegExpExecArray | null
|
|
|
|
|
|
const re = new RegExp(SONG_EXT_RE.source, 'gi')
|
|
|
|
|
|
while ((match = re.exec(text)) !== null) {
|
|
|
|
|
|
const title = match[1].trim()
|
|
|
|
|
|
const artist = match[2].trim()
|
|
|
|
|
|
const year = match[3] ? parseInt(match[3], 10) : undefined
|
|
|
|
|
|
const key = `${title.toLowerCase()}|${artist.toLowerCase()}`
|
|
|
|
|
|
if (seen.has(key)) continue
|
|
|
|
|
|
seen.add(key)
|
|
|
|
|
|
songs.push({
|
|
|
|
|
|
id: `ext-${key.replace(/\W/g, '-')}`,
|
|
|
|
|
|
title,
|
|
|
|
|
|
artist,
|
|
|
|
|
|
year,
|
|
|
|
|
|
sources: [],
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
return songs
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 19:37:00 +00:00
|
|
|
|
/** Infer songs from plain text when no tags present. Requires title+artist within 100 chars, whole-word artist. */
|
|
|
|
|
|
function extractSongsFromLibraryMatch(text: string): Song[] {
|
2026-03-02 18:16:04 +00:00
|
|
|
|
const lower = text.toLowerCase()
|
2026-03-02 19:37:00 +00:00
|
|
|
|
const found: { song: Song; pos: number }[] = []
|
2026-03-02 18:16:04 +00:00
|
|
|
|
const seen = new Set<string>()
|
|
|
|
|
|
for (const song of mockSongs) {
|
|
|
|
|
|
const key = song.id
|
|
|
|
|
|
if (seen.has(key)) continue
|
2026-03-02 19:37:00 +00:00
|
|
|
|
const title = song.title.toLowerCase()
|
|
|
|
|
|
const artist = song.artist.toLowerCase()
|
|
|
|
|
|
if (!lower.includes(title)) continue
|
|
|
|
|
|
const artistRe = new RegExp('\\b' + artist.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'i')
|
|
|
|
|
|
if (!artistRe.test(lower)) continue
|
|
|
|
|
|
const titlePos = lower.indexOf(title)
|
|
|
|
|
|
const artistMatch = lower.match(artistRe)
|
|
|
|
|
|
const artistPos = artistMatch?.index ?? -1
|
|
|
|
|
|
if (artistPos < 0) continue
|
|
|
|
|
|
const dist = Math.abs(titlePos - artistPos)
|
|
|
|
|
|
if (dist > 120) continue
|
|
|
|
|
|
seen.add(key)
|
|
|
|
|
|
found.push({ song, pos: Math.min(titlePos, artistPos) })
|
|
|
|
|
|
}
|
|
|
|
|
|
return found.sort((a, b) => a.pos - b.pos).map((f) => f.song)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Extract song-like patterns: "Title" by Artist, Title – Artist, 1. Title - Artist */
|
|
|
|
|
|
function extractSongsFromPatterns(text: string): Song[] {
|
|
|
|
|
|
const songs: { title: string; artist: string; pos: number }[] = []
|
|
|
|
|
|
const seen = new Set<string>()
|
|
|
|
|
|
|
|
|
|
|
|
const patterns: { re: RegExp; titleIdx: number; artistIdx: number }[] = [
|
|
|
|
|
|
{ re: /"([^"]{2,80})"\s+by\s+([A-Za-z0-9][^,\n\.]{1,50}?)(?:\s*[,\n\.]|$)/gi, titleIdx: 1, artistIdx: 2 },
|
|
|
|
|
|
{ re: /\*\*([^*]{2,80})\*\*\s+by\s+([A-Za-z0-9][^,\n\.]{1,50}?)(?:\s*[,\n\.]|$)/g, titleIdx: 1, artistIdx: 2 },
|
|
|
|
|
|
{ re: /(?:^|\n)\s*(?:\d+\.\s*|[-•]\s*)?([^\n\-–—]{2,60}?)\s*[-–—]\s*([A-Za-z0-9][^,\n]{1,50}?)(?:\s*[,\n\.]|$)/gm, titleIdx: 1, artistIdx: 2 },
|
|
|
|
|
|
{ re: /([A-Za-z0-9][^\-–—\n]{2,60}?)\s+[-–—]\s+([A-Za-z0-9][^,\n]{1,50}?)(?=\s*[,\n\.]|$)/g, titleIdx: 1, artistIdx: 2 },
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
for (const { re, titleIdx, artistIdx } of patterns) {
|
|
|
|
|
|
let m: RegExpExecArray | null
|
|
|
|
|
|
const rx = new RegExp(re.source, re.flags)
|
|
|
|
|
|
while ((m = rx.exec(text)) !== null) {
|
|
|
|
|
|
const title = m[titleIdx].trim()
|
|
|
|
|
|
const artist = m[artistIdx].trim()
|
|
|
|
|
|
if (title.length < 2 || artist.length < 2) continue
|
|
|
|
|
|
if (/^\d{4}$/.test(title) || /^\d{4}$/.test(artist)) continue
|
|
|
|
|
|
if (/\[\[(film|song)(_ext)?:/.test(title) || /\[\[(film|song)(_ext)?:/.test(artist)) continue
|
|
|
|
|
|
if (/\*\*\[\[/.test(title) || title.includes(']]**')) continue
|
|
|
|
|
|
const key = `${title.toLowerCase()}|${artist.toLowerCase()}`
|
|
|
|
|
|
if (seen.has(key)) continue
|
2026-03-02 18:16:04 +00:00
|
|
|
|
seen.add(key)
|
2026-03-02 19:37:00 +00:00
|
|
|
|
songs.push({ title, artist, pos: m.index })
|
2026-03-02 16:48:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-02 19:37:00 +00:00
|
|
|
|
|
|
|
|
|
|
return songs
|
|
|
|
|
|
.sort((a, b) => a.pos - b.pos)
|
|
|
|
|
|
.map(({ title, artist }) => ({
|
|
|
|
|
|
id: `ext-${`${title}|${artist}`.toLowerCase().replace(/\W/g, '-')}`,
|
|
|
|
|
|
title,
|
|
|
|
|
|
artist,
|
|
|
|
|
|
sources: [],
|
|
|
|
|
|
}))
|
2026-03-02 18:16:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function extractAllSongs(text: string): Song[] {
|
|
|
|
|
|
const librarySongs = resolveSongs(extractSongIds(text))
|
|
|
|
|
|
const externalSongs = extractExternalSongs(text)
|
|
|
|
|
|
if (librarySongs.length > 0 || externalSongs.length > 0) {
|
|
|
|
|
|
return [...librarySongs, ...externalSongs]
|
|
|
|
|
|
}
|
2026-03-02 19:37:00 +00:00
|
|
|
|
if (extractFilmIds(text).length > 0 || /\[\[film_ext:/.test(text)) return []
|
|
|
|
|
|
const libMatches = extractSongsFromLibraryMatch(text)
|
|
|
|
|
|
const patternMatches = extractSongsFromPatterns(text)
|
|
|
|
|
|
const libKeys = new Set(libMatches.map((s) => `${s.title.toLowerCase()}|${s.artist.toLowerCase()}`))
|
|
|
|
|
|
const fromPatterns = patternMatches.filter(
|
|
|
|
|
|
(p) => !libKeys.has(`${p.title.toLowerCase()}|${p.artist.toLowerCase()}`)
|
|
|
|
|
|
)
|
|
|
|
|
|
return [...libMatches, ...fromPatterns]
|
2026-03-02 18:16:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 19:57:44 +00:00
|
|
|
|
function normalizePodcastId(raw: string): string {
|
|
|
|
|
|
return raw.startsWith('p') ? raw : `p${raw}`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function extractPodcastIds(text: string): string[] {
|
|
|
|
|
|
const ids: string[] = []
|
|
|
|
|
|
let match: RegExpExecArray | null
|
|
|
|
|
|
const re = new RegExp(PODCAST_TAG_RE.source, 'gi')
|
|
|
|
|
|
while ((match = re.exec(text)) !== null) {
|
|
|
|
|
|
const id = normalizePodcastId(match[1])
|
|
|
|
|
|
if (!ids.includes(id)) ids.push(id)
|
|
|
|
|
|
}
|
|
|
|
|
|
return ids
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolvePodcasts(ids: string[]): Podcast[] {
|
|
|
|
|
|
return ids
|
|
|
|
|
|
.map((id) => mockPodcasts.find((p) => p.id === id))
|
|
|
|
|
|
.filter((p): p is Podcast => !!p)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function extractExternalPodcasts(text: string): Podcast[] {
|
|
|
|
|
|
const podcasts: Podcast[] = []
|
|
|
|
|
|
const seen = new Set<string>()
|
|
|
|
|
|
let match: RegExpExecArray | null
|
|
|
|
|
|
const re = new RegExp(PODCAST_EXT_RE.source, 'gi')
|
|
|
|
|
|
while ((match = re.exec(text)) !== null) {
|
|
|
|
|
|
const title = match[1].trim()
|
|
|
|
|
|
const host = match[2].trim()
|
|
|
|
|
|
const year = match[3] ? parseInt(match[3], 10) : undefined
|
|
|
|
|
|
const key = `${title.toLowerCase()}|${host.toLowerCase()}`
|
|
|
|
|
|
if (seen.has(key)) continue
|
|
|
|
|
|
seen.add(key)
|
|
|
|
|
|
podcasts.push({
|
|
|
|
|
|
id: `ext-${key.replace(/\W/g, '-')}`,
|
|
|
|
|
|
title,
|
|
|
|
|
|
host,
|
|
|
|
|
|
year,
|
|
|
|
|
|
sources: [],
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
return podcasts
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function extractAllPodcasts(text: string): Podcast[] {
|
|
|
|
|
|
const libraryPodcasts = resolvePodcasts(extractPodcastIds(text))
|
|
|
|
|
|
const externalPodcasts = extractExternalPodcasts(text)
|
|
|
|
|
|
return [...libraryPodcasts, ...externalPodcasts]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 18:16:04 +00:00
|
|
|
|
function updatePanelFromText(text: string) {
|
|
|
|
|
|
const songs = extractAllSongs(text)
|
|
|
|
|
|
const films = extractAllFilms(text)
|
2026-03-02 19:57:44 +00:00
|
|
|
|
const podcasts = extractAllPodcasts(text)
|
2026-03-02 18:16:04 +00:00
|
|
|
|
|
|
|
|
|
|
if (songs.length > 0) {
|
|
|
|
|
|
panelSongs.value = songs
|
|
|
|
|
|
panelFilms.value = []
|
2026-03-02 19:57:44 +00:00
|
|
|
|
panelPodcasts.value = []
|
2026-03-02 18:16:04 +00:00
|
|
|
|
selectedFilm.value = null
|
2026-03-02 19:57:44 +00:00
|
|
|
|
selectedPodcast.value = null
|
2026-03-02 18:16:04 +00:00
|
|
|
|
contentType.value = 'song'
|
|
|
|
|
|
panelTitle.value = songs.length === 1
|
|
|
|
|
|
? songs[0].title
|
|
|
|
|
|
: `${songs.length} Recommended Songs`
|
|
|
|
|
|
panelOpen.value = true
|
|
|
|
|
|
} else if (films.length > 0) {
|
|
|
|
|
|
panelFilms.value = films
|
|
|
|
|
|
panelSongs.value = []
|
2026-03-02 19:57:44 +00:00
|
|
|
|
panelPodcasts.value = []
|
2026-03-02 18:16:04 +00:00
|
|
|
|
selectedSong.value = null
|
2026-03-02 19:57:44 +00:00
|
|
|
|
selectedPodcast.value = null
|
2026-03-02 18:16:04 +00:00
|
|
|
|
contentType.value = 'film'
|
|
|
|
|
|
panelTitle.value = films.length === 1
|
|
|
|
|
|
? films[0].title
|
|
|
|
|
|
: `${films.length} Recommended Films`
|
|
|
|
|
|
panelOpen.value = true
|
2026-03-02 19:57:44 +00:00
|
|
|
|
} else if (podcasts.length > 0) {
|
|
|
|
|
|
panelPodcasts.value = podcasts
|
|
|
|
|
|
panelFilms.value = []
|
|
|
|
|
|
panelSongs.value = []
|
|
|
|
|
|
selectedFilm.value = null
|
|
|
|
|
|
selectedSong.value = null
|
|
|
|
|
|
contentType.value = 'podcast'
|
|
|
|
|
|
panelTitle.value = podcasts.length === 1
|
|
|
|
|
|
? podcasts[0].title
|
|
|
|
|
|
: `${podcasts.length} Recommended Podcasts`
|
|
|
|
|
|
panelOpen.value = true
|
2026-03-02 18:16:04 +00:00
|
|
|
|
}
|
2026-03-02 16:48:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function stripFilmTags(text: string): string {
|
2026-03-02 18:16:04 +00:00
|
|
|
|
return text
|
|
|
|
|
|
.replace(FILM_TAG_RE, '')
|
|
|
|
|
|
.replace(FILM_EXT_RE, '')
|
|
|
|
|
|
.replace(/\n{3,}/g, '\n\n')
|
|
|
|
|
|
.trim()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function stripSongTags(text: string): string {
|
|
|
|
|
|
return text
|
|
|
|
|
|
.replace(SONG_TAG_RE, '')
|
|
|
|
|
|
.replace(SONG_EXT_RE, '')
|
|
|
|
|
|
.replace(/\n{3,}/g, '\n\n')
|
|
|
|
|
|
.trim()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 19:57:44 +00:00
|
|
|
|
function stripPodcastTags(text: string): string {
|
|
|
|
|
|
return text
|
|
|
|
|
|
.replace(PODCAST_TAG_RE, '')
|
|
|
|
|
|
.replace(PODCAST_EXT_RE, '')
|
|
|
|
|
|
.replace(/\n{3,}/g, '\n\n')
|
|
|
|
|
|
.trim()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 18:16:04 +00:00
|
|
|
|
function stripContentTags(text: string): string {
|
2026-03-02 19:57:44 +00:00
|
|
|
|
return stripFilmTags(stripSongTags(stripPodcastTags(text)))
|
2026-03-02 16:48:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openFilmDetail(film: Film) {
|
|
|
|
|
|
selectedFilm.value = film
|
2026-03-02 18:16:04 +00:00
|
|
|
|
selectedSong.value = null
|
2026-03-02 19:57:44 +00:00
|
|
|
|
selectedPodcast.value = null
|
2026-03-02 16:48:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closeFilmDetail() {
|
|
|
|
|
|
selectedFilm.value = null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 18:16:04 +00:00
|
|
|
|
function openSongDetail(song: Song) {
|
|
|
|
|
|
selectedSong.value = song
|
|
|
|
|
|
selectedFilm.value = null
|
2026-03-02 19:57:44 +00:00
|
|
|
|
selectedPodcast.value = null
|
2026-03-02 18:16:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closeSongDetail() {
|
|
|
|
|
|
selectedSong.value = null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 19:57:44 +00:00
|
|
|
|
function openPodcastDetail(podcast: Podcast) {
|
|
|
|
|
|
selectedPodcast.value = podcast
|
|
|
|
|
|
selectedFilm.value = null
|
|
|
|
|
|
selectedSong.value = null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closePodcastDetail() {
|
|
|
|
|
|
selectedPodcast.value = null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 16:48:17 +00:00
|
|
|
|
function closePanel() {
|
|
|
|
|
|
panelOpen.value = false
|
|
|
|
|
|
selectedFilm.value = null
|
2026-03-02 18:16:04 +00:00
|
|
|
|
selectedSong.value = null
|
2026-03-02 19:57:44 +00:00
|
|
|
|
selectedPodcast.value = null
|
2026-03-02 16:48:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function showAllFilms() {
|
|
|
|
|
|
panelFilms.value = [...mockFilms]
|
2026-03-02 18:16:04 +00:00
|
|
|
|
panelSongs.value = []
|
2026-03-02 19:57:44 +00:00
|
|
|
|
panelPodcasts.value = []
|
2026-03-02 16:48:17 +00:00
|
|
|
|
panelTitle.value = 'Your Film Library'
|
2026-03-02 18:16:04 +00:00
|
|
|
|
contentType.value = 'film'
|
2026-03-02 16:48:17 +00:00
|
|
|
|
panelOpen.value = true
|
|
|
|
|
|
selectedFilm.value = null
|
2026-03-02 18:16:04 +00:00
|
|
|
|
selectedSong.value = null
|
2026-03-02 19:57:44 +00:00
|
|
|
|
selectedPodcast.value = null
|
2026-03-02 18:16:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function showAllSongs() {
|
|
|
|
|
|
panelFilms.value = []
|
|
|
|
|
|
panelSongs.value = [...mockSongs]
|
2026-03-02 19:57:44 +00:00
|
|
|
|
panelPodcasts.value = []
|
2026-03-02 18:16:04 +00:00
|
|
|
|
panelTitle.value = 'Your Song Library'
|
|
|
|
|
|
contentType.value = 'song'
|
|
|
|
|
|
panelOpen.value = true
|
|
|
|
|
|
selectedFilm.value = null
|
|
|
|
|
|
selectedSong.value = null
|
2026-03-02 19:57:44 +00:00
|
|
|
|
selectedPodcast.value = null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function showAllPodcasts() {
|
|
|
|
|
|
panelFilms.value = []
|
|
|
|
|
|
panelSongs.value = []
|
|
|
|
|
|
panelPodcasts.value = [...mockPodcasts]
|
|
|
|
|
|
panelTitle.value = 'Your Podcast Library'
|
|
|
|
|
|
contentType.value = 'podcast'
|
|
|
|
|
|
panelOpen.value = true
|
|
|
|
|
|
selectedFilm.value = null
|
|
|
|
|
|
selectedSong.value = null
|
|
|
|
|
|
selectedPodcast.value = null
|
2026-03-02 16:48:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
panelOpen,
|
|
|
|
|
|
panelFilms,
|
2026-03-02 18:16:04 +00:00
|
|
|
|
panelSongs,
|
2026-03-02 19:57:44 +00:00
|
|
|
|
panelPodcasts,
|
2026-03-02 16:48:17 +00:00
|
|
|
|
selectedFilm,
|
2026-03-02 18:16:04 +00:00
|
|
|
|
selectedSong,
|
2026-03-02 19:57:44 +00:00
|
|
|
|
selectedPodcast,
|
2026-03-02 16:48:17 +00:00
|
|
|
|
panelTitle,
|
2026-03-02 18:16:04 +00:00
|
|
|
|
contentType,
|
2026-03-02 16:48:17 +00:00
|
|
|
|
extractFilmIds,
|
|
|
|
|
|
resolveFilms,
|
2026-03-02 18:16:04 +00:00
|
|
|
|
extractAllFilms,
|
|
|
|
|
|
extractSongIds,
|
|
|
|
|
|
resolveSongs,
|
|
|
|
|
|
extractAllSongs,
|
2026-03-02 19:57:44 +00:00
|
|
|
|
extractPodcastIds,
|
|
|
|
|
|
resolvePodcasts,
|
|
|
|
|
|
extractAllPodcasts,
|
2026-03-02 16:48:17 +00:00
|
|
|
|
updatePanelFromText,
|
|
|
|
|
|
stripFilmTags,
|
2026-03-02 18:16:04 +00:00
|
|
|
|
stripSongTags,
|
2026-03-02 19:57:44 +00:00
|
|
|
|
stripPodcastTags,
|
2026-03-02 18:16:04 +00:00
|
|
|
|
stripContentTags,
|
2026-03-02 16:48:17 +00:00
|
|
|
|
openFilmDetail,
|
|
|
|
|
|
closeFilmDetail,
|
2026-03-02 18:16:04 +00:00
|
|
|
|
openSongDetail,
|
|
|
|
|
|
closeSongDetail,
|
2026-03-02 19:57:44 +00:00
|
|
|
|
openPodcastDetail,
|
|
|
|
|
|
closePodcastDetail,
|
2026-03-02 16:48:17 +00:00
|
|
|
|
closePanel,
|
|
|
|
|
|
showAllFilms,
|
2026-03-02 18:16:04 +00:00
|
|
|
|
showAllSongs,
|
2026-03-02 19:57:44 +00:00
|
|
|
|
showAllPodcasts,
|
2026-03-02 16:48:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|