Version 2.1 #41

Merged
danieljsummers merged 123 commits from version-2.1 into main 2024-03-27 00:13:28 +00:00
12 changed files with 72 additions and 26 deletions
Showing only changes of commit 641a7499cc - Show all commits

View File

@ -419,7 +419,7 @@ type EditChapterModel = {
let pattern = let pattern =
match value |> Seq.fold (fun count chr -> if chr = ':' then count + 1 else count) 0 with match value |> Seq.fold (fun count chr -> if chr = ':' then count + 1 else count) 0 with
| 0 -> "S" | 0 -> "S"
| 1 -> "MM:ss" | 1 -> "M:ss"
| 2 -> "H:mm:ss" | 2 -> "H:mm:ss"
| _ -> invalidArg name "Max time format is H:mm:ss" | _ -> invalidArg name "Max time format is H:mm:ss"
|> function |> function

View File

@ -6,8 +6,8 @@ open System.IO
open System.Web open System.Web
open DotLiquid open DotLiquid
open Giraffe.ViewEngine open Giraffe.ViewEngine
open MyWebLog.AdminViews.Helpers
open MyWebLog.ViewModels open MyWebLog.ViewModels
open MyWebLog.Views
/// Extensions on the DotLiquid Context object /// Extensions on the DotLiquid Context object
type Context with type Context with

View File

@ -27,7 +27,7 @@ module Dashboard =
ListedPages = listed ListedPages = listed
Categories = cats Categories = cats
TopLevelCategories = topCats } TopLevelCategories = topCats }
return! adminPage "Dashboard" false (AdminViews.Admin.dashboard model) next ctx return! adminPage "Dashboard" false (Views.Admin.dashboard model) next ctx
} }
// GET /admin/administration // GET /admin/administration

View File

@ -3,8 +3,7 @@ module private MyWebLog.Handlers.Helpers
open System.Text.Json open System.Text.Json
open Microsoft.AspNetCore.Http open Microsoft.AspNetCore.Http
open MyWebLog.AdminViews open MyWebLog.Views
open MyWebLog.AdminViews.Helpers
/// Session extensions to get and set objects /// Session extensions to get and set objects
type ISession with type ISession with

View File

@ -379,7 +379,7 @@ let chapters postId : HttpHandler = requireAccess Author >=> fun next ctx -> tas
&& Option.isSome post.Episode.Value.Chapters && Option.isSome post.Episode.Value.Chapters
&& canEdit post.AuthorId ctx -> && canEdit post.AuthorId ctx ->
return! return!
adminPage "Manage Chapters" true (AdminViews.Post.chapters false (ManageChaptersModel.Create post)) next ctx adminPage "Manage Chapters" true (Views.Post.chapters false (ManageChaptersModel.Create post)) next ctx
| Some _ | None -> return! Error.notFound next ctx | Some _ | None -> return! Error.notFound next ctx
} }
@ -398,9 +398,9 @@ let editChapter (postId, index) : HttpHandler = requireAccess Author >=> fun nex
match chapter with match chapter with
| Some chap -> | Some chap ->
return! return!
adminPage adminBarePage
(if index = -1 then "Add a Chapter" else "Edit Chapter") true (if index = -1 then "Add a Chapter" else "Edit Chapter") true
(AdminViews.Post.chapterEdit (EditChapterModel.FromChapter post.Id index chap)) next ctx (Views.Post.chapterEdit (EditChapterModel.FromChapter post.Id index chap)) next ctx
| None -> return! Error.notFound next ctx | None -> return! Error.notFound next ctx
| Some _ | None -> return! Error.notFound next ctx | Some _ | None -> return! Error.notFound next ctx
} }
@ -418,7 +418,7 @@ let saveChapter (postId, index) : HttpHandler = requireAccess Author >=> fun nex
if index >= -1 && index < List.length chapters then if index >= -1 && index < List.length chapters then
try try
let chapter = form.ToChapter() let chapter = form.ToChapter()
let existing = if index = -1 then chapters else chapters |> List.removeAt index let existing = if index = -1 then chapters else List.removeAt index chapters
let updatedPost = let updatedPost =
{ post with { post with
Episode = Some Episode = Some
@ -429,9 +429,32 @@ let saveChapter (postId, index) : HttpHandler = requireAccess Author >=> fun nex
return! return!
adminPage adminPage
"Manage Chapters" true "Manage Chapters" true
(AdminViews.Post.chapterList form.AddAnother (ManageChaptersModel.Create updatedPost)) next ctx (Views.Post.chapterList form.AddAnother (ManageChaptersModel.Create updatedPost)) next ctx
with with
| ex -> return! Error.notFound next ctx // TODO: return error | ex -> return! Error.server ex.Message next ctx
else return! Error.notFound next ctx
| Some _ | None -> return! Error.notFound next ctx
}
// DELETE /admin/post/{id}/chapter/{idx}
let deleteChapter (postId, index) : HttpHandler = requireAccess Author >=> fun next ctx -> task {
let data = ctx.Data
match! data.Post.FindById (PostId postId) ctx.WebLog.Id with
| Some post
when Option.isSome post.Episode
&& Option.isSome post.Episode.Value.Chapters
&& canEdit post.AuthorId ctx ->
let chapters = post.Episode.Value.Chapters.Value
if index >= 0 && index < List.length chapters then
let updatedPost =
{ post with
Episode = Some { post.Episode.Value with Chapters = Some (List.removeAt index chapters) } }
do! data.Post.Update updatedPost
do! addMessage ctx { UserMessage.Success with Message = "Chapter deleted successfully" }
return!
adminPage
"Manage Chapters" true (Views.Post.chapterList false (ManageChaptersModel.Create updatedPost)) next
ctx
else return! Error.notFound next ctx else return! Error.notFound next ctx
| Some _ | None -> return! Error.notFound next ctx | Some _ | None -> return! Error.notFound next ctx
} }

