diff --git a/src/MyWebLog.Tests/Domain/ViewModelsTests.fs b/src/MyWebLog.Tests/Domain/ViewModelsTests.fs index 0915383..e00af60 100644 --- a/src/MyWebLog.Tests/Domain/ViewModelsTests.fs +++ b/src/MyWebLog.Tests/Domain/ViewModelsTests.fs @@ -52,12 +52,14 @@ let displayChapterTests = testList "DisplayChapter.FromChapter" [ { StartTime = Duration.FromSeconds 7201.43242 Title = Some "My Test Chapter" ImageUrl = Some "two-hours-in.jpg" + Url = Some "https://example.com/about" IsHidden = Some true EndTime = Some (Duration.FromSeconds 7313.788) - Location = Some { Name = "Over Here"; Geo = Some "geo:23432"; Osm = Some "SF98fFSu-8" } } + Location = Some { Name = "Over Here"; Geo = "geo:23432"; Osm = Some "SF98fFSu-8" } } Expect.equal chapter.StartTime "2:00:01.43" "Start time not filled/formatted properly" Expect.equal chapter.Title "My Test Chapter" "Title not filled properly" Expect.equal chapter.ImageUrl "two-hours-in.jpg" "Image URL not filled properly" + Expect.equal chapter.Url "https://example.com/about" "URL not filled properly" Expect.isTrue chapter.IsHidden "Is hidden flag not filled properly" Expect.equal chapter.EndTime "2:01:53.78" "End time not filled/formatted properly" Expect.equal chapter.LocationName "Over Here" "Location name not filled properly" @@ -1121,9 +1123,9 @@ let manageChaptersModelTests = testList "ManageChaptersModel.Create" [ Expect.equal model.Id "test-post" "ID not filled properly" Expect.equal model.Title "Look at all these chapters" "Title not filled properly" Expect.hasLength model.Chapters 3 "There should be three chapters" - Expect.equal model.Chapters[0].StartTime "0:00:18" "First chapter not filled properly" - Expect.equal model.Chapters[1].StartTime "0:00:36" "Second chapter not filled properly" - Expect.equal model.Chapters[2].StartTime "0:03:00.7" "Third chapter not filled properly" + Expect.equal model.Chapters[0].StartTime (Duration.FromSeconds 18L) "First chapter not filled properly" + Expect.equal model.Chapters[1].StartTime (Duration.FromSeconds 36L) "Second chapter not filled properly" + Expect.equal model.Chapters[2].StartTime (Duration.FromSeconds 180.7) "Third chapter not filled properly" } ] diff --git a/src/MyWebLog/Handlers/Admin.fs b/src/MyWebLog/Handlers/Admin.fs index e0f61ef..e04a9b1 100644 --- a/src/MyWebLog/Handlers/Admin.fs +++ b/src/MyWebLog/Handlers/Admin.fs @@ -214,34 +214,24 @@ module RedirectRules = open Microsoft.AspNetCore.Http // GET /admin/settings/redirect-rules - let all : HttpHandler = fun next ctx -> task { - return! - hashForPage "Redirect Rules" - |> withAntiCsrf ctx - |> addToHash "redirections" ctx.WebLog.RedirectRules - |> adminView "redirect-list" next ctx - } + let all : HttpHandler = fun next ctx -> + adminPage "Redirect Rules" true (Views.Admin.redirectList ctx.WebLog.RedirectRules) next ctx // GET /admin/settings/redirect-rules/[index] - let edit idx : HttpHandler = fun next ctx -> task { - if idx = -1 then - return! - hashForPage "Add Redirect Rule" - |> addToHash "model" (EditRedirectRuleModel.FromRule -1 RedirectRule.Empty) - |> withAntiCsrf ctx - |> adminBareView "redirect-edit" next ctx - else - let rules = ctx.WebLog.RedirectRules - if rules.Length < idx || idx < 0 then - return! Error.notFound next ctx - else - return! - hashForPage "Edit Redirect Rule" - |> addToHash "model" (EditRedirectRuleModel.FromRule idx (List.item idx rules)) - |> withAntiCsrf ctx - |> adminBareView "redirect-edit" next ctx - } - + let edit idx : HttpHandler = fun next ctx -> + let titleAndModel = + if idx = -1 then + Some ("Add", Views.Admin.redirectEdit (EditRedirectRuleModel.FromRule -1 RedirectRule.Empty)) + else + let rules = ctx.WebLog.RedirectRules + if rules.Length < idx || idx < 0 then + None + else + Some ("Edit", (Views.Admin.redirectEdit (EditRedirectRuleModel.FromRule idx (List.item idx rules)))) + match titleAndModel with + | Some (title, model) -> adminBarePage $"{title} Redirect Rule" true model next ctx + | None -> Error.notFound next ctx + /// Update the web log's redirect rules in the database, the request web log, and the web log cache let private updateRedirectRules (ctx: HttpContext) webLog = backgroundTask { do! ctx.Data.WebLog.UpdateRedirectRules webLog @@ -251,16 +241,15 @@ module RedirectRules = // POST /admin/settings/redirect-rules/[index] let save idx : HttpHandler = fun next ctx -> task { - let! model = ctx.BindFormAsync() - let isNew = idx = -1 - let rules = ctx.WebLog.RedirectRules - let rule = model.ToRule() - let newRules = - match isNew with - | true when model.InsertAtTop -> List.insertAt 0 rule rules - | true -> List.insertAt rules.Length rule rules - | false -> rules |> List.removeAt idx |> List.insertAt idx rule - do! updateRedirectRules ctx { ctx.WebLog with RedirectRules = newRules } + let! model = ctx.BindFormAsync() + let rule = model.ToRule() + let rules = + ctx.WebLog.RedirectRules + |> match idx with + | -1 when model.InsertAtTop -> List.insertAt 0 rule + | -1 -> List.insertAt ctx.WebLog.RedirectRules.Length rule + | _ -> List.removeAt idx >> List.insertAt idx rule + do! updateRedirectRules ctx { ctx.WebLog with RedirectRules = rules } do! addMessage ctx { UserMessage.Success with Message = "Redirect rule saved successfully" } return! all next ctx } @@ -287,7 +276,7 @@ module RedirectRules = return! all next ctx } - // POST /admin/settings/redirect-rules/[index]/delete + // DELETE /admin/settings/redirect-rules/[index] let delete idx : HttpHandler = fun next ctx -> task { if idx < 0 || idx >= ctx.WebLog.RedirectRules.Length then return! Error.notFound next ctx @@ -302,25 +291,10 @@ module RedirectRules = /// ~~~ TAG MAPPINGS ~~~ module TagMapping = - open Microsoft.AspNetCore.Http - - /// Add tag mappings to the given hash - let withTagMappings (ctx: HttpContext) hash = task { - let! mappings = ctx.Data.TagMap.FindByWebLog ctx.WebLog.Id - return - addToHash "mappings" mappings hash - |> addToHash "mapping_ids" ( - mappings - |> List.map (fun it -> { Name = it.Tag; Value = string it.Id })) - } - // GET /admin/settings/tag-mappings let all : HttpHandler = fun next ctx -> task { - let! hash = - hashForPage "" - |> withAntiCsrf ctx - |> withTagMappings ctx - return! adminBareView "tag-mapping-list-body" next ctx hash + let! mappings = ctx.Data.TagMap.FindByWebLog ctx.WebLog.Id + return! adminBarePage "Tag Mapping List" true (Views.Admin.tagMapList mappings) next ctx } // GET /admin/settings/tag-mapping/{id}/edit @@ -332,10 +306,9 @@ module TagMapping = match! tagMap with | Some tm -> return! - hashForPage (if isNew then "Add Tag Mapping" else $"Mapping for {tm.Tag} Tag") - |> withAntiCsrf ctx - |> addToHash ViewContext.Model (EditTagMapModel.FromMapping tm) - |> adminBareView "tag-mapping-edit" next ctx + adminBarePage + (if isNew then "Add Tag Mapping" else $"Mapping for {tm.Tag} Tag") true + (Views.Admin.tagMapEdit (EditTagMapModel.FromMapping tm)) next ctx | None -> return! Error.notFound next ctx } @@ -354,7 +327,7 @@ module TagMapping = | None -> return! Error.notFound next ctx } - // POST /admin/settings/tag-mapping/{id}/delete + // DELETE /admin/settings/tag-mapping/{id} let delete tagMapId : HttpHandler = fun next ctx -> task { match! ctx.Data.TagMap.Delete (TagMapId tagMapId) ctx.WebLog.Id with | true -> do! addMessage ctx { UserMessage.Success with Message = "Tag mapping deleted successfully" } @@ -531,44 +504,36 @@ module WebLog = // GET /admin/settings let settings : HttpHandler = fun next ctx -> task { let data = ctx.Data - match! TemplateCache.get adminTheme "tag-mapping-list-body" ctx.Data with - | Ok tagMapTemplate -> - let! allPages = data.Page.All ctx.WebLog.Id - let! themes = data.Theme.All() - let! hash = - hashForPage "Web Log Settings" - |> withAntiCsrf ctx - |> addToHash ViewContext.Model (SettingsModel.FromWebLog ctx.WebLog) - |> addToHash "pages" ( - seq { - KeyValuePair.Create("posts", "- First Page of Posts -") - yield! allPages - |> List.sortBy _.Title.ToLower() - |> List.map (fun p -> KeyValuePair.Create(string p.Id, p.Title)) - } - |> Array.ofSeq) - |> addToHash "themes" ( - themes - |> Seq.ofList - |> Seq.map (fun it -> - KeyValuePair.Create(string it.Id, $"{it.Name} (v{it.Version})")) - |> Array.ofSeq) - |> addToHash "upload_values" [| - KeyValuePair.Create(string Database, "Database") - KeyValuePair.Create(string Disk, "Disk") - |] - |> addToHash "rss_model" (EditRssModel.FromRssOptions ctx.WebLog.Rss) - |> addToHash "custom_feeds" ( - ctx.WebLog.Rss.CustomFeeds - |> List.map (DisplayCustomFeed.FromFeed (CategoryCache.get ctx)) - |> Array.ofList) - |> addViewContext ctx - let! hash' = TagMapping.withTagMappings ctx hash - return! - hash' - |> addToHash "tag_mapping_list" (tagMapTemplate.Render hash') - |> adminView "settings" next ctx - | Error message -> return! Error.server message next ctx + let! allPages = data.Page.All ctx.WebLog.Id + let! themes = data.Theme.All() + return! + hashForPage "Web Log Settings" + |> withAntiCsrf ctx + |> addToHash ViewContext.Model (SettingsModel.FromWebLog ctx.WebLog) + |> addToHash "pages" ( + seq { + KeyValuePair.Create("posts", "- First Page of Posts -") + yield! allPages + |> List.sortBy _.Title.ToLower() + |> List.map (fun p -> KeyValuePair.Create(string p.Id, p.Title)) + } + |> Array.ofSeq) + |> addToHash "themes" ( + themes + |> Seq.ofList + |> Seq.map (fun it -> + KeyValuePair.Create(string it.Id, $"{it.Name} (v{it.Version})")) + |> Array.ofSeq) + |> addToHash "upload_values" [| + KeyValuePair.Create(string Database, "Database") + KeyValuePair.Create(string Disk, "Disk") + |] + |> addToHash "rss_model" (EditRssModel.FromRssOptions ctx.WebLog.Rss) + |> addToHash "custom_feeds" ( + ctx.WebLog.Rss.CustomFeeds + |> List.map (DisplayCustomFeed.FromFeed (CategoryCache.get ctx)) + |> Array.ofList) + |> adminView "settings" next ctx } // POST /admin/settings diff --git a/src/MyWebLog/Handlers/Routes.fs b/src/MyWebLog/Handlers/Routes.fs index 835a171..a4ef6f9 100644 --- a/src/MyWebLog/Handlers/Routes.fs +++ b/src/MyWebLog/Handlers/Routes.fs @@ -196,16 +196,12 @@ let router : HttpHandler = choose [ routef "/%s/delete" Feed.deleteCustomFeed ]) subRoute "/redirect-rules" (choose [ - routef "/%i" Admin.RedirectRules.save - routef "/%i/up" Admin.RedirectRules.moveUp - routef "/%i/down" Admin.RedirectRules.moveDown - routef "/%i/delete" Admin.RedirectRules.delete + routef "/%i" Admin.RedirectRules.save + routef "/%i/up" Admin.RedirectRules.moveUp + routef "/%i/down" Admin.RedirectRules.moveDown ]) - subRoute "/tag-mapping" (choose [ - route "/save" >=> Admin.TagMapping.save - routef "/%s/delete" Admin.TagMapping.delete - ]) - route "/user/save" >=> User.save + route "/tag-mapping/save" >=> Admin.TagMapping.save + route "/user/save" >=> User.save ]) subRoute "/theme" (choose [ route "/new" >=> Admin.Theme.save @@ -223,7 +219,9 @@ let router : HttpHandler = choose [ routef "/%s/chapter/%i" Post.deleteChapter ]) subRoute "/settings" (requireAccess WebLogAdmin >=> choose [ - routef "/user/%s" User.delete + routef "/user/%s" User.delete + routef "/redirect-rules/%i" Admin.RedirectRules.delete + routef "/tag-mapping/%s" Admin.TagMapping.delete ]) ] ]) diff --git a/src/MyWebLog/Views/Admin.fs b/src/MyWebLog/Views/Admin.fs index 335d4ce..7e4aac1 100644 --- a/src/MyWebLog/Views/Admin.fs +++ b/src/MyWebLog/Views/Admin.fs @@ -1,6 +1,10 @@ module MyWebLog.Views.Admin +open Giraffe.Htmx.Common open Giraffe.ViewEngine +open Giraffe.ViewEngine.Accessibility +open Giraffe.ViewEngine.Htmx +open MyWebLog open MyWebLog.ViewModels /// The main dashboard @@ -75,3 +79,217 @@ let dashboard (model: DashboardModel) app = [ ] ] ] + + +/// Redirect Rule edit form +let redirectEdit (model: EditRedirectRuleModel) app = [ + let url = relUrl app $"admin/settings/redirect-rules/{model.RuleId}" + h3 [] [ raw (if model.RuleId < 0 then "Add" else "Edit"); raw " Redirect Rule" ] + form [ _action url; _hxPost url; _hxTarget "body"; _method "post"; _class "container" ] [ + antiCsrf app + input [ _type "hidden"; _name "RuleId"; _value (string model.RuleId) ] + div [ _class "row" ] [ + div [ _class "col-12 col-lg-5 mb-3" ] [ + div [ _class "form-floating" ] [ + input [ _type "text"; _name "From"; _id "from"; _class "form-control" + _placeholder "From local URL/pattern"; _autofocus; _required; _value model.From ] + label [ _for "from" ] [ raw "From" ] + ] + ] + div [ _class "col-12 col-lg-5 mb-3" ] [ + div [ _class "form-floating" ] [ + input [ _type "text"; _name "To"; _id "to"; _class "form-control"; _placeholder "To URL/pattern" + _required; _value model.To ] + label [ _for "to" ] [ raw "To" ] + ] + ] + div [ _class "col-12 col-lg-2 mb-3" ] [ + div [ _class "form-check form-switch" ] [ + input [ _type "checkbox"; _name "IsRegex"; _id "isRegex"; _class "form-check-input"; _value "true" + if model.IsRegex then _checked ] + label [ _for "isRegex" ] [ raw "Use RegEx" ] + ] + ] + ] + if model.RuleId < 0 then + div [ _class "row mb-3" ] [ + div [ _class "col-12 text-center" ] [ + label [ _class "me-1" ] [ raw "Add Rule" ] + div [ _class "btn-group btn-group-sm"; _roleGroup; _ariaLabel "New rule placement button group" ] [ + input [ _type "radio"; _name "InsertAtTop"; _id "at_top"; _class "btn-check"; _value "true" ] + label [ _class "btn btn-sm btn-outline-secondary"; _for "at_top" ] [ raw "Top" ] + input [ _type "radio"; _name "InsertAtTop"; _id "at_bot"; _class "btn-check"; _value "false" + _checked ] + label [ _class "btn btn-sm btn-outline-secondary"; _for "at_bot" ] [ raw "Bottom" ] + ] + ] + ] + div [ _class "row mb-3" ] [ + div [ _class "col text-center" ] [ + button [ _type "submit"; _class "btn btn-sm btn-primary" ] [ raw "Save Changes" ] + a [ _href (relUrl app "admin/settings/redirect-rules"); _class "btn btn-sm btn-secondary ms-3" ] [ + raw "Cancel" + ] + ] + ] + ] +] + + +/// The list of current redirect rules +let redirectList (model: RedirectRule list) app = [ + // Generate the detail for a redirect rule + let ruleDetail idx (rule: RedirectRule) = + let ruleId = $"rule_{idx}" + div [ _class "row mwl-table-detail"; _id ruleId ] [ + div [ _class "col-5 no-wrap" ] [ + txt rule.From; br [] + small [] [ + let ruleUrl = relUrl app $"admin/settings/redirect-rules/{idx}" + a [ _href ruleUrl; _hxTarget $"#{ruleId}"; _hxSwap $"{HxSwap.InnerHtml} show:#{ruleId}:top" ] [ + raw "Edit" + ] + if idx > 0 then + span [ _class "text-muted" ] [ raw " • " ] + a [ _href $"{ruleUrl}/up"; _hxPost $"{ruleUrl}/up" ] [ raw "Move Up" ] + if idx <> model.Length - 1 then + span [ _class "text-muted" ] [ raw " • " ] + a [ _href $"{ruleUrl}/down"; _hxPost $"{ruleUrl}/down" ] [ raw "Move Down" ] + span [ _class "text-muted" ] [ raw " • " ] + a [ _class "text-danger"; _href ruleUrl; _hxDelete ruleUrl + _hxConfirm "Are you sure you want to delete this redirect rule?" ] [ + raw "Delete" + ] + ] + ] + div [ _class "col-5" ] [ txt rule.To ] + div [ _class "col-2 text-center" ] [ yesOrNo rule.IsRegex ] + ] + h2 [ _class "my-3" ] [ raw app.PageTitle ] + article [] [ + p [ _class "mb-3" ] [ + a [ _href (relUrl app "admin/settings") ] [ raw "« Back to Settings" ] + ] + div [ _class "container" ] [ + div [ _class "row" ] [ + div [ _class "col" ] [ + a [ _href (relUrl app "admin/settings/redirect-rules/-1"); _class "btn btn-primary btn-sm mb-3" + _hxTarget "#rule_new" ] [ + raw "Add Redirect Rule" + ] + ] + ] + div [ _class "row" ] [ + div [ _class "col" ] [ + if List.isEmpty model then + div [ _id "rule_new" ] [ + p [ _class "text-muted text-center fst-italic" ] [ + raw "This web log has no redirect rules defined" + ] + ] + else + div [ _class "container g-0" ] [ + div [ _class "row mwl-table-heading" ] [ + div [ _class "col-5" ] [ raw "From" ] + div [ _class "col-5" ] [ raw "To" ] + div [ _class "col-2 text-center" ] [ raw "RegEx?" ] + ] + ] + div [ _class "row mwl-table-detail"; _id "rule_new" ] [] + form [ _method "post"; _class "container g-0"; _hxTarget "body" ] [ + antiCsrf app; yield! List.mapi ruleDetail model + ] + ] + ] + ] + p [ _class "mt-3 text-muted fst-italic text-center" ] [ + raw "This is an advanced feature; please " + a [ _href "https://bitbadger.solutions/open-source/myweblog/advanced.html#redirect-rules" + _target "_blank" ] [ + raw "read and understand the documentation on this feature" + ] + raw " before adding rules." + ] + ] +] + + +/// Edit a tag mapping +let tagMapEdit (model: EditTagMapModel) app = [ + h5 [ _class "my-3" ] [ txt app.PageTitle ] + form [ _hxPost (relUrl app "admin/settings/tag-mapping/save"); _method "post"; _class "container" + _hxTarget "#tagList"; _hxSwap $"{HxSwap.OuterHtml} show:window:top" ] [ + antiCsrf app + input [ _type "hidden"; _name "Id"; _value model.Id ] + div [ _class "row mb-3" ] [ + div [ _class "col-6 col-lg-4 offset-lg-2" ] [ + div [ _class "form-floating" ] [ + input [ _type "text"; _name "Tag"; _id "tag"; _class "form-control"; _placeholder "Tag"; _autofocus + _required; _value model.Tag ] + label [ _for "tag" ] [ raw "Tag" ] + ] + ] + div [ _class "col-6 col-lg-4" ] [ + div [ _class "form-floating" ] [ + input [ _type "text"; _name "UrlValue"; _id "urlValue"; _class "form-control" + _placeholder "URL Value"; _required; _value model.UrlValue ] + label [ _for "urlValue" ] [ raw "URL Value" ] + ] + ] + ] + div [ _class "row mb-3" ] [ + div [ _class "col text-center" ] [ + button [ _type "submit"; _class "btn btn-sm btn-primary" ] [ raw "Save Changes" ]; raw "   " + a [ _href (relUrl app "admin/settings/tag-mappings"); _class "btn btn-sm btn-secondary ms-3" ] [ + raw "Cancel" + ] + ] + ] + ] +] + + +/// Display a list of the web log's current tag mappings +let tagMapList (model: TagMap list) app = + let tagMapDetail (map: TagMap) = + let url = relUrl app $"admin/settings/tag-mapping/{map.Id}" + div [ _class "row mwl-table-detail"; _id $"tag_{map.Id}" ] [ + div [ _class "col no-wrap" ] [ + txt map.Tag; br [] + small [] [ + a [ _href $"{url}/edit"; _hxTarget $"#tag_{map.Id}" + _hxSwap $"{HxSwap.InnerHtml} show:#tag_{map.Id}:top" ] [ + raw "Edit" + ] + span [ _class "text-muted" ] [ raw " • " ] + a [ _href url; _hxDelete url; _class "text-danger" + _hxConfirm $"Are you sure you want to delete the mapping for “{map.Tag}”? This action cannot be undone." ] [ + raw "Delete" + ] + ] + ] + div [ _class "col" ] [ txt map.UrlValue ] + ] + div [ _id "tagList"; _class "container" ] [ + div [ _class "row" ] [ + div [ _class "col" ] [ + if List.isEmpty model then + div [ _id "tag_new" ] [ + p [ _class "text-muted text-center fst-italic" ] [ raw "This web log has no tag mappings" ] + ] + else + div [ _class "container g-0" ] [ + div [ _class "row mwl-table-heading" ] [ + div [ _class "col" ] [ raw "Tag" ] + div [ _class "col" ] [ raw "URL Value" ] + ] + ] + form [ _method "post"; _class "container g-0"; _hxTarget "#tagList"; _hxSwap HxSwap.OuterHtml ] [ + antiCsrf app + div [ _class "row mwl-table-detail"; _id "tag_new" ] [] + yield! List.map tagMapDetail model + ] + ] + ] + ] + |> List.singleton diff --git a/src/MyWebLog/Views/Helpers.fs b/src/MyWebLog/Views/Helpers.fs index 3e8f5d6..cd09280 100644 --- a/src/MyWebLog/Views/Helpers.fs +++ b/src/MyWebLog/Views/Helpers.fs @@ -98,6 +98,10 @@ let shortTime app (instant: Instant) = |> Option.defaultValue "--" |> txt +/// Display "Yes" or "No" based on the state of a boolean value +let yesOrNo value = + raw (if value then "Yes" else "No") + /// Functions for generating content in varying layouts module Layout = diff --git a/src/MyWebLog/Views/Post.fs b/src/MyWebLog/Views/Post.fs index 2da63c4..d3e5660 100644 --- a/src/MyWebLog/Views/Post.fs +++ b/src/MyWebLog/Views/Post.fs @@ -1,5 +1,6 @@ module MyWebLog.Views.Post +open Giraffe.Htmx.Common open Giraffe.ViewEngine open Giraffe.ViewEngine.Htmx open MyWebLog @@ -147,7 +148,7 @@ let chapterEdit (model: EditChapterModel) app = [ /// Display a list of chapters let chapterList withNew (model: ManageChaptersModel) app = - form [ _method "post"; _id "chapter_list"; _class "container mb-3"; _hxTarget "this"; _hxSwap "outerHTML" ] [ + form [ _method "post"; _id "chapter_list"; _class "container mb-3"; _hxTarget "this"; _hxSwap HxSwap.OuterHtml ] [ antiCsrf app input [ _type "hidden"; _name "Id"; _value model.Id ] div [ _class "row mwl-table-heading" ] [ @@ -170,7 +171,7 @@ let chapterList withNew (model: ManageChaptersModel) app = 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" ] [ + _hxSwap $"{HxSwap.InnerHtml} show:#chapter{idx}:top" ] [ raw "Edit" ] span [ _class "text-muted" ] [ raw " • " ] @@ -179,12 +180,8 @@ let chapterList withNew (model: ManageChaptersModel) app = ] ] ] - 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 "col-3 col-md-2 col-lg-1 text-center" ] [ yesOrNo (Option.isSome chapter.ImageUrl) ] + div [ _class "col-3 col-md-2 col-lg-1 text-center" ] [ yesOrNo (Option.isSome chapter.Location) ] ]) div [ _class "row pb-3"; _id "chapter-1" ] [ let newLink = relUrl app $"admin/post/{model.Id}/chapter/-1" diff --git a/src/MyWebLog/Views/User.fs b/src/MyWebLog/Views/User.fs index dedfab2..90c0116 100644 --- a/src/MyWebLog/Views/User.fs +++ b/src/MyWebLog/Views/User.fs @@ -1,5 +1,6 @@ module MyWebLog.Views.User +open Giraffe.Htmx.Common open Giraffe.ViewEngine open Giraffe.ViewEngine.Htmx open MyWebLog @@ -12,7 +13,7 @@ let edit (model: EditUserModel) app = div [ _class "col-12" ] [ h5 [ _class "my-3" ] [ txt app.PageTitle ] form [ _hxPost (relUrl app "admin/settings/user/save"); _method "post"; _class "container" - _hxTarget "#userList"; _hxSwap "outerHTML show:window:top" ] [ + _hxTarget "#userList"; _hxSwap $"{HxSwap.OuterHtml} show:window:top" ] [ antiCsrf app input [ _type "hidden"; _name "Id"; _value model.Id ] div [ _class "row" ] [ @@ -167,7 +168,8 @@ let userList (model: WebLogUser list) app = div [ _class "container g-0" ] [ div [ _class "row mwl-table-detail"; _id "user_new" ] [] ] - form [ _method "post"; _class "container g-0"; _hxTarget "this"; _hxSwap "outerHTML show:window:top" ] [ + form [ _method "post"; _class "container g-0"; _hxTarget "this" + _hxSwap $"{HxSwap.OuterHtml} show:window:top" ] [ antiCsrf app for user in model do div [ _class "row mwl-table-detail"; _id $"user_{user.Id}" ] [ @@ -183,7 +185,7 @@ let userList (model: WebLogUser list) app = let userUrl = relUrl app $"admin/settings/user/{user.Id}" small [] [ a [ _href $"{userUrl}/edit"; _hxTarget $"#user_{user.Id}" - _hxSwap $"innerHTML show:#user_{user.Id}:top" ] [ + _hxSwap $"{HxSwap.InnerHtml} show:#user_{user.Id}:top" ] [ raw "Edit" ] if app.UserId.Value <> user.Id then diff --git a/src/MyWebLog/appsettings.json b/src/MyWebLog/appsettings.json index 5956ea7..102b5a3 100644 --- a/src/MyWebLog/appsettings.json +++ b/src/MyWebLog/appsettings.json @@ -1,5 +1,4 @@ { - "Generator": "myWebLog 2.0", "Generator": "myWebLog 2.1", "Logging": { "LogLevel": { diff --git a/src/admin-theme/redirect-edit.liquid b/src/admin-theme/redirect-edit.liquid deleted file mode 100644 index 15b273b..0000000 --- a/src/admin-theme/redirect-edit.liquid +++ /dev/null @@ -1,48 +0,0 @@ -

