Render feed from episode (#9)
- Render chapter if present (#5) - Render transcript if present (#8) - Require transcript type if URL entered (#8)
This commit is contained in:
parent
80c65bcad6
commit
16603bbcaf
@ -233,22 +233,24 @@ type SQLiteWebLogData (conn : SqliteConnection) =
|
|||||||
let postSubQuery = subQuery "post"
|
let postSubQuery = subQuery "post"
|
||||||
let pageSubQuery = subQuery "page"
|
let pageSubQuery = subQuery "page"
|
||||||
cmd.CommandText <-
|
cmd.CommandText <-
|
||||||
$"""DELETE FROM post_comment WHERE post_id IN {postSubQuery};
|
$"""DELETE FROM post_comment WHERE post_id IN {postSubQuery};
|
||||||
DELETE FROM post_revision WHERE post_id IN {postSubQuery};
|
DELETE FROM post_revision WHERE post_id IN {postSubQuery};
|
||||||
DELETE FROM post_episode WHERE post_id IN {postSubQuery};
|
DELETE FROM post_permalink WHERE post_id IN {postSubQuery};
|
||||||
DELETE FROM post_tag WHERE post_id IN {postSubQuery};
|
DELETE FROM post_episode WHERE post_id IN {postSubQuery};
|
||||||
DELETE FROM post_category WHERE post_id IN {postSubQuery};
|
DELETE FROM post_tag WHERE post_id IN {postSubQuery};
|
||||||
DELETE FROM post_meta WHERE post_id IN {postSubQuery};
|
DELETE FROM post_category WHERE post_id IN {postSubQuery};
|
||||||
DELETE FROM post WHERE web_log_id = @webLogId;
|
DELETE FROM post_meta WHERE post_id IN {postSubQuery};
|
||||||
DELETE FROM page_revision WHERE page_id IN {pageSubQuery};
|
DELETE FROM post WHERE web_log_id = @webLogId;
|
||||||
DELETE FROM page_meta WHERE page_id IN {pageSubQuery};
|
DELETE FROM page_revision WHERE page_id IN {pageSubQuery};
|
||||||
DELETE FROM page WHERE web_log_id = @webLogId;
|
DELETE FROM page_permalink WHERE page_id IN {pageSubQuery};
|
||||||
DELETE FROM category WHERE web_log_id = @webLogId;
|
DELETE FROM page_meta WHERE page_id IN {pageSubQuery};
|
||||||
DELETE FROM tag_map WHERE web_log_id = @webLogId;
|
DELETE FROM page WHERE web_log_id = @webLogId;
|
||||||
DELETE FROM web_log_user WHERE web_log_id = @webLogId;
|
DELETE FROM category WHERE web_log_id = @webLogId;
|
||||||
|
DELETE FROM tag_map WHERE web_log_id = @webLogId;
|
||||||
|
DELETE FROM web_log_user WHERE web_log_id = @webLogId;
|
||||||
DELETE FROM web_log_feed_podcast WHERE feed_id IN {subQuery "web_log_feed"};
|
DELETE FROM web_log_feed_podcast WHERE feed_id IN {subQuery "web_log_feed"};
|
||||||
DELETE FROM web_log_feed WHERE web_log_id = @webLogId;
|
DELETE FROM web_log_feed WHERE web_log_id = @webLogId;
|
||||||
DELETE FROM web_log WHERE id = @webLogId"""
|
DELETE FROM web_log WHERE id = @webLogId"""
|
||||||
do! write cmd
|
do! write cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,6 +76,9 @@ module private Namespace =
|
|||||||
/// iTunes elements
|
/// iTunes elements
|
||||||
let iTunes = "http://www.itunes.com/dtds/podcast-1.0.dtd"
|
let iTunes = "http://www.itunes.com/dtds/podcast-1.0.dtd"
|
||||||
|
|
||||||
|
/// Podcast Index (AKA "podcasting 2.0")
|
||||||
|
let podcast = "https://podcastindex.org/namespace/1.0"
|
||||||
|
|
||||||
/// Enables chapters
|
/// Enables chapters
|
||||||
let psc = "http://podlove.org/simple-chapters/"
|
let psc = "http://podlove.org/simple-chapters/"
|
||||||
|
|
||||||
@ -127,36 +130,23 @@ let private toFeedItem webLog (authors : MetaItem list) (cats : DisplayCategory[
|
|||||||
item
|
item
|
||||||
|
|
||||||
/// Add episode information to a podcast feed item
|
/// Add episode information to a podcast feed item
|
||||||
let private addEpisode webLog (feed : CustomFeed) (post : Post) (item : SyndicationItem) =
|
let private addEpisode webLog (podcast : PodcastOptions) (episode : Episode) (post : Post) (item : SyndicationItem) =
|
||||||
let podcast = Option.get feed.podcast
|
// Convert non-absolute URLs to an absolute URL for this web log
|
||||||
let meta name = post.metadata |> List.tryFind (fun it -> it.name = name)
|
let toAbsolute (link : string) = if link.StartsWith "http" then link else WebLog.absoluteUrl webLog (Permalink link)
|
||||||
let value (item : MetaItem) = item.value
|
|
||||||
let epMediaUrl =
|
let epMediaUrl =
|
||||||
match (meta >> Option.get >> value) "episode_media_file" with
|
match episode.media with
|
||||||
| link when link.StartsWith "http" -> link
|
| link when link.StartsWith "http" -> link
|
||||||
| link when Option.isSome podcast.mediaBaseUrl -> $"{podcast.mediaBaseUrl.Value}{link}"
|
| link when Option.isSome podcast.mediaBaseUrl -> $"{podcast.mediaBaseUrl.Value}{link}"
|
||||||
| link -> WebLog.absoluteUrl webLog (Permalink link)
|
| link -> WebLog.absoluteUrl webLog (Permalink link)
|
||||||
let epMediaType =
|
let epMediaType = [ episode.mediaType; podcast.defaultMediaType ] |> List.tryFind Option.isSome |> Option.flatten
|
||||||
match meta "episode_media_type", podcast.defaultMediaType with
|
let epImageUrl = defaultArg episode.imageUrl (Permalink.toString podcast.imageUrl) |> toAbsolute
|
||||||
| Some epType, _ -> Some epType.value
|
let epExplicit = defaultArg episode.explicit podcast.explicit |> ExplicitRating.toString
|
||||||
| None, Some defType -> Some defType
|
|
||||||
| _ -> None
|
|
||||||
let epImageUrl =
|
|
||||||
match defaultArg ((meta >> Option.map value) "episode_image") (Permalink.toString podcast.imageUrl) with
|
|
||||||
| link when link.StartsWith "http" -> link
|
|
||||||
| link -> WebLog.absoluteUrl webLog (Permalink link)
|
|
||||||
let epExplicit =
|
|
||||||
try
|
|
||||||
(meta >> Option.map (value >> ExplicitRating.parse)) "episode_explicit"
|
|
||||||
|> Option.defaultValue podcast.explicit
|
|
||||||
|> ExplicitRating.toString
|
|
||||||
with :? ArgumentException -> ExplicitRating.toString podcast.explicit
|
|
||||||
|
|
||||||
let xmlDoc = XmlDocument ()
|
let xmlDoc = XmlDocument ()
|
||||||
let enclosure =
|
let enclosure =
|
||||||
let it = xmlDoc.CreateElement "enclosure"
|
let it = xmlDoc.CreateElement "enclosure"
|
||||||
it.SetAttribute ("url", epMediaUrl)
|
it.SetAttribute ("url", epMediaUrl)
|
||||||
meta "episode_media_length" |> Option.iter (fun len -> it.SetAttribute ("length", len.value))
|
it.SetAttribute ("length", string episode.length)
|
||||||
epMediaType |> Option.iter (fun typ -> it.SetAttribute ("type", typ))
|
epMediaType |> Option.iter (fun typ -> it.SetAttribute ("type", typ))
|
||||||
it
|
it
|
||||||
let image =
|
let image =
|
||||||
@ -169,10 +159,57 @@ let private addEpisode webLog (feed : CustomFeed) (post : Post) (item : Syndicat
|
|||||||
item.ElementExtensions.Add ("creator", Namespace.dc, podcast.displayedAuthor)
|
item.ElementExtensions.Add ("creator", Namespace.dc, podcast.displayedAuthor)
|
||||||
item.ElementExtensions.Add ("author", Namespace.iTunes, podcast.displayedAuthor)
|
item.ElementExtensions.Add ("author", Namespace.iTunes, podcast.displayedAuthor)
|
||||||
item.ElementExtensions.Add ("explicit", Namespace.iTunes, epExplicit)
|
item.ElementExtensions.Add ("explicit", Namespace.iTunes, epExplicit)
|
||||||
meta "episode_subtitle"
|
episode.subtitle |> Option.iter (fun it -> item.ElementExtensions.Add ("subtitle", Namespace.iTunes, it))
|
||||||
|> Option.iter (fun it -> item.ElementExtensions.Add ("subtitle", Namespace.iTunes, it.value))
|
episode.duration
|
||||||
meta "episode_duration"
|
|> Option.iter (fun it -> item.ElementExtensions.Add ("duration", Namespace.iTunes, it.ToString """hh\:mm\:ss"""))
|
||||||
|> Option.iter (fun it -> item.ElementExtensions.Add ("duration", Namespace.iTunes, it.value))
|
|
||||||
|
match episode.chapterFile with
|
||||||
|
| Some chapters ->
|
||||||
|
let url = toAbsolute chapters
|
||||||
|
let typ =
|
||||||
|
match episode.chapterType with
|
||||||
|
| Some mime -> Some mime
|
||||||
|
| None when chapters.EndsWith ".json" -> Some "application/json+chapters"
|
||||||
|
| None -> None
|
||||||
|
let elt = xmlDoc.CreateElement ("podcast", "chapters", Namespace.podcast)
|
||||||
|
elt.SetAttribute ("url", url)
|
||||||
|
typ |> Option.iter (fun it -> elt.SetAttribute ("type", it))
|
||||||
|
item.ElementExtensions.Add elt
|
||||||
|
| None -> ()
|
||||||
|
|
||||||
|
match episode.transcriptUrl with
|
||||||
|
| Some transcript ->
|
||||||
|
let url = toAbsolute transcript
|
||||||
|
let elt = xmlDoc.CreateElement ("podcast", "transcript", Namespace.podcast)
|
||||||
|
elt.SetAttribute ("url", url)
|
||||||
|
elt.SetAttribute ("type", Option.get episode.transcriptType)
|
||||||
|
episode.transcriptLang |> Option.iter (fun it -> elt.SetAttribute ("language", it))
|
||||||
|
if defaultArg episode.transcriptCaptions false then
|
||||||
|
elt.SetAttribute ("rel", "captions")
|
||||||
|
item.ElementExtensions.Add elt
|
||||||
|
| None -> ()
|
||||||
|
|
||||||
|
match episode.seasonNumber with
|
||||||
|
| Some season ->
|
||||||
|
match episode.seasonDescription with
|
||||||
|
| Some desc ->
|
||||||
|
let elt = xmlDoc.CreateElement ("podcast", "season", Namespace.podcast)
|
||||||
|
elt.SetAttribute ("name", desc)
|
||||||
|
elt.InnerText <- string season
|
||||||
|
item.ElementExtensions.Add elt
|
||||||
|
| None -> item.ElementExtensions.Add ("season", Namespace.podcast, string season)
|
||||||
|
| None -> ()
|
||||||
|
|
||||||
|
match episode.episodeNumber with
|
||||||
|
| Some epNumber ->
|
||||||
|
match episode.episodeDescription with
|
||||||
|
| Some desc ->
|
||||||
|
let elt = xmlDoc.CreateElement ("podcast", "episode", Namespace.podcast)
|
||||||
|
elt.SetAttribute ("name", desc)
|
||||||
|
elt.InnerText <- string epNumber
|
||||||
|
item.ElementExtensions.Add elt
|
||||||
|
| None -> item.ElementExtensions.Add ("episode", Namespace.podcast, string epNumber)
|
||||||
|
| None -> ()
|
||||||
|
|
||||||
if post.metadata |> List.exists (fun it -> it.name = "chapter") then
|
if post.metadata |> List.exists (fun it -> it.name = "chapter") then
|
||||||
try
|
try
|
||||||
@ -216,7 +253,12 @@ let private addPodcast webLog (rssFeed : SyndicationFeed) (feed : CustomFeed) =
|
|||||||
|
|
||||||
let xmlDoc = XmlDocument ()
|
let xmlDoc = XmlDocument ()
|
||||||
|
|
||||||
[ "dc", Namespace.dc; "itunes", Namespace.iTunes; "psc", Namespace.psc; "rawvoice", Namespace.rawVoice ]
|
[ "dc", Namespace.dc
|
||||||
|
"itunes", Namespace.iTunes
|
||||||
|
"podcast", Namespace.podcast
|
||||||
|
"psc", Namespace.psc
|
||||||
|
"rawvoice", Namespace.rawVoice
|
||||||
|
]
|
||||||
|> List.iter (fun (alias, nsUrl) -> addNamespace rssFeed alias nsUrl)
|
|> List.iter (fun (alias, nsUrl) -> addNamespace rssFeed alias nsUrl)
|
||||||
|
|
||||||
let categorization =
|
let categorization =
|
||||||
@ -314,10 +356,9 @@ let createFeed (feedType : FeedType) posts : HttpHandler = fun next ctx -> backg
|
|||||||
|
|
||||||
let toItem post =
|
let toItem post =
|
||||||
let item = toFeedItem webLog authors cats tagMaps post
|
let item = toFeedItem webLog authors cats tagMaps post
|
||||||
match podcast with
|
match podcast, post.episode with
|
||||||
| Some feed when post.metadata |> List.exists (fun it -> it.name = "episode_media_file") ->
|
| Some feed, Some episode -> addEpisode webLog (Option.get feed.podcast) episode post item
|
||||||
addEpisode webLog feed post item
|
| Some _, _ ->
|
||||||
| Some _ ->
|
|
||||||
warn "Feed" ctx $"[{webLog.name} {Permalink.toString self}] \"{stripHtml post.title}\" has no media"
|
warn "Feed" ctx $"[{webLog.name} {Permalink.toString self}] \"{stripHtml post.title}\" has no media"
|
||||||
item
|
item
|
||||||
| _ -> item
|
| _ -> item
|
||||||
|
@ -157,7 +157,8 @@
|
|||||||
<div class="col-12 col-md-8 pb-3">
|
<div class="col-12 col-md-8 pb-3">
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<input type="text" name="transcriptUrl" id="transcriptUrl" class="form-control"
|
<input type="text" name="transcriptUrl" id="transcriptUrl" class="form-control"
|
||||||
placeholder="Transcript URL" value="{{ model.transcript_url }}">
|
placeholder="Transcript URL" value="{{ model.transcript_url }}"
|
||||||
|
onkeyup="Admin.requireTranscriptType()">
|
||||||
<label for="transcriptUrl">Transcript URL</label>
|
<label for="transcriptUrl">Transcript URL</label>
|
||||||
<div class="form-text">Optional; relative URL served from this web log</div>
|
<div class="form-text">Optional; relative URL served from this web log</div>
|
||||||
</div>
|
</div>
|
||||||
@ -165,7 +166,8 @@
|
|||||||
<div class="col-12 col-md-4 pb-3">
|
<div class="col-12 col-md-4 pb-3">
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<input type="text" name="transcriptType" id="transcriptType" class="form-control"
|
<input type="text" name="transcriptType" id="transcriptType" class="form-control"
|
||||||
placeholder="Transcript Type" value="{{ model.transcript_type }}">
|
placeholder="Transcript Type" value="{{ model.transcript_type }}"
|
||||||
|
{%- if model.transcript_url != "" %} required{% endif %}>
|
||||||
<label for="transcriptType">Transcript MIME Type</label>
|
<label for="transcriptType">Transcript MIME Type</label>
|
||||||
<div class="form-text">Recommended if transcript file provided</div>
|
<div class="form-text">Recommended if transcript file provided</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -255,6 +255,13 @@
|
|||||||
document.getElementById(`link_${idx}`).remove()
|
document.getElementById(`link_${idx}`).remove()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Require transcript type if transcript URL is present
|
||||||
|
*/
|
||||||
|
requireTranscriptType() {
|
||||||
|
document.getElementById("transcriptType").required = document.getElementById("transcriptUrl").value.trim() !== ""
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show messages that may have come with an htmx response
|
* Show messages that may have come with an htmx response
|
||||||
* @param messages The messages from the response
|
* @param messages The messages from the response
|
||||||
|
Loading…
Reference in New Issue
Block a user