From e9bd3b28025ec69a3dae46373f3863d1cfef3de0 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Wed, 13 Mar 2024 18:17:14 -0400 Subject: [PATCH] Migrate web log settings page to GVE - Add field functions to tighten up forms --- src/MyWebLog.Tests/Domain/ViewModelsTests.fs | 10 - src/MyWebLog/Handlers/Admin.fs | 40 +--- src/MyWebLog/Views/Admin.fs | 217 +++++++++++++++-- src/MyWebLog/Views/Helpers.fs | 53 +++++ src/MyWebLog/Views/Post.fs | 71 ++---- src/MyWebLog/Views/User.fs | 101 ++------ src/admin-theme/settings.liquid | 236 ------------------- 7 files changed, 287 insertions(+), 441 deletions(-) delete mode 100644 src/admin-theme/settings.liquid diff --git a/src/MyWebLog.Tests/Domain/ViewModelsTests.fs b/src/MyWebLog.Tests/Domain/ViewModelsTests.fs index a4bb736..e55e488 100644 --- a/src/MyWebLog.Tests/Domain/ViewModelsTests.fs +++ b/src/MyWebLog.Tests/Domain/ViewModelsTests.fs @@ -1158,16 +1158,11 @@ let manageRevisionsModelTests = testList "ManageRevisionsModel" [ { AsOf = Noda.epoch + Duration.FromDays 20; Text = Html "

huh

" } ] let model = ManageRevisionsModel.FromPage - { WebLog.Empty with TimeZone = "Etc/GMT+1" } { Page.Empty with Id = PageId "revs"; Title = "A Revised Page"; Revisions = revisions } Expect.equal model.Id "revs" "Id not filled properly" Expect.equal model.Entity "page" "Entity not filled properly" Expect.equal model.CurrentTitle "A Revised Page" "CurrentTitle not filled properly" Expect.equal model.Revisions.Length 2 "There should be two revisions" - Expect.equal - model.Revisions[0].AsOfLocal - ((revisions[0].AsOf - Duration.FromHours 1).ToDateTimeUtc()) - "AsOfLocal not filled properly" } test "FromPost succeeds" { let revisions = @@ -1175,16 +1170,11 @@ let manageRevisionsModelTests = testList "ManageRevisionsModel" [ { AsOf = Noda.epoch + Duration.FromDays 12; Text = Html "

original

" } ] let model = ManageRevisionsModel.FromPost - { WebLog.Empty with TimeZone = "Etc/GMT-3" } { Post.Empty with Id = PostId "altered"; Title = "Round Two"; Revisions = revisions } Expect.equal model.Id "altered" "Id not filled properly" Expect.equal model.Entity "post" "Entity not filled properly" Expect.equal model.CurrentTitle "Round Two" "CurrentTitle not filled properly" Expect.equal model.Revisions.Length 2 "There should be two revisions" - Expect.equal - model.Revisions[0].AsOfLocal - ((revisions[0].AsOf + Duration.FromHours 3).ToDateTimeUtc()) - "AsOfLocal not filled properly" } ] diff --git a/src/MyWebLog/Handlers/Admin.fs b/src/MyWebLog/Handlers/Admin.fs index d00239d..df5c257 100644 --- a/src/MyWebLog/Handlers/Admin.fs +++ b/src/MyWebLog/Handlers/Admin.fs @@ -483,42 +483,24 @@ module Theme = /// ~~~ WEB LOG SETTINGS ~~~ module WebLog = - open System.Collections.Generic open System.IO // GET /admin/settings let settings : HttpHandler = fun next ctx -> task { - let data = ctx.Data + let data = ctx.Data let! allPages = data.Page.All ctx.WebLog.Id + let pages = + allPages + |> List.sortBy _.Title.ToLower() + |> List.append [ { Page.Empty with Id = PageId "posts"; Title = "- First Page of Posts -" } ] let! themes = data.Theme.All() + let uploads = [ Database; Disk ] + let feeds = ctx.WebLog.Rss.CustomFeeds |> List.map (DisplayCustomFeed.FromFeed (CategoryCache.get ctx)) 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 + Views.Admin.webLogSettings + (SettingsModel.FromWebLog ctx.WebLog) themes pages uploads (EditRssModel.FromRssOptions ctx.WebLog.Rss) + feeds + |> adminPage "Web Log Settings" true next ctx } // POST /admin/settings diff --git a/src/MyWebLog/Views/Admin.fs b/src/MyWebLog/Views/Admin.fs index f773b46..8568499 100644 --- a/src/MyWebLog/Views/Admin.fs +++ b/src/MyWebLog/Views/Admin.fs @@ -90,25 +90,17 @@ let redirectEdit (model: EditRedirectRuleModel) 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" ] + textField [ _autofocus; _required ] (nameof model.From) "From" model.From [ + span [ _class "form-text" ] [ raw "From local URL/pattern" ] ] ] 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" ] + textField [ _required ] (nameof model.To) "To" model.To [ + span [ _class "form-text" ] [ raw "To URL/pattern" ] ] ] 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" ] - ] + checkboxSwitch [] (nameof model.IsRegex) "Use RegEx" model.IsRegex [] ] ] if model.RuleId < 0 then @@ -126,7 +118,7 @@ let redirectEdit (model: EditRedirectRuleModel) app = [ ] div [ _class "row mb-3" ] [ div [ _class "col text-center" ] [ - button [ _type "submit"; _class "btn btn-sm btn-primary" ] [ raw "Save Changes" ] + saveButton; raw "   " a [ _href (relUrl app "admin/settings/redirect-rules"); _class "btn btn-sm btn-secondary ms-3" ] [ raw "Cancel" ] @@ -223,23 +215,15 @@ let tagMapEdit (model: EditTagMapModel) 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" ] - ] + textField [ _autofocus; _required ] (nameof model.Tag) "Tag" model.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" ] - ] + textField [ _required ] (nameof model.UrlValue) "URL Value" model.UrlValue [] ] ] div [ _class "row mb-3" ] [ div [ _class "col text-center" ] [ - button [ _type "submit"; _class "btn btn-sm btn-primary" ] [ raw "Save Changes" ]; raw "   " + saveButton; raw "   " a [ _href (relUrl app "admin/settings/tag-mappings"); _class "btn btn-sm btn-secondary ms-3" ] [ raw "Cancel" ] @@ -380,4 +364,185 @@ let themeUpload app = ] ] |> List.singleton - \ No newline at end of file + + +/// Web log settings page +let webLogSettings + (model: SettingsModel) (themes: Theme list) (pages: Page list) (uploads: UploadDestination list) + (rss: EditRssModel) (feeds: DisplayCustomFeed list) app = [ + h2 [ _class "my-3" ] [ txt app.WebLog.Name; raw " Settings" ] + article [] [ + p [ _class "text-muted" ] [ + raw "Go to: "; a [ _href "#users" ] [ raw "Users" ]; raw " • " + a [ _href "#rss-settings" ] [ raw "RSS Settings" ]; raw " • " + a [ _href "#tag-mappings" ] [ raw "Tag Mappings" ]; raw " • " + a [ _href (relUrl app "admin/settings/redirect-rules") ] [ raw "Redirect Rules" ] + ] + fieldset [ _class "container mb-3" ] [ + legend [] [ raw "Web Log Settings" ] + form [ _action (relUrl app "admin/settings"); _method "post" ] [ + antiCsrf app + div [ _class "container g-0" ] [ + div [ _class "row" ] [ + div [ _class "col-12 col-md-6 col-xl-4 pb-3" ] [ + textField [ _required; _autofocus ] (nameof model.Name) "Name" model.Name [] + ] + div [ _class "col-12 col-md-6 col-xl-4 pb-3" ] [ + textField [ _required ] (nameof model.Slug) "Slug" model.Slug [ + span [ _class "form-text" ] [ + span [ _class "badge rounded-pill bg-warning text-dark" ] [ raw "WARNING" ] + raw " changing this value may break links (" + a [ _href "https://bitbadger.solutions/open-source/myweblog/configuring.html#blog-settings" + _target "_blank" ] [ + raw "more" + ]; raw ")" + ] + ] + ] + div [ _class "col-12 col-md-6 col-xl-4 pb-3" ] [ + textField [] (nameof model.Subtitle) "Subtitle" model.Subtitle [] + ] + div [ _class "col-12 col-md-6 col-xl-4 offset-xl-1 pb-3" ] [ + selectField [ _required ] (nameof model.ThemeId) "Theme" model.ThemeId themes + (fun t -> string t.Id) (fun t -> $"{t.Name} (v{t.Version})") [] + ] + div [ _class "col-12 col-md-6 offset-md-1 col-xl-4 offset-xl-0 pb-3" ] [ + selectField [ _required ] (nameof model.DefaultPage) "Default Page" model.DefaultPage pages + (fun p -> string p.Id) (_.Title) [] + ] + div [ _class "col-12 col-md-4 col-xl-2 pb-3" ] [ + numberField [ _required; _min "0"; _max "50" ] (nameof model.PostsPerPage) "Posts per Page" + model.PostsPerPage [] + ] + ] + div [ _class "row" ] [ + div [ _class "col-12 col-md-4 col-xl-3 offset-xl-2 pb-3" ] [ + textField [ _required ] (nameof model.TimeZone) "Time Zone" model.TimeZone [] + ] + div [ _class "col-12 col-md-4 col-xl-2" ] [ + checkboxSwitch [] (nameof model.AutoHtmx) "Auto-Load htmx" model.AutoHtmx [] + span [ _class "form-text fst-italic" ] [ + a [ _href "https://htmx.org"; _target "_blank"; _rel "noopener" ] [ + raw "What is this?" + ] + ] + ] + div [ _class "col-12 col-md-4 col-xl-3 pb-3" ] [ + selectField [] (nameof model.Uploads) "Default Upload Destination" model.Uploads uploads + string string [] + ] + ] + div [ _class "row pb-3" ] [ + div [ _class "col text-center" ] [ + button [ _type "submit"; _class "btn btn-primary" ] [ raw "Save Changes" ] + ] + ] + ] + ] + ] + fieldset [ _id "users"; _class "container mb-3 pb-0" ] [ + legend [] [ raw "Users" ] + span [ _hxGet (relUrl app "admin/settings/users"); _hxTrigger HxTrigger.Load; _hxSwap HxSwap.OuterHtml ] [] + ] + fieldset [ _id "rss-settings"; _class "container mb-3 pb-0" ] [ + legend [] [ raw "RSS Settings" ] + form [ _action (relUrl app "admin/settings/rss"); _method "post"; _class "container g-0" ] [ + antiCsrf app + div [ _class "row pb-3" ] [ + div [ _class "col col-xl-8 offset-xl-2" ] [ + fieldset [ _class "d-flex justify-content-evenly flex-row" ] [ + legend [] [ raw "Feeds Enabled" ] + checkboxSwitch [] (nameof rss.IsFeedEnabled) "All Posts" rss.IsFeedEnabled [] + checkboxSwitch [] (nameof rss.IsCategoryEnabled) "Posts by Category" rss.IsCategoryEnabled + [] + checkboxSwitch [] (nameof rss.IsTagEnabled) "Posts by Tag" rss.IsTagEnabled [] + ] + ] + ] + div [ _class "row" ] [ + div [ _class "col-12 col-sm-6 col-md-3 col-xl-2 offset-xl-2 pb-3" ] [ + textField [] (nameof rss.FeedName) "Feed File Name" rss.FeedName [ + span [ _class "form-text" ] [ raw "Default is "; code [] [ raw "feed.xml" ] ] + ] + ] + div [ _class "col-12 col-sm-6 col-md-4 col-xl-2 pb-3" ] [ + numberField [ _required; _min "0" ] (nameof rss.ItemsInFeed) "Items in Feed" rss.ItemsInFeed [ + span [ _class "form-text" ] [ + raw "Set to “0” to use “Posts per Page” setting (" + raw (string app.WebLog.PostsPerPage); raw ")" + ] + ] + ] + div [ _class "col-12 col-md-5 col-xl-4 pb-3" ] [ + textField [] (nameof rss.Copyright) "Copyright String" rss.Copyright [ + span [ _class "form-text" ] [ + raw "Can be a " + a [ _href "https://creativecommons.org/share-your-work/"; _target "_blank" + _rel "noopener" ] [ + raw "Creative Commons license string" + ] + ] + ] + ] + ] + div [ _class "row pb-3" ] [ + div [ _class "col text-center" ] [ + button [ _type "submit"; _class "btn btn-primary" ] [ raw "Save Changes" ] + ] + ] + ] + fieldset [ _class "container mb-3 pb-0" ] [ + legend [] [ raw "Custom Feeds" ] + a [ _class "btn btn-sm btn-secondary"; _href (relUrl app "admin/settings/rss/new/edit") ] [ + raw "Add a New Custom Feed" + ] + if feeds.Length = 0 then + p [ _class "text-muted fst-italic text-center" ] [ raw "No custom feeds defined" ] + else + form [ _method "post"; _class "container g-0"; _hxTarget "body" ] [ + antiCsrf app + div [ _class "row mwl-table-heading" ] [ + div [ _class "col-12 col-md-6" ] [ + span [ _class "d-md-none" ] [ raw "Feed" ] + span [ _class "d-none d-md-inline" ] [ raw "Source" ] + ] + div [ _class $"col-12 col-md-6 d-none d-md-inline-block" ] [ raw "Relative Path" ] + ] + for feed in feeds do + div [ _class "row mwl-table-detail" ] [ + div [ _class "col-12 col-md-6" ] [ + txt feed.Source + if feed.IsPodcast then + raw "   "; span [ _class "badge bg-primary" ] [ raw "PODCAST" ] + br [] + small [] [ + let feedUrl = relUrl app $"admin/settings/rss/{feed.Id}" + a [ _href (relUrl app feed.Path); _target "_blank" ] [ raw "View Feed" ] + span [ _class "text-muted" ] [ raw " • " ] + a [ _href $"{feedUrl}/edit" ] [ raw "Edit" ] + span [ _class "text-muted" ] [ raw " • " ] + a [ _href feedUrl; _hxDelete feedUrl; _class "text-danger" + _hxConfirm $"Are you sure you want to delete the custom RSS feed based on {feed.Source}? This action cannot be undone." ] [ + raw "Delete" + ] + ] + ] + div [ _class "col-12 col-md-6" ] [ + small [ _class "d-md-none" ] [ raw "Served at "; txt feed.Path ] + span [ _class "d-none d-md-inline" ] [ txt feed.Path ] + ] + ] + ] + ] + ] + fieldset [ _id "tag-mappings"; _class "container mb-3 pb-0" ] [ + legend [] [ raw "Tag Mappings" ] + a [ _href (relUrl app "admin/settings/tag-mapping/new/edit"); _class "btn btn-primary btn-sm mb-3" + _hxTarget "#tag_new" ] [ + raw "Add a New Tag Mapping" + ] + span [ _hxGet (relUrl app "admin/settings/tag-mappings"); _hxTrigger HxTrigger.Load + _hxSwap HxSwap.OuterHtml ] [] + ] + ] +] diff --git a/src/MyWebLog/Views/Helpers.fs b/src/MyWebLog/Views/Helpers.fs index ab28e95..5d4dfee 100644 --- a/src/MyWebLog/Views/Helpers.fs +++ b/src/MyWebLog/Views/Helpers.fs @@ -102,6 +102,59 @@ let shortTime app (instant: Instant) = let yesOrNo value = raw (if value then "Yes" else "No") +/// Create a text input field +let inputField fieldType attrs name labelText value extra = + div [ _class "form-floating" ] [ + [ _type fieldType; _name name; _id name; _class "form-control"; _placeholder labelText; _value value ] + |> List.append attrs + |> input + label [ _for name ] [ raw labelText ] + yield! extra + ] + +/// Create a text input field +let textField attrs name labelText value extra = + inputField "text" attrs name labelText value extra + +/// Create a number input field +let numberField attrs name labelText (value: int) extra = + inputField "number" attrs name labelText (string value) extra + +/// Create an e-mail input field +let emailField attrs name labelText value extra = + inputField "email" attrs name labelText value extra + +/// Create a password input field +let passwordField attrs name labelText value extra = + inputField "password" attrs name labelText value extra + +/// Create a select (dropdown) field +let selectField<'T, 'a> + attrs name labelText value (values: 'T list) (idFunc: 'T -> 'a) (displayFunc: 'T -> string) extra = + div [ _class "form-floating" ] [ + select ([ _name name; _id name; _class "form-control" ] |> List.append attrs) [ + for item in values do + let itemId = string (idFunc item) + option [ _value itemId; if value = itemId then _selected ] [ txt (displayFunc item) ] + ] + label [ _for name ] [ raw labelText ] + yield! extra + ] + +/// Create a checkbox input styled as a switch +let checkboxSwitch attrs name labelText (value: bool) extra = + div [ _class "form-check form-switch" ] [ + [ _type "checkbox"; _name name; _id name; _class "form-check-input"; _value "true"; if value then _checked ] + |> List.append attrs + |> input + label [ _for name; _class "form-check-label" ] [ raw labelText ] + yield! extra + ] + +/// A standard save button +let saveButton = + button [ _type "submit"; _class "btn btn-sm btn-primary" ] [ raw "Save Changes" ] + /// Functions for generating content in varying layouts module Layout = diff --git a/src/MyWebLog/Views/Post.fs b/src/MyWebLog/Views/Post.fs index d3e5660..36339f9 100644 --- a/src/MyWebLog/Views/Post.fs +++ b/src/MyWebLog/Views/Post.fs @@ -24,83 +24,47 @@ let chapterEdit (model: EditChapterModel) app = [ input [ _type "hidden"; _name "Index"; _value (string model.Index) ] div [ _class "row" ] [ div [ _class "col-6 col-lg-3 mb-3" ] [ - div [ _class "form-floating" ] [ - input [ _type "text"; _id "start_time"; _name "StartTime"; _class "form-control"; _required - _autofocus; _placeholder "Start Time" - if model.Index >= 0 then _value model.StartTime ] - label [ _for "start_time" ] [ raw "Start Time" ] - ] + textField [ _required; _autofocus ] (nameof model.StartTime) "Start Time" + (if model.Index < 0 then "" else model.StartTime) [] ] div [ _class "col-6 col-lg-3 mb-3" ] [ - div [ _class "form-floating" ] [ - input [ _type "text"; _id "end_time"; _name "EndTime"; _class "form-control"; _value model.EndTime - _placeholder "End Time" ] - label [ _for "end_time" ] [ raw "End Time" ] + textField [] (nameof model.EndTime) "End Time" model.EndTime [ span [ _class "form-text" ] [ raw "Optional; ends when next starts" ] ] ] div [ _class "col-12 col-lg-6 mb-3" ] [ - div [ _class "form-floating" ] [ - input [ _type "text"; _id "title"; _name "Title"; _class "form-control"; _value model.Title - _placeholder "Title" ] - label [ _for "title" ] [ raw "Chapter Title" ] + textField [] (nameof model.Title) "Chapter Title" model.Title [ span [ _class "form-text" ] [ raw "Optional" ] ] ] 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" ] - label [ _for "image_url" ] [ raw "Image URL" ] + textField [] (nameof model.ImageUrl) "Image URL" model.ImageUrl [ span [ _class "form-text" ] [ raw "Optional; a separate image to display while this chapter is playing" ] ] ] 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" - ] + textField [] (nameof model.Url) "URL" model.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" - if model.IsHidden then _checked ] - label [ _for "is_hidden" ] [ raw "Hidden Chapter" ] - ] - span [ _class "form-text" ] [ raw "Not displayed, but may update image and location" ] + checkboxSwitch [] (nameof model.IsHidden) "Hidden Chapter" model.IsHidden [] + span [ _class "mt-2 form-text" ] [ raw "Not displayed, but may update image and location" ] ] ] div [ _class "row" ] [ let hasLoc = model.LocationName <> "" + let attrs = if hasLoc then [] else [ _disabled ] div [ _class "col-12 col-md-4 col-lg-3 offset-lg-1 mb-3 align-self-end" ] [ - div [ _class "form-check form-switch mb-3" ] [ - input [ _type "checkbox"; _id "has_location"; _class "form-check-input"; _value "true" - if hasLoc then _checked - _onclick "Admin.checkChapterLocation()" ] - label [ _for "has_location" ] [ raw "Associate Location" ] - ] + checkboxSwitch [ _onclick "Admin.checkChapterLocation()" ] "has_location" "Associate Location" hasLoc [] ] div [ _class "col-12 col-md-8 col-lg-6 offset-lg-1 mb-3" ] [ - div [ _class "form-floating" ] [ - input [ _type "text"; _id "location_name"; _name "LocationName"; _class "form-control" - _value model.LocationName; _placeholder "Location Name"; _required - if not hasLoc then _disabled ] - label [ _for "location_name" ] [ raw "Name" ] - ] + textField (_required :: attrs) (nameof model.LocationName) "Name" model.LocationName [] ] 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"; _required - if not hasLoc then _disabled ] - label [ _for "location_geo" ] [ raw "Geo URL" ] + textField (_required :: attrs) (nameof model.LocationGeo) "Geo URL" model.LocationGeo [ em [ _class "form-text" ] [ a [ _href "https://github.com/Podcastindex-org/podcast-namespace/blob/main/location/location.md#geo-recommended" _target "_blank"; _rel "noopener" ] [ @@ -110,11 +74,7 @@ let chapterEdit (model: EditChapterModel) app = [ ] ] div [ _class "col-6 col-lg-4 mb-3" ] [ - div [ _class "form-floating" ] [ - input [ _type "text"; _id "location_osm"; _name "LocationOsm"; _class "form-control" - _value model.LocationOsm; _placeholder "Location OSM Query" - if not hasLoc then _disabled ] - label [ _for "location_osm" ] [ raw "OpenStreetMap ID" ] + textField attrs (nameof model.LocationOsm) "OpenStreetMap ID" model.LocationOsm [ em [ _class "form-text" ] [ raw "Optional; " a [ _href "https://www.openstreetmap.org/"; _target "_blank"; _rel "noopener" ] [ raw "get ID" ] @@ -138,8 +98,7 @@ let chapterEdit (model: EditChapterModel) app = [ ] else input [ _type "hidden"; _name "AddAnother"; _value "false" ] - button [ _type "submit"; _class "btn btn-primary" ] [ raw "Save" ] - raw "   " + saveButton; raw "   " a [ _href cancelLink; _hxGet cancelLink; _class "btn btn-secondary"; _hxTarget "body" ] [ raw "Cancel" ] ] ] diff --git a/src/MyWebLog/Views/User.fs b/src/MyWebLog/Views/User.fs index 98c9dc2..5db2f57 100644 --- a/src/MyWebLog/Views/User.fs +++ b/src/MyWebLog/Views/User.fs @@ -30,41 +30,21 @@ let edit (model: EditUserModel) app = ] ] div [ _class "col-12 col-md-7 col-lg-4 col-xxl-3 mb-3" ] [ - div [ _class "form-floating" ] [ - input [ _type "email"; _name "Email"; _id "email"; _class "form-control"; _placeholder "E-mail" - _required; _value model.Email ] - label [ _for "email" ] [ raw "E-mail Address" ] - ] + emailField [ _required ] (nameof model.Email) "E-mail Address" model.Email [] ] div [ _class "col-12 col-lg-5 mb-3" ] [ - div [ _class "form-floating" ] [ - input [ _type "text"; _name "Url"; _id "url"; _class "form-control"; _placeholder "URL" - _value model.Url ] - label [ _for "url" ] [ raw "User’s Personal URL" ] - ] + textField [] (nameof model.Url) "User’s Personal URL" model.Url [] ] ] div [ _class "row mb-3" ] [ div [ _class "col-12 col-md-6 col-lg-4 col-xl-3 offset-xl-1 pb-3" ] [ - div [ _class "form-floating" ] [ - input [ _type "text"; _name "FirstName"; _id "firstName"; _class "form-control" - _placeholder "First"; _required; _value model.FirstName ] - label [ _for "firstName" ] [ raw "First Name" ] - ] + textField [ _required ] (nameof model.FirstName) "First Name" model.FirstName [] ] div [ _class "col-12 col-md-6 col-lg-4 col-xl-3 pb-3" ] [ - div [ _class "form-floating" ] [ - input [ _type "text"; _name "LastName"; _id "lastName"; _class "form-control" - _placeholder "Last"; _required; _value model.LastName ] - label [ _for "lastName" ] [ raw "Last Name" ] - ] + textField [ _required ] (nameof model.LastName) "Last Name" model.LastName [] ] div [ _class "col-12 col-md-6 offset-md-3 col-lg-4 offset-lg-0 col-xl-3 offset-xl-1 pb-3" ] [ - div [ _class "form-floating " ] [ - input [ _type "text"; _name "PreferredName"; _id "preferredName"; _class "form-control" - _placeholder "Preferred"; _required; _value model.PreferredName ] - label [ _for "preferredName" ] [ raw "Preferred Name" ] - ] + textField [ _required ] (nameof model.PreferredName) "Preferred Name" model.PreferredName [] ] ] div [ _class "row mb-3" ] [ @@ -83,28 +63,12 @@ let edit (model: EditUserModel) app = ] ] div [ _class "row" ] [ + let attrs, newLbl = if model.IsNew then [ _required ], "" else [], "New " div [ _class "col-12 col-md-6 pb-3" ] [ - div [ _class "form-floating" ] [ - input [ _type "password"; _name "Password"; _id "password"; _class "form-control" - _placeholder "Password" - if model.IsNew then _required ] - label [ _for "password" ] [ - if not model.IsNew then raw "New " - raw "Password" - ] - ] + passwordField attrs (nameof model.Password) $"{newLbl}Password" "" [] ] div [ _class "col-12 col-md-6 pb-3" ] [ - div [ _class "form-floating" ] [ - input [ _type "password"; _name "PasswordConfirm"; _id "passwordConfirm" - _class "form-control"; _placeholder "Confirm" - if model.IsNew then _required ] - label [ _for "passwordConfirm" ] [ - raw "Confirm" - if not model.IsNew then raw " New" - raw " Password" - ] - ] + passwordField attrs (nameof model.PasswordConfirm) $"Confirm {newLbl}Password" "" [] ] ] ] @@ -112,7 +76,7 @@ let edit (model: EditUserModel) app = ] div [ _class "row mb-3" ] [ div [ _class "col text-center" ] [ - button [ _type "submit"; _class "btn btn-sm btn-primary" ] [ raw "Save Changes" ]; raw "   " + saveButton; raw "   " if model.IsNew then button [ _type "button"; _class "btn btn-sm btn-secondary ms-3" _onclick "document.getElementById('user_new').innerHTML = ''" ] [ @@ -138,17 +102,10 @@ let logOn (model: LogOnModel) (app: AppViewContext) = [ if Option.isSome model.ReturnTo then input [ _type "hidden"; _name "ReturnTo"; _value model.ReturnTo.Value ] div [ _class "row" ] [ div [ _class "col-12 col-md-6 col-lg-4 offset-lg-2 pb-3" ] [ - div [ _class "form-floating" ] [ - input [ _type "email"; _id "email"; _name "EmailAddress"; _class "form-control"; _autofocus - _required ] - label [ _for "email" ] [ rawText "E-mail Address" ] - ] + emailField [ _required; _autofocus ] (nameof model.EmailAddress) "E-mail Address" "" [] ] div [ _class "col-12 col-md-6 col-lg-4 pb-3" ] [ - div [ _class "form-floating" ] [ - input [ _type "password"; _id "password"; _name "Password"; _class "form-control"; _required ] - label [ _for "password" ] [ rawText "Password" ] - ] + passwordField [ _required ] (nameof model.Password) "Password" "" [] ] ] div [ _class "row pb-3" ] [ @@ -263,25 +220,13 @@ let myInfo (model: EditMyInfoModel) (user: WebLogUser) app = [ div [ _class "row" ] [ div [ _class "col" ] [ hr [ _class "mt-0" ] ] ] div [ _class "row mb-3" ] [ div [ _class "col-12 col-md-6 col-lg-4 pb-3" ] [ - div [ _class "form-floating" ] [ - input [ _type "text"; _name "FirstName"; _id "firstName"; _class "form-control"; _autofocus - _required; _placeholder "First"; _value model.FirstName ] - label [ _for "firstName" ] [ raw "First Name" ] - ] + textField [ _required; _autofocus ] (nameof model.FirstName) "First Name" model.FirstName [] ] div [ _class "col-12 col-md-6 col-lg-4 pb-3" ] [ - div [ _class "form-floating" ] [ - input [ _type "text"; _name "LastName"; _id "lastName"; _class "form-control"; _required - _placeholder "Last"; _value model.LastName ] - label [ _for "lastName" ] [ raw "Last Name" ] - ] + textField [ _required ] (nameof model.LastName) "Last Name" model.LastName [] ] div [ _class "col-12 col-md-6 col-lg-4 pb-3" ] [ - div [ _class "form-floating" ] [ - input [ _type "text"; _name "PreferredName"; _id "preferredName"; _class "form-control" - _required; _placeholder "Preferred"; _value model.PreferredName ] - label [ _for "preferredName" ] [ raw "Preferred Name" ] - ] + textField [ _required ] (nameof model.PreferredName) "Preferred Name" model.PreferredName [] ] ] div [ _class "row mb-3" ] [ @@ -297,28 +242,16 @@ let myInfo (model: EditMyInfoModel) (user: WebLogUser) app = [ ] div [ _class "row" ] [ div [ _class "col-12 col-md-6 pb-3" ] [ - div [ _class "form-floating" ] [ - input [ _type "password"; _name "NewPassword"; _id "newPassword" - _class "form-control"; _placeholder "Password" ] - label [ _for "newPassword" ] [ raw "New Password" ] - ] + passwordField [] (nameof model.NewPassword) "New Password" "" [] ] div [ _class "col-12 col-md-6 pb-3" ] [ - div [ _class "form-floating" ] [ - input [ _type "password"; _name "NewPasswordConfirm"; _id "newPasswordConfirm" - _class "form-control"; _placeholder "Confirm" ] - label [ _for "newPasswordConfirm" ] [ raw "Confirm New Password" ] - ] + passwordField [] (nameof model.NewPasswordConfirm) "Confirm New Password" "" [] ] ] ] ] ] - div [ _class "row" ] [ - div [ _class "col text-center mb-3" ] [ - button [ _type "submit"; _class "btn btn-primary" ] [ raw "Save Changes" ] - ] - ] + div [ _class "row" ] [ div [ _class "col text-center mb-3" ] [ saveButton ] ] ] ] ] diff --git a/src/admin-theme/settings.liquid b/src/admin-theme/settings.liquid deleted file mode 100644 index 45d21dc..0000000 --- a/src/admin-theme/settings.liquid +++ /dev/null @@ -1,236 +0,0 @@ -