{% if model.rule_id < 0 %}Add{% else %}Edit{% endif %} Redirect Rule

-{%- assign post_url = "admin/settings/redirect-rules/" | append: model.rule_id | relative_link -%} -
- - -
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
- {% if model.rule_id < 0 %} -
-
- -
- - - - -
-
-
- {% endif %} -
-
- - Cancel -
-
-
diff --git a/src/admin-theme/redirect-list.liquid b/src/admin-theme/redirect-list.liquid deleted file mode 100644 index d78eaaf..0000000 --- a/src/admin-theme/redirect-list.liquid +++ /dev/null @@ -1,75 +0,0 @@ -

{{ page_title }}

-
-

- « Back to Settings -

-
- -
-
- {%- assign redir_count = redirections | size -%} - {% if redir_count > 0 -%} -
-
-
From
-
To
-
RegEx?
-
-
-
-
- - {% for redir in redirections -%} - {%- assign redir_id = "redir_" | append: forloop.index0 -%} -
-
- {{ redir.from }}
- - {%- assign redir_url = "admin/settings/redirect-rules/" | append: forloop.index0 -%} - - Edit - - {% unless forloop.first %} - - {%- assign move_up = redir_url | append: "/up" | relative_link -%} - Move Up - {% endunless %} - {% unless forloop.last %} - - {%- assign move_down = redir_url | append: "/down" | relative_link -%} - Move Down - {% endunless %} - - {%- assign del_url = redir_url | append: "/delete" | relative_link -%} - - Delete - - -
-
{{ redir.to }}
-
{% if redir.is_regex %}Yes{% else %}No{% endif %}
-
- {%- endfor %} -
- {%- else -%} -
-