View File

@ -216,6 +216,11 @@ let router : HttpHandler = choose [
routef "/%s/delete" Upload.deleteFromDb routef "/%s/delete" Upload.deleteFromDb
]) ])
] ]
DELETE >=> validateCsrf >=> choose [
subRoute "/post" (choose [
routef "/%s/chapter/%i" Post.deleteChapter
])
]
]) ])
GET_HEAD >=> routexp "/category/(.*)" Post.pageOfCategorizedPosts GET_HEAD >=> routexp "/category/(.*)" Post.pageOfCategorizedPosts
GET_HEAD >=> routef "/page/%i" Post.pageOfPosts GET_HEAD >=> routef "/page/%i" Post.pageOfPosts

View File

@ -36,7 +36,7 @@ let logOn returnUrl : HttpHandler = fun next ctx ->
match returnUrl with match returnUrl with
| Some _ -> returnUrl | Some _ -> returnUrl
| None -> if ctx.Request.Query.ContainsKey "returnUrl" then Some ctx.Request.Query["returnUrl"].[0] else None | None -> if ctx.Request.Query.ContainsKey "returnUrl" then Some ctx.Request.Query["returnUrl"].[0] else None
adminPage "Log On" true (AdminViews.User.logOn { LogOnModel.Empty with ReturnTo = returnTo }) next ctx adminPage "Log On" true (Views.User.logOn { LogOnModel.Empty with ReturnTo = returnTo }) next ctx
open System.Security.Claims open System.Security.Claims
@ -93,7 +93,7 @@ let private goAway : HttpHandler = RequestErrors.BAD_REQUEST "really?"
// GET /admin/settings/users // GET /admin/settings/users
let all : HttpHandler = fun next ctx -> task { let all : HttpHandler = fun next ctx -> task {
let! users = ctx.Data.WebLogUser.FindByWebLog ctx.WebLog.Id let! users = ctx.Data.WebLogUser.FindByWebLog ctx.WebLog.Id
return! adminBarePage "User Administration" true (AdminViews.User.userList users) next ctx return! adminBarePage "User Administration" true (Views.User.userList users) next ctx
} }
/// Show the edit user page /// Show the edit user page

View File

@ -9,10 +9,10 @@
<ItemGroup> <ItemGroup>
<Content Include="appsettings*.json" CopyToOutputDirectory="Always" /> <Content Include="appsettings*.json" CopyToOutputDirectory="Always" />
<Compile Include="Caches.fs" /> <Compile Include="Caches.fs" />
<Compile Include="AdminViews\Helpers.fs" /> <Compile Include="Views\Helpers.fs" />
<Compile Include="AdminViews\Admin.fs" /> <Compile Include="Views\Admin.fs" />
<Compile Include="AdminViews\Post.fs" /> <Compile Include="Views\Post.fs" />
<Compile Include="AdminViews\User.fs" /> <Compile Include="Views\User.fs" />
<Compile Include="Handlers\Helpers.fs" /> <Compile Include="Handlers\Helpers.fs" />
<Compile Include="Handlers\Admin.fs" /> <Compile Include="Handlers\Admin.fs" />
<Compile Include="Handlers\Feed.fs" /> <Compile Include="Handlers\Feed.fs" />

View File

@ -1,4 +1,4 @@
module MyWebLog.AdminViews.Admin module MyWebLog.Views.Admin
open Giraffe.ViewEngine open Giraffe.ViewEngine
open MyWebLog.ViewModels open MyWebLog.ViewModels

View File

