From b8849677de53859aa7f49d944984d7de06890e73 Mon Sep 17 00:00:00 2001 From: advplyr <advplyr@protonmail.com> Date: Wed, 18 Jun 2025 17:20:36 -0500 Subject: [PATCH] Episode view modal makes timestamps in description clickable --- .../components/modals/podcast/ViewEpisode.vue | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/client/components/modals/podcast/ViewEpisode.vue b/client/components/modals/podcast/ViewEpisode.vue index 5a520ef4..b4358c5d 100644 --- a/client/components/modals/podcast/ViewEpisode.vue +++ b/client/components/modals/podcast/ViewEpisode.vue @@ -16,7 +16,7 @@ </div> </div> <p dir="auto" class="text-lg font-semibold mb-6">{{ title }}</p> - <div v-if="description" dir="auto" class="default-style less-spacing" v-html="description" /> + <div v-if="description" dir="auto" class="default-style less-spacing" @click="handleDescriptionClick" v-html="description" /> <p v-else class="mb-2">{{ $strings.MessageNoDescription }}</p> <div class="w-full h-px bg-white/5 my-4" /> @@ -68,7 +68,7 @@ export default { return this.episode.title || 'No Episode Title' }, description() { - return this.episode.description || '' + return this.parseDescription(this.episode.description || '') }, media() { return this.libraryItem?.media || {} @@ -94,7 +94,41 @@ export default { return this.$store.getters['libraries/getBookCoverAspectRatio'] } }, - methods: {}, + methods: { + handleDescriptionClick(e) { + if (e.target.matches('span.time-marker')) { + const time = parseInt(e.target.dataset.time) + if (!isNaN(time)) { + this.$eventBus.$emit('play-item', { + episodeId: this.episodeId, + libraryItemId: this.libraryItem.id, + startTime: time + }) + } + e.preventDefault() + } + }, + parseDescription(description) { + const timeMarkerLinkRegex = /<a href="#([^"]*?\b\d{1,2}:\d{1,2}(?::\d{1,2})?)">(.*?)<\/a>/g + const timeMarkerRegex = /\b\d{1,2}:\d{1,2}(?::\d{1,2})?\b/g + + function convertToSeconds(time) { + const timeParts = time.split(':').map(Number) + return timeParts.reduce((acc, part, index) => acc * 60 + part, 0) + } + + return description + .replace(timeMarkerLinkRegex, (match, href, displayTime) => { + const time = displayTime.match(timeMarkerRegex)[0] + const seekTimeInSeconds = convertToSeconds(time) + return `<span class="time-marker cursor-pointer text-blue-400 hover:text-blue-300" data-time="${seekTimeInSeconds}">${displayTime}</span>` + }) + .replace(timeMarkerRegex, (match) => { + const seekTimeInSeconds = convertToSeconds(match) + return `<span class="time-marker cursor-pointer text-blue-400 hover:text-blue-300" data-time="${seekTimeInSeconds}">${match}</span>` + }) + } + }, mounted() {} } </script>