Complete main chapter functionality (#6)
- Edit page still needs UI tweaks
This commit is contained in:
parent
81039579ea
commit
5b8a632e9d
|
@ -164,7 +164,7 @@ type Location = {
|
|||
Name: string
|
||||
|
||||
/// A geographic coordinate string (RFC 5870)
|
||||
Geo: string option
|
||||
Geo: string
|
||||
|
||||
/// An OpenStreetMap query
|
||||
Osm: string option
|
||||
|
@ -182,6 +182,9 @@ type Chapter = {
|
|||
/// A URL for an image for this chapter
|
||||
ImageUrl: string option
|
||||
|
||||
/// A URL with information pertaining to this chapter
|
||||
Url: string option
|
||||
|
||||
/// Whether this chapter is hidden
|
||||
IsHidden: bool option
|
||||
|
||||
|
@ -197,6 +200,7 @@ type Chapter = {
|
|||
{ StartTime = Duration.Zero
|
||||
Title = None
|
||||
ImageUrl = None
|
||||
Url = None
|
||||
IsHidden = None
|
||||
EndTime = None
|
||||
Location = None }
|
||||
|
|
|
@ -84,6 +84,9 @@ type DisplayChapter = {
|
|||
/// An image to display for this chapter
|
||||
ImageUrl: string
|
||||
|
||||
/// A URL with information about this chapter
|
||||
Url: string
|
||||
|
||||
/// Whether this chapter should be displayed in podcast players
|
||||
IsHidden: bool
|
||||
|
||||
|
@ -106,10 +109,11 @@ type DisplayChapter = {
|
|||
{ StartTime = pattern.Format chapter.StartTime
|
||||
Title = defaultArg chapter.Title ""
|
||||
ImageUrl = defaultArg chapter.ImageUrl ""
|
||||
Url = defaultArg chapter.Url ""
|
||||
IsHidden = defaultArg chapter.IsHidden false
|
||||
EndTime = chapter.EndTime |> Option.map pattern.Format |> Option.defaultValue ""
|
||||
LocationName = chapter.Location |> Option.map _.Name |> Option.defaultValue ""
|
||||
LocationGeo = chapter.Location |> Option.map _.Geo |> Option.flatten |> Option.defaultValue ""
|
||||
LocationGeo = chapter.Location |> Option.map _.Geo |> Option.defaultValue ""
|
||||
LocationOsm = chapter.Location |> Option.map _.Osm |> Option.flatten |> Option.defaultValue "" }
|
||||
|
||||
|
||||
|
@ -379,6 +383,9 @@ type EditChapterModel = {
|
|||
/// An image to display for this chapter
|
||||
ImageUrl: string
|
||||
|
||||
/// A URL with information about this chapter
|
||||
Url: string
|
||||
|
||||
/// Whether this chapter should be displayed in podcast players
|
||||
IsHidden: bool
|
||||
|
||||
|
@ -406,6 +413,7 @@ type EditChapterModel = {
|
|||
StartTime = it.StartTime
|
||||
Title = it.Title
|
||||
ImageUrl = it.ImageUrl
|
||||
Url = it.Url
|
||||
IsHidden = it.IsHidden
|
||||
EndTime = it.EndTime
|
||||
LocationName = it.LocationName
|
||||
|
@ -429,10 +437,11 @@ type EditChapterModel = {
|
|||
let location =
|
||||
match noneIfBlank this.LocationName with
|
||||
| None -> None
|
||||
| Some name -> Some { Name = name; Geo = noneIfBlank this.LocationGeo; Osm = noneIfBlank this.LocationOsm }
|
||||
| Some name -> Some { Name = name; Geo = this.LocationGeo; Osm = noneIfBlank this.LocationOsm }
|
||||
{ StartTime = parseDuration (nameof this.StartTime) this.StartTime
|
||||
Title = noneIfBlank this.Title
|
||||
ImageUrl = noneIfBlank this.ImageUrl
|
||||
Url = noneIfBlank this.Url
|
||||
IsHidden = if this.IsHidden then Some true else None
|
||||
EndTime = noneIfBlank this.EndTime |> Option.map (parseDuration (nameof this.EndTime))
|
||||
Location = location }
|
||||
|
|
|
@ -421,6 +421,11 @@ open System.Threading.Tasks
|
|||
/// Create a Task with a Some result for the given object
|
||||
let someTask<'T> (it: 'T) = Task.FromResult(Some it)
|
||||
|
||||
/// Create an absolute URL from a string that may already be an absolute URL
|
||||
let absoluteUrl (url: string) (ctx: HttpContext) =
|
||||
if url.StartsWith "http" then url else ctx.WebLog.AbsoluteUrl (Permalink url)
|
||||
|
||||
|
||||
open System.Collections.Generic
|
||||
open MyWebLog.Data
|
||||
|
||||
|
|
|
@ -208,18 +208,42 @@ let home : HttpHandler = fun next ctx -> task {
|
|||
}
|
||||
|
||||
// GET /{post-permalink}?chapters
|
||||
let chapters (post: Post) : HttpHandler =
|
||||
let chapters (post: Post) : HttpHandler = fun next ctx ->
|
||||
match post.Episode with
|
||||
| Some ep ->
|
||||
match ep.Chapters with
|
||||
| Some chapters ->
|
||||
|
||||
json chapters
|
||||
let chapterData =
|
||||
chapters
|
||||
|> Seq.ofList
|
||||
|> Seq.map (fun it ->
|
||||
let dic = Dictionary<string, obj>()
|
||||
dic["startTime"] <- Math.Round(it.StartTime.TotalSeconds, 2)
|
||||
it.Title |> Option.iter (fun ttl -> dic["title"] <- ttl)
|
||||
it.ImageUrl |> Option.iter (fun img -> dic["img"] <- absoluteUrl img ctx)
|
||||
it.Url |> Option.iter (fun url -> dic["url"] <- absoluteUrl url ctx)
|
||||
it.IsHidden |> Option.iter (fun toc -> dic["toc"] <- not toc)
|
||||
it.EndTime |> Option.iter (fun ent -> dic["endTime"] <- Math.Round(ent.TotalSeconds, 2))
|
||||
it.Location |> Option.iter (fun loc ->
|
||||
let locData = Dictionary<string, obj>()
|
||||
locData["name"] <- loc.Name
|
||||
locData["geo"] <- loc.Geo
|
||||
loc.Osm |> Option.iter (fun osm -> locData["osm"] <- osm)
|
||||
dic["location"] <- locData)
|
||||
dic)
|
||||
|> ResizeArray
|
||||
let jsonFile = Dictionary<string, obj>()
|
||||
jsonFile["version"] <- "1.2.0"
|
||||
jsonFile["title"] <- post.Title
|
||||
jsonFile["fileName"] <- absoluteUrl ep.Media ctx
|
||||
if defaultArg ep.ChapterWaypoints false then jsonFile["waypoints"] <- true
|
||||
jsonFile["chapters"] <- chapterData
|
||||
json jsonFile next ctx
|
||||
| None ->
|
||||
match ep.ChapterFile with
|
||||
| Some file -> redirectTo true file
|
||||
| None -> Error.notFound
|
||||
| None -> Error.notFound
|
||||
| Some file -> redirectTo true file next ctx
|
||||
| None -> Error.notFound next ctx
|
||||
| None -> Error.notFound next ctx
|
||||
|
||||
|
||||
// ~~ ADMINISTRATION ~~
|
||||
|
|
|
@ -46,7 +46,7 @@ let chapterEdit (model: EditChapterModel) app = [
|
|||
span [ _class "form-text" ] [ raw "Optional" ]
|
||||
]
|
||||
]
|
||||
div [ _class "col-12 col-lg-6 offset-xl-1 mb-3" ] [
|
||||
div [ _class "col-12 col-lg-6 col-xl-5 mb-3" ] [
|
||||
div [ _class "form-floating" ] [
|
||||
input [ _type "text"; _id "image_url"; _name "ImageUrl"; _class "form-control"
|
||||
_value model.ImageUrl; _placeholder "Image URL" ]
|
||||
|
@ -56,7 +56,17 @@ let chapterEdit (model: EditChapterModel) app = [
|
|||
]
|
||||
]
|
||||
]
|
||||
div [ _class "col-12 col-lg-6 col-xl-4 mb-3 align-self-end d-flex flex-column" ] [
|
||||
div [ _class "col-12 col-lg-6 col-xl-5 mb-3" ] [
|
||||
div [ _class "form-floating" ] [
|
||||
input [ _type "text"; _id "url"; _name "Url"; _class "form-control"; _value model.Url
|
||||
_placeholder "URL" ]
|
||||
label [ _for "url" ] [ raw "URL" ]
|
||||
span [ _class "form-text" ] [
|
||||
raw "Optional; informational link for this chapter"
|
||||
]
|
||||
]
|
||||
]
|
||||
div [ _class "col-12 col-lg-6 offset-lg-3 col-xl-2 offset-xl-0 mb-3 align-self-end d-flex flex-column" ] [
|
||||
div [ _class "form-check form-switch mb-3" ] [
|
||||
input [ _type "checkbox"; _id "is_hidden"; _name "IsHidden"; _class "form-check-input"
|
||||
_value "true"
|
||||
|
@ -87,11 +97,10 @@ let chapterEdit (model: EditChapterModel) app = [
|
|||
div [ _class "col-6 col-lg-4 offset-lg-2 mb-3" ] [
|
||||
div [ _class "form-floating" ] [
|
||||
input [ _type "text"; _id "location_geo"; _name "LocationGeo"; _class "form-control"
|
||||
_value model.LocationGeo; _placeholder "Location Geo URL"
|
||||
_value model.LocationGeo; _placeholder "Location Geo URL"; _required
|
||||
if not hasLoc then _disabled ]
|
||||
label [ _for "location_geo" ] [ raw "Geo URL" ]
|
||||
em [ _class "form-text" ] [
|
||||
raw "Optional; "
|
||||
a [ _href "https://github.com/Podcastindex-org/podcast-namespace/blob/main/location/location.md#geo-recommended"
|
||||
_target "_blank"; _rel "noopener" ] [
|
||||
raw "see spec"
|
||||
|
@ -142,16 +151,19 @@ let chapterList withNew (model: ManageChaptersModel) app =
|
|||
antiCsrf app
|
||||
input [ _type "hidden"; _name "Id"; _value model.Id ]
|
||||
div [ _class "row mwl-table-heading" ] [
|
||||
div [ _class "col" ] [ raw "Start" ]
|
||||
div [ _class "col" ] [ raw "Title" ]
|
||||
div [ _class "col" ] [ raw "Image?" ]
|
||||
div [ _class "col" ] [ raw "Location?" ]
|
||||
div [ _class "col-3 col-md-2" ] [ raw "Start" ]
|
||||
div [ _class "col-3 col-md-6 col-lg-8" ] [ raw "Title" ]
|
||||
div [ _class "col-3 col-md-2 col-lg-1 text-center" ] [ raw "Image?" ]
|
||||
div [ _class "col-3 col-md-2 col-lg-1 text-center" ] [ raw "Location?" ]
|
||||
]
|
||||
yield! model.Chapters |> List.mapi (fun idx chapter ->
|
||||
div [ _class "row mwl-table-detail"; _id $"chapter{idx}" ] [
|
||||
div [ _class "col" ] [ txt (startTimePattern.Format chapter.StartTime) ]
|
||||
div [ _class "col" ] [
|
||||
txt (defaultArg chapter.Title ""); br []
|
||||
div [ _class "col-3 col-md-2" ] [ txt (startTimePattern.Format chapter.StartTime) ]
|
||||
div [ _class "col-3 col-md-6 col-lg-8" ] [
|
||||
match chapter.Title with
|
||||
| Some title -> txt title
|
||||
| None -> em [ _class "text-muted" ] [ raw "no title" ]
|
||||
br []
|
||||
small [] [
|
||||
if withNew then
|
||||
raw " "
|
||||
|
@ -167,8 +179,12 @@ let chapterList withNew (model: ManageChaptersModel) app =
|
|||
]
|
||||
]
|
||||
]
|
||||
div [ _class "col" ] [ raw (if Option.isSome chapter.ImageUrl then "Y" else "N") ]
|
||||
div [ _class "col" ] [ raw (if Option.isSome chapter.Location then "Y" else "N") ]
|
||||
div [ _class "col-3 col-md-2 col-lg-1 text-center" ] [
|
||||
raw (match chapter.ImageUrl with Some _ -> "Y" | None -> "N")
|
||||
]
|
||||
div [ _class "col-3 col-md-2 col-lg-1 text-center" ] [
|
||||
raw (match chapter.Location with Some _ -> "Y" | None -> "N")
|
||||
]
|
||||
])
|
||||
div [ _class "row pb-3"; _id "chapter-1" ] [
|
||||
let newLink = relUrl app $"admin/post/{model.Id}/chapter/-1"
|
||||
|
|
Loading…
Reference in New Issue
Block a user