Version 2.1 #41
@ -1158,16 +1158,11 @@ let manageRevisionsModelTests = testList "ManageRevisionsModel" [
|
|||||||
{ AsOf = Noda.epoch + Duration.FromDays 20; Text = Html "<p>huh</p>" } ]
|
{ AsOf = Noda.epoch + Duration.FromDays 20; Text = Html "<p>huh</p>" } ]
|
||||||
let model =
|
let model =
|
||||||
ManageRevisionsModel.FromPage
|
ManageRevisionsModel.FromPage
|
||||||
{ WebLog.Empty with TimeZone = "Etc/GMT+1" }
|
|
||||||
{ Page.Empty with Id = PageId "revs"; Title = "A Revised Page"; Revisions = revisions }
|
{ Page.Empty with Id = PageId "revs"; Title = "A Revised Page"; Revisions = revisions }
|
||||||
Expect.equal model.Id "revs" "Id not filled properly"
|
Expect.equal model.Id "revs" "Id not filled properly"
|
||||||
Expect.equal model.Entity "page" "Entity 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.CurrentTitle "A Revised Page" "CurrentTitle not filled properly"
|
||||||
Expect.equal model.Revisions.Length 2 "There should be two revisions"
|
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" {
|
test "FromPost succeeds" {
|
||||||
let revisions =
|
let revisions =
|
||||||
@ -1175,16 +1170,11 @@ let manageRevisionsModelTests = testList "ManageRevisionsModel" [
|
|||||||
{ AsOf = Noda.epoch + Duration.FromDays 12; Text = Html "<p>original</p>" } ]
|
{ AsOf = Noda.epoch + Duration.FromDays 12; Text = Html "<p>original</p>" } ]
|
||||||
let model =
|
let model =
|
||||||
ManageRevisionsModel.FromPost
|
ManageRevisionsModel.FromPost
|
||||||
{ WebLog.Empty with TimeZone = "Etc/GMT-3" }
|
|
||||||
{ Post.Empty with Id = PostId "altered"; Title = "Round Two"; Revisions = revisions }
|
{ Post.Empty with Id = PostId "altered"; Title = "Round Two"; Revisions = revisions }
|
||||||
Expect.equal model.Id "altered" "Id not filled properly"
|
Expect.equal model.Id "altered" "Id not filled properly"
|
||||||
Expect.equal model.Entity "post" "Entity 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.CurrentTitle "Round Two" "CurrentTitle not filled properly"
|
||||||
Expect.equal model.Revisions.Length 2 "There should be two revisions"
|
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"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -483,42 +483,24 @@ module Theme =
|
|||||||
/// ~~~ WEB LOG SETTINGS ~~~
|
/// ~~~ WEB LOG SETTINGS ~~~
|
||||||
module WebLog =
|
module WebLog =
|
||||||
|
|
||||||
open System.Collections.Generic
|
|
||||||
open System.IO
|
open System.IO
|
||||||
|
|
||||||
// GET /admin/settings
|
// GET /admin/settings
|
||||||
let settings : HttpHandler = fun next ctx -> task {
|
let settings : HttpHandler = fun next ctx -> task {
|
||||||
let data = ctx.Data
|
let data = ctx.Data
|
||||||
let! allPages = data.Page.All ctx.WebLog.Id
|
let! allPages = data.Page.All ctx.WebLog.Id
|
||||||
let! themes = data.Theme.All()
|
let pages =
|
||||||
return!
|
allPages
|
||||||
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.sortBy _.Title.ToLower()
|
||||||
|> List.map (fun p -> KeyValuePair.Create(string p.Id, p.Title))
|
|> List.append [ { Page.Empty with Id = PageId "posts"; Title = "- First Page of Posts -" } ]
|
||||||
}
|
let! themes = data.Theme.All()
|
||||||
|> Array.ofSeq)
|
let uploads = [ Database; Disk ]
|
||||||
|> addToHash "themes" (
|
let feeds = ctx.WebLog.Rss.CustomFeeds |> List.map (DisplayCustomFeed.FromFeed (CategoryCache.get ctx))
|
||||||
themes
|
return!
|
||||||
|> Seq.ofList
|
Views.Admin.webLogSettings
|
||||||
|> Seq.map (fun it ->
|
(SettingsModel.FromWebLog ctx.WebLog) themes pages uploads (EditRssModel.FromRssOptions ctx.WebLog.Rss)
|
||||||
KeyValuePair.Create(string it.Id, $"{it.Name} (v{it.Version})"))
|
feeds
|
||||||
|> Array.ofSeq)
|
|> adminPage "Web Log Settings" true next ctx
|
||||||
|> 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
|
// POST /admin/settings
|
||||||
|
@ -90,25 +90,17 @@ let redirectEdit (model: EditRedirectRuleModel) app = [
|
|||||||
input [ _type "hidden"; _name "RuleId"; _value (string model.RuleId) ]
|
input [ _type "hidden"; _name "RuleId"; _value (string model.RuleId) ]
|
||||||
div [ _class "row" ] [
|
div [ _class "row" ] [
|
||||||
div [ _class "col-12 col-lg-5 mb-3" ] [
|
div [ _class "col-12 col-lg-5 mb-3" ] [
|
||||||
div [ _class "form-floating" ] [
|
textField [ _autofocus; _required ] (nameof model.From) "From" model.From [
|
||||||
input [ _type "text"; _name "From"; _id "from"; _class "form-control"
|
span [ _class "form-text" ] [ raw "From local URL/pattern" ]
|
||||||
_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 "col-12 col-lg-5 mb-3" ] [
|
||||||
div [ _class "form-floating" ] [
|
textField [ _required ] (nameof model.To) "To" model.To [
|
||||||
input [ _type "text"; _name "To"; _id "to"; _class "form-control"; _placeholder "To URL/pattern"
|
span [ _class "form-text" ] [ raw "To URL/pattern" ]
|
||||||
_required; _value model.To ]
|
|
||||||
label [ _for "to" ] [ raw "To" ]
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
div [ _class "col-12 col-lg-2 mb-3" ] [
|
div [ _class "col-12 col-lg-2 mb-3" ] [
|
||||||
div [ _class "form-check form-switch" ] [
|
checkboxSwitch [] (nameof model.IsRegex) "Use RegEx" model.IsRegex []
|
||||||
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
|
if model.RuleId < 0 then
|
||||||
@ -126,7 +118,7 @@ let redirectEdit (model: EditRedirectRuleModel) app = [
|
|||||||
]
|
]
|
||||||
div [ _class "row mb-3" ] [
|
div [ _class "row mb-3" ] [
|
||||||
div [ _class "col text-center" ] [
|
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" ] [
|
a [ _href (relUrl app "admin/settings/redirect-rules"); _class "btn btn-sm btn-secondary ms-3" ] [
|
||||||
raw "Cancel"
|
raw "Cancel"
|
||||||
]
|
]
|
||||||
@ -223,23 +215,15 @@ let tagMapEdit (model: EditTagMapModel) app = [
|
|||||||
input [ _type "hidden"; _name "Id"; _value model.Id ]
|
input [ _type "hidden"; _name "Id"; _value model.Id ]
|
||||||
div [ _class "row mb-3" ] [
|
div [ _class "row mb-3" ] [
|
||||||
div [ _class "col-6 col-lg-4 offset-lg-2" ] [
|
div [ _class "col-6 col-lg-4 offset-lg-2" ] [
|
||||||
div [ _class "form-floating" ] [
|
textField [ _autofocus; _required ] (nameof model.Tag) "Tag" model.Tag []
|
||||||
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 "col-6 col-lg-4" ] [
|
||||||
div [ _class "form-floating" ] [
|
textField [ _required ] (nameof model.UrlValue) "URL Value" model.UrlValue []
|
||||||
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 "row mb-3" ] [
|
||||||
div [ _class "col text-center" ] [
|
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" ] [
|
a [ _href (relUrl app "admin/settings/tag-mappings"); _class "btn btn-sm btn-secondary ms-3" ] [
|
||||||
raw "Cancel"
|
raw "Cancel"
|
||||||
]
|
]
|
||||||
@ -381,3 +365,184 @@ let themeUpload app =
|
|||||||
]
|
]
|
||||||
|> List.singleton
|
|> List.singleton
|
||||||
|
|
||||||
|
|
||||||
|
/// 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 ] []
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
@ -102,6 +102,59 @@ let shortTime app (instant: Instant) =
|
|||||||
let yesOrNo value =
|
let yesOrNo value =
|
||||||
raw (if value then "Yes" else "No")
|
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
|
/// Functions for generating content in varying layouts
|
||||||
module Layout =
|
module Layout =
|
||||||
|
|
||||||
|
@ -24,83 +24,47 @@ let chapterEdit (model: EditChapterModel) app = [
|
|||||||
input [ _type "hidden"; _name "Index"; _value (string model.Index) ]
|
input [ _type "hidden"; _name "Index"; _value (string model.Index) ]
|
||||||
div [ _class "row" ] [
|
div [ _class "row" ] [
|
||||||
div [ _class "col-6 col-lg-3 mb-3" ] [
|
div [ _class "col-6 col-lg-3 mb-3" ] [
|
||||||
div [ _class "form-floating" ] [
|
textField [ _required; _autofocus ] (nameof model.StartTime) "Start Time"
|
||||||
input [ _type "text"; _id "start_time"; _name "StartTime"; _class "form-control"; _required
|
(if model.Index < 0 then "" else model.StartTime) []
|
||||||
_autofocus; _placeholder "Start Time"
|
|
||||||
if model.Index >= 0 then _value model.StartTime ]
|
|
||||||
label [ _for "start_time" ] [ raw "Start Time" ]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
div [ _class "col-6 col-lg-3 mb-3" ] [
|
div [ _class "col-6 col-lg-3 mb-3" ] [
|
||||||
div [ _class "form-floating" ] [
|
textField [] (nameof model.EndTime) "End Time" model.EndTime [
|
||||||
input [ _type "text"; _id "end_time"; _name "EndTime"; _class "form-control"; _value model.EndTime
|
|
||||||
_placeholder "End Time" ]
|
|
||||||
label [ _for "end_time" ] [ raw "End Time" ]
|
|
||||||
span [ _class "form-text" ] [ raw "Optional; ends when next starts" ]
|
span [ _class "form-text" ] [ raw "Optional; ends when next starts" ]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
div [ _class "col-12 col-lg-6 mb-3" ] [
|
div [ _class "col-12 col-lg-6 mb-3" ] [
|
||||||
div [ _class "form-floating" ] [
|
textField [] (nameof model.Title) "Chapter Title" model.Title [
|
||||||
input [ _type "text"; _id "title"; _name "Title"; _class "form-control"; _value model.Title
|
|
||||||
_placeholder "Title" ]
|
|
||||||
label [ _for "title" ] [ raw "Chapter Title" ]
|
|
||||||
span [ _class "form-text" ] [ raw "Optional" ]
|
span [ _class "form-text" ] [ raw "Optional" ]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
div [ _class "col-12 col-lg-6 col-xl-5 mb-3" ] [
|
div [ _class "col-12 col-lg-6 col-xl-5 mb-3" ] [
|
||||||
div [ _class "form-floating" ] [
|
textField [] (nameof model.ImageUrl) "Image URL" model.ImageUrl [
|
||||||
input [ _type "text"; _id "image_url"; _name "ImageUrl"; _class "form-control"
|
|
||||||
_value model.ImageUrl; _placeholder "Image URL" ]
|
|
||||||
label [ _for "image_url" ] [ raw "Image URL" ]
|
|
||||||
span [ _class "form-text" ] [
|
span [ _class "form-text" ] [
|
||||||
raw "Optional; a separate image to display while this chapter is playing"
|
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 "col-12 col-lg-6 col-xl-5 mb-3" ] [
|
||||||
div [ _class "form-floating" ] [
|
textField [] (nameof model.Url) "URL" model.Url [
|
||||||
input [ _type "text"; _id "url"; _name "Url"; _class "form-control"; _value model.Url
|
span [ _class "form-text" ] [ raw "Optional; informational link for this chapter" ]
|
||||||
_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 "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" ] [
|
checkboxSwitch [] (nameof model.IsHidden) "Hidden Chapter" model.IsHidden []
|
||||||
input [ _type "checkbox"; _id "is_hidden"; _name "IsHidden"; _class "form-check-input"
|
span [ _class "mt-2 form-text" ] [ raw "Not displayed, but may update image and location" ]
|
||||||
_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" ]
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
div [ _class "row" ] [
|
div [ _class "row" ] [
|
||||||
let hasLoc = model.LocationName <> ""
|
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 "col-12 col-md-4 col-lg-3 offset-lg-1 mb-3 align-self-end" ] [
|
||||||
div [ _class "form-check form-switch mb-3" ] [
|
checkboxSwitch [ _onclick "Admin.checkChapterLocation()" ] "has_location" "Associate Location" hasLoc []
|
||||||
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" ]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
div [ _class "col-12 col-md-8 col-lg-6 offset-lg-1 mb-3" ] [
|
div [ _class "col-12 col-md-8 col-lg-6 offset-lg-1 mb-3" ] [
|
||||||
div [ _class "form-floating" ] [
|
textField (_required :: attrs) (nameof model.LocationName) "Name" model.LocationName []
|
||||||
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" ]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
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" ] [
|
textField (_required :: attrs) (nameof model.LocationGeo) "Geo URL" model.LocationGeo [
|
||||||
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" ]
|
|
||||||
em [ _class "form-text" ] [
|
em [ _class "form-text" ] [
|
||||||
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" ] [
|
||||||
@ -110,11 +74,7 @@ let chapterEdit (model: EditChapterModel) app = [
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
div [ _class "col-6 col-lg-4 mb-3" ] [
|
div [ _class "col-6 col-lg-4 mb-3" ] [
|
||||||
div [ _class "form-floating" ] [
|
textField attrs (nameof model.LocationOsm) "OpenStreetMap ID" model.LocationOsm [
|
||||||
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" ]
|
|
||||||
em [ _class "form-text" ] [
|
em [ _class "form-text" ] [
|
||||||
raw "Optional; "
|
raw "Optional; "
|
||||||
a [ _href "https://www.openstreetmap.org/"; _target "_blank"; _rel "noopener" ] [ raw "get ID" ]
|
a [ _href "https://www.openstreetmap.org/"; _target "_blank"; _rel "noopener" ] [ raw "get ID" ]
|
||||||
@ -138,8 +98,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" ]
|
saveButton; raw " "
|
||||||
raw " "
|
|
||||||
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" ]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
@ -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 "col-12 col-md-7 col-lg-4 col-xxl-3 mb-3" ] [
|
||||||
div [ _class "form-floating" ] [
|
emailField [ _required ] (nameof model.Email) "E-mail Address" model.Email []
|
||||||
input [ _type "email"; _name "Email"; _id "email"; _class "form-control"; _placeholder "E-mail"
|
|
||||||
_required; _value model.Email ]
|
|
||||||
label [ _for "email" ] [ raw "E-mail Address" ]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
div [ _class "col-12 col-lg-5 mb-3" ] [
|
div [ _class "col-12 col-lg-5 mb-3" ] [
|
||||||
div [ _class "form-floating" ] [
|
textField [] (nameof model.Url) "User’s Personal URL" model.Url []
|
||||||
input [ _type "text"; _name "Url"; _id "url"; _class "form-control"; _placeholder "URL"
|
|
||||||
_value model.Url ]
|
|
||||||
label [ _for "url" ] [ raw "User’s Personal URL" ]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
div [ _class "row mb-3" ] [
|
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 "col-12 col-md-6 col-lg-4 col-xl-3 offset-xl-1 pb-3" ] [
|
||||||
div [ _class "form-floating" ] [
|
textField [ _required ] (nameof model.FirstName) "First Name" model.FirstName []
|
||||||
input [ _type "text"; _name "FirstName"; _id "firstName"; _class "form-control"
|
|
||||||
_placeholder "First"; _required; _value model.FirstName ]
|
|
||||||
label [ _for "firstName" ] [ raw "First Name" ]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
div [ _class "col-12 col-md-6 col-lg-4 col-xl-3 pb-3" ] [
|
div [ _class "col-12 col-md-6 col-lg-4 col-xl-3 pb-3" ] [
|
||||||
div [ _class "form-floating" ] [
|
textField [ _required ] (nameof model.LastName) "Last Name" model.LastName []
|
||||||
input [ _type "text"; _name "LastName"; _id "lastName"; _class "form-control"
|
|
||||||
_placeholder "Last"; _required; _value model.LastName ]
|
|
||||||
label [ _for "lastName" ] [ raw "Last Name" ]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
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 "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 " ] [
|
textField [ _required ] (nameof model.PreferredName) "Preferred Name" model.PreferredName []
|
||||||
input [ _type "text"; _name "PreferredName"; _id "preferredName"; _class "form-control"
|
|
||||||
_placeholder "Preferred"; _required; _value model.PreferredName ]
|
|
||||||
label [ _for "preferredName" ] [ raw "Preferred Name" ]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
div [ _class "row mb-3" ] [
|
div [ _class "row mb-3" ] [
|
||||||
@ -83,28 +63,12 @@ let edit (model: EditUserModel) app =
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
div [ _class "row" ] [
|
div [ _class "row" ] [
|
||||||
|
let attrs, newLbl = if model.IsNew then [ _required ], "" else [], "New "
|
||||||
div [ _class "col-12 col-md-6 pb-3" ] [
|
div [ _class "col-12 col-md-6 pb-3" ] [
|
||||||
div [ _class "form-floating" ] [
|
passwordField attrs (nameof model.Password) $"{newLbl}Password" "" []
|
||||||
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"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
div [ _class "col-12 col-md-6 pb-3" ] [
|
div [ _class "col-12 col-md-6 pb-3" ] [
|
||||||
div [ _class "form-floating" ] [
|
passwordField attrs (nameof model.PasswordConfirm) $"Confirm {newLbl}Password" "" []
|
||||||
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"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
@ -112,7 +76,7 @@ let edit (model: EditUserModel) app =
|
|||||||
]
|
]
|
||||||
div [ _class "row mb-3" ] [
|
div [ _class "row mb-3" ] [
|
||||||
div [ _class "col text-center" ] [
|
div [ _class "col text-center" ] [
|
||||||
button [ _type "submit"; _class "btn btn-sm btn-primary" ] [ raw "Save Changes" ]; raw " "
|
saveButton; raw " "
|
||||||
if model.IsNew then
|
if model.IsNew then
|
||||||
button [ _type "button"; _class "btn btn-sm btn-secondary ms-3"
|
button [ _type "button"; _class "btn btn-sm btn-secondary ms-3"
|
||||||
_onclick "document.getElementById('user_new').innerHTML = ''" ] [
|
_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 ]
|
if Option.isSome model.ReturnTo then input [ _type "hidden"; _name "ReturnTo"; _value model.ReturnTo.Value ]
|
||||||
div [ _class "row" ] [
|
div [ _class "row" ] [
|
||||||
div [ _class "col-12 col-md-6 col-lg-4 offset-lg-2 pb-3" ] [
|
div [ _class "col-12 col-md-6 col-lg-4 offset-lg-2 pb-3" ] [
|
||||||
div [ _class "form-floating" ] [
|
emailField [ _required; _autofocus ] (nameof model.EmailAddress) "E-mail Address" "" []
|
||||||
input [ _type "email"; _id "email"; _name "EmailAddress"; _class "form-control"; _autofocus
|
|
||||||
_required ]
|
|
||||||
label [ _for "email" ] [ rawText "E-mail Address" ]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
div [ _class "col-12 col-md-6 col-lg-4 pb-3" ] [
|
div [ _class "col-12 col-md-6 col-lg-4 pb-3" ] [
|
||||||
div [ _class "form-floating" ] [
|
passwordField [ _required ] (nameof model.Password) "Password" "" []
|
||||||
input [ _type "password"; _id "password"; _name "Password"; _class "form-control"; _required ]
|
|
||||||
label [ _for "password" ] [ rawText "Password" ]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
div [ _class "row pb-3" ] [
|
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" ] [ div [ _class "col" ] [ hr [ _class "mt-0" ] ] ]
|
||||||
div [ _class "row mb-3" ] [
|
div [ _class "row mb-3" ] [
|
||||||
div [ _class "col-12 col-md-6 col-lg-4 pb-3" ] [
|
div [ _class "col-12 col-md-6 col-lg-4 pb-3" ] [
|
||||||
div [ _class "form-floating" ] [
|
textField [ _required; _autofocus ] (nameof model.FirstName) "First Name" model.FirstName []
|
||||||
input [ _type "text"; _name "FirstName"; _id "firstName"; _class "form-control"; _autofocus
|
|
||||||
_required; _placeholder "First"; _value model.FirstName ]
|
|
||||||
label [ _for "firstName" ] [ raw "First Name" ]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
div [ _class "col-12 col-md-6 col-lg-4 pb-3" ] [
|
div [ _class "col-12 col-md-6 col-lg-4 pb-3" ] [
|
||||||
div [ _class "form-floating" ] [
|
textField [ _required ] (nameof model.LastName) "Last Name" model.LastName []
|
||||||
input [ _type "text"; _name "LastName"; _id "lastName"; _class "form-control"; _required
|
|
||||||
_placeholder "Last"; _value model.LastName ]
|
|
||||||
label [ _for "lastName" ] [ raw "Last Name" ]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
div [ _class "col-12 col-md-6 col-lg-4 pb-3" ] [
|
div [ _class "col-12 col-md-6 col-lg-4 pb-3" ] [
|
||||||
div [ _class "form-floating" ] [
|
textField [ _required ] (nameof model.PreferredName) "Preferred Name" model.PreferredName []
|
||||||
input [ _type "text"; _name "PreferredName"; _id "preferredName"; _class "form-control"
|
|
||||||
_required; _placeholder "Preferred"; _value model.PreferredName ]
|
|
||||||
label [ _for "preferredName" ] [ raw "Preferred Name" ]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
div [ _class "row mb-3" ] [
|
div [ _class "row mb-3" ] [
|
||||||
@ -297,28 +242,16 @@ let myInfo (model: EditMyInfoModel) (user: WebLogUser) app = [
|
|||||||
]
|
]
|
||||||
div [ _class "row" ] [
|
div [ _class "row" ] [
|
||||||
div [ _class "col-12 col-md-6 pb-3" ] [
|
div [ _class "col-12 col-md-6 pb-3" ] [
|
||||||
div [ _class "form-floating" ] [
|
passwordField [] (nameof model.NewPassword) "New Password" "" []
|
||||||
input [ _type "password"; _name "NewPassword"; _id "newPassword"
|
|
||||||
_class "form-control"; _placeholder "Password" ]
|
|
||||||
label [ _for "newPassword" ] [ raw "New Password" ]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
div [ _class "col-12 col-md-6 pb-3" ] [
|
div [ _class "col-12 col-md-6 pb-3" ] [
|
||||||
div [ _class "form-floating" ] [
|
passwordField [] (nameof model.NewPasswordConfirm) "Confirm New Password" "" []
|
||||||
input [ _type "password"; _name "NewPasswordConfirm"; _id "newPasswordConfirm"
|
|
||||||
_class "form-control"; _placeholder "Confirm" ]
|
|
||||||
label [ _for "newPasswordConfirm" ] [ raw "Confirm New Password" ]
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
div [ _class "row" ] [ div [ _class "col text-center mb-3" ] [ saveButton ] ]
|
||||||
div [ _class "row" ] [
|
|
||||||
div [ _class "col text-center mb-3" ] [
|
|
||||||
button [ _type "submit"; _class "btn btn-primary" ] [ raw "Save Changes" ]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
@ -1,236 +0,0 @@
|
|||||||
<h2 class=my-3>{{ web_log.name }} Settings</h2>
|
|
||||||
<article>
|
|
||||||
<p class=text-muted>
|
|
||||||
Go to: <a href=#users>Users</a> • <a href=#rss-settings>RSS Settings</a> •
|
|
||||||
<a href=#tag-mappings>Tag Mappings</a> •
|
|
||||||
<a href="{{ "admin/settings/redirect-rules" | relative_link }}">Redirect Rules</a>
|
|
||||||
</p>
|
|
||||||
<fieldset class="container mb-3">
|
|
||||||
<legend>Web Log Settings</legend>
|
|
||||||
<form action="{{ "admin/settings" | relative_link }}" method=post>
|
|
||||||
<input type=hidden name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
|
|
||||||
<div class=container>
|
|
||||||
<div class=row>
|
|
||||||
<div class="col-12 col-md-6 col-xl-4 pb-3">
|
|
||||||
<div class=form-floating>
|
|
||||||
<input type=text name=Name id=name class=form-control placeholder=Name required autofocus
|
|
||||||
value="{{ model.name }}">
|
|
||||||
<label for=name>Name</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-6 col-xl-4 pb-3">
|
|
||||||
<div class=form-floating>
|
|
||||||
<input type=text name=Slug id=slug class=form-control placeholder="Slug" required
|
|
||||||
value="{{ model.slug }}">
|
|
||||||
<label for=slug>Slug</label>
|
|
||||||
<span class=form-text>
|
|
||||||
<span class="badge rounded-pill bg-warning text-dark">WARNING</span> changing this value may break
|
|
||||||
links
|
|
||||||
(<a href=https://bitbadger.solutions/open-source/myweblog/configuring.html#blog-settings
|
|
||||||
target=_blank>more</a>)
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-6 col-xl-4 pb-3">
|
|
||||||
<div class=form-floating>
|
|
||||||
<input type=text name=Subtitle id=subtitle class=form-control placeholder=Subtitle
|
|
||||||
value="{{ model.subtitle }}">
|
|
||||||
<label for=subtitle>Subtitle</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-6 col-xl-4 offset-xl-1 pb-3">
|
|
||||||
<div class=form-floating>
|
|
||||||
<select name=ThemeId id=themeId class=form-control required>
|
|
||||||
{% for theme in themes -%}
|
|
||||||
<option value="{{ theme[0] }}"{% if model.theme_id == theme[0] %} selected{% endif %}>
|
|
||||||
{{ theme[1] }}
|
|
||||||
</option>
|
|
||||||
{%- endfor %}
|
|
||||||
</select>
|
|
||||||
<label for=themeId>Theme</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-6 offset-md-1 col-xl-4 offset-xl-0 pb-3">
|
|
||||||
<div class=form-floating>
|
|
||||||
<select name=DefaultPage id=defaultPage class=form-control required>
|
|
||||||
{%- for pg in pages %}
|
|
||||||
<option value="{{ pg[0] }}"{% if pg[0] == model.default_page %} selected{% endif %}>
|
|
||||||
{{ pg[1] }}
|
|
||||||
</option>
|
|
||||||
{%- endfor %}
|
|
||||||
</select>
|
|
||||||
<label for=defaultPage>Default Page</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-4 col-xl-2 pb-3">
|
|
||||||
<div class=form-floating>
|
|
||||||
<input type=number name=PostsPerPage id=postsPerPage class=form-control min=0 max=50 required
|
|
||||||
value="{{ model.posts_per_page }}">
|
|
||||||
<label for=postsPerPage>Posts per Page</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class=row>
|
|
||||||
<div class="col-12 col-md-4 col-xl-3 offset-xl-2 pb-3">
|
|
||||||
<div class=form-floating>
|
|
||||||
<input type=text name=TimeZone id=timeZone class=form-control placeholder="Time Zone" required
|
|
||||||
value="{{ model.time_zone }}">
|
|
||||||
<label for=timeZone>Time Zone</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-4 col-xl-2">
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input type=checkbox name=AutoHtmx id=autoHtmx class=form-check-input value=true
|
|
||||||
{%- if model.auto_htmx %} checked{% endif %}>
|
|
||||||
<label for=autoHtmx class=form-check-label>Auto-Load htmx</label>
|
|
||||||
</div>
|
|
||||||
<span class="form-text fst-italic">
|
|
||||||
<a href=https://htmx.org target=_blank rel=noopener>What is this?</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-4 col-xl-3 pb-3">
|
|
||||||
<div class=form-floating>
|
|
||||||
<select name=Uploads id=uploads class=form-control>
|
|
||||||
{%- for it in upload_values %}
|
|
||||||
<option value="{{ it[0] }}"{% if model.uploads == it[0] %} selected{% endif %}>{{ it[1] }}</option>
|
|
||||||
{%- endfor %}
|
|
||||||
</select>
|
|
||||||
<label for=uploads>Default Upload Destination</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row pb-3">
|
|
||||||
<div class="col text-center">
|
|
||||||
<button type=submit class="btn btn-primary">Save Changes</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</fieldset>
|
|
||||||
<fieldset id=users class="container mb-3 pb-0">
|
|
||||||
<legend>Users</legend>
|
|
||||||
<span hx-get="{{ "admin/settings/users" | relative_link }}" hx-trigger="load" hx-swap="outerHTML"></span>
|
|
||||||
</fieldset>
|
|
||||||
<fieldset id=rss-settings class="container mb-3 pb-0">
|
|
||||||
<legend>RSS Settings</legend>
|
|
||||||
<form action="{{ "admin/settings/rss" | relative_link }}" method=post>
|
|
||||||
<input type=hidden name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
|
|
||||||
<div class=container>
|
|
||||||
<div class="row pb-3">
|
|
||||||
<div class="col col-xl-8 offset-xl-2">
|
|
||||||
<fieldset class="d-flex justify-content-evenly flex-row">
|
|
||||||
<legend>Feeds Enabled</legend>
|
|
||||||
<div class="form-check form-switch pb-2">
|
|
||||||
<input type=checkbox name=IsFeedEnabled id=feedEnabled class=form-check-input value=true
|
|
||||||
{%- if rss_model.is_feed_enabled %} checked{% endif %}>
|
|
||||||
<label for=feedEnabled class=form-check-label>All Posts</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check form-switch pb-2">
|
|
||||||
<input type=checkbox name=IsCategoryEnabled id=categoryEnabled class=form-check-input value=true
|
|
||||||
{%- if rss_model.is_category_enabled %} checked{% endif %}>
|
|
||||||
<label for=categoryEnabled class=form-check-label>Posts by Category</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check form-switch pb-2">
|
|
||||||
<input type=checkbox name=IsTagEnabled id=tagEnabled class=form-check-input value=true
|
|
||||||
{%- if rss_model.is_tag_enabled %} checked{% endif %}>
|
|
||||||
<label for=tagEnabled class=form-check-label>Posts by Tag</label>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class=row>
|
|
||||||
<div class="col-12 col-sm-6 col-md-3 col-xl-2 offset-xl-2 pb-3">
|
|
||||||
<div class=form-floating>
|
|
||||||
<input type=text name=FeedName id=feedName class=form-control placeholder="Feed File Name"
|
|
||||||
value="{{ rss_model.feed_name }}">
|
|
||||||
<label for=feedName>Feed File Name</label>
|
|
||||||
<span class=form-text>Default is <code>feed.xml</code></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-sm-6 col-md-4 col-xl-2 pb-3">
|
|
||||||
<div class=form-floating>
|
|
||||||
<input type=number name=ItemsInFeed id=itemsInFeed class=form-control min=0 placeholder="Items in Feed"
|
|
||||||
required value="{{ rss_model.items_in_feed }}">
|
|
||||||
<label for=itemsInFeed>Items in Feed</label>
|
|
||||||
<span class=form-text>
|
|
||||||
Set to “0” to use “Posts per Page” setting ({{ web_log.posts_per_page }})
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-5 col-xl-4 pb-3">
|
|
||||||
<div class=form-floating>
|
|
||||||
<input type=text name=Copyright id=copyright class=form-control placeholder="Copyright String"
|
|
||||||
value="{{ rss_model.copyright }}">
|
|
||||||
<label for=copyright>Copyright String</label>
|
|
||||||
<span class=form-text>
|
|
||||||
Can be a
|
|
||||||
<a href="https://creativecommons.org/share-your-work/" target=_blank rel=noopener>
|
|
||||||
Creative Commons license string
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row pb-3">
|
|
||||||
<div class="col text-center">
|
|
||||||
<button type=submit class="btn btn-primary">Save Changes</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<fieldset class="container mb-3 pb-0">
|
|
||||||
<legend>Custom Feeds</legend>
|
|
||||||
<a class="btn btn-sm btn-secondary" href="{{ 'admin/settings/rss/new/edit' | relative_link }}">
|
|
||||||
Add a New Custom Feed
|
|
||||||
</a>
|
|
||||||
{%- assign feed_count = custom_feeds | size -%}
|
|
||||||
{%- if feed_count > 0 %}
|
|
||||||
<form method=post class="container g-0" hx-target=body>
|
|
||||||
{%- assign source_col = "col-12 col-md-6" -%}
|
|
||||||
{%- assign path_col = "col-12 col-md-6" -%}
|
|
||||||
<input type=hidden name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
|
|
||||||
<div class="row mwl-table-heading">
|
|
||||||
<div class="{{ source_col }}">
|
|
||||||
<span class=d-md-none>Feed</span><span class="d-none d-md-inline">Source</span>
|
|
||||||
</div>
|
|
||||||
<div class="{{ path_col }} d-none d-md-inline-block">Relative Path</div>
|
|
||||||
</div>
|
|
||||||
{% for feed in custom_feeds %}
|
|
||||||
<div class="row mwl-table-detail">
|
|
||||||
<div class="{{ source_col }}">
|
|
||||||
{{ feed.source }}
|
|
||||||
{%- if feed.is_podcast %} <span class="badge bg-primary">PODCAST</span>{% endif %}<br>
|
|
||||||
<small>
|
|
||||||
{%- assign feed_url = "admin/settings/rss/" | append: feed.id -%}
|
|
||||||
<a href="{{ feed.path | relative_link }}" target=_blank>View Feed</a>
|
|
||||||
<span class=text-muted> • </span>
|
|
||||||
<a href="{{ feed_url | append: "/edit" | relative_link }}">Edit</a>
|
|
||||||
<span class=text-muted> • </span>
|
|
||||||
{%- assign feed_del_link = feed_url | append: "/delete" | relative_link -%}
|
|
||||||
<a href="{{ feed_del_link }}" hx-post="{{ feed_del_link }}" class=text-danger
|
|
||||||
hx-confirm="Are you sure you want to delete the custom RSS feed based on {{ feed.source | strip_html | escape }}? This action cannot be undone.">
|
|
||||||
Delete
|
|
||||||
</a>
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
<div class="{{ path_col }}">
|
|
||||||
<small class=d-md-none>Served at {{ feed.path }}</small>
|
|
||||||
<span class="d-none d-md-inline">{{ feed.path }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{%- endfor %}
|
|
||||||
</form>
|
|
||||||
{%- else %}
|
|
||||||
<p class="text-muted fst-italic text-center">No custom feeds defined
|
|
||||||
{%- endif %}
|
|
||||||
</fieldset>
|
|
||||||
</fieldset>
|
|
||||||
<fieldset id="tag-mappings" class="container mb-3 pb-0">
|
|
||||||
<legend>Tag Mappings</legend>
|
|
||||||
<a href="{{ "admin/settings/tag-mapping/new/edit" | relative_link }}" class="btn btn-primary btn-sm mb-3"
|
|
||||||
hx-target=#tag_new>
|
|
||||||
Add a New Tag Mapping
|
|
||||||
</a>
|
|
||||||
<span hx-get="{{ "admin/settings/tag-mappings" | relative_link }}" hx-trigger="load" hx-swap="outerHTML"></span>
|
|
||||||
</fieldset>
|
|
||||||
</article>
|
|
Loading…
Reference in New Issue
Block a user