diff --git a/src/MyPrayerJournal/Server/Data.fs b/src/MyPrayerJournal/Server/Data.fs index 9948e4a..f4b6138 100644 --- a/src/MyPrayerJournal/Server/Data.fs +++ b/src/MyPrayerJournal/Server/Data.fs @@ -116,20 +116,20 @@ module private Helpers = /// Retrieve a request, including its history and notes, by its ID and user ID -let tryFullRequestById reqId userId (db : LiteDatabase) = task { +let tryFullRequestById reqId userId (db : LiteDatabase) = backgroundTask { let! req = db.requests.Find (Query.EQ ("_id", RequestId.toString reqId)) |> firstAsync return match box req with null -> None | _ when req.userId = userId -> Some req | _ -> None } /// Add a history entry -let addHistory reqId userId hist db = task { +let addHistory reqId userId hist db = backgroundTask { match! tryFullRequestById reqId userId db with | Some req -> do! doUpdate db { req with history = hist :: req.history } | None -> invalidOp $"{RequestId.toString reqId} not found" } /// Add a note -let addNote reqId userId note db = task { +let addNote reqId userId note db = backgroundTask { match! tryFullRequestById reqId userId db with | Some req -> do! doUpdate db { req with notes = note :: req.notes } | None -> invalidOp $"{RequestId.toString reqId} not found" @@ -140,7 +140,7 @@ let addRequest (req : Request) (db : LiteDatabase) = db.requests.Insert req |> ignore /// Retrieve all answered requests for the given user -let answeredRequests userId (db : LiteDatabase) = task { +let answeredRequests userId (db : LiteDatabase) = backgroundTask { let! reqs = db.requests.Find (Query.EQ ("userId", UserId.toString userId)) |> toListAsync return reqs @@ -151,7 +151,7 @@ let answeredRequests userId (db : LiteDatabase) = task { } /// Retrieve the user's current journal -let journalByUserId userId (db : LiteDatabase) = task { +let journalByUserId userId (db : LiteDatabase) = backgroundTask { let! jrnl = db.requests.Find (Query.EQ ("userId", UserId.toString userId)) |> toListAsync return jrnl @@ -162,38 +162,38 @@ let journalByUserId userId (db : LiteDatabase) = task { } /// Retrieve a request by its ID and user ID (without notes and history) -let tryRequestById reqId userId db = task { +let tryRequestById reqId userId db = backgroundTask { let! req = tryFullRequestById reqId userId db return req |> Option.map (fun r -> { r with history = []; notes = [] }) } /// Retrieve notes for a request by its ID and user ID -let notesById reqId userId (db : LiteDatabase) = task { +let notesById reqId userId (db : LiteDatabase) = backgroundTask { match! tryFullRequestById reqId userId db with | Some req -> return req.notes | None -> return [] } /// Retrieve a journal request by its ID and user ID -let tryJournalById reqId userId (db : LiteDatabase) = task { +let tryJournalById reqId userId (db : LiteDatabase) = backgroundTask { let! req = tryFullRequestById reqId userId db return req |> Option.map JournalRequest.ofRequestLite } /// Update the recurrence for a request -let updateRecurrence reqId userId recurType recurCount db = task { +let updateRecurrence reqId userId recurType recurCount db = backgroundTask { match! tryFullRequestById reqId userId db with | Some req -> do! doUpdate db { req with recurType = recurType; recurCount = recurCount } | None -> invalidOp $"{RequestId.toString reqId} not found" } /// Update a snoozed request -let updateSnoozed reqId userId until db = task { +let updateSnoozed reqId userId until db = backgroundTask { match! tryFullRequestById reqId userId db with | Some req -> do! doUpdate db { req with snoozedUntil = until; showAfter = until } | None -> invalidOp $"{RequestId.toString reqId} not found" } /// Update the "show after" timestamp for a request -let updateShowAfter reqId userId showAfter db = task { +let updateShowAfter reqId userId showAfter db = backgroundTask { match! tryFullRequestById reqId userId db with | Some req -> do! doUpdate db { req with showAfter = showAfter } | None -> invalidOp $"{RequestId.toString reqId} not found" diff --git a/src/MyPrayerJournal/Server/Handlers.fs b/src/MyPrayerJournal/Server/Handlers.fs index 1e7142a..f53d1aa 100644 --- a/src/MyPrayerJournal/Server/Handlers.fs +++ b/src/MyPrayerJournal/Server/Handlers.fs @@ -17,7 +17,7 @@ module private LogOnHelpers = /// Log on, optionally specifying a redirected URL once authentication is complete let logOn url : HttpHandler = - fun next ctx -> task { + fun next ctx -> backgroundTask { match url with | Some it -> do! ctx.ChallengeAsync ("Auth0", AuthenticationProperties (RedirectUri = it)) @@ -102,7 +102,7 @@ module private Helpers = /// Render a component result let renderComponent nodes : HttpHandler = noResponseCaching - >=> fun next ctx -> task { + >=> fun next ctx -> backgroundTask { return! ctx.WriteHtmlStringAsync (ViewEngine.RenderView.AsString.htmlNodes nodes) } @@ -117,7 +117,7 @@ module private Helpers = /// Composable handler to write a view to the output let writeView view : HttpHandler = - fun next ctx -> task { + fun next ctx -> backgroundTask { return! ctx.WriteHtmlViewAsync view } @@ -166,6 +166,10 @@ module private Helpers = /// Add a success message header to the response let withSuccessMessage : string -> HttpHandler = sprintf "success|||%s" >> setHttpHeader "X-Toast" + + /// Hide a modal window when the response is sent + let hideModal (name : string) : HttpHandler = + setHttpHeader "X-Hide-Modal" name /// Strongly-typed models for post requests @@ -213,7 +217,7 @@ module Components = // GET /components/journal-items let journalItems : HttpHandler = requiresAuthentication Error.notAuthorized - >=> fun next ctx -> task { + >=> fun next ctx -> backgroundTask { let shouldShow now r = now > Ticks.toLong r.snoozedUntil && now > Ticks.toLong r.showAfter let! jrnl = Data.journalByUserId (userId ctx) (db ctx) let shown = jrnl |> List.filter (shouldShow ((jsNow >> Ticks.toLong) ())) @@ -223,7 +227,7 @@ module Components = // GET /components/request-item/[req-id] let requestItem reqId : HttpHandler = requiresAuthentication Error.notAuthorized - >=> fun next ctx -> task { + >=> fun next ctx -> backgroundTask { match! Data.tryJournalById (RequestId.ofString reqId) (userId ctx) (db ctx) with | Some req -> return! renderComponent [ Views.Request.reqListItem req ] next ctx | None -> return! Error.notFound next ctx @@ -237,7 +241,7 @@ module Components = /// GET /components/request/[req-id]/notes let notes requestId : HttpHandler = requiresAuthentication Error.notAuthorized - >=> fun next ctx -> task { + >=> fun next ctx -> backgroundTask { let! notes = Data.notesById (RequestId.ofString requestId) (userId ctx) (db ctx) return! renderComponent (Views.Request.notes notes) next ctx } @@ -257,7 +261,7 @@ module Journal = // GET /journal let journal : HttpHandler = requiresAuthentication Error.notAuthorized - >=> fun next ctx -> task { + >=> fun next ctx -> backgroundTask { let usr = ctx.User.Claims |> Seq.tryFind (fun c -> c.Type = ClaimTypes.GivenName) @@ -286,7 +290,7 @@ module Request = // GET /request/[req-id]/edit let edit requestId : HttpHandler = requiresAuthentication Error.notAuthorized - >=> fun next ctx -> task { + >=> fun next ctx -> backgroundTask { let returnTo = match ctx.Request.Headers.Referer.[0] with | it when it.EndsWith "/active" -> "active" @@ -305,7 +309,7 @@ module Request = // PATCH /request/[req-id]/prayed let prayed requestId : HttpHandler = requiresAuthentication Error.notAuthorized - >=> fun next ctx -> task { + >=> fun next ctx -> backgroundTask { let db = db ctx let usrId = userId ctx let reqId = RequestId.ofString requestId @@ -323,26 +327,26 @@ module Request = | None -> return! Error.notFound next ctx } - /// POST /api/request/[req-id]/note + /// POST /request/[req-id]/note let addNote requestId : HttpHandler = requiresAuthentication Error.notAuthorized - >=> fun next ctx -> task { + >=> fun next ctx -> backgroundTask { let db = db ctx let usrId = userId ctx let reqId = RequestId.ofString requestId match! Data.tryRequestById reqId usrId db with | Some _ -> - let! notes = ctx.BindJsonAsync () + let! notes = ctx.BindFormAsync () do! Data.addNote reqId usrId { asOf = jsNow (); notes = notes.notes } db do! db.saveChanges () - return! created next ctx + return! (withSuccessMessage "Added Notes" >=> hideModal "notes" >=> created) next ctx | None -> return! Error.notFound next ctx } // GET /requests/active let active : HttpHandler = requiresAuthentication Error.notAuthorized - >=> fun next ctx -> task { + >=> fun next ctx -> backgroundTask { let! reqs = Data.journalByUserId (userId ctx) (db ctx) return! partial "Active Requests" (Views.Request.active reqs) next ctx } @@ -350,7 +354,7 @@ module Request = // GET /requests/snoozed let snoozed : HttpHandler = requiresAuthentication Error.notAuthorized - >=> fun next ctx -> task { + >=> fun next ctx -> backgroundTask { let! reqs = Data.journalByUserId (userId ctx) (db ctx) let now = (jsNow >> Ticks.toLong) () let snoozed = reqs |> List.filter (fun r -> Ticks.toLong r.snoozedUntil > now) @@ -360,7 +364,7 @@ module Request = // GET /requests/answered let answered : HttpHandler = requiresAuthentication Error.notAuthorized - >=> fun next ctx -> task { + >=> fun next ctx -> backgroundTask { let! reqs = Data.answeredRequests (userId ctx) (db ctx) return! partial "Answered Requests" (Views.Request.answered reqs) next ctx } @@ -368,7 +372,7 @@ module Request = /// GET /api/request/[req-id] let get requestId : HttpHandler = requiresAuthentication Error.notAuthorized - >=> fun next ctx -> task { + >=> fun next ctx -> backgroundTask { match! Data.tryJournalById (RequestId.ofString requestId) (userId ctx) (db ctx) with | Some req -> return! json req next ctx | None -> return! Error.notFound next ctx @@ -377,7 +381,7 @@ module Request = // GET /request/[req-id]/full let getFull requestId : HttpHandler = requiresAuthentication Error.notAuthorized - >=> fun next ctx -> task { + >=> fun next ctx -> backgroundTask { match! Data.tryFullRequestById (RequestId.ofString requestId) (userId ctx) (db ctx) with | Some req -> return! partial "Prayer Request" (Views.Request.full req) next ctx | None -> return! Error.notFound next ctx @@ -386,7 +390,7 @@ module Request = // PATCH /request/[req-id]/show let show requestId : HttpHandler = requiresAuthentication Error.notAuthorized - >=> fun next ctx -> task { + >=> fun next ctx -> backgroundTask { let db = db ctx let usrId = userId ctx let reqId = RequestId.ofString requestId @@ -401,7 +405,7 @@ module Request = /// PATCH /api/request/[req-id]/snooze let snooze requestId : HttpHandler = requiresAuthentication Error.notAuthorized - >=> fun next ctx -> task { + >=> fun next ctx -> backgroundTask { let db = db ctx let usrId = userId ctx let reqId = RequestId.ofString requestId @@ -417,7 +421,7 @@ module Request = // PATCH /request/[req-id]/cancel-snooze let cancelSnooze requestId : HttpHandler = requiresAuthentication Error.notAuthorized - >=> fun next ctx -> task { + >=> fun next ctx -> backgroundTask { let db = db ctx let usrId = userId ctx let reqId = RequestId.ofString requestId @@ -437,7 +441,7 @@ module Request = // POST /request let add : HttpHandler = requiresAuthentication Error.notAuthorized - >=> fun next ctx -> task { + >=> fun next ctx -> backgroundTask { let! form = ctx.BindModelAsync () let db = db ctx let usrId = userId ctx @@ -466,7 +470,7 @@ module Request = // PATCH /request let update : HttpHandler = requiresAuthentication Error.notAuthorized - >=> fun next ctx -> task { + >=> fun next ctx -> backgroundTask { let! form = ctx.BindModelAsync () let db = db ctx let usrId = userId ctx @@ -552,7 +556,8 @@ let routes = routef "/%s/show" Request.show ] POST [ - route "" Request.add + route "" Request.add + routef "/%s/note" Request.addNote ] ] subRoute "/user/" [ @@ -572,10 +577,5 @@ let routes = routef "/%s/snooze" Request.snooze ] ] - POST [ - subRoute "request" [ - routef "/%s/note" Request.addNote - ] - ] ] ] diff --git a/src/MyPrayerJournal/Server/Views/Journal.fs b/src/MyPrayerJournal/Server/Views/Journal.fs index 45f7d87..807ecb9 100644 --- a/src/MyPrayerJournal/Server/Views/Journal.fs +++ b/src/MyPrayerJournal/Server/Views/Journal.fs @@ -75,7 +75,9 @@ let journal user = article [ _class "container-fluid mt-3" ] [ ] div [ _class "modal-body"; _id "notesBody" ] [ ] div [ _class "modal-footer" ] [ - button [ _type "button"; _class "btn btn-secondary"; _data "bs-dismiss" "modal" ] [ str "Close" ] + button [ _type "button"; _id "notesDismiss"; _class "btn btn-secondary"; _data "bs-dismiss" "modal" ] [ + str "Close" + ] ] ] ] @@ -102,12 +104,23 @@ let journalItems items = /// The notes edit modal body let notesEdit requestId = let reqId = RequestId.toString requestId - [ form [ _hxPost $"/request/{reqId}/note"; _hxTarget "#top" ] [ - str "TODO" - button [ _type "submit"; _class "btn btn-primary" ] [ str "Add Notes" ] + [ form [ _hxPost $"/request/{reqId}/note" ] [ + div [ _class "form-floating pb-3" ] [ + textarea [ + _id "notes" + _name "notes" + _class "form-control" + _style "min-height: 8rem;" + _placeholder "Notes" + _autofocus; _required + ] [ ] + label [ _for "notes" ] [ str "Notes" ] + ] + p [ _class "text-end" ] [ button [ _type "submit"; _class "btn btn-primary" ] [ str "Add Notes" ] ] ] + hr [ _style "margin: .5rem -1rem" ] div [ _id "priorNotes" ] [ - p [ _class "text-center pt-5" ] [ + p [ _class "text-center pt-3" ] [ button [ _type "button" _class "btn btn-secondary" diff --git a/src/MyPrayerJournal/Server/Views/Request.fs b/src/MyPrayerJournal/Server/Views/Request.fs index cb341f0..1113fcd 100644 --- a/src/MyPrayerJournal/Server/Views/Request.fs +++ b/src/MyPrayerJournal/Server/Views/Request.fs @@ -263,8 +263,7 @@ let edit (req : JournalRequest) returnTo isNew = /// Display a list of notes for a request let notes notes = let toItem (note : Note) = p [] [ small [ _class "text-muted" ] [ relativeDate note.asOf ]; br []; str note.notes ] - [ hr [ _style "margin: .5rem -1rem" ] - p [ _class "text-center" ] [ strong [] [ str "Prior Notes for This Request" ] ] + [ p [ _class "text-center" ] [ strong [] [ str "Prior Notes for This Request" ] ] match notes with | [] -> p [ _class "text-center text-muted" ] [ str "There are no prior notes for this request" ] | _ -> yield! notes |> List.map toItem diff --git a/src/MyPrayerJournal/Server/wwwroot/script/mpj.js b/src/MyPrayerJournal/Server/wwwroot/script/mpj.js index 55e874b..f2fd0a1 100644 --- a/src/MyPrayerJournal/Server/wwwroot/script/mpj.js +++ b/src/MyPrayerJournal/Server/wwwroot/script/mpj.js @@ -66,19 +66,6 @@ const mpj = { const isDisabled = target.value === "Immediate" ;["recurCount", "recurInterval"].forEach(it => document.getElementById(it).disabled = isDisabled) } - }, - /** Script for the journal page */ - journal: { - /** - * Set up the journal page modals - */ - setUp () { - document.getElementById("notesModal").addEventListener("show.bs.modal", function (event) { - const reqId = event.relatedTarget.getAttribute("data-request-id") - document.getElementById("notesForm").setAttribute("action", `/request/${reqId}/note`) - document.getElementById("notesLoad").setAttribute("hx-get", `/components/request/${reqId}/notes`) - }) - } } } @@ -88,5 +75,8 @@ htmx.on("htmx:afterOnLoad", function (evt) { if (hdrs.indexOf("x-toast") >= 0) { mpj.showToast(evt.detail.xhr.getResponseHeader("x-toast")) } + // Hide a modal window if requested + if (hdrs.indexOf("x-hide-modal") >= 0) { + document.getElementById(evt.detail.xhr.getResponseHeader("x-hide-modal") + "Dismiss").click() + } }) -