{{ web_log.name }} Settings

-
-

- Go to: UsersRSS Settings • - Tag Mappings • - Redirect Rules -

-
- Web Log Settings -
- -
-
-
-
- - -
-
-
-
- - - - WARNING changing this value may break - links - (more) - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
-
-
- - -
-
-
-
- - -
- - What is this? - -
-
-
- - -
-
-
-
-
- -
-
-
-
-
-
- Users - -
-
- RSS Settings -
- -
-
-
-
- Feeds Enabled -
- - -
-
- - -
-
- - -
-
-
-
-
-
-
- - - Default is feed.xml -
-
-
-
- - - - Set to “0” to use “Posts per Page” setting ({{ web_log.posts_per_page }}) - -
-
-
-
- - - - Can be a - - Creative Commons license string - - -
-
-
-
-
- -
-
-
-
-
- Custom Feeds - - Add a New Custom Feed - - {%- assign feed_count = custom_feeds | size -%} - {%- if feed_count > 0 %} -
- {%- assign source_col = "col-12 col-md-6" -%} - {%- assign path_col = "col-12 col-md-6" -%} - -
-
- FeedSource -
-
Relative Path
-
- {% for feed in custom_feeds %} -
-
- {{ feed.source }} - {%- if feed.is_podcast %}   PODCAST{% endif %}
- - {%- assign feed_url = "admin/settings/rss/" | append: feed.id -%} - View Feed - - Edit - - {%- assign feed_del_link = feed_url | append: "/delete" | relative_link -%} - - Delete - - -
-
- Served at {{ feed.path }} - {{ feed.path }} -
-
- {%- endfor %} -
- {%- else %} -

No custom feeds defined - {%- endif %} -

-
-
- Tag Mappings - - Add a New Tag Mapping - - -
-