WIP on htmx 4 integration (#60)
This commit is contained in:
@@ -107,7 +107,7 @@ let isHtmx (ctx: HttpContext) =
|
|||||||
ctx.Request.IsHtmx && not ctx.Request.IsHtmxRefresh
|
ctx.Request.IsHtmx && not ctx.Request.IsHtmxRefresh
|
||||||
|
|
||||||
/// Convert messages to headers (used for htmx responses)
|
/// Convert messages to headers (used for htmx responses)
|
||||||
let messagesToHeaders (messages: UserMessage array) : HttpHandler =
|
let messagesToHeaders (messages: UserMessage array) pushUrl : HttpHandler =
|
||||||
seq {
|
seq {
|
||||||
yield!
|
yield!
|
||||||
messages
|
messages
|
||||||
@@ -116,7 +116,10 @@ let messagesToHeaders (messages: UserMessage array) : HttpHandler =
|
|||||||
| Some detail -> $"{m.Level}|||{m.Message}|||{detail}"
|
| Some detail -> $"{m.Level}|||{m.Message}|||{detail}"
|
||||||
| None -> $"{m.Level}|||{m.Message}"
|
| None -> $"{m.Level}|||{m.Message}"
|
||||||
|> setHttpHeader "X-Message")
|
|> setHttpHeader "X-Message")
|
||||||
withHxNoPushUrl
|
match pushUrl with
|
||||||
|
| Some true -> withHxPushUrl "true"
|
||||||
|
| Some false -> withHxNoPushUrl
|
||||||
|
| None -> ()
|
||||||
}
|
}
|
||||||
|> Seq.reduce (>=>)
|
|> Seq.reduce (>=>)
|
||||||
|
|
||||||
@@ -155,7 +158,7 @@ module Error =
|
|||||||
{ UserMessage.Error with
|
{ UserMessage.Error with
|
||||||
Message = $"You are not authorized to access the URL {ctx.Request.Path.Value}" }
|
Message = $"You are not authorized to access the URL {ctx.Request.Path.Value}" }
|
||||||
|]
|
|]
|
||||||
(messagesToHeaders messages >=> setStatusCode 401) earlyReturn ctx
|
(messagesToHeaders messages (Some false) >=> setStatusCode 401) earlyReturn ctx
|
||||||
else setStatusCode 401 earlyReturn ctx
|
else setStatusCode 401 earlyReturn ctx
|
||||||
|
|
||||||
/// Handle 404s
|
/// Handle 404s
|
||||||
@@ -165,14 +168,14 @@ module Error =
|
|||||||
let messages = [|
|
let messages = [|
|
||||||
{ UserMessage.Error with Message = $"The URL {ctx.Request.Path.Value} was not found" }
|
{ UserMessage.Error with Message = $"The URL {ctx.Request.Path.Value} was not found" }
|
||||||
|]
|
|]
|
||||||
RequestErrors.notFound (messagesToHeaders messages) earlyReturn ctx
|
RequestErrors.notFound (messagesToHeaders messages (Some false)) earlyReturn ctx
|
||||||
else RequestErrors.NOT_FOUND "Not found" earlyReturn ctx)
|
else RequestErrors.NOT_FOUND "Not found" earlyReturn ctx)
|
||||||
|
|
||||||
let server message : HttpHandler =
|
let server message : HttpHandler =
|
||||||
handleContext (fun ctx ->
|
handleContext (fun ctx ->
|
||||||
if isHtmx ctx then
|
if isHtmx ctx then
|
||||||
let messages = [| { UserMessage.Error with Message = message } |]
|
let messages = [| { UserMessage.Error with Message = message } |]
|
||||||
ServerErrors.internalError (messagesToHeaders messages) earlyReturn ctx
|
ServerErrors.internalError (messagesToHeaders messages (Some false)) earlyReturn ctx
|
||||||
else ServerErrors.INTERNAL_ERROR message earlyReturn ctx)
|
else ServerErrors.INTERNAL_ERROR message earlyReturn ctx)
|
||||||
|
|
||||||
|
|
||||||
@@ -212,8 +215,8 @@ let bareForTheme themeId template next ctx viewCtx = task {
|
|||||||
match! Template.Cache.get themeId "layout-bare" ctx.Data with
|
match! Template.Cache.get themeId "layout-bare" ctx.Data with
|
||||||
| Ok layoutTemplate ->
|
| Ok layoutTemplate ->
|
||||||
return!
|
return!
|
||||||
(messagesToHeaders completeCtx.Messages >=> htmlString (Template.render layoutTemplate completeCtx ctx.Data))
|
(messagesToHeaders completeCtx.Messages (Some false)
|
||||||
next ctx
|
>=> htmlString (Template.render layoutTemplate completeCtx ctx.Data)) next ctx
|
||||||
| Error message -> return! Error.server message next ctx
|
| Error message -> return! Error.server message next ctx
|
||||||
| Error message -> return! Error.server message next ctx
|
| Error message -> return! Error.server message next ctx
|
||||||
}
|
}
|
||||||
@@ -228,7 +231,12 @@ let adminPage pageTitle next ctx (content: AppViewContext -> XmlNode list) = tas
|
|||||||
let! messages = getCurrentMessages ctx
|
let! messages = getCurrentMessages ctx
|
||||||
let appCtx = generateViewContext messages (viewCtxForPage pageTitle) ctx
|
let appCtx = generateViewContext messages (viewCtxForPage pageTitle) ctx
|
||||||
let layout = if isHtmx ctx then Layout.partial else Layout.full
|
let layout = if isHtmx ctx then Layout.partial else Layout.full
|
||||||
return! htmlString (layout content appCtx |> RenderView.AsString.htmlDocument) next ctx
|
let returnFunc html =
|
||||||
|
if isHtmx ctx && messages.Length > 0 then
|
||||||
|
messagesToHeaders messages None >=> htmlString html
|
||||||
|
else
|
||||||
|
htmlString html
|
||||||
|
return! (layout content appCtx |> RenderView.AsString.htmlDocument |> returnFunc) next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Display a bare page for an admin endpoint
|
/// Display a bare page for an admin endpoint
|
||||||
@@ -236,7 +244,7 @@ let adminBarePage pageTitle next ctx (content: AppViewContext -> XmlNode list) =
|
|||||||
let! messages = getCurrentMessages ctx
|
let! messages = getCurrentMessages ctx
|
||||||
let appCtx = generateViewContext messages (viewCtxForPage pageTitle) ctx
|
let appCtx = generateViewContext messages (viewCtxForPage pageTitle) ctx
|
||||||
return!
|
return!
|
||||||
( messagesToHeaders appCtx.Messages
|
( messagesToHeaders appCtx.Messages (Some false)
|
||||||
>=> htmlString (Layout.bare content appCtx |> RenderView.AsString.htmlDocument)) next ctx
|
>=> htmlString (Layout.bare content appCtx |> RenderView.AsString.htmlDocument)) next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,8 @@ let dashboard (themes: Theme list) app = [
|
|||||||
article [] [
|
article [] [
|
||||||
fieldset [ _class "container mb-3 pb-0" ] [
|
fieldset [ _class "container mb-3 pb-0" ] [
|
||||||
legend [] [ raw "Themes" ]
|
legend [] [ raw "Themes" ]
|
||||||
span [ _hxGet (relUrl app "admin/theme/list"); _hxTrigger HxTrigger.Load; _hxSwap HxSwap.OuterHtml ] []
|
span [ _hxGet (relUrl app "admin/theme/list"); _hxTrigger HxTrigger.Load; _hxSwap HxSwap.OuterHtml
|
||||||
|
_hxTarget "this" ] []
|
||||||
]
|
]
|
||||||
fieldset [ _class "container mb-3 pb-0" ] [
|
fieldset [ _class "container mb-3 pb-0" ] [
|
||||||
legend [] [ raw "Caches" ]
|
legend [] [ raw "Caches" ]
|
||||||
@@ -65,8 +66,8 @@ let dashboard (themes: Theme list) app = [
|
|||||||
raw "These caches include the page list and categories for each web log"
|
raw "These caches include the page list and categories for each web log"
|
||||||
]
|
]
|
||||||
let webLogUrl = $"{cacheBaseUrl}web-log/"
|
let webLogUrl = $"{cacheBaseUrl}web-log/"
|
||||||
form [ _method "post"; _class "container g-0"; _hxNoBoost; _hxTarget "body"
|
form [ _method "post"; _class "container g-0"; hxInherited (_hxTarget "body")
|
||||||
_hxSwap $"{HxSwap.InnerHtml} show:window:top" ] [
|
hxInherited (_hxSwap $"{HxSwap.InnerHtml} show:top showTarget:window") ] [
|
||||||
antiCsrf app
|
antiCsrf app
|
||||||
button [ _type "submit"; _class "btn btn-sm btn-primary mb-2"
|
button [ _type "submit"; _class "btn btn-sm btn-primary mb-2"
|
||||||
_hxPost $"{webLogUrl}all/refresh" ] [
|
_hxPost $"{webLogUrl}all/refresh" ] [
|
||||||
@@ -86,8 +87,8 @@ let dashboard (themes: Theme list) app = [
|
|||||||
raw "The theme template cache is filled on demand as pages are displayed; "
|
raw "The theme template cache is filled on demand as pages are displayed; "
|
||||||
raw "refreshing a theme with no cached templates will still refresh its asset cache"
|
raw "refreshing a theme with no cached templates will still refresh its asset cache"
|
||||||
]
|
]
|
||||||
form [ _method "post"; _class "container g-0"; _hxNoBoost; _hxTarget "body"
|
form [ _method "post"; _class "container g-0"
|
||||||
_hxSwap $"{HxSwap.InnerHtml} show:window:top" ] [
|
hxInherited (_hxSwap $"{HxSwap.InnerHtml} show:top showTarget:window") ] [
|
||||||
antiCsrf app
|
antiCsrf app
|
||||||
button [ _type "submit"; _class "btn btn-sm btn-primary mb-2"
|
button [ _type "submit"; _class "btn btn-sm btn-primary mb-2"
|
||||||
_hxPost $"{themeCacheUrl}all/refresh" ] [
|
_hxPost $"{themeCacheUrl}all/refresh" ] [
|
||||||
@@ -111,8 +112,9 @@ let themeList (model: DisplayTheme list) app =
|
|||||||
let themeCol = "col-12 col-md-6"
|
let themeCol = "col-12 col-md-6"
|
||||||
let slugCol = "d-none d-md-block col-md-3"
|
let slugCol = "d-none d-md-block col-md-3"
|
||||||
let tmplCol = "d-none d-md-block col-md-3"
|
let tmplCol = "d-none d-md-block col-md-3"
|
||||||
|
let newUrl = relUrl app "admin/theme/new"
|
||||||
div [ _id "theme_panel" ] [
|
div [ _id "theme_panel" ] [
|
||||||
a [ _href (relUrl app "admin/theme/new"); _class "btn btn-primary btn-sm mb-3"; _hxTarget "#theme_new" ] [
|
a [ _href newUrl; _hxGet newUrl; _class "btn btn-primary btn-sm mb-3"; _hxTarget "#theme_new" ] [
|
||||||
raw "Upload a New Theme"
|
raw "Upload a New Theme"
|
||||||
]
|
]
|
||||||
div [ _class "container g-0" ] [
|
div [ _class "container g-0" ] [
|
||||||
@@ -124,7 +126,7 @@ let themeList (model: DisplayTheme list) app =
|
|||||||
]
|
]
|
||||||
div [ _class "row mwl-table-detail"; _id "theme_new" ] []
|
div [ _class "row mwl-table-detail"; _id "theme_new" ] []
|
||||||
form [ _method "post"; _id "themeList"; _class "container g-0"; _hxTarget "#theme_panel"
|
form [ _method "post"; _id "themeList"; _class "container g-0"; _hxTarget "#theme_panel"
|
||||||
_hxSwap $"{HxSwap.OuterHtml} show:window:top" ] [
|
_hxSwap $"{HxSwap.OuterHtml} show:top showTarget:window" ] [
|
||||||
antiCsrf app
|
antiCsrf app
|
||||||
for theme in model do
|
for theme in model do
|
||||||
let url = relUrl app $"admin/theme/{theme.Id}"
|
let url = relUrl app $"admin/theme/{theme.Id}"
|
||||||
@@ -161,7 +163,7 @@ let themeUpload app =
|
|||||||
div [ _class "col" ] [
|
div [ _class "col" ] [
|
||||||
h5 [ _class "mt-2" ] [ raw app.PageTitle ]
|
h5 [ _class "mt-2" ] [ raw app.PageTitle ]
|
||||||
form [ _action (relUrl app "admin/theme/new"); _method "post"; _class "container"
|
form [ _action (relUrl app "admin/theme/new"); _method "post"; _class "container"
|
||||||
_enctype "multipart/form-data"; _hxNoBoost ] [
|
_enctype "multipart/form-data" ] [
|
||||||
antiCsrf app
|
antiCsrf app
|
||||||
div [ _class "row " ] [
|
div [ _class "row " ] [
|
||||||
div [ _class "col-12 col-sm-6 pb-3" ] [
|
div [ _class "col-12 col-sm-6 pb-3" ] [
|
||||||
|
|||||||
@@ -205,16 +205,16 @@ module Layout =
|
|||||||
let extraPath = app.WebLog.ExtraPath
|
let extraPath = app.WebLog.ExtraPath
|
||||||
let path = if extraPath = "" then "" else $"{extraPath[1..]}/"
|
let path = if extraPath = "" then "" else $"{extraPath[1..]}/"
|
||||||
let active = if app.CurrentPage.StartsWith $"{path}{url}" then " active" else ""
|
let active = if app.CurrentPage.StartsWith $"{path}{url}" then " active" else ""
|
||||||
li [ _class "nav-item" ] [
|
let linkUrl = relUrl app url
|
||||||
a [ _class $"nav-link{active}"; _href (relUrl app url) ] [ txt name ]
|
li [ _class "nav-item" ] [ a [ _class $"nav-link{active}"; _href linkUrl; _hxBoost ] [ txt name ] ]
|
||||||
]
|
|
||||||
|
|
||||||
/// Create a page view for the given content
|
/// Create a page view for the given content
|
||||||
let private pageView (content: AppViewContext -> XmlNode list) app = [
|
let private pageView (content: AppViewContext -> XmlNode list) app = [
|
||||||
header [] [
|
header [] [
|
||||||
nav [ _class "navbar navbar-dark bg-dark navbar-expand-md justify-content-start px-2 position-fixed top-0 w-100" ] [
|
nav [ _class "navbar navbar-dark bg-dark navbar-expand-md justify-content-start px-2 position-fixed top-0 w-100"
|
||||||
|
hxInherited (_hxTarget "body") ] [
|
||||||
div [ _class "container-fluid" ] [
|
div [ _class "container-fluid" ] [
|
||||||
a [ _class "navbar-brand"; _href (relUrl app ""); _hxNoBoost ] [ txt app.WebLog.Name ]
|
a [ _class "navbar-brand"; _href (relUrl app "") ] [ txt app.WebLog.Name ]
|
||||||
button [ _type "button"; _class "navbar-toggler"; _data "bs-toggle" "collapse"
|
button [ _type "button"; _class "navbar-toggler"; _data "bs-toggle" "collapse"
|
||||||
_data "bs-target" "#navbarText"; _ariaControls "navbarText"; _ariaExpanded "false"
|
_data "bs-target" "#navbarText"; _ariaControls "navbarText"; _ariaExpanded "false"
|
||||||
_ariaLabel "Toggle navigation" ] [
|
_ariaLabel "Toggle navigation" ] [
|
||||||
@@ -244,9 +244,7 @@ module Layout =
|
|||||||
]
|
]
|
||||||
if app.IsLoggedOn then
|
if app.IsLoggedOn then
|
||||||
li [ _class "nav-item" ] [
|
li [ _class "nav-item" ] [
|
||||||
a [ _class "nav-link"; _hxPost (relUrl app "user/log-off"); _hxNoBoost ] [
|
a [ _class "nav-link"; _hxPost (relUrl app "user/log-off") ] [ raw "Log Off" ]
|
||||||
raw "Log Off"
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
else
|
else
|
||||||
navLink app "Log On" "user/log-on"
|
navLink app "Log On" "user/log-on"
|
||||||
@@ -313,7 +311,7 @@ module Layout =
|
|||||||
_integrity "sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
|
_integrity "sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
|
||||||
_crossorigin "anonymous" ]
|
_crossorigin "anonymous" ]
|
||||||
link [ _rel "stylesheet"; _href (relUrl app "themes/admin/admin.css") ]
|
link [ _rel "stylesheet"; _href (relUrl app "themes/admin/admin.css") ]
|
||||||
body [ _hxBoost; _hxIndicator "#loadOverlay" ] [
|
body [ hxInherited (_hxIndicator "#loadOverlay"); hxInherited (_hxTarget "body") ] [
|
||||||
yield! pageView content app
|
yield! pageView content app
|
||||||
script [ _src "https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
|
script [ _src "https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
|
||||||
_integrity "sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
|
_integrity "sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
|
||||||
@@ -362,7 +360,7 @@ let commonEdit (model: EditCommonModel) app = [
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
div [ _class "mb-2" ] [
|
div [ _class "mb-2" ] [
|
||||||
label [ _for "text" ] [ raw "Text" ]; raw " "
|
raw "Text"; raw " "
|
||||||
div [ _class "btn-group btn-group-sm"; _roleGroup; _ariaLabel "Text format button group" ] [
|
div [ _class "btn-group btn-group-sm"; _roleGroup; _ariaLabel "Text format button group" ] [
|
||||||
input [ _type "radio"; _name (nameof model.Source); _id "source_html"; _class "btn-check"
|
input [ _type "radio"; _name (nameof model.Source); _id "source_html"; _class "btn-check"
|
||||||
_value "HTML"; if model.Source = "HTML" then _checked ]
|
_value "HTML"; if model.Source = "HTML" then _checked ]
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ open MyWebLog.ViewModels
|
|||||||
|
|
||||||
/// The form to edit pages
|
/// The form to edit pages
|
||||||
let pageEdit (model: EditPageModel) templates app = [
|
let pageEdit (model: EditPageModel) templates app = [
|
||||||
|
let url = relUrl app "admin/page/save"
|
||||||
h2 [ _class "my-3" ] [ raw app.PageTitle ]
|
h2 [ _class "my-3" ] [ raw app.PageTitle ]
|
||||||
article [] [
|
article [] [
|
||||||
form [ _action (relUrl app "admin/page/save"); _method "post"; _hxPushUrl "true"; _class "container" ] [
|
form [ _action url; _method "post"; _hxPost url; _class "container" ] [
|
||||||
antiCsrf app
|
antiCsrf app
|
||||||
input [ _type "hidden"; _name (nameof model.Id); _value model.Id ]
|
input [ _type "hidden"; _name (nameof model.Id); _value model.Id ]
|
||||||
div [ _class "row mb-3" ] [
|
div [ _class "row mb-3" ] [
|
||||||
@@ -30,7 +31,9 @@ let pageEdit (model: EditPageModel) templates app = [
|
|||||||
let pageList (pages: DisplayPage list) pageNbr hasNext app = [
|
let pageList (pages: DisplayPage list) pageNbr hasNext app = [
|
||||||
h2 [ _class "my-3" ] [ raw app.PageTitle ]
|
h2 [ _class "my-3" ] [ raw app.PageTitle ]
|
||||||
article [ _class "container mb-3" ] [
|
article [ _class "container mb-3" ] [
|
||||||
a [ _href (relUrl app "admin/page/new/edit"); _class "btn btn-primary btn-sm mb-3" ] [ raw "Create a New Page" ]
|
a [ _href (relUrl app "admin/page/new/edit"); _hxBoost; _class "btn btn-primary btn-sm mb-3" ] [
|
||||||
|
raw "Create a New Page"
|
||||||
|
]
|
||||||
if pages.Length = 0 then
|
if pages.Length = 0 then
|
||||||
p [ _class "text-muted fst-italic text-center" ] [ raw "This web log has no pages" ]
|
p [ _class "text-muted fst-italic text-center" ] [ raw "This web log has no pages" ]
|
||||||
else
|
else
|
||||||
@@ -58,7 +61,7 @@ let pageList (pages: DisplayPage list) pageNbr hasNext app = [
|
|||||||
a [ _href (relUrl app pageLink); _target "_blank" ] [ raw "View Page" ]
|
a [ _href (relUrl app pageLink); _target "_blank" ] [ raw "View Page" ]
|
||||||
if app.IsEditor || (app.IsAuthor && app.UserId.Value = WebLogUserId pg.AuthorId) then
|
if app.IsEditor || (app.IsAuthor && app.UserId.Value = WebLogUserId pg.AuthorId) then
|
||||||
span [ _class "text-muted" ] [ raw " • " ]
|
span [ _class "text-muted" ] [ raw " • " ]
|
||||||
a [ _href $"{adminUrl}/edit" ] [ raw "Edit" ]
|
a [ _href $"{adminUrl}/edit"; _hxBoost ] [ raw "Edit" ]
|
||||||
if app.IsWebLogAdmin then
|
if app.IsWebLogAdmin then
|
||||||
span [ _class "text-muted" ] [ raw " • " ]
|
span [ _class "text-muted" ] [ raw " • " ]
|
||||||
a [ _href adminUrl; _hxDelete adminUrl; _hxTarget "body"; _class "text-danger"
|
a [ _href adminUrl; _hxDelete adminUrl; _hxTarget "body"; _class "text-danger"
|
||||||
@@ -79,23 +82,16 @@ let pageList (pages: DisplayPage list) pageNbr hasNext app = [
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
if pageNbr > 1 || hasNext then
|
if pageNbr > 1 || hasNext then
|
||||||
|
let aAttrs link = [ _href (relUrl app link); _hxBoost; _class "btn btn-secondary" ]
|
||||||
div [ _class "d-flex justify-content-evenly mb-3" ] [
|
div [ _class "d-flex justify-content-evenly mb-3" ] [
|
||||||
div [] [
|
div [] [
|
||||||
if pageNbr > 1 then
|
if pageNbr > 1 then
|
||||||
let prevPage = if pageNbr = 2 then "" else $"/page/{pageNbr - 1}"
|
let prevPage = if pageNbr = 2 then "" else $"/page/{pageNbr - 1}"
|
||||||
p [] [
|
p [] [ a (aAttrs $"admin/pages{prevPage}") [ raw "« Previous" ] ]
|
||||||
a [ _class "btn btn-secondary"; _href (relUrl app $"admin/pages{prevPage}") ] [
|
|
||||||
raw "« Previous"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
div [ _class "text-right" ] [
|
div [ _class "text-right" ] [
|
||||||
if hasNext then
|
if hasNext then
|
||||||
p [] [
|
p [] [ a (aAttrs $"admin/pages/page/{pageNbr + 1}") [ raw "Next »" ] ]
|
||||||
a [ _class "btn btn-secondary"; _href (relUrl app $"admin/pages/page/{pageNbr + 1}") ] [
|
|
||||||
raw "Next »"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|||||||
+17
-19
@@ -102,7 +102,8 @@ let chapterEdit (model: EditChapterModel) app = [
|
|||||||
|
|
||||||
/// Display a list of chapters
|
/// Display a list of chapters
|
||||||
let chapterList withNew (model: ManageChaptersModel) app =
|
let chapterList withNew (model: ManageChaptersModel) app =
|
||||||
div [ _id "chapter_list"; _class "container mb-3"; _hxTarget "this"; _hxSwap HxSwap.OuterHtml ] [
|
div [ _id "chapter_list"; _class "container mb-3"; hxInherited (_hxTarget "this")
|
||||||
|
hxInherited (_hxSwap HxSwap.OuterHtml) ] [
|
||||||
div [ _class "row mwl-table-heading" ] [
|
div [ _class "row mwl-table-heading" ] [
|
||||||
div [ _class "col-3 col-md-2" ] [ raw "Start" ]
|
div [ _class "col-3 col-md-2" ] [ raw "Start" ]
|
||||||
div [ _class "col-3 col-md-6 col-lg-8" ] [ raw "Title" ]
|
div [ _class "col-3 col-md-6 col-lg-8" ] [ raw "Title" ]
|
||||||
@@ -123,7 +124,7 @@ let chapterList withNew (model: ManageChaptersModel) app =
|
|||||||
else
|
else
|
||||||
let chapterUrl = relUrl app $"admin/post/{model.Id}/chapter/{idx}"
|
let chapterUrl = relUrl app $"admin/post/{model.Id}/chapter/{idx}"
|
||||||
a [ _href chapterUrl; _hxGet chapterUrl; _hxTarget $"#chapter{idx}"
|
a [ _href chapterUrl; _hxGet chapterUrl; _hxTarget $"#chapter{idx}"
|
||||||
_hxSwap $"{HxSwap.InnerHtml} show:#chapter{idx}:top" ] [
|
_hxSwap $"{HxSwap.InnerHtml} show:top showTarget:#chapter{idx}" ] [
|
||||||
raw "Edit"
|
raw "Edit"
|
||||||
]
|
]
|
||||||
span [ _class "text-muted" ] [ raw " • " ]
|
span [ _class "text-muted" ] [ raw " • " ]
|
||||||
@@ -138,12 +139,13 @@ let chapterList withNew (model: ManageChaptersModel) app =
|
|||||||
div [ _class "row pb-3"; _id "chapter-1" ] [
|
div [ _class "row pb-3"; _id "chapter-1" ] [
|
||||||
let newLink = relUrl app $"admin/post/{model.Id}/chapter/-1"
|
let newLink = relUrl app $"admin/post/{model.Id}/chapter/-1"
|
||||||
if withNew then
|
if withNew then
|
||||||
span [ _hxGet newLink; _hxTarget "#chapter-1"; _hxTrigger "load"; _hxSwap "show:#chapter-1:top" ] []
|
span [ _hxGet newLink; _hxTarget "#chapter-1"; _hxTrigger "load"
|
||||||
|
_hxSwap "show:top showTarget:#chapter-1" ] []
|
||||||
else
|
else
|
||||||
div [ _class "row pb-3 mwl-table-detail" ] [
|
div [ _class "row pb-3 mwl-table-detail" ] [
|
||||||
div [ _class "col-12" ] [
|
div [ _class "col-12" ] [
|
||||||
a [ _class "btn btn-primary"; _href newLink; _hxGet newLink; _hxTarget "#chapter-1"
|
a [ _class "btn btn-primary"; _href newLink; _hxGet newLink; _hxTarget "#chapter-1"
|
||||||
_hxSwap "show:#chapter-1:top" ] [
|
_hxSwap "show:top showTarget:#chapter-1" ] [
|
||||||
raw "Add a New Chapter"
|
raw "Add a New Chapter"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
@@ -154,12 +156,13 @@ let chapterList withNew (model: ManageChaptersModel) app =
|
|||||||
|
|
||||||
/// Manage Chapters page
|
/// Manage Chapters page
|
||||||
let chapters withNew (model: ManageChaptersModel) app = [
|
let chapters withNew (model: ManageChaptersModel) app = [
|
||||||
|
let backUrl = relUrl app $"admin/post/{model.Id}/edit"
|
||||||
h2 [ _class "my-3" ] [ txt app.PageTitle ]
|
h2 [ _class "my-3" ] [ txt app.PageTitle ]
|
||||||
article [] [
|
article [] [
|
||||||
p [ _style "line-height:1.2rem;" ] [
|
p [ _style "line-height:1.2rem;" ] [
|
||||||
strong [] [ txt model.Title ]; br []
|
strong [] [ txt model.Title ]; br []
|
||||||
small [ _class "text-muted" ] [
|
small [ _class "text-muted" ] [
|
||||||
a [ _href (relUrl app $"admin/post/{model.Id}/edit") ] [
|
a [ _href backUrl; _hxGet backUrl; _hxPushUrl "true" ] [
|
||||||
raw "« Back to Edit Post"
|
raw "« Back to Edit Post"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
@@ -176,9 +179,11 @@ let list (model: PostDisplay) app = [
|
|||||||
let tagCol = "col-lg-3 col-xl-4 col-xxl-5 d-none d-lg-inline-block"
|
let tagCol = "col-lg-3 col-xl-4 col-xxl-5 d-none d-lg-inline-block"
|
||||||
h2 [ _class "my-3" ] [ txt app.PageTitle ]
|
h2 [ _class "my-3" ] [ txt app.PageTitle ]
|
||||||
article [] [
|
article [] [
|
||||||
a [ _href (relUrl app "admin/post/new/edit"); _class "btn btn-primary btn-sm mb-3" ] [ raw "Write a New Post" ]
|
a [ _href (relUrl app "admin/post/new/edit"); _hxBoost; _class "btn btn-primary btn-sm mb-3" ] [
|
||||||
|
raw "Write a New Post"
|
||||||
|
]
|
||||||
if model.Posts.Length > 0 then
|
if model.Posts.Length > 0 then
|
||||||
form [ _method "post"; _class "container mb-3"; _hxTarget "body" ] [
|
form [ _method "post"; _class "container mb-3" ] [
|
||||||
antiCsrf app
|
antiCsrf app
|
||||||
div [ _class "row mwl-table-heading" ] [
|
div [ _class "row mwl-table-heading" ] [
|
||||||
div [ _class dateCol ] [
|
div [ _class dateCol ] [
|
||||||
@@ -219,7 +224,7 @@ let list (model: PostDisplay) app = [
|
|||||||
a [ _href (relUrl app post.Permalink); _target "_blank" ] [ raw "View Post" ]
|
a [ _href (relUrl app post.Permalink); _target "_blank" ] [ raw "View Post" ]
|
||||||
if app.IsEditor || (app.IsAuthor && app.UserId.Value = WebLogUserId post.AuthorId) then
|
if app.IsEditor || (app.IsAuthor && app.UserId.Value = WebLogUserId post.AuthorId) then
|
||||||
span [ _class "text-muted" ] [ raw " • " ]
|
span [ _class "text-muted" ] [ raw " • " ]
|
||||||
a [ _href $"{postUrl}/edit" ] [ raw "Edit" ]
|
a [ _href $"{postUrl}/edit"; _hxBoost ] [ raw "Edit" ]
|
||||||
if app.IsWebLogAdmin then
|
if app.IsWebLogAdmin then
|
||||||
span [ _class "text-muted" ] [ raw " • " ]
|
span [ _class "text-muted" ] [ raw " • " ]
|
||||||
a [ _href postUrl; _hxDelete postUrl; _class "text-danger"
|
a [ _href postUrl; _hxDelete postUrl; _class "text-danger"
|
||||||
@@ -252,22 +257,15 @@ let list (model: PostDisplay) app = [
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
if Option.isSome model.NewerLink || Option.isSome model.OlderLink then
|
if Option.isSome model.NewerLink || Option.isSome model.OlderLink then
|
||||||
|
let aAttrs link = [ _href link; _hxBoost; _class "btn btn-secondary" ]
|
||||||
div [ _class "d-flex justify-content-evenly mb-3" ] [
|
div [ _class "d-flex justify-content-evenly mb-3" ] [
|
||||||
div [] [
|
div [] [
|
||||||
if Option.isSome model.NewerLink then
|
if Option.isSome model.NewerLink then
|
||||||
p [] [
|
p [] [ a (aAttrs model.NewerLink.Value) [ raw "« Newer Posts" ] ]
|
||||||
a [ _href model.NewerLink.Value; _class "btn btn-secondary"; ] [
|
|
||||||
raw "« Newer Posts"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
div [ _class "text-right" ] [
|
div [ _class "text-right" ] [
|
||||||
if Option.isSome model.OlderLink then
|
if Option.isSome model.OlderLink then
|
||||||
p [] [
|
p [] [ a (aAttrs model.OlderLink.Value) [ raw "Older Posts »" ] ]
|
||||||
a [ _href model.OlderLink.Value; _class "btn btn-secondary" ] [
|
|
||||||
raw "Older Posts »"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
else
|
else
|
||||||
@@ -278,7 +276,7 @@ let list (model: PostDisplay) app = [
|
|||||||
let postEdit (model: EditPostModel) templates (ratings: MetaItem list) app = [
|
let postEdit (model: EditPostModel) templates (ratings: MetaItem list) app = [
|
||||||
h2 [ _class "my-3" ] [ raw app.PageTitle ]
|
h2 [ _class "my-3" ] [ raw app.PageTitle ]
|
||||||
article [] [
|
article [] [
|
||||||
form [ _action (relUrl app "admin/post/save"); _method "post"; _hxPushUrl "true"; _class "container" ] [
|
form [ _action (relUrl app "admin/post/save"); _method "post"; _hxBoost; _class "container" ] [
|
||||||
antiCsrf app
|
antiCsrf app
|
||||||
input [ _type "hidden"; _name (nameof model.Id); _value model.Id ]
|
input [ _type "hidden"; _name (nameof model.Id); _value model.Id ]
|
||||||
div [ _class "row mb-3" ] [
|
div [ _class "row mb-3" ] [
|
||||||
|
|||||||
@@ -138,8 +138,8 @@ let userList (model: WebLogUser list) app =
|
|||||||
if app.IsAdministrator || (app.IsWebLogAdmin && not (user.AccessLevel = Administrator)) then
|
if app.IsAdministrator || (app.IsWebLogAdmin && not (user.AccessLevel = Administrator)) then
|
||||||
let userUrl = relUrl app $"admin/settings/user/{user.Id}"
|
let userUrl = relUrl app $"admin/settings/user/{user.Id}"
|
||||||
small [] [
|
small [] [
|
||||||
a [ _href $"{userUrl}/edit"; _hxTarget $"#user_{user.Id}"
|
a [ _href $"{userUrl}/edit"; _hxBoost; _hxTarget $"#user_{user.Id}"
|
||||||
_hxSwap $"{HxSwap.InnerHtml} show:#user_{user.Id}:top" ] [
|
_hxSwap $"{HxSwap.InnerHtml} show:top showTarget:#user_{user.Id}" ] [
|
||||||
raw "Edit"
|
raw "Edit"
|
||||||
]
|
]
|
||||||
if app.UserId.Value <> user.Id then
|
if app.UserId.Value <> user.Id then
|
||||||
@@ -168,7 +168,7 @@ let userList (model: WebLogUser list) app =
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
div [ _id "user_panel" ] [
|
div [ _id "user_panel" ] [
|
||||||
a [ _href (relUrl app "admin/settings/user/new/edit"); _class "btn btn-primary btn-sm mb-3"
|
a [ _href (relUrl app "admin/settings/user/new/edit"); _hxBoost; _class "btn btn-primary btn-sm mb-3"
|
||||||
_hxTarget "#user_new" ] [
|
_hxTarget "#user_new" ] [
|
||||||
raw "Add a New User"
|
raw "Add a New User"
|
||||||
]
|
]
|
||||||
@@ -187,7 +187,8 @@ let userList (model: WebLogUser list) app =
|
|||||||
div [ _class "row mwl-table-detail"; _id "user_new" ] []
|
div [ _class "row mwl-table-detail"; _id "user_new" ] []
|
||||||
]
|
]
|
||||||
List.map userDetail model
|
List.map userDetail model
|
||||||
|> div [ _class "container g-0"; _hxTarget "#user_panel"; _hxSwap $"{HxSwap.OuterHtml} show:window:top" ]
|
|> div [ _class "container g-0"; hxInherited (_hxTarget "#user_panel")
|
||||||
|
hxInherited (_hxSwap $"{HxSwap.OuterHtml} show:top showTarget:window") ]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|> List.singleton
|
|> List.singleton
|
||||||
@@ -197,7 +198,7 @@ let userList (model: WebLogUser list) app =
|
|||||||
let myInfo (model: EditMyInfoModel) (user: WebLogUser) app = [
|
let myInfo (model: EditMyInfoModel) (user: WebLogUser) app = [
|
||||||
h2 [ _class "my-3" ] [ txt app.PageTitle ]
|
h2 [ _class "my-3" ] [ txt app.PageTitle ]
|
||||||
article [] [
|
article [] [
|
||||||
form [ _action (relUrl app "admin/my-info"); _method "post" ] [
|
form [ _action (relUrl app "admin/my-info"); _method "post"; _hxBoost ] [
|
||||||
antiCsrf app
|
antiCsrf app
|
||||||
div [ _class "d-flex flex-row flex-wrap justify-content-around" ] [
|
div [ _class "d-flex flex-row flex-wrap justify-content-around" ] [
|
||||||
div [ _class "text-center mb-3 lh-sm" ] [
|
div [ _class "text-center mb-3 lh-sm" ] [
|
||||||
|
|||||||
@@ -814,7 +814,8 @@ let webLogSettings
|
|||||||
]
|
]
|
||||||
fieldset [ _id "users"; _class "container mb-3 pb-0" ] [
|
fieldset [ _id "users"; _class "container mb-3 pb-0" ] [
|
||||||
legend [] [ raw "Users" ]
|
legend [] [ raw "Users" ]
|
||||||
span [ _hxGet (relUrl app "admin/settings/users"); _hxTrigger HxTrigger.Load; _hxSwap HxSwap.OuterHtml ] []
|
span [ _hxGet (relUrl app "admin/settings/users"); _hxTarget "this"; _hxTrigger HxTrigger.Load
|
||||||
|
_hxSwap HxSwap.OuterHtml ] []
|
||||||
]
|
]
|
||||||
fieldset [ _id "rss-settings"; _class "container mb-3 pb-0" ] [
|
fieldset [ _id "rss-settings"; _class "container mb-3 pb-0" ] [
|
||||||
legend [] [ raw "RSS Settings" ]
|
legend [] [ raw "RSS Settings" ]
|
||||||
@@ -891,7 +892,7 @@ let webLogSettings
|
|||||||
_hxTarget "#tag_new" ] [
|
_hxTarget "#tag_new" ] [
|
||||||
raw "Add a New Tag Mapping"
|
raw "Add a New Tag Mapping"
|
||||||
]
|
]
|
||||||
span [ _hxGet (relUrl app "admin/settings/tag-mappings"); _hxTrigger HxTrigger.Load
|
span [ _hxGet (relUrl app "admin/settings/tag-mappings"); _hxTarget "this"; _hxTrigger HxTrigger.Load
|
||||||
_hxSwap HxSwap.OuterHtml ] []
|
_hxSwap HxSwap.OuterHtml ] []
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user