@ -1,5 +1,5 @@
[<AutoOpen>] [<AutoOpen>]
module MyWebLog.AdminViews.Helpers module MyWebLog.Views.Helpers
open Microsoft.AspNetCore.Antiforgery open Microsoft.AspNetCore.Antiforgery
open Giraffe.ViewEngine open Giraffe.ViewEngine

View File

@ -1,4 +1,4 @@
module MyWebLog.AdminViews.Post module MyWebLog.Views.Post
open Giraffe.ViewEngine open Giraffe.ViewEngine
open Giraffe.ViewEngine.Htmx open Giraffe.ViewEngine.Htmx
@ -9,6 +9,7 @@ open NodaTime.Text
/// The pattern for chapter start times /// The pattern for chapter start times
let startTimePattern = DurationPattern.CreateWithInvariantCulture "H:mm:ss.FF" let startTimePattern = DurationPattern.CreateWithInvariantCulture "H:mm:ss.FF"
/// The form to add or edit a chapter
let chapterEdit (model: EditChapterModel) app = [ let chapterEdit (model: EditChapterModel) app = [
let postUrl = relUrl app $"admin/post/{model.PostId}/chapter/{model.Index}" let postUrl = relUrl app $"admin/post/{model.PostId}/chapter/{model.Index}"
h3 [ _class "my-3" ] [ raw (if model.Index < 0 then "Add" else "Edit"); raw " Chapter" ] h3 [ _class "my-3" ] [ raw (if model.Index < 0 then "Add" else "Edit"); raw " Chapter" ]
@ -128,6 +129,7 @@ let chapterEdit (model: EditChapterModel) app = [
else else
input [ _type "hidden"; _name "AddAnother"; _value "false" ] input [ _type "hidden"; _name "AddAnother"; _value "false" ]
button [ _type "submit"; _class "btn btn-primary" ] [ raw "Save" ] button [ _type "submit"; _class "btn btn-primary" ] [ raw "Save" ]
raw " &nbsp; "
a [ _href cancelLink; _hxGet cancelLink; _class "btn btn-secondary"; _hxTarget "body" ] [ raw "Cancel" ] a [ _href cancelLink; _hxGet cancelLink; _class "btn btn-secondary"; _hxTarget "body" ] [ raw "Cancel" ]
] ]
] ]
@ -146,20 +148,37 @@ let chapterList withNew (model: ManageChaptersModel) app =
div [ _class "col" ] [ raw "Location?" ] div [ _class "col" ] [ raw "Location?" ]
] ]
yield! model.Chapters |> List.mapi (fun idx chapter -> yield! model.Chapters |> List.mapi (fun idx chapter ->
div [ _class "row pb-3 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" ] [ txt (startTimePattern.Format chapter.StartTime) ]
div [ _class "col" ] [ txt (defaultArg chapter.Title "") ] div [ _class "col" ] [
txt (defaultArg chapter.Title ""); br []
small [] [
if withNew then
raw "&nbsp;"
else
let chapterUrl = relUrl app $"admin/post/{model.Id}/chapter/{idx}"
a [ _href chapterUrl; _hxGet chapterUrl; _hxTarget $"#chapter{idx}"
_hxSwap $"innerHTML show:#chapter{idx}:top" ] [
raw "Edit"
]
span [ _class "text-muted" ] [ raw " &bull; " ]
a [ _href chapterUrl; _hxDelete chapterUrl; _class "text-danger" ] [
raw "Delete"
]
]
]
div [ _class "col" ] [ raw (if Option.isSome chapter.ImageUrl then "Y" else "N") ] 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" ] [ raw (if Option.isSome chapter.Location then "Y" else "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"
if withNew then if withNew then
yield! chapterEdit (EditChapterModel.FromChapter (PostId model.Id) -1 Chapter.Empty) app span [ _hxGet newLink; _hxTarget "#chapter-1"; _hxTrigger "load"; _hxSwap "show:#chapter-1:top" ] []
else else
let newLink = relUrl app $"admin/post/{model.Id}/chapter/-1"
div [ _class "row pb-3 mwl-table-detail" ] [ div [ _class "row pb-3 mwl-table-detail" ] [
div [ _class "col-12" ] [ div [ _class "col-12" ] [
a [ _class "btn btn-primary"; _href newLink; _hxGet newLink; _hxTarget "#chapter-1" ] [ a [ _class "btn btn-primary"; _href newLink; _hxGet newLink; _hxTarget "#chapter-1"
_hxSwap "show:#chapter-1:top" ] [
raw "Add a New Chapter" raw "Add a New Chapter"
] ]
] ]

View File

@ -1,4 +1,4 @@
module MyWebLog.AdminViews.User module MyWebLog.Views.User
open Giraffe.ViewEngine open Giraffe.ViewEngine
open Giraffe.ViewEngine.Htmx open Giraffe.ViewEngine.Htmx