diff --git a/client/assets/app.css b/client/assets/app.css index c323a37b..5d67df53 100644 --- a/client/assets/app.css +++ b/client/assets/app.css @@ -1,6 +1,7 @@ @import './fonts.css'; @import './transitions.css'; @import './draggable.css'; +@import './defaultStyles.css'; :root { --bookshelf-texture-img: url(/textures/wood_default.jpg); diff --git a/client/assets/defaultStyles.css b/client/assets/defaultStyles.css new file mode 100644 index 00000000..a4bef5e2 --- /dev/null +++ b/client/assets/defaultStyles.css @@ -0,0 +1,44 @@ +/* + + This is for setting regular html styles for places where embedding HTML will be + like podcast episode descriptions. Otherwise TailwindCSS will have stripped all default markup. + +*/ + +.default-style p { + display: block; + margin-block-start: 1em; + margin-block-end: 1em; + margin-inline-start: 0px; + margin-inline-end: 0px; +} + +.default-style a { + text-decoration: none; + color: #5985ff; +} + +.default-style ul { + display: block; + list-style: circle; + list-style-type: disc; + margin-block-start: 1em; + margin-block-end: 1em; + margin-inline-start: 0px; + margin-inline-end: 0px; + padding-inline-start: 40px; +} + +.default-style li { + display: list-item; + text-align: -webkit-match-parent; +} + +.default-style li::marker { + unicode-bidi: isolate; + font-variant-numeric: tabular-nums; + text-transform: none; + text-indent: 0px !important; + text-align: start !important; + text-align-last: start !important; +} \ No newline at end of file diff --git a/client/components/modals/podcast/ViewEpisode.vue b/client/components/modals/podcast/ViewEpisode.vue new file mode 100644 index 00000000..04ab78c4 --- /dev/null +++ b/client/components/modals/podcast/ViewEpisode.vue @@ -0,0 +1,75 @@ + + + + + Episode + + + + + + + + + {{ podcastTitle }} + {{ podcastAuthor }} + + + {{ title }} + + No description + + + + + diff --git a/client/components/tables/podcast/EpisodeTableRow.vue b/client/components/tables/podcast/EpisodeTableRow.vue index 0e318858..b7040098 100644 --- a/client/components/tables/podcast/EpisodeTableRow.vue +++ b/client/components/tables/podcast/EpisodeTableRow.vue @@ -1,16 +1,18 @@ - + {{ title }} - {{ description }} + + {{ subtitle }} + - + {{ streamIsPlaying ? 'pause' : 'play_arrow' }} {{ timeRemaining }} - + @@ -66,10 +68,11 @@ export default { title() { return this.episode.title || '' }, + subtitle() { + return this.episode.subtitle || '' + }, description() { - if (this.episode.subtitle) return this.episode.subtitle - var desc = this.episode.description || '' - return desc + return this.episode.description || '' }, duration() { return this.$secondsToTimestamp(this.episode.duration) diff --git a/client/components/tables/podcast/EpisodesTable.vue b/client/components/tables/podcast/EpisodesTable.vue index 6401c893..300ecf0d 100644 --- a/client/components/tables/podcast/EpisodesTable.vue +++ b/client/components/tables/podcast/EpisodesTable.vue @@ -7,7 +7,7 @@ No Episodes - + @@ -68,6 +68,11 @@ export default { this.$store.commit('globals/setSelectedEpisode', episode) this.$store.commit('globals/setShowEditPodcastEpisodeModal', true) }, + viewEpisode(episode) { + this.$store.commit('setSelectedLibraryItem', this.libraryItem) + this.$store.commit('globals/setSelectedEpisode', episode) + this.$store.commit('globals/setShowViewPodcastEpisodeModal', true) + }, init() { this.episodesCopy = this.episodes.map((ep) => ({ ...ep })) } diff --git a/client/layouts/default.vue b/client/layouts/default.vue index 19e9b882..5c5ae4d1 100644 --- a/client/layouts/default.vue +++ b/client/layouts/default.vue @@ -14,6 +14,7 @@ + diff --git a/client/store/globals.js b/client/store/globals.js index bbc383dd..16d1ef91 100644 --- a/client/store/globals.js +++ b/client/store/globals.js @@ -6,6 +6,7 @@ export const state = () => ({ showUserCollectionsModal: false, showEditCollectionModal: false, showEditPodcastEpisode: false, + showViewPodcastEpisodeModal: false, showEditAuthorModal: false, selectedEpisode: null, selectedCollection: null, @@ -53,6 +54,9 @@ export const mutations = { setShowEditPodcastEpisodeModal(state, val) { state.showEditPodcastEpisode = val }, + setShowViewPodcastEpisodeModal(state, val) { + state.showViewPodcastEpisodeModal = val + }, setEditCollection(state, collection) { state.selectedCollection = collection state.showEditCollectionModal = true diff --git a/server/utils/htmlSanitizer.js b/server/utils/htmlSanitizer.js index 81ccdcff..cc046de8 100644 --- a/server/utils/htmlSanitizer.js +++ b/server/utils/htmlSanitizer.js @@ -3,7 +3,7 @@ const sanitizeHtml = require('../libs/sanitizeHtml') function sanitize(html) { const sanitizerOptions = { allowedTags: [ - 'p', 'ol', 'ul', 'a', 'strong', 'em' + 'p', 'ol', 'ul', 'li', 'a', 'strong', 'em' ], disallowedTagsMode: 'discard', allowedAttributes: { diff --git a/server/utils/podcastUtils.js b/server/utils/podcastUtils.js index c29f02e9..a5c2ab4e 100644 --- a/server/utils/podcastUtils.js +++ b/server/utils/podcastUtils.js @@ -81,9 +81,16 @@ function extractEpisodeData(item) { } } + // Full description with html + if (item['content:encoded']) { + const rawDescription = (extractFirstArrayItem(item, 'content:encoded') || '').trim() + episode.description = htmlSanitizer.sanitize(rawDescription) + } + + // Supposed to be the plaintext description but not always followed if (item['description']) { const rawDescription = extractFirstArrayItem(item, 'description') || '' - episode.description = htmlSanitizer.sanitize(rawDescription) + if (!episode.description) episode.description = htmlSanitizer.sanitize(rawDescription) episode.descriptionPlain = htmlSanitizer.stripAllTags(rawDescription) }
Episode
{{ podcastTitle }}
{{ podcastAuthor }}
{{ title }}
No description
{{ description }}
{{ subtitle }}
{{ timeRemaining }}
No Episodes