WIP on htmx 4; user messages now work (#60)
This commit is contained in:
@@ -107,7 +107,7 @@ let isHtmx (ctx: HttpContext) =
|
||||
ctx.Request.IsHtmx && not ctx.Request.IsHtmxRefresh
|
||||
|
||||
/// Convert messages to headers (used for htmx responses)
|
||||
let messagesToHeaders (messages: UserMessage array) pushUrl : HttpHandler =
|
||||
let messagesToHeaders (messages: UserMessage array) : HttpHandler =
|
||||
seq {
|
||||
yield!
|
||||
messages
|
||||
@@ -116,10 +116,7 @@ let messagesToHeaders (messages: UserMessage array) pushUrl : HttpHandler =
|
||||
| Some detail -> $"{m.Level}|||{m.Message}|||{detail}"
|
||||
| None -> $"{m.Level}|||{m.Message}"
|
||||
|> setHttpHeader "X-Message")
|
||||
match pushUrl with
|
||||
| Some true -> withHxPushUrl "true"
|
||||
| Some false -> withHxNoPushUrl
|
||||
| None -> ()
|
||||
withHxNoPushUrl
|
||||
}
|
||||
|> Seq.reduce (>=>)
|
||||
|
||||
@@ -158,7 +155,7 @@ module Error =
|
||||
{ UserMessage.Error with
|
||||
Message = $"You are not authorized to access the URL {ctx.Request.Path.Value}" }
|
||||
|]
|
||||
(messagesToHeaders messages (Some false) >=> setStatusCode 401) earlyReturn ctx
|
||||
(messagesToHeaders messages >=> setStatusCode 401) earlyReturn ctx
|
||||
else setStatusCode 401 earlyReturn ctx
|
||||
|
||||
/// Handle 404s
|
||||
@@ -168,14 +165,14 @@ module Error =
|
||||
let messages = [|
|
||||
{ UserMessage.Error with Message = $"The URL {ctx.Request.Path.Value} was not found" }
|
||||
|]
|
||||
RequestErrors.notFound (messagesToHeaders messages (Some false)) earlyReturn ctx
|
||||
RequestErrors.notFound (messagesToHeaders messages) earlyReturn ctx
|
||||
else RequestErrors.NOT_FOUND "Not found" earlyReturn ctx)
|
||||
|
||||
let server message : HttpHandler =
|
||||
handleContext (fun ctx ->
|
||||
if isHtmx ctx then
|
||||
let messages = [| { UserMessage.Error with Message = message } |]
|
||||
ServerErrors.internalError (messagesToHeaders messages (Some false)) earlyReturn ctx
|
||||
ServerErrors.internalError (messagesToHeaders messages) earlyReturn ctx
|
||||
else ServerErrors.INTERNAL_ERROR message earlyReturn ctx)
|
||||
|
||||
|
||||
@@ -215,7 +212,7 @@ let bareForTheme themeId template next ctx viewCtx = task {
|
||||
match! Template.Cache.get themeId "layout-bare" ctx.Data with
|
||||
| Ok layoutTemplate ->
|
||||
return!
|
||||
(messagesToHeaders completeCtx.Messages (Some false)
|
||||
(messagesToHeaders completeCtx.Messages
|
||||
>=> 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
|
||||
@@ -231,12 +228,7 @@ let adminPage pageTitle next ctx (content: AppViewContext -> XmlNode list) = tas
|
||||
let! messages = getCurrentMessages ctx
|
||||
let appCtx = generateViewContext messages (viewCtxForPage pageTitle) ctx
|
||||
let layout = if isHtmx ctx then Layout.partial else Layout.full
|
||||
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
|
||||
return! (layout content appCtx |> RenderView.AsString.htmlDocument |> htmlString) next ctx
|
||||
}
|
||||
|
||||
/// Display a bare page for an admin endpoint
|
||||
@@ -244,7 +236,7 @@ let adminBarePage pageTitle next ctx (content: AppViewContext -> XmlNode list) =
|
||||
let! messages = getCurrentMessages ctx
|
||||
let appCtx = generateViewContext messages (viewCtxForPage pageTitle) ctx
|
||||
return!
|
||||
( messagesToHeaders appCtx.Messages (Some false)
|
||||
(messagesToHeaders appCtx.Messages
|
||||
>=> htmlString (Layout.bare content appCtx |> RenderView.AsString.htmlDocument)) next ctx
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ let edit (model: EditUserModel) app =
|
||||
div [ _class "col-12" ] [
|
||||
h5 [ _class "my-3" ] [ txt app.PageTitle ]
|
||||
form [ _hxPost (relUrl app "admin/settings/user/save"); _method "post"; _class "container"
|
||||
_hxTarget "#user_panel"; _hxSwap $"{HxSwap.OuterHtml} show:window:top" ] [
|
||||
_hxTarget "#user_panel"; _hxSwap $"{HxSwap.OuterHtml} show:top showTarget:window" ] [
|
||||
antiCsrf app
|
||||
input [ _type "hidden"; _name "Id"; _value model.Id ]
|
||||
div [ _class "row" ] [
|
||||
@@ -97,7 +97,7 @@ let edit (model: EditUserModel) app =
|
||||
let logOn (model: LogOnModel) (app: AppViewContext) = [
|
||||
h2 [ _class "my-3" ] [ rawText "Log On to "; encodedText app.WebLog.Name ]
|
||||
article [ _class "py-3" ] [
|
||||
form [ _action (relUrl app "user/log-on"); _method "post"; _class "container"; _hxPushUrl "true" ] [
|
||||
form [ _action (relUrl app "user/log-on"); _method "post"; _hxBoost; _class "container" ] [
|
||||
antiCsrf app
|
||||
if Option.isSome model.ReturnTo then input [ _type "hidden"; _name "ReturnTo"; _value model.ReturnTo.Value ]
|
||||
div [ _class "row" ] [
|
||||
|
||||
@@ -70,8 +70,8 @@ let categoryList includeNew app = [
|
||||
a [ _href (relUrl app $"category/{cat.Slug}"); _target "_blank" ] [
|
||||
raw $"View { cat.PostCount} Post"; if cat.PostCount <> 1 then raw "s"
|
||||
]; actionSpacer
|
||||
a [ _href $"{catUrl}/edit"; _hxTarget $"#cat_{cat.Id}"
|
||||
_hxSwap $"{HxSwap.InnerHtml} show:#cat_{cat.Id}:top" ] [
|
||||
a [ _href $"{catUrl}/edit"; _hxBoost; _hxTarget $"#cat_{cat.Id}"
|
||||
_hxSwap $"{HxSwap.InnerHtml} show:top showTarget:#cat_{cat.Id}" ] [
|
||||
raw "Edit"
|
||||
]; actionSpacer
|
||||
a [ _href catUrl; _hxDelete catUrl; _hxTarget "body"; _class "text-danger"
|
||||
@@ -85,11 +85,13 @@ let categoryList includeNew app = [
|
||||
]
|
||||
]
|
||||
let loadNew =
|
||||
span [ _hxGet (relUrl app "admin/category/new/edit"); _hxTrigger HxTrigger.Load; _hxSwap HxSwap.OuterHtml ] []
|
||||
span [ _hxGet (relUrl app "admin/category/new/edit"); _hxTrigger HxTrigger.Load; _hxSwap HxSwap.OuterHtml
|
||||
_hxTarget "this" ] []
|
||||
|
||||
h2 [ _class "my-3" ] [ raw app.PageTitle ]
|
||||
article [] [
|
||||
a [ _href (relUrl app "admin/category/new/edit"); _class "btn btn-primary btn-sm mb-3"; _hxTarget "#cat_new" ] [
|
||||
a [ _href (relUrl app "admin/category/new/edit"); _hxBoost; _class "btn btn-primary btn-sm mb-3"
|
||||
_hxTarget "#cat_new" ] [
|
||||
raw "Add a New Category"
|
||||
]
|
||||
div [ _id "catList"; _class "container" ] [
|
||||
@@ -118,7 +120,7 @@ let categoryList includeNew app = [
|
||||
/// The main dashboard
|
||||
let dashboard (model: DashboardModel) app = [
|
||||
h2 [ _class "my-3" ] [ txt app.WebLog.Name; raw " • Dashboard" ]
|
||||
article [ _class "container" ] [
|
||||
article [ _class "container"; hxInherited _hxBoost ] [
|
||||
div [ _class "row" ] [
|
||||
section [ _class "col-lg-5 offset-lg-1 col-xl-4 offset-xl-2 pb-3" ] [
|
||||
div [ _class "card" ] [
|
||||
@@ -193,7 +195,7 @@ let dashboard (model: DashboardModel) app = [
|
||||
let feedEdit (model: EditCustomFeedModel) (ratings: MetaItem list) (mediums: MetaItem list) app = [
|
||||
h2 [ _class "my-3" ] [ raw app.PageTitle ]
|
||||
article [] [
|
||||
form [ _action (relUrl app "admin/settings/rss/save"); _method "post"; _class "container" ] [
|
||||
form [ _action (relUrl app "admin/settings/rss/save"); _method "post"; _hxBoost; _class "container" ] [
|
||||
antiCsrf app
|
||||
input [ _type "hidden"; _name "Id"; _value model.Id ]
|
||||
div [ _class "row pb-3" ] [
|
||||
|
||||
@@ -442,22 +442,21 @@ this.Admin = {
|
||||
}
|
||||
}
|
||||
|
||||
htmx.on("htmx:afterOnLoad", function (evt) {
|
||||
const hdrs = evt.detail.xhr.getAllResponseHeaders()
|
||||
htmx.on("htmx:after:swap", function (evt) {
|
||||
// Initialize any toasts that were pre-rendered from the server
|
||||
Admin.showPreRenderedMessages()
|
||||
// Show messages if there were any in the response
|
||||
if (hdrs.indexOf("x-message") >= 0) {
|
||||
Admin.showMessage(evt.detail.xhr.getResponseHeader("x-message"))
|
||||
const hdrs = evt.detail.ctx.response.headers
|
||||
if (hdrs.has("x-message")) {
|
||||
hdrs.get("x-message").split(", ").forEach(Admin.showMessage)
|
||||
}
|
||||
})
|
||||
|
||||
htmx.on("htmx:responseError", function (evt) {
|
||||
const xhr = evt.detail.xhr
|
||||
const hdrs = xhr.getAllResponseHeaders()
|
||||
htmx.on("htmx:response:error", function (evt) {
|
||||
const resp = evt.detail.ctx.response
|
||||
// Show an error message if there were none in the response
|
||||
if (hdrs.indexOf("x-message") < 0) {
|
||||
Admin.showMessage(`danger|||${xhr.status}: ${xhr.statusText}`)
|
||||
if (!resp.headers.has("x-message")) {
|
||||
Admin.showMessage(`danger|||${resp.status}: ${resp.statusText}`)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user