diff --git a/src/MyWebLog.Data/SQLite/SQLitePostData.fs b/src/MyWebLog.Data/SQLite/SQLitePostData.fs index 70a13c9..972669a 100644 --- a/src/MyWebLog.Data/SQLite/SQLitePostData.fs +++ b/src/MyWebLog.Data/SQLite/SQLitePostData.fs @@ -141,7 +141,7 @@ type SQLitePostData (conn : SqliteConnection) = let updatePostEpisode (post : Post) = backgroundTask { use cmd = conn.CreateCommand () cmd.CommandText <- "SELECT COUNT(post_id) FROM post_episode WHERE post_id = @postId" - cmd.Parameters.AddWithValue ("@postId", post.id) |> ignore + cmd.Parameters.AddWithValue ("@postId", PostId.toString post.id) |> ignore let! count = count cmd if count = 1 then match post.episode with @@ -176,13 +176,13 @@ type SQLitePostData (conn : SqliteConnection) = | Some ep -> cmd.CommandText <- """INSERT INTO post_episode ( - post_id, media, length, duration, media_type, image_url, subtitle, explicit, chapter_file, - chapter_type, transcript_url, transcript_type, transcript_lang, transcript_captions, - season_number, season_description, episode_number, episode_description + post_id, media, length, duration, media_type, image_url, subtitle, explicit, + chapter_file, chapter_type, transcript_url, transcript_type, transcript_lang, + transcript_captions, season_number, season_description, episode_number, episode_description ) VALUES ( - @media, @length, @duration, @mediaType, @imageUrl, @subtitle, @explicit, @chapterFile, - @chapterType, @transcriptUrl, @transcriptType, @transcriptLang, @transcriptCaptions, - @seasonNumber, @seasonDescription, @episodeNumber, @episodeDescription + @postId, @media, @length, @duration, @mediaType, @imageUrl, @subtitle, @explicit, + @chapterFile, @chapterType, @transcriptUrl, @transcriptType, @transcriptLang, + @transcriptCaptions, @seasonNumber, @seasonDescription, @episodeNumber, @episodeDescription )""" addEpisodeParameters cmd ep do! write cmd @@ -530,7 +530,7 @@ type SQLitePostData (conn : SqliteConnection) = use cmd = conn.CreateCommand () cmd.CommandText <- """UPDATE post - SET author_id = @author_id, + SET author_id = @authorId, status = @status, title = @title, permalink = @permalink, diff --git a/src/MyWebLog.Domain/ViewModels.fs b/src/MyWebLog.Domain/ViewModels.fs index 60f7767..06d4848 100644 --- a/src/MyWebLog.Domain/ViewModels.fs +++ b/src/MyWebLog.Domain/ViewModels.fs @@ -9,7 +9,7 @@ module private Helpers = /// Create a string option if a string is blank let noneIfBlank (it : string) = - match it.Trim () with "" -> None | trimmed -> Some trimmed + match (defaultArg (Option.ofObj it) "").Trim () with "" -> None | trimmed -> Some trimmed /// Details about a category, used to display category lists @@ -448,10 +448,10 @@ type EditPostModel = transcriptLang : string /// Whether the provided transcript should be presented as captions - transcriptCaptions : bool option + transcriptCaptions : bool /// The season number (optional) - seasonNumber : int option + seasonNumber : int /// A description of this season (optional, ignored if season number is not provided) seasonDescription : string @@ -489,7 +489,7 @@ type EditPostModel = isEpisode = Option.isSome post.episode media = episode.media length = episode.length - duration = defaultArg (episode.duration |> Option.map (fun it -> it.ToString "HH:mm:SS")) "" + duration = defaultArg (episode.duration |> Option.map (fun it -> it.ToString """hh\:mm\:ss""")) "" mediaType = defaultArg episode.mediaType "" imageUrl = defaultArg episode.imageUrl "" subtitle = defaultArg episode.subtitle "" @@ -499,8 +499,8 @@ type EditPostModel = transcriptUrl = defaultArg episode.transcriptUrl "" transcriptType = defaultArg episode.transcriptType "" transcriptLang = defaultArg episode.transcriptLang "" - transcriptCaptions = episode.transcriptCaptions - seasonNumber = episode.seasonNumber + transcriptCaptions = defaultArg episode.transcriptCaptions false + seasonNumber = defaultArg episode.seasonNumber 0 seasonDescription = defaultArg episode.seasonDescription "" episodeNumber = defaultArg (episode.episodeNumber |> Option.map string) "" episodeDescription = defaultArg episode.episodeDescription "" @@ -536,27 +536,24 @@ type EditPostModel = Some { media = this.media length = this.length - duration = match this.duration.Trim () with - | "" -> None - | dur -> Some (TimeSpan.Parse dur) - mediaType = match this.mediaType.Trim () with "" -> None | mt -> Some mt - imageUrl = match this.imageUrl.Trim () with "" -> None | iu -> Some iu - subtitle = match this.subtitle.Trim () with "" -> None | sub -> Some sub - explicit = match this.explicit.Trim () with - | "" -> None - | exp -> Some (ExplicitRating.parse exp) - chapterFile = match this.chapterFile.Trim () with "" -> None | cf -> Some cf - chapterType = match this.chapterType.Trim () with "" -> None | ct -> Some ct - transcriptUrl = match this.transcriptUrl.Trim () with "" -> None | tu -> Some tu - transcriptType = match this.transcriptType.Trim () with "" -> None | tt -> Some tt - transcriptLang = match this.transcriptLang.Trim () with "" -> None | tl -> Some tl - transcriptCaptions = this.transcriptCaptions - seasonNumber = this.seasonNumber - seasonDescription = match this.seasonDescription.Trim () with "" -> None | sd -> Some sd - episodeNumber = match this.episodeNumber.Trim () with - | "" -> None - | en -> Some (Double.Parse en) - episodeDescription = match this.episodeDescription.Trim () with "" -> None | ed -> Some ed + duration = noneIfBlank this.duration |> Option.map TimeSpan.Parse + mediaType = noneIfBlank this.mediaType + imageUrl = noneIfBlank this.imageUrl + subtitle = noneIfBlank this.subtitle + explicit = noneIfBlank this.explicit |> Option.map ExplicitRating.parse + chapterFile = noneIfBlank this.chapterFile + chapterType = noneIfBlank this.chapterType + transcriptUrl = noneIfBlank this.transcriptUrl + transcriptType = noneIfBlank this.transcriptType + transcriptLang = noneIfBlank this.transcriptLang + transcriptCaptions = if this.transcriptCaptions then Some true else None + seasonNumber = if this.seasonNumber = 0 then None else Some this.seasonNumber + seasonDescription = noneIfBlank this.seasonDescription + episodeNumber = match noneIfBlank this.episodeNumber |> Option.map Double.Parse with + | Some it when it = 0.0 -> None + | Some it -> Some (double it) + | None -> None + episodeDescription = noneIfBlank this.episodeDescription } else None diff --git a/src/MyWebLog/Handlers/Post.fs b/src/MyWebLog/Handlers/Post.fs index 6f5d899..f35cdf1 100644 --- a/src/MyWebLog/Handlers/Post.fs +++ b/src/MyWebLog/Handlers/Post.fs @@ -2,6 +2,7 @@ module MyWebLog.Handlers.Post open System +open System.Collections.Generic open MyWebLog /// Parse a slug and page number from an "everything else" URL @@ -237,13 +238,19 @@ let edit postId : HttpHandler = fun next ctx -> task { let model = EditPostModel.fromPost webLog post return! Hash.FromAnonymousObject {| - csrf = csrfToken ctx - model = model - metadata = Array.zip model.metaNames model.metaValues - |> Array.mapi (fun idx (name, value) -> [| string idx; name; value |]) - page_title = title - templates = templates - categories = cats + csrf = csrfToken ctx + model = model + metadata = Array.zip model.metaNames model.metaValues + |> Array.mapi (fun idx (name, value) -> [| string idx; name; value |]) + page_title = title + templates = templates + categories = cats + explicit_values = [| + KeyValuePair.Create ("", "– Default –") + KeyValuePair.Create (ExplicitRating.toString Yes, "Yes") + KeyValuePair.Create (ExplicitRating.toString No, "No") + KeyValuePair.Create (ExplicitRating.toString Clean, "Clean") + |] |} |> viewForTheme "admin" "post-edit" next ctx | None -> return! Error.notFound next ctx diff --git a/src/admin-theme/post-edit.liquid b/src/admin-theme/post-edit.liquid index d36161e..b670ae1 100644 --- a/src/admin-theme/post-edit.liquid +++ b/src/admin-theme/post-edit.liquid @@ -22,12 +22,14 @@
    - - - - +
+ + + + +
@@ -39,13 +41,194 @@
comma-delimited
{% if model.status == "Draft" %} -
+
{% endif %}
+
+ + + + + + + + +
+
+
+
+ + +
Path to media; relative URL will be appended to base media path
+
+
+
+
+ + +
Optional; overrides podcast default
+
+
+
+
+
+
+ + +
TODO: derive from above file name
+
+
+
+
+ + +
Recommended; enter in HH:MM:SS format
+
+
+
+
+
+
+ + +
Optional; a subtitle for this episode
+
+
+
+
+
+
+ + +
+ Optional; overrides podcast default; relative URL served from this web log +
+
+
+
+
+ + +
Optional; overrides podcast default
+
+
+
+
+
+
+ + +
Optional; relative URL served from this web log
+
+
+
+
+ + +
+ Optional; application/json+chapters assumed if chapter file ends with + .json +
+
+
+
+
+
+
+ + +
Optional; relative URL served from this web log
+
+
+
+
+ + +
Recommended if transcript file provided
+
+
+
+
+
+
+ + +
Optional; overrides podcast default
+
+
+
+
+ + +
+
+
+
+
+
+ + +
Optional
+
+
+
+
+ + +
Optional
+
+
+
+
+
+
+ + +
Optional; up to 2 decimal points
+
+
+
+
+ + +
Optional
+
+
+
+
+ +
Metadata @@ -55,11 +238,6 @@
- {%- unless model.meta_names contains "episode_media_file" %} - - {%- endunless %}
{%- for meta in metadata %}
diff --git a/src/admin-theme/wwwroot/admin.js b/src/admin-theme/wwwroot/admin.js index 3122671..1e36d4f 100644 --- a/src/admin-theme/wwwroot/admin.js +++ b/src/admin-theme/wwwroot/admin.js @@ -204,32 +204,16 @@ }, /** - * Add podcast episode metadata fields + * Enable or disable podcast fields */ - addPodcastFields() { - [ [ "episode_media_file", true, "required; relative URL will be appended to configured base media path" ], - [ "episode_media_length", true, "required; file size in bytes" ], - [ "episode_duration", false, "suggested; format is HH:MM:SS" ], - [ "episode_media_type", false, "optional; blank uses podcast default" ], - [ "episode_image", false, "optional; relative URLs are served from this web log" ], - [ "episode_subtitle", false, "optional" ], - [ "episode_explicit", false, "optional; blank uses podcast default"] - ].forEach(([fieldName, isRequired, hintText]) => { - const nameField = this.createMetaNameField() - nameField.value = fieldName - nameField.readOnly = true - - const valueField = this.createMetaValueField() - valueField.required = isRequired - - this.createMetaRow( - this.createMetaRemoveColumn(), - this.createMetaNameColumn(nameField), - this.createMetaValueColumn(valueField, hintText)) - }) - document.getElementById("addPodcastFieldButton").remove() + toggleEpisodeFields() { + const disabled = !document.getElementById("isEpisode").checked + ;[ "media", "mediaType", "length", "duration", "subtitle", "imageUrl", "explicit", "chapterFile", "chapterType", + "transcriptUrl", "transcriptType", "transcriptLang", "transcriptCaptions", "seasonNumber", "seasonDescription", + "episodeNumber", "episodeDescription" + ].forEach(it => document.getElementById(it).disabled = disabled) }, - + /** * Check to enable or disable podcast fields */