Version 2.1 #41
| @ -164,7 +164,7 @@ type Location = { | |||||||
|     Name: string |     Name: string | ||||||
| 
 | 
 | ||||||
|     /// A geographic coordinate string (RFC 5870) |     /// A geographic coordinate string (RFC 5870) | ||||||
|     Geo: string option |     Geo: string | ||||||
| 
 | 
 | ||||||
|     /// An OpenStreetMap query |     /// An OpenStreetMap query | ||||||
|     Osm: string option |     Osm: string option | ||||||
| @ -182,6 +182,9 @@ type Chapter = { | |||||||
|     /// A URL for an image for this chapter |     /// A URL for an image for this chapter | ||||||
|     ImageUrl: string option |     ImageUrl: string option | ||||||
| 
 | 
 | ||||||
|  |     /// A URL with information pertaining to this chapter | ||||||
|  |     Url: string option | ||||||
|  | 
 | ||||||
|     /// Whether this chapter is hidden |     /// Whether this chapter is hidden | ||||||
|     IsHidden: bool option |     IsHidden: bool option | ||||||
| 
 | 
 | ||||||
| @ -197,6 +200,7 @@ type Chapter = { | |||||||
|         { StartTime = Duration.Zero |         { StartTime = Duration.Zero | ||||||
|           Title     = None |           Title     = None | ||||||
|           ImageUrl  = None |           ImageUrl  = None | ||||||
|  |           Url       = None | ||||||
|           IsHidden  = None |           IsHidden  = None | ||||||
|           EndTime   = None |           EndTime   = None | ||||||
|           Location  = None } |           Location  = None } | ||||||
|  | |||||||
| @ -84,6 +84,9 @@ type DisplayChapter = { | |||||||
|     /// An image to display for this chapter |     /// An image to display for this chapter | ||||||
|     ImageUrl: string |     ImageUrl: string | ||||||
|      |      | ||||||
|  |     /// A URL with information about this chapter | ||||||
|  |     Url: string | ||||||
|  |      | ||||||
|     /// Whether this chapter should be displayed in podcast players |     /// Whether this chapter should be displayed in podcast players | ||||||
|     IsHidden: bool |     IsHidden: bool | ||||||
|      |      | ||||||
| @ -106,10 +109,11 @@ type DisplayChapter = { | |||||||
|         { StartTime    = pattern.Format chapter.StartTime |         { StartTime    = pattern.Format chapter.StartTime | ||||||
|           Title        = defaultArg chapter.Title "" |           Title        = defaultArg chapter.Title "" | ||||||
|           ImageUrl     = defaultArg chapter.ImageUrl "" |           ImageUrl     = defaultArg chapter.ImageUrl "" | ||||||
|  |           Url          = defaultArg chapter.Url "" | ||||||
|           IsHidden     = defaultArg chapter.IsHidden false |           IsHidden     = defaultArg chapter.IsHidden false | ||||||
|           EndTime      = chapter.EndTime  |> Option.map pattern.Format          |> Option.defaultValue "" |           EndTime      = chapter.EndTime  |> Option.map pattern.Format          |> Option.defaultValue "" | ||||||
|           LocationName = chapter.Location |> Option.map _.Name                  |> 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 "" } |           LocationOsm  = chapter.Location |> Option.map _.Osm |> Option.flatten |> Option.defaultValue "" } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -379,6 +383,9 @@ type EditChapterModel = { | |||||||
|     /// An image to display for this chapter |     /// An image to display for this chapter | ||||||
|     ImageUrl: string |     ImageUrl: string | ||||||
|      |      | ||||||
|  |     /// A URL with information about this chapter | ||||||
|  |     Url: string | ||||||
|  |      | ||||||
|     /// Whether this chapter should be displayed in podcast players |     /// Whether this chapter should be displayed in podcast players | ||||||
|     IsHidden: bool |     IsHidden: bool | ||||||
|      |      | ||||||
| @ -406,6 +413,7 @@ type EditChapterModel = { | |||||||
|           StartTime    = it.StartTime |           StartTime    = it.StartTime | ||||||
|           Title        = it.Title |           Title        = it.Title | ||||||
|           ImageUrl     = it.ImageUrl |           ImageUrl     = it.ImageUrl | ||||||
|  |           Url          = it.Url | ||||||
|           IsHidden     = it.IsHidden |           IsHidden     = it.IsHidden | ||||||
|           EndTime      = it.EndTime |           EndTime      = it.EndTime | ||||||
|           LocationName = it.LocationName |           LocationName = it.LocationName | ||||||
| @ -429,10 +437,11 @@ type EditChapterModel = { | |||||||
|         let location = |         let location = | ||||||
|             match noneIfBlank this.LocationName with |             match noneIfBlank this.LocationName with | ||||||
|             | None -> None |             | 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 |         { StartTime = parseDuration (nameof this.StartTime) this.StartTime | ||||||
|           Title     = noneIfBlank this.Title |           Title     = noneIfBlank this.Title | ||||||
|           ImageUrl  = noneIfBlank this.ImageUrl |           ImageUrl  = noneIfBlank this.ImageUrl | ||||||
|  |           Url       = noneIfBlank this.Url | ||||||
|           IsHidden  = if this.IsHidden then Some true else None |           IsHidden  = if this.IsHidden then Some true else None | ||||||
|           EndTime   = noneIfBlank this.EndTime |> Option.map (parseDuration (nameof this.EndTime)) |           EndTime   = noneIfBlank this.EndTime |> Option.map (parseDuration (nameof this.EndTime)) | ||||||
|           Location  = location } |           Location  = location } | ||||||
|  | |||||||
| @ -421,6 +421,11 @@ open System.Threading.Tasks | |||||||
| /// Create a Task with a Some result for the given object | /// Create a Task with a Some result for the given object | ||||||
| let someTask<'T> (it: 'T) = Task.FromResult(Some it) | 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 System.Collections.Generic | ||||||
| open MyWebLog.Data | open MyWebLog.Data | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -208,18 +208,42 @@ let home : HttpHandler = fun next ctx -> task { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GET /{post-permalink}?chapters | // GET /{post-permalink}?chapters | ||||||
| let chapters (post: Post) : HttpHandler = | let chapters (post: Post) : HttpHandler = fun next ctx -> | ||||||
|     match post.Episode with |     match post.Episode with | ||||||
|     | Some ep -> |     | Some ep -> | ||||||
|         match ep.Chapters with |         match ep.Chapters with | ||||||
|         | Some chapters -> |         | Some chapters -> | ||||||
|              |             let chapterData = | ||||||
|             json chapters |                 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 -> |         | None -> | ||||||
|             match ep.ChapterFile with |             match ep.ChapterFile with | ||||||
|             | Some file -> redirectTo true file |             | Some file -> redirectTo true file next ctx | ||||||
|             | None -> Error.notFound |             | None -> Error.notFound next ctx | ||||||
|     | None -> Error.notFound |     | None -> Error.notFound next ctx | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| // ~~ ADMINISTRATION ~~ | // ~~ ADMINISTRATION ~~ | ||||||
|  | |||||||
| @ -46,7 +46,7 @@ let chapterEdit (model: EditChapterModel) app = [ | |||||||
|                     span [ _class "form-text" ] [ raw "Optional" ] |                     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" ] [ |                 div [ _class "form-floating" ] [ | ||||||
|                     input [ _type "text"; _id "image_url"; _name "ImageUrl"; _class "form-control" |                     input [ _type "text"; _id "image_url"; _name "ImageUrl"; _class "form-control" | ||||||
|                             _value model.ImageUrl; _placeholder "Image URL" ] |                             _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" ] [ |                 div [ _class "form-check form-switch mb-3" ] [ | ||||||
|                     input [ _type "checkbox"; _id "is_hidden"; _name "IsHidden"; _class "form-check-input" |                     input [ _type "checkbox"; _id "is_hidden"; _name "IsHidden"; _class "form-check-input" | ||||||
|                             _value "true" |                             _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 "col-6 col-lg-4 offset-lg-2 mb-3" ] [ | ||||||
|                 div [ _class "form-floating" ] [ |                 div [ _class "form-floating" ] [ | ||||||
|                     input [ _type "text"; _id "location_geo"; _name "LocationGeo"; _class "form-control" |                     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 ] |                             if not hasLoc then _disabled ] | ||||||
|                     label [ _for "location_geo" ] [ raw "Geo URL" ] |                     label [ _for "location_geo" ] [ raw "Geo URL" ] | ||||||
|                     em [ _class "form-text" ] [ |                     em [ _class "form-text" ] [ | ||||||
|                         raw "Optional; " |  | ||||||
|                         a [ _href "https://github.com/Podcastindex-org/podcast-namespace/blob/main/location/location.md#geo-recommended" |                         a [ _href "https://github.com/Podcastindex-org/podcast-namespace/blob/main/location/location.md#geo-recommended" | ||||||
|                             _target "_blank"; _rel "noopener" ] [ |                             _target "_blank"; _rel "noopener" ] [ | ||||||
|                             raw "see spec" |                             raw "see spec" | ||||||
| @ -142,16 +151,19 @@ let chapterList withNew (model: ManageChaptersModel) app = | |||||||
|         antiCsrf app |         antiCsrf app | ||||||
|         input [ _type "hidden"; _name "Id"; _value model.Id ] |         input [ _type "hidden"; _name "Id"; _value model.Id ] | ||||||
|         div [ _class "row mwl-table-heading" ] [ |         div [ _class "row mwl-table-heading" ] [ | ||||||
|             div [ _class "col" ] [ raw "Start" ] |             div [ _class "col-3 col-md-2" ] [ raw "Start" ] | ||||||
|             div [ _class "col" ] [ raw "Title" ] |             div [ _class "col-3 col-md-6 col-lg-8" ] [ raw "Title" ] | ||||||
|             div [ _class "col" ] [ raw "Image?" ] |             div [ _class "col-3 col-md-2 col-lg-1 text-center" ] [ raw "Image?" ] | ||||||
|             div [ _class "col" ] [ raw "Location?" ] |             div [ _class "col-3 col-md-2 col-lg-1 text-center" ] [ raw "Location?" ] | ||||||
|         ] |         ] | ||||||
|         yield! model.Chapters |> List.mapi (fun idx chapter -> |         yield! model.Chapters |> List.mapi (fun idx chapter -> | ||||||
|             div [ _class "row mwl-table-detail"; _id $"chapter{idx}" ] [ |             div [ _class "row mwl-table-detail"; _id $"chapter{idx}" ] [ | ||||||
|                 div [ _class "col" ] [ txt (startTimePattern.Format chapter.StartTime) ] |                 div [ _class "col-3 col-md-2" ] [ txt (startTimePattern.Format chapter.StartTime) ] | ||||||
|                 div [ _class "col" ] [ |                 div [ _class "col-3 col-md-6 col-lg-8" ] [ | ||||||
|                     txt (defaultArg chapter.Title ""); br [] |                     match chapter.Title with | ||||||
|  |                     | Some title -> txt title | ||||||
|  |                     | None -> em [ _class "text-muted" ] [ raw "no title" ] | ||||||
|  |                     br [] | ||||||
|                     small [] [ |                     small [] [ | ||||||
|                         if withNew then |                         if withNew then | ||||||
|                             raw " " |                             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-3 col-md-2 col-lg-1 text-center" ] [ | ||||||
|                 div [ _class "col" ] [ raw (if Option.isSome chapter.Location then "Y" else "N") ] |                     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" ] [ |         div [ _class "row pb-3"; _id "chapter-1" ] [ | ||||||
|             let newLink = relUrl app $"admin/post/{model.Id}/chapter/-1" |             let newLink = relUrl app $"admin/post/{model.Id}/chapter/-1" | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user