This web log has no redirect rules defined -

- {%- endif %} -
-
-
-

- This is an advanced feature; please - read and understand the documentation on this feature before adding rules. -

diff --git a/src/admin-theme/settings.liquid b/src/admin-theme/settings.liquid index 10ac441..4f8e2d8 100644 --- a/src/admin-theme/settings.liquid +++ b/src/admin-theme/settings.liquid @@ -244,6 +244,6 @@ hx-target=#tag_new> Add a New Tag Mapping - {{ tag_mapping_list }} + diff --git a/src/admin-theme/tag-mapping-edit.liquid b/src/admin-theme/tag-mapping-edit.liquid deleted file mode 100644 index 1c5e8c7..0000000 --- a/src/admin-theme/tag-mapping-edit.liquid +++ /dev/null @@ -1,27 +0,0 @@ -
{{ page_title }}
-
- - -
-
-
- - -
-
-
-
- - -
-
-
-
-
- - Cancel -
-
-
diff --git a/src/admin-theme/tag-mapping-list-body.liquid b/src/admin-theme/tag-mapping-list-body.liquid deleted file mode 100644 index 6ed58e2..0000000 --- a/src/admin-theme/tag-mapping-list-body.liquid +++ /dev/null @@ -1,43 +0,0 @@ -
-
-
- {%- assign map_count = mappings | size -%} - {% if map_count > 0 -%} -
-
-
Tag
-
URL Value
-
-
-
- -
- {% for map in mappings -%} - {%- assign map_id = mapping_ids | value: map.tag -%} -
-
- {{ map.tag }}
- - {%- assign map_url = "admin/settings/tag-mapping/" | append: map_id -%} - - Edit - - - {%- assign map_del_link = map_url | append: "/delete" | relative_link -%} - - Delete - - -
-
{{ map.url_value }}
-
- {%- endfor %} -
- {%- else -%} -

This web log has no tag mappings

- {%- endif %} -
-
-