Version 8 #43
| @ -11,10 +11,10 @@ let edit (model : EditChurch) ctx viewInfo = | ||||
|     let vi        = | ||||
|         viewInfo | ||||
|         |> AppViewInfo.withScopedStyles [ | ||||
|             "#name { width: 20rem; }" | ||||
|             "#city { width: 10rem; }" | ||||
|             "#st { width: 3rem; }" | ||||
|             "#interfaceAddress { width: 30rem; }" | ||||
|             $"#{nameof model.Name} {{ width: 20rem; }}" | ||||
|             $"#{nameof model.City} {{ width: 10rem; }}" | ||||
|             $"#{nameof model.State} {{ width: 3rem; }}" | ||||
|             $"#{nameof model.InterfaceAddress} {{ width: 30rem; }}" | ||||
|         ] | ||||
|         |> AppViewInfo.withOnLoadScript "PT.church.edit.onPageLoad" | ||||
|     form [ _action "/church/save"; _method "post"; _class "pt-center-columns"; Target.content ] [ | ||||
| @ -22,40 +22,29 @@ let edit (model : EditChurch) ctx viewInfo = | ||||
|         input [ _type "hidden"; _name (nameof model.ChurchId); _value (flatGuid model.ChurchId) ] | ||||
|         div [ _fieldRow ] [ | ||||
|             div [ _inputField ] [ | ||||
|                 label [ _for "name" ] [ locStr s["Church Name"] ] | ||||
|                 input [ _type "text"; _name (nameof model.Name); _id "name"; _required; _autofocus; _value model.Name ] | ||||
|                 label [ _for (nameof model.Name) ] [ locStr s["Church Name"] ] | ||||
|                 inputField "text" (nameof model.Name) model.Name [ _required; _autofocus ] | ||||
|             ] | ||||
|             div [ _inputField ] [ | ||||
|                 label [ _for "City"] [ locStr s["City"] ] | ||||
|                 input [ _type "text"; _name (nameof model.City); _id "city"; _required; _value model.City ] | ||||
|                 label [ _for (nameof model.City) ] [ locStr s["City"] ] | ||||
|                 inputField "text" (nameof model.City) model.City [ _required ] | ||||
|             ] | ||||
|             div [ _inputField ] [ | ||||
|                 label [ _for "state" ] [ locStr s["State or Province"] ] | ||||
|                 input [ _type      "text" | ||||
|                         _name      (nameof model.State) | ||||
|                         _id        "state" | ||||
|                         _minlength "2"; _maxlength "2" | ||||
|                         _value     model.State | ||||
|                         _required ] | ||||
|                 label [ _for (nameof model.State) ] [ locStr s["State or Province"] ] | ||||
|                 inputField "text" (nameof model.State) model.State [ _minlength "2"; _maxlength "2"; _required ] | ||||
|             ] | ||||
|         ] | ||||
|         div [ _fieldRow ] [ | ||||
|             div [ _checkboxField ] [ | ||||
|                 input [ _type  "checkbox" | ||||
|                         _name  (nameof model.HasInterface) | ||||
|                         _id    "hasInterface" | ||||
|                         _value "True" | ||||
|                         if defaultArg model.HasInterface false then _checked ] | ||||
|                 label [ _for "hasInterface" ] [ locStr s["Has an interface with Virtual Prayer Room"] ] | ||||
|                 inputField "checkbox" (nameof model.HasInterface) "True" | ||||
|                            [ if defaultArg model.HasInterface false then _checked ] | ||||
|                 label [ _for (nameof model.HasInterface) ] [ locStr s["Has an interface with Virtual Prayer Room"] ] | ||||
|             ] | ||||
|         ] | ||||
|         div [ _fieldRowWith [ "pt-fadeable" ]; _id "divInterfaceAddress" ] [ | ||||
|             div [ _inputField ] [ | ||||
|                 label [ _for "interfaceAddress" ] [ locStr s["VPR Interface URL"] ] | ||||
|                 input [ _type  "url" | ||||
|                         _name  (nameof model.InterfaceAddress) | ||||
|                         _id    "interfaceAddress"; | ||||
|                         _value (defaultArg model.InterfaceAddress "") ] | ||||
|                 label [ _for (nameof model.InterfaceAddress) ] [ locStr s["VPR Interface URL"] ] | ||||
|                 inputField "url" (nameof model.InterfaceAddress) (defaultArg model.InterfaceAddress "") [] | ||||
|             ] | ||||
|         ] | ||||
|         div [ _fieldRow ] [ submit [] "save" s["Save Church"] ] | ||||
| @ -73,17 +62,7 @@ let maintain (churches : Church list) (stats : Map<string, ChurchStats>) ctx vi | ||||
|         | [] -> space | ||||
|         | _ -> | ||||
|             table [ _class "pt-table pt-action-table" ] [ | ||||
|                 thead [] [ | ||||
|                     tr [] [ | ||||
|                         th [] [ locStr s["Actions"] ] | ||||
|                         th [] [ locStr s["Name"] ] | ||||
|                         th [] [ locStr s["Location"] ] | ||||
|                         th [] [ locStr s["Groups"] ] | ||||
|                         th [] [ locStr s["Requests"] ] | ||||
|                         th [] [ locStr s["Users"] ] | ||||
|                         th [] [ locStr s["Interface?"] ] | ||||
|                     ] | ||||
|                 ] | ||||
|                 tableHeadings s [ "Actions"; "Name"; "Location"; "Groups"; "Requests"; "Users"; "Interface?" ] | ||||
|                 churches | ||||
|                 |> List.map (fun ch -> | ||||
|                     let chId      = flatGuid ch.churchId | ||||
|  | ||||
| @ -138,6 +138,21 @@ let _inputField = _inputFieldWith [] | ||||
| /// The class that designates a checkbox / label pair | ||||
| let _checkboxField = _class "pt-checkbox-field" | ||||
| 
 | ||||
| /// Create an input field of the given type, with matching name and ID and the given value | ||||
| let inputField typ name value attrs = | ||||
|     List.concat [ [ _type typ; _name name; _id name; if value <> "" then _value value ]; attrs ] |> input | ||||
| 
 | ||||
| /// Generate a table heading with the given localized column names | ||||
| let tableHeadings (s : IStringLocalizer) (headings : string list) = | ||||
|     headings | ||||
|     |> List.map (fun heading -> th [ _scope "col" ] [ locStr s[heading] ]) | ||||
|     |> tr [] | ||||
|     |> List.singleton | ||||
|     |> thead [] | ||||
| 
 | ||||
| /// For a list of strings, prepend a pound sign and string them together with commas (CSS selector by ID) | ||||
| let toHtmlIds it = it |> List.map (fun x -> $"#%s{x}") |> String.concat ", " | ||||
| 
 | ||||
| /// The name this function used to have when the view engine was part of Giraffe | ||||
| let renderHtmlNode = RenderView.AsString.htmlNode | ||||
| 
 | ||||
|  | ||||
| @ -198,13 +198,13 @@ let private commonHead = [ | ||||
| ] | ||||
| 
 | ||||
| /// Render the <head> portion of the page | ||||
| let private htmlHead m pageTitle = | ||||
| let private htmlHead viewInfo pgTitle = | ||||
|     let s = I18N.localizer.Force () | ||||
|     head [] [ | ||||
|         meta [ _charset "UTF-8" ] | ||||
|         title [] [ locStr pageTitle; titleSep; locStr s["PrayerTracker"] ] | ||||
|         title [] [ locStr pgTitle; titleSep; locStr s["PrayerTracker"] ] | ||||
|         yield! commonHead | ||||
|         for cssFile in m.Style do | ||||
|         for cssFile in viewInfo.Style do | ||||
|             link [ _rel "stylesheet"; _href $"/css/{cssFile}.css"; _type "text/css" ] | ||||
|     ] | ||||
| 
 | ||||
| @ -224,16 +224,18 @@ let private helpLink link = | ||||
|     ] | ||||
| 
 | ||||
| /// Render the page title, and optionally a help link | ||||
| let private renderPageTitle m pageTitle = | ||||
| let private renderPageTitle viewInfo pgTitle = | ||||
|     h2 [ _id "pt-page-title" ] [ | ||||
|         match m.HelpLink with Some link -> PrayerTracker.Utils.Help.fullLink (langCode ()) link |> helpLink | None -> () | ||||
|         locStr pageTitle | ||||
|         match viewInfo.HelpLink with | ||||
|         | Some link -> PrayerTracker.Utils.Help.fullLink (langCode ()) link |> helpLink | ||||
|         | None -> () | ||||
|         locStr pgTitle | ||||
|     ] | ||||
| 
 | ||||
| /// Render the messages that may need to be displayed to the user | ||||
| let private messages m = | ||||
| let private messages viewInfo = | ||||
|     let s = I18N.localizer.Force () | ||||
|     m.Messages | ||||
|     viewInfo.Messages | ||||
|     |> List.map (fun msg -> | ||||
|         table [ _class $"pt-msg {MessageLevel.toCssClass msg.Level}" ] [ | ||||
|             tr [] [ | ||||
| @ -257,10 +259,10 @@ let private messages m = | ||||
| open System | ||||
| 
 | ||||
| /// Render the <footer> at the bottom of the page | ||||
| let private htmlFooter m = | ||||
| let private htmlFooter viewInfo = | ||||
|     let s          = I18N.localizer.Force () | ||||
|     let imgText    = $"""%O{s["PrayerTracker"]} %O{s["from Bit Badger Solutions"]}""" | ||||
|     let resultTime = TimeSpan(DateTime.Now.Ticks - m.RequestStart).TotalSeconds | ||||
|     let resultTime = TimeSpan(DateTime.Now.Ticks - viewInfo.RequestStart).TotalSeconds | ||||
|     footer [] [ | ||||
|         div [ _id "pt-legal" ] [ | ||||
|             a [ _href "/legal/privacy-policy" ] [ locStr s["Privacy Policy"] ] | ||||
| @ -278,7 +280,7 @@ let private htmlFooter m = | ||||
|             a [ _href "/"; _style "line-height:28px;" ] [ | ||||
|                 img [ _src $"""/img/%O{s["footer_en"]}.png"""; _alt imgText; _title imgText ] | ||||
|             ] | ||||
|             str m.Version | ||||
|             str viewInfo.Version | ||||
|             space | ||||
|             i [ _title s["This page loaded in {0:N3} seconds", resultTime].Value; _class "material-icons md-18" ] [ | ||||
|                 str "schedule" | ||||
| @ -289,9 +291,9 @@ let private htmlFooter m = | ||||
|     ] | ||||
| 
 | ||||
| /// The content portion of the PrayerTracker layout | ||||
| let private contentSection viewInfo title (content : XmlNode) = [ | ||||
| let private contentSection viewInfo pgTitle (content : XmlNode) = [ | ||||
|     Navigation.identity viewInfo | ||||
|     renderPageTitle viewInfo title | ||||
|     renderPageTitle viewInfo pgTitle | ||||
|     yield! messages viewInfo | ||||
|     match viewInfo.ScopedStyle with | ||||
|     | [] -> () | ||||
| @ -301,7 +303,7 @@ let private contentSection viewInfo title (content : XmlNode) = [ | ||||
|     for jsFile in viewInfo.Script do | ||||
|         script [ _src $"/js/{jsFile}.js" ] [] | ||||
|     match viewInfo.OnLoadScript with | ||||
|     | Some onLoad -> script [] [ rawText $"PT.onLoad({onLoad}" ] | ||||
|     | Some onLoad -> script [] [ rawText $"{onLoad}()" ] | ||||
|     | None -> () | ||||
| ] | ||||
| 
 | ||||
| @ -316,11 +318,11 @@ let private partialHead pgTitle = | ||||
| open Giraffe.Htmx.Common | ||||
| 
 | ||||
| /// The body of the PrayerTracker layout | ||||
| let private pageLayout viewInfo title content = | ||||
| let private pageLayout viewInfo pgTitle content = | ||||
|     body [ _hxBoost ] [ | ||||
|         Navigation.top viewInfo | ||||
|         div [ _id "pt-body"; Target.content; _hxSwap $"{HxSwap.InnerHtml} show:window:top" ] | ||||
|             (contentSection viewInfo title content) | ||||
|             (contentSection viewInfo pgTitle content) | ||||
|     ] | ||||
|      | ||||
| /// The standard layout(s) for PrayerTracker | ||||
|  | ||||
| @ -17,7 +17,7 @@ let edit (model : EditRequest) today ctx viewInfo = | ||||
|     let vi        = AppViewInfo.withOnLoadScript "PT.initCKEditor" viewInfo | ||||
|     form [ _action "/prayer-request/save"; _method "post"; _class "pt-center-columns"; Target.content ] [ | ||||
|         csrfToken ctx | ||||
|         input [ _type "hidden"; _name (nameof model.RequestId); _value (flatGuid model.RequestId) ] | ||||
|         inputField "hidden" (nameof model.RequestId) (flatGuid model.RequestId) [] | ||||
|         div [ _fieldRow ] [ | ||||
|             div [ _inputField ] [ | ||||
|                 label [ _for (nameof model.RequestType) ] [ locStr s["Request Type"] ] | ||||
| @ -27,27 +27,21 @@ let edit (model : EditRequest) today ctx viewInfo = | ||||
|                 |> selectList (nameof model.RequestType) model.RequestType [ _required; _autofocus ] | ||||
|             ] | ||||
|             div [ _inputField ] [ | ||||
|                 label [ _for "requestor" ] [ locStr s["Requestor / Subject"] ] | ||||
|                 input [ _type  "text" | ||||
|                         _id    "requestor" | ||||
|                         _name  (nameof model.Requestor) | ||||
|                         _value (defaultArg model.Requestor "") ] | ||||
|                 label [ _for (nameof model.Requestor) ] [ locStr s["Requestor / Subject"] ] | ||||
|                 inputField "text" (nameof model.Requestor) (defaultArg model.Requestor "") [] | ||||
|             ] | ||||
|             if model.IsNew then | ||||
|                 div [ _inputField ] [ | ||||
|                     label [ _for "enteredDate" ] [ locStr s["Date"] ] | ||||
|                     input [ _type "date"; _name (nameof model.EnteredDate); _id "enteredDate"; _placeholder today ] | ||||
|                     label [ _for (nameof model.EnteredDate) ] [ locStr s["Date"] ] | ||||
|                     inputField "date" (nameof model.EnteredDate) "" [ _placeholder today ] | ||||
|                 ] | ||||
|             else | ||||
|                 // TODO: do these need to be nested like this? | ||||
|                 div [ _inputField ] [ | ||||
|                     div [ _checkboxField ] [ | ||||
|                         br [] | ||||
|                         input [ _type  "checkbox" | ||||
|                                 _name  (nameof model.SkipDateUpdate) | ||||
|                                 _id    "skipDateUpdate" | ||||
|                                 _value "True" ] | ||||
|                         label [ _for "skipDateUpdate" ] [ locStr s["Check to not update the date"] ] | ||||
|                         inputField "checkbox" (nameof model.SkipDateUpdate) "True" [] | ||||
|                         label [ _for (nameof model.SkipDateUpdate) ] [ locStr s["Check to not update the date"] ] | ||||
|                         br [] | ||||
|                         small [] [ em [] [ str (s["Typo Corrections"].Value.ToLower ()); rawText ", etc." ] ] | ||||
|                     ] | ||||
| @ -58,7 +52,7 @@ let edit (model : EditRequest) today ctx viewInfo = | ||||
|                 label [] [ locStr s["Expiration"] ] | ||||
|                 ReferenceList.expirationList s (not model.IsNew) | ||||
|                 |> List.map (fun exp -> | ||||
|                     let radioId = $"expiration_{fst exp}" | ||||
|                     let radioId = String.concat "_" [ nameof model.Expiration; fst exp ]  | ||||
|                     span [ _class "text-nowrap" ] [ | ||||
|                         radio (nameof model.Expiration) radioId (fst exp) model.Expiration | ||||
|                         label [ _for radioId ] [ locStr (snd exp) ] | ||||
| @ -69,8 +63,8 @@ let edit (model : EditRequest) today ctx viewInfo = | ||||
|         ] | ||||
|         div [ _fieldRow ] [ | ||||
|             div [ _inputFieldWith [ "pt-editor" ] ] [ | ||||
|                 label [ _for "text" ] [ locStr s["Request"] ] | ||||
|                 textarea [ _name (nameof model.Text); _id "text" ] [ str model.Text ] | ||||
|                 label [ _for (nameof model.Text) ] [ locStr s["Request"] ] | ||||
|                 textarea [ _name (nameof model.Text); _id (nameof model.Text) ] [ str model.Text ] | ||||
|             ] | ||||
|         ] | ||||
|         div [ _fieldRow ] [ submit [] "save" s["Save Request"] ] | ||||
| @ -129,13 +123,7 @@ let lists (groups : SmallGroup list) viewInfo = | ||||
|         | count -> | ||||
|             tableSummary count s | ||||
|             table [ _class "pt-table pt-action-table" ] [ | ||||
|                 thead [] [ | ||||
|                     tr [] [ | ||||
|                         th [] [ locStr s["Actions"] ] | ||||
|                         th [] [ locStr s["Church"] ] | ||||
|                         th [] [ locStr s["Group"] ] | ||||
|                     ] | ||||
|                 ] | ||||
|                 tableHeadings s [ "Actions"; "Church"; "Group" ] | ||||
|                 groups | ||||
|                 |> List.map (fun grp -> | ||||
|                     let grpId = flatGuid grp.smallGroupId | ||||
| @ -238,11 +226,7 @@ let maintain (model : MaintainRequests) (ctx : HttpContext) viewInfo = | ||||
|             | None -> () | ||||
|         ] | ||||
|         form [ _action "/prayer-requests"; _method "get"; _class "pt-center-text pt-search-form"; Target.content ] [ | ||||
|             input [ _type "text" | ||||
|                     _name "search" | ||||
|                     _placeholder l["Search requests..."].Value | ||||
|                     _value (defaultArg model.SearchTerm "") | ||||
|                   ] | ||||
|             inputField "text" "search" (defaultArg model.SearchTerm "") [ _placeholder l["Search requests..."].Value ] | ||||
|             space | ||||
|             submit [] "search" s["Search"] | ||||
|         ] | ||||
| @ -252,15 +236,7 @@ let maintain (model : MaintainRequests) (ctx : HttpContext) viewInfo = | ||||
|         | 0 -> () | ||||
|         | _ -> | ||||
|             table [ _class "pt-table pt-action-table" ] [ | ||||
|                 thead [] [ | ||||
|                     tr [] [ | ||||
|                         th [] [ locStr s["Actions"] ] | ||||
|                         th [] [ locStr s["Updated Date"] ] | ||||
|                         th [] [ locStr s["Type"] ] | ||||
|                         th [] [ locStr s["Requestor"] ] | ||||
|                         th [] [ locStr s["Request"] ] | ||||
|                     ] | ||||
|                 ] | ||||
|                 tableHeadings s [ "Actions"; "Updated Date"; "Type"; "Requestor"; "Request"] | ||||
|                 tbody [] requests | ||||
|             ] | ||||
|         div [ _class "pt-center-text" ] [ | ||||
|  | ||||
| @ -8,15 +8,15 @@ open PrayerTracker.ViewModels | ||||
| /// View for the announcement page | ||||
| let announcement isAdmin ctx viewInfo = | ||||
|     let s        = I18N.localizer.Force () | ||||
|     let model        = { SendToClass = ""; Text = ""; AddToRequestList = None; RequestType = None } | ||||
|     let model    = { SendToClass = ""; Text = ""; AddToRequestList = None; RequestType = None } | ||||
|     let reqTypes = ReferenceList.requestTypeList s | ||||
|     let vi       = AppViewInfo.withOnLoadScript "PT.smallGroup.announcement.onPageLoad" viewInfo | ||||
|     form [ _action "/small-group/announcement/send"; _method "post"; _class "pt-center-columns"; Target.content ] [ | ||||
|         csrfToken ctx | ||||
|         div [ _fieldRow ] [ | ||||
|             div [ _inputFieldWith [ "pt-editor" ] ] [ | ||||
|                 label [ _for "text" ] [ locStr s["Announcement Text"] ] | ||||
|                 textarea [ _name (nameof model.Text); _id "text"; _autofocus ] [] | ||||
|                 label [ _for (nameof model.Text) ] [ locStr s["Announcement Text"] ] | ||||
|                 textarea [ _name (nameof model.Text); _id (nameof model.Text); _autofocus ] [] | ||||
|             ] | ||||
|         ] | ||||
|         if isAdmin then | ||||
| @ -24,18 +24,20 @@ let announcement isAdmin ctx viewInfo = | ||||
|                 div [ _inputField ] [ | ||||
|                     label [] [ locStr s["Send Announcement to"]; rawText ":" ] | ||||
|                     div [ _class "pt-center-text" ] [ | ||||
|                         radio (nameof model.SendToClass) "sendY" "Y" "Y" | ||||
|                         label [ _for "sendY" ] [ locStr s["This Group"]; rawText "     " ] | ||||
|                         radio (nameof model.SendToClass) "sendN" "N" "Y" | ||||
|                         label [ _for "sendN" ] [ locStr s["All {0} Users", s["PrayerTracker"]] ] | ||||
|                         radio (nameof model.SendToClass) $"{nameof model.SendToClass}_Y" "Y" "Y" | ||||
|                         label [ _for $"{nameof model.SendToClass}_Y" ] [ | ||||
|                             locStr s["This Group"]; rawText "     " | ||||
|                         ] | ||||
|                         radio (nameof model.SendToClass) $"{nameof model.SendToClass}_N" "N" "Y" | ||||
|                         label [ _for $"{nameof model.SendToClass}_N" ] [ locStr s["All {0} Users", s["PrayerTracker"]] ] | ||||
|                     ] | ||||
|                 ] | ||||
|             ] | ||||
|         else input [ _type "hidden"; _name (nameof model.SendToClass); _value "Y" ] | ||||
|         div [ _fieldRowWith [ "pt-fadeable"; "pt-shown" ]; _id "divAddToList" ] [ | ||||
|             div [ _checkboxField ] [ | ||||
|                 input [ _type "checkbox"; _name (nameof model.AddToRequestList); _id "addToRequestList"; _value "True" ] | ||||
|                 label [ _for "addToRequestList" ] [ locStr s["Add to Request List"] ] | ||||
|                 inputField "checkbox" (nameof model.AddToRequestList) "True" [] | ||||
|                 label [ _for (nameof model.AddToRequestList) ] [ locStr s["Add to Request List"] ] | ||||
|             ] | ||||
|         ] | ||||
|         div [ _fieldRowWith [ "pt-fadeable" ]; _id "divCategory" ] [ | ||||
| @ -74,11 +76,11 @@ let edit (model : EditSmallGroup) (churches : Church list) ctx viewInfo = | ||||
|     let pageTitle = if model.IsNew then "Add a New Group" else "Edit Group" | ||||
|     form [ _action "/small-group/save"; _method "post"; _class "pt-center-columns"; Target.content ] [ | ||||
|         csrfToken ctx | ||||
|         input [ _type "hidden"; _name (nameof model.SmallGroupId); _value (flatGuid model.SmallGroupId) ] | ||||
|         inputField "hidden" (nameof model.SmallGroupId) (flatGuid model.SmallGroupId) [] | ||||
|         div [ _fieldRow ] [ | ||||
|             div [ _inputField ] [ | ||||
|                 label [ _for "name" ] [ locStr s["Group Name"] ] | ||||
|                 input [ _type "text"; _name (nameof model.Name); _id "name"; _value model.Name; _required; _autofocus ] | ||||
|                 label [ _for (nameof model.Name) ] [ locStr s["Group Name"] ] | ||||
|                 inputField "text" (nameof model.Name) model.Name [ _required; _autofocus ] | ||||
|             ] | ||||
|         ] | ||||
|         div [ _fieldRow ] [ | ||||
| @ -102,18 +104,22 @@ let edit (model : EditSmallGroup) (churches : Church list) ctx viewInfo = | ||||
| let editMember (model : EditMember) (types : (string * LocalizedString) seq) ctx viewInfo = | ||||
|     let s         = I18N.localizer.Force () | ||||
|     let pageTitle = if model.IsNew then "Add a New Group Member" else "Edit Group Member" | ||||
|     let vi        = AppViewInfo.withScopedStyles [ "#name { width: 15rem; }"; "#email { width: 20rem; }" ] viewInfo | ||||
|     let vi        = | ||||
|         AppViewInfo.withScopedStyles [ | ||||
|             $"#{nameof model.Name} {{ width: 15rem; }}" | ||||
|             $"#{nameof model.Email} {{ width: 20rem; }}" | ||||
|         ] viewInfo | ||||
|     form [ _action "/small-group/member/save"; _method "post"; _class "pt-center-columns"; Target.content ] [ | ||||
|         csrfToken ctx | ||||
|         input [ _type "hidden"; _name (nameof model.MemberId); _value (flatGuid model.MemberId) ] | ||||
|         inputField "hidden" (nameof model.MemberId) (flatGuid model.MemberId) [] | ||||
|         div [ _fieldRow ] [ | ||||
|             div [ _inputField ] [ | ||||
|                 label [ _for "name" ] [ locStr s["Member Name"] ] | ||||
|                 input [ _type "text"; _name (nameof model.Name); _id "name"; _required; _autofocus; _value model.Name ] | ||||
|                 label [ _for (nameof model.Name) ] [ locStr s["Member Name"] ] | ||||
|                 inputField "text" (nameof model.Name) model.Name [ _required; _autofocus ] | ||||
|             ] | ||||
|             div [ _inputField ] [ | ||||
|                 label [ _for "email" ] [ locStr s["E-mail Address"] ] | ||||
|                 input [ _type "email"; _name (nameof model.Email); _id "email"; _required; _value model.Email ] | ||||
|                 label [ _for (nameof model.Email) ] [ locStr s["E-mail Address"] ] | ||||
|                 inputField "email" (nameof model.Email) model.Email [ _required ] | ||||
|             ] | ||||
|         ] | ||||
|         div [ _fieldRow ] [ | ||||
| @ -152,17 +158,14 @@ let logOn (groups : SmallGroup list) grpId ctx viewInfo = | ||||
|                 |> selectList (nameof model.SmallGroupId) grpId [ _required ] | ||||
|             ] | ||||
|             div [ _inputField ] [ | ||||
|                 label [ _for "password" ] [ locStr s["Password"] ] | ||||
|                 input [ _type        "password" | ||||
|                         _name        (nameof model.Password) | ||||
|                         _id          "password" | ||||
|                         _placeholder (s["Case-Sensitive"].Value.ToLower ()) | ||||
|                         _required ] | ||||
|                 label [ _for (nameof model.Password) ] [ locStr s["Password"] ] | ||||
|                 inputField "password" (nameof model.Password) "" | ||||
|                            [ _placeholder (s["Case-Sensitive"].Value.ToLower ()); _required ] | ||||
|             ] | ||||
|         ] | ||||
|         div [ _checkboxField ] [ | ||||
|             input [ _type "checkbox"; _name (nameof model.RememberMe); _id "rememberMe"; _value "True" ] | ||||
|             label [ _for "rememberMe" ] [ locStr s["Remember Me"] ] | ||||
|             inputField "checkbox" (nameof model.RememberMe) "True" [] | ||||
|             label [ _for (nameof model.RememberMe) ] [ locStr s["Remember Me"] ] | ||||
|             br [] | ||||
|             small [] [ em [] [ str (s["Requires Cookies"].Value.ToLower ()) ] ] | ||||
|         ] | ||||
| @ -181,14 +184,7 @@ let maintain (groups : SmallGroup list) ctx viewInfo = | ||||
|         | [] -> space | ||||
|         | _ -> | ||||
|             table [ _class "pt-table pt-action-table" ] [ | ||||
|                 thead [] [ | ||||
|                     tr [] [ | ||||
|                         th [] [ locStr s["Actions"] ] | ||||
|                         th [] [ locStr s["Name"] ] | ||||
|                         th [] [ locStr s["Church"] ] | ||||
|                         th [] [ locStr s["Time Zone"] ] | ||||
|                     ] | ||||
|                 ] | ||||
|                 tableHeadings s [ "Actions"; "Name"; "Church"; "Time Zone"] | ||||
|                 groups | ||||
|                 |> List.map (fun g -> | ||||
|                     let grpId     = flatGuid g.smallGroupId | ||||
| @ -234,14 +230,7 @@ let members (members : Member list) (emailTypes : Map<string, LocalizedString>) | ||||
|         | [] -> space | ||||
|         | _ -> | ||||
|             table [ _class "pt-table pt-action-table" ] [ | ||||
|                 thead [] [ | ||||
|                     tr [] [ | ||||
|                         th [] [ locStr s["Actions"] ] | ||||
|                         th [] [ locStr s["Name"] ] | ||||
|                         th [] [ locStr s["E-mail Address"] ] | ||||
|                         th [] [ locStr s["Format"] ] | ||||
|                     ] | ||||
|                 ] | ||||
|                 tableHeadings s [ "Actions"; "Name"; "E-mail Address"; "Format"] | ||||
|                 members | ||||
|                 |> List.map (fun mbr -> | ||||
|                     let mbrId     = flatGuid mbr.memberId | ||||
| @ -348,10 +337,16 @@ let preferences (model : EditPreferences) (tzs : TimeZone list) ctx viewInfo = | ||||
|     let vi  = | ||||
|         viewInfo | ||||
|         |> AppViewInfo.withScopedStyles [ | ||||
|             "#expireDays, #daysToKeepNew, #longTermUpdateWeeks, #headingFontSize, #listFontSize, #pageSize { width: 3rem; }" | ||||
|             "#emailFromAddress { width: 20rem; }" | ||||
|             "#fonts { width: 40rem; }" | ||||
|             "@media screen and (max-width: 40rem) { #fonts { width: 100%; } }" | ||||
|             let numberFields = | ||||
|                 [   nameof model.ExpireDays;      nameof model.DaysToKeepNew; nameof model.LongTermUpdateWeeks | ||||
|                     nameof model.HeadingFontSize; nameof model.ListFontSize;  nameof model.PageSize | ||||
|                 ] | ||||
|                 |> toHtmlIds | ||||
|             let fontsId = $"#{nameof model.Fonts}" | ||||
|             $"{numberFields} {{ width: 3rem; }}" | ||||
|             $"#{nameof model.EmailFromAddress} {{ width: 20rem; }}" | ||||
|             $"{fontsId} {{ width: 40rem; }}" | ||||
|             $"@media screen and (max-width: 40rem) {{ {fontsId} {{ width: 100%%; }} }}" | ||||
|         ] | ||||
|         |> AppViewInfo.withOnLoadScript "PT.smallGroup.preferences.onPageLoad" | ||||
|     form [ _action "/small-group/preferences/save"; _method "post"; _class "pt-center-columns"; Target.content ] [ | ||||
| @ -360,40 +355,32 @@ let preferences (model : EditPreferences) (tzs : TimeZone list) ctx viewInfo = | ||||
|             legend [] [ strong [] [ icon "date_range"; rawText "  "; locStr s["Dates"] ] ] | ||||
|             div [ _fieldRow ] [ | ||||
|                 div [ _inputField ] [ | ||||
|                     label [ _for "expireDays" ] [ locStr s["Requests Expire After"] ] | ||||
|                     label [ _for (nameof model.ExpireDays) ] [ locStr s["Requests Expire After"] ] | ||||
|                     span [] [ | ||||
|                         input [ _type  "number" | ||||
|                                 _name  (nameof model.ExpireDays) | ||||
|                                 _id    "expireDays" | ||||
|                                 _value (string model.ExpireDays) | ||||
|                                 _min   "1"; _max "30" | ||||
|                                 _required | ||||
|                                 _autofocus ] | ||||
|                         inputField "number" (nameof model.ExpireDays) (string model.ExpireDays) [ | ||||
|                             _min "1"; _max "30"; _required; _autofocus | ||||
|                         ] | ||||
|                         space | ||||
|                         str (s["Days"].Value.ToLower ()) | ||||
|                     ] | ||||
|                 ] | ||||
|                 div [ _inputField ] [ | ||||
|                     label [ _for "daysToKeepNew" ] [ locStr s["Requests “New” For"] ] | ||||
|                     label [ _for (nameof model.DaysToKeepNew) ] [ locStr s["Requests “New” For"] ] | ||||
|                     span [] [ | ||||
|                         input [ _type  "number" | ||||
|                                 _name  (nameof model.DaysToKeepNew) | ||||
|                                 _id    "daysToKeepNew" | ||||
|                                 _min   "1"; _max "30" | ||||
|                                 _value (string model.DaysToKeepNew) | ||||
|                                 _required ] | ||||
|                         inputField "number" (nameof model.DaysToKeepNew) (string model.DaysToKeepNew) [ | ||||
|                             _min "1"; _max "30"; _required | ||||
|                         ] | ||||
|                         space; str (s["Days"].Value.ToLower ()) | ||||
|                     ] | ||||
|                 ] | ||||
|                 div [ _inputField ] [ | ||||
|                     label [ _for "longTermUpdateWeeks" ] [ locStr s["Long-Term Requests Alerted for Update"] ] | ||||
|                     label [ _for (nameof model.LongTermUpdateWeeks) ] [ | ||||
|                         locStr s["Long-Term Requests Alerted for Update"] | ||||
|                     ] | ||||
|                     span [] [ | ||||
|                         input [ _type  "number" | ||||
|                                 _name  (nameof model.LongTermUpdateWeeks) | ||||
|                                 _id    "longTermUpdateWeeks" | ||||
|                                 _min   "1"; _max "30" | ||||
|                                 _value (string model.LongTermUpdateWeeks) | ||||
|                                 _required ] | ||||
|                         inputField "number" (nameof model.LongTermUpdateWeeks) (string model.LongTermUpdateWeeks) [ | ||||
|                             _min "1"; _max "30"; _required | ||||
|                         ] | ||||
|                         space; str (s["Weeks"].Value.ToLower ()) | ||||
|                     ] | ||||
|                 ] | ||||
| @ -401,30 +388,22 @@ let preferences (model : EditPreferences) (tzs : TimeZone list) ctx viewInfo = | ||||
|         ] | ||||
|         fieldset [] [ | ||||
|             legend [] [ strong [] [ icon "sort"; rawText "  "; locStr s["Request Sorting"] ] ] | ||||
|             radio (nameof model.RequestSort) "requestSort_D" "D" model.RequestSort | ||||
|             label [ _for "requestSort_D" ] [ locStr s["Sort by Last Updated Date"] ] | ||||
|             radio (nameof model.RequestSort) $"{nameof model.RequestSort}_D" "D" model.RequestSort | ||||
|             label [ _for $"{nameof model.RequestSort}_D" ] [ locStr s["Sort by Last Updated Date"] ] | ||||
|             rawText "   " | ||||
|             radio (nameof model.RequestSort) "requestSort_R" "R" model.RequestSort | ||||
|             label [ _for "requestSort_R" ] [ locStr s["Sort by Requestor Name"] ] | ||||
|             radio (nameof model.RequestSort) $"{nameof model.RequestSort}_R" "R" model.RequestSort | ||||
|             label [ _for $"{nameof model.RequestSort}_R" ] [ locStr s["Sort by Requestor Name"] ] | ||||
|         ] | ||||
|         fieldset [] [ | ||||
|             legend [] [ strong [] [ icon "mail_outline"; rawText "  "; locStr s["E-mail"] ] ] | ||||
|             div [ _fieldRow ] [ | ||||
|                 div [ _inputField ] [ | ||||
|                     label [ _for "emailFromName" ] [ locStr s["From Name"] ] | ||||
|                     input [ _type  "text" | ||||
|                             _name  (nameof model.EmailFromName) | ||||
|                             _id    "emailFromName" | ||||
|                             _value model.EmailFromName | ||||
|                             _required ] | ||||
|                     label [ _for (nameof model.EmailFromName) ] [ locStr s["From Name"] ] | ||||
|                     inputField "text" (nameof model.EmailFromName) model.EmailFromName [ _required ] | ||||
|                 ] | ||||
|                 div [ _inputField ] [ | ||||
|                     label [ _for "emailFromAddress" ] [ locStr s["From Address"] ] | ||||
|                     input [ _type  "email" | ||||
|                             _name  (nameof model.EmailFromAddress) | ||||
|                             _id    "emailFromAddress" | ||||
|                             _value model.EmailFromAddress | ||||
|                             _required ] | ||||
|                     label [ _for (nameof model.EmailFromAddress) ] [ locStr s["From Address"] ] | ||||
|                     inputField "email" (nameof model.EmailFromAddress) model.EmailFromAddress [ _required ] | ||||
|                 ] | ||||
|             ] | ||||
|             div [ _fieldRow ] [ | ||||
| @ -447,17 +426,18 @@ let preferences (model : EditPreferences) (tzs : TimeZone list) ctx viewInfo = | ||||
|                 div [ _inputField ] [ | ||||
|                     label [ _class "pt-center-text" ] [ locStr s["Color of Heading Lines"] ] | ||||
|                     span [] [ | ||||
|                         radio (nameof model.LineColorType) "lineColorType_Name" "Name" model.LineColorType | ||||
|                         label [ _for "lineColorType_Name" ] [ locStr s["Named Color"] ] | ||||
|                         radio (nameof model.LineColorType) $"{nameof model.LineColorType}_Name" "Name" | ||||
|                               model.LineColorType | ||||
|                         label [ _for $"{nameof model.LineColorType}_Name" ] [ locStr s["Named Color"] ] | ||||
|                         namedColorList (nameof model.LineColor) model.LineColor [ | ||||
|                             _id "lineColor_Select" | ||||
|                             _id $"{nameof model.LineColor}_Select" | ||||
|                             if model.LineColor.StartsWith "#" then _disabled ] s | ||||
|                         rawText "    "; str (s["or"].Value.ToUpper ()) | ||||
|                         radio (nameof model.LineColorType) "lineColorType_RGB" "RGB" model.LineColorType | ||||
|                         label [ _for "lineColorType_RGB" ] [ locStr s["Custom Color"] ] | ||||
|                         radio (nameof model.LineColorType) $"{nameof model.LineColorType}_RGB" "RGB" model.LineColorType | ||||
|                         label [ _for $"{nameof model.LineColorType}_RGB" ] [ locStr s["Custom Color"] ] | ||||
|                         input [ _type  "color"  | ||||
|                                 _name  (nameof model.LineColor) | ||||
|                                 _id    "lineColor_Color" | ||||
|                                 _id    $"{nameof model.LineColor}_Color" | ||||
|                                 _value model.LineColor // TODO: convert to hex or skip if named | ||||
|                                 if not (model.LineColor.StartsWith "#") then _disabled ] | ||||
|                     ] | ||||
| @ -467,17 +447,19 @@ let preferences (model : EditPreferences) (tzs : TimeZone list) ctx viewInfo = | ||||
|                 div [ _inputField ] [ | ||||
|                     label [ _class "pt-center-text" ] [ locStr s["Color of Heading Text"] ] | ||||
|                     span [] [ | ||||
|                         radio (nameof model.HeadingColorType) "headingColorType_Name" "Name" model.HeadingColorType | ||||
|                         label [ _for "headingColorType_Name" ] [ locStr s["Named Color"] ] | ||||
|                         radio (nameof model.HeadingColorType) $"{nameof model.HeadingColorType}_Name" "Name" | ||||
|                               model.HeadingColorType | ||||
|                         label [ _for $"{nameof model.HeadingColorType}_Name" ] [ locStr s["Named Color"] ] | ||||
|                         namedColorList (nameof model.HeadingColor) model.HeadingColor [ | ||||
|                             _id "headingColor_Select" | ||||
|                             _id $"{nameof model.HeadingColor}_Select" | ||||
|                             if model.HeadingColor.StartsWith "#" then _disabled ] s | ||||
|                         rawText "    "; str (s["or"].Value.ToUpper ()) | ||||
|                         radio (nameof model.HeadingColorType) "headingColorType_RGB" "RGB" model.HeadingColorType | ||||
|                         label [ _for "headingColorType_RGB" ] [ locStr s["Custom Color"] ] | ||||
|                         radio (nameof model.HeadingColorType) $"{nameof model.HeadingColorType}_RGB" "RGB" | ||||
|                               model.HeadingColorType | ||||
|                         label [ _for $"{nameof model.HeadingColorType}_RGB" ] [ locStr s["Custom Color"] ] | ||||
|                         input [ _type  "color" | ||||
|                                 _name  (nameof model.HeadingColor) | ||||
|                                 _id    "headingColor_Color" | ||||
|                                 _id    $"{nameof model.HeadingColor}_Color" | ||||
|                                 _value model.HeadingColor // TODO: convert to hex or skip if named | ||||
|                                 if not (model.HeadingColor.StartsWith "#") then _disabled ] | ||||
|                     ] | ||||
| @ -487,27 +469,21 @@ let preferences (model : EditPreferences) (tzs : TimeZone list) ctx viewInfo = | ||||
|         fieldset [] [ | ||||
|             legend [] [ strong [] [ icon "font_download"; rawText "  "; locStr s["Fonts"] ] ] | ||||
|             div [ _inputField ] [ | ||||
|                 label [ _for "fonts" ] [ locStr s["Fonts** for List"] ] | ||||
|                 input [ _type "text"; _name (nameof model.Fonts); _id "fonts"; _required; _value model.Fonts ] | ||||
|                 label [ _for (nameof model.Fonts) ] [ locStr s["Fonts** for List"] ] | ||||
|                 inputField "text" (nameof model.Fonts) model.Fonts [ _required ] | ||||
|             ] | ||||
|             div [ _fieldRow ] [ | ||||
|                 div [ _inputField ] [ | ||||
|                     label [ _for "headingFontSize" ] [ locStr s["Heading Text Size"] ] | ||||
|                     input [ _type  "number" | ||||
|                             _name  (nameof model.HeadingFontSize) | ||||
|                             _id    "headingFontSize" | ||||
|                             _min   "8"; _max "24" | ||||
|                             _value (string model.HeadingFontSize) | ||||
|                             _required ] | ||||
|                     label [ _for (nameof model.HeadingFontSize) ] [ locStr s["Heading Text Size"] ] | ||||
|                     inputField "number" (nameof model.HeadingFontSize) (string model.HeadingFontSize) [ | ||||
|                         _min "8"; _max "24"; _required | ||||
|                     ] | ||||
|                 ] | ||||
|                 div [ _inputField ] [ | ||||
|                     label [ _for "listFontSize" ] [ locStr s["List Text Size"] ] | ||||
|                     input [ _type  "number" | ||||
|                             _name  (nameof model.ListFontSize) | ||||
|                             _id    "listFontSize" | ||||
|                             _min   "8"; _max "24" | ||||
|                             _value (string model.ListFontSize) | ||||
|                             _required ] | ||||
|                     label [ _for (nameof model.ListFontSize) ] [ locStr s["List Text Size"] ] | ||||
|                     inputField "number" (nameof model.ListFontSize) (string model.ListFontSize) [ | ||||
|                         _min "8"; _max "24"; _required | ||||
|                     ] | ||||
|                 ] | ||||
|             ] | ||||
|         ] | ||||
| @ -526,38 +502,31 @@ let preferences (model : EditPreferences) (tzs : TimeZone list) ctx viewInfo = | ||||
|             div [ _inputField ] [ | ||||
|                 label [] [ locStr s["Request List Visibility"] ] | ||||
|                 span [] [ | ||||
|                     radio (nameof model.Visibility) "viz_Public" (string RequestVisibility.``public``) | ||||
|                           (string model.Visibility) | ||||
|                     label [ _for "viz_Public" ] [ locStr s["Public"] ] | ||||
|                     let name  = nameof model.Visibility | ||||
|                     let value = string model.Visibility | ||||
|                     radio name $"{name}_Public" (string RequestVisibility.``public``) value | ||||
|                     label [ _for $"{name}_Public" ] [ locStr s["Public"] ] | ||||
|                     rawText "  " | ||||
|                     radio (nameof model.Visibility) "viz_Private" (string RequestVisibility.``private``) | ||||
|                           (string model.Visibility) | ||||
|                     label [ _for "viz_Private" ] [ locStr s["Private"] ] | ||||
|                     radio name $"{name}_Private" (string RequestVisibility.``private``) value | ||||
|                     label [ _for $"{name}_Private" ] [ locStr s["Private"] ] | ||||
|                     rawText "  " | ||||
|                     radio (nameof model.Visibility) "viz_Password" (string RequestVisibility.passwordProtected) | ||||
|                           (string model.Visibility) | ||||
|                     label [ _for "viz_Password" ] [ locStr s["Password Protected"] ] | ||||
|                     radio name $"{name}_Password" (string RequestVisibility.passwordProtected) value | ||||
|                     label [ _for $"{name}_Password" ] [ locStr s["Password Protected"] ] | ||||
|                 ] | ||||
|             ] | ||||
|             let classSuffix = if model.Visibility = RequestVisibility.passwordProtected then [ "pt-show" ] else [] | ||||
|             div [ _id "divClassPassword"; _fieldRowWith ("pt-fadeable" :: classSuffix) ] [ | ||||
|                 div [ _inputField ] [ | ||||
|                     label [ _for "groupPassword" ] [ locStr s["Group Password (Used to Read Online)"] ] | ||||
|                     input [ _type  "text" | ||||
|                             _name  (nameof model.GroupPassword) | ||||
|                             _id    "groupPassword" | ||||
|                             _value (defaultArg model.GroupPassword "") ] | ||||
|                     label [ _for (nameof model.GroupPassword) ] [ locStr s["Group Password (Used to Read Online)"] ] | ||||
|                     inputField "text" (nameof model.GroupPassword) (defaultArg model.GroupPassword "") [] | ||||
|                 ] | ||||
|             ] | ||||
|             div [ _fieldRow ] [ | ||||
|                 div [ _inputField ] [ | ||||
|                     label [ _for "pageSize" ] [ locStr s["Page Size"] ] | ||||
|                     input [ _type  "number" | ||||
|                             _name  (nameof model.PageSize) | ||||
|                             _id    "pageSize" | ||||
|                             _min   "10"; _max "255" | ||||
|                             _value (string model.PageSize) | ||||
|                             _required ] | ||||
|                     label [ _for (nameof model.PageSize) ] [ locStr s["Page Size"] ] | ||||
|                     inputField "number" (nameof model.PageSize) (string model.PageSize) [ | ||||
|                         _min "10"; _max "255"; _required | ||||
|                     ] | ||||
|                 ] | ||||
|                 div [ _inputField ] [ | ||||
|                     label [ _for (nameof model.AsOfDate) ] [ locStr s["“As of” Date Display"] ] | ||||
|  | ||||
| @ -9,8 +9,8 @@ let assignGroups model groups curGroups ctx viewInfo = | ||||
|     let pageTitle = sprintf "%s • %A" model.UserName s["Assign Groups"] | ||||
|     form [ _action "/user/small-groups/save"; _method "post"; _class "pt-center-columns"; Target.content ] [ | ||||
|         csrfToken ctx | ||||
|         input [ _type "hidden"; _name (nameof model.UserId); _value (flatGuid model.UserId) ] | ||||
|         input [ _type "hidden"; _name (nameof model.UserName); _value model.UserName ] | ||||
|         inputField "hidden" (nameof model.UserId) (flatGuid model.UserId) [] | ||||
|         inputField "hidden" (nameof model.UserName) model.UserName [] | ||||
|         table [ _class "pt-table" ] [ | ||||
|             thead [] [ | ||||
|                 tr [] [ | ||||
| @ -42,10 +42,15 @@ let assignGroups model groups curGroups ctx viewInfo = | ||||
| 
 | ||||
| /// View for the password change page | ||||
| let changePassword ctx viewInfo = | ||||
|     let s = I18N.localizer.Force () | ||||
|     let s    = I18N.localizer.Force () | ||||
|     let model = { OldPassword = ""; NewPassword = ""; NewPasswordConfirm = "" } | ||||
|     let vi = | ||||
|         AppViewInfo.withScopedStyles [ "#oldPassword, #newPassword, #newPasswordConfirm { width: 10rem; }"] viewInfo | ||||
|     let vi    = | ||||
|         viewInfo | ||||
|         |> AppViewInfo.withScopedStyles [ | ||||
|             let fields = | ||||
|                 toHtmlIds [ nameof model.OldPassword; nameof model.NewPassword; nameof model.NewPasswordConfirm ] | ||||
|             $"{fields} {{ width: 10rem; }}" | ||||
|         ] | ||||
|     p [ _class "pt-center-text" ] [ | ||||
|         locStr s["To change your password, enter your current password in the specified box below, then enter your new password twice."] | ||||
|     ] | ||||
| @ -53,35 +58,28 @@ let changePassword ctx viewInfo = | ||||
|     |> List.append [ | ||||
|         form [ _action   "/user/password/change" | ||||
|                _method   "post" | ||||
|                _onsubmit $"""return PT.compareValidation('newPassword','newPasswordConfirm','%A{s["The passwords do not match"]}')""" | ||||
|                _onsubmit $"""return PT.compareValidation('{nameof model.NewPassword}','{nameof model.NewPasswordConfirm}','%A{s["The passwords do not match"]}')""" | ||||
|                Target.content ] [ | ||||
|             csrfToken ctx | ||||
|             div [ _fieldRow ] [ | ||||
|                 div [ _inputField ] [ | ||||
|                     label [ _for "oldPassword" ] [ locStr s["Current Password"] ] | ||||
|                     input [ _type "password" | ||||
|                             _name (nameof model.OldPassword) | ||||
|                             _id   "oldPassword" | ||||
|                             _required | ||||
|                             _autofocus ] | ||||
|                     label [ _for (nameof model.OldPassword) ] [ locStr s["Current Password"] ] | ||||
|                     inputField "password" (nameof model.OldPassword) "" [ _required; _autofocus ] | ||||
|                 ] | ||||
|             ] | ||||
|             div [ _fieldRow ] [ | ||||
|                 div [ _inputField ] [ | ||||
|                     label [ _for "newPassword" ] [ locStr s["New Password Twice"] ] | ||||
|                     input [ _type "password"; _name (nameof model.NewPassword); _id "newPassword"; _required ] | ||||
|                     label [ _for (nameof model.NewPassword) ] [ locStr s["New Password Twice"] ] | ||||
|                     inputField "password" (nameof model.NewPassword) "" [ _required ] | ||||
|                 ] | ||||
|                 div [ _inputField ] [ | ||||
|                     label [] [ rawText " " ] | ||||
|                     input [ _type "password" | ||||
|                             _name (nameof model.NewPasswordConfirm) | ||||
|                             _id   "newPasswordConfirm" | ||||
|                             _required ] | ||||
|                     label [ _for (nameof model.NewPasswordConfirm) ] [ rawText " " ] | ||||
|                     inputField "password" (nameof model.NewPasswordConfirm) "" [ _required ] | ||||
|                 ] | ||||
|             ] | ||||
|             div [ _fieldRow ] [ | ||||
|                 submit [ _onclick "document.getElementById('newPasswordConfirm').setCustomValidity('')" ] "done" | ||||
|                        s["Change Your Password"] | ||||
|                 submit [ _onclick $"document.getElementById('{nameof model.NewPasswordConfirm}').setCustomValidity('')" ] | ||||
|                        "done" s["Change Your Password"] | ||||
|             ] | ||||
|         ] | ||||
|     ] | ||||
| @ -94,59 +92,50 @@ let edit (model : EditUser) ctx viewInfo = | ||||
|     let s             = I18N.localizer.Force () | ||||
|     let pageTitle     = if model.IsNew then "Add a New User" else "Edit User" | ||||
|     let pwPlaceholder = s[if model.IsNew then "" else "No change"].Value | ||||
|     let vi = | ||||
|     let vi            = | ||||
|         viewInfo | ||||
|         |> AppViewInfo.withScopedStyles [ | ||||
|             "#firstName, #lastName, #password, #passwordConfirm { width: 10rem; }" | ||||
|             "#email { width: 20rem; }" | ||||
|             let fields = | ||||
|                 [ nameof model.FirstName; nameof model.LastName; nameof model.Password; nameof model.PasswordConfirm ] | ||||
|                 |> toHtmlIds | ||||
|             $"{fields} {{ width: 10rem; }}" | ||||
|             $"#{nameof model.Email} {{ width: 20rem; }}" | ||||
|         ] | ||||
|         |> AppViewInfo.withOnLoadScript $"PT.user.edit.onPageLoad({(string model.IsNew).ToLowerInvariant ()})" | ||||
|     form [ _action   "/user/edit/save" | ||||
|            _method   "post" | ||||
|            _class    "pt-center-columns" | ||||
|            _onsubmit $"""return PT.compareValidation('password','passwordConfirm','%A{s["The passwords do not match"]}')""" | ||||
|            _onsubmit $"""return PT.compareValidation('{nameof model.Password}','{nameof model.PasswordConfirm}','%A{s["The passwords do not match"]}')""" | ||||
|            Target.content ] [ | ||||
|         csrfToken ctx | ||||
|         input [ _type "hidden"; _name (nameof model.UserId); _value (flatGuid model.UserId) ] | ||||
|         inputField "hidden" (nameof model.UserId) (flatGuid model.UserId) [] | ||||
|         div [ _fieldRow ] [ | ||||
|             div [ _inputField ] [ | ||||
|                 label [ _for "firstName" ] [ locStr s["First Name"] ] | ||||
|                 input [ _type  "text" | ||||
|                         _name  (nameof model.FirstName) | ||||
|                         _id    "firstName" | ||||
|                         _value model.FirstName | ||||
|                         _required | ||||
|                         _autofocus ] | ||||
|                 label [ _for (nameof model.FirstName) ] [ locStr s["First Name"] ] | ||||
|                 inputField "text" (nameof model.FirstName) model.FirstName [ _required; _autofocus ] | ||||
|             ] | ||||
|             div [ _inputField ] [ | ||||
|                 label [ _for "lastName" ] [ locStr s["Last Name"] ] | ||||
|                 input [ _type "text"; _name (nameof model.LastName); _id "lastName"; _value model.LastName; _required ] | ||||
|                 label [ _for (nameof model.LastName) ] [ locStr s["Last Name"] ] | ||||
|                 inputField "text" (nameof model.LastName) model.LastName [ _required ] | ||||
|             ] | ||||
|             div [ _inputField ] [ | ||||
|                 label [ _for "email" ] [ locStr s["E-mail Address"] ] | ||||
|                 input [ _type "email"; _name (nameof model.Email); _id "email"; _value model.Email; _required ] | ||||
|                 label [ _for (nameof model.Email) ] [ locStr s["E-mail Address"] ] | ||||
|                 inputField "email" (nameof model.Email) model.Email [ _required ] | ||||
|             ] | ||||
|         ] | ||||
|         div [ _fieldRow ] [ | ||||
|             div [ _inputField ] [ | ||||
|                 label [ _for "password" ] [ locStr s["Password"] ] | ||||
|                 input [ _type "password"; _name (nameof model.Password); _id "password"; _placeholder pwPlaceholder ] | ||||
|                 label [ _for (nameof model.Password) ] [ locStr s["Password"] ] | ||||
|                 inputField "password" (nameof model.Password) "" [ _placeholder pwPlaceholder ] | ||||
|             ] | ||||
|             div [ _inputField ] [ | ||||
|                 label [ _for "passwordConfirm" ] [ locStr s["Password Again"] ] | ||||
|                 input [ _type        "password" | ||||
|                         _name        (nameof model.PasswordConfirm) | ||||
|                         _id          "passwordConfirm" | ||||
|                         _placeholder pwPlaceholder ] | ||||
|                 inputField "password" (nameof model.PasswordConfirm) "" [ _placeholder pwPlaceholder ] | ||||
|             ] | ||||
|         ] | ||||
|         div [ _checkboxField ] [ | ||||
|             input [ _type  "checkbox" | ||||
|                     _name  (nameof model.IsAdmin) | ||||
|                     _id    "isAdmin" | ||||
|                     _value "True" | ||||
|                     if defaultArg model.IsAdmin false then _checked ] | ||||
|             label [ _for "isAdmin" ] [ locStr s["This user is a PrayerTracker administrator"] ] | ||||
|             inputField "checkbox" (nameof model.IsAdmin) "True" [ if defaultArg model.IsAdmin false then _checked ] | ||||
|             label [ _for (nameof model.IsAdmin) ] [ locStr s["This user is a PrayerTracker administrator"] ] | ||||
|         ] | ||||
|         div [ _fieldRow ] [ submit [] "save" s["Save User"] ] | ||||
|     ] | ||||
| @ -160,24 +149,17 @@ let logOn (model : UserLogOn) groups ctx viewInfo = | ||||
|     let vi = AppViewInfo.withScopedStyles [ "#email { width: 20rem; }" ] viewInfo | ||||
|     form [ _action "/user/log-on"; _method "post"; _class "pt-center-columns"; Target.body ] [ | ||||
|         csrfToken ctx | ||||
|         input [ _type "hidden"; _name (nameof model.RedirectUrl); _value (defaultArg model.RedirectUrl "") ] | ||||
|         inputField "hidden" (nameof model.RedirectUrl) (defaultArg model.RedirectUrl "") [] | ||||
|         div [ _fieldRow ] [ | ||||
|             div [ _inputField ] [ | ||||
|                 label [ _for "email"] [ locStr s["E-mail Address"] ] | ||||
|                 input [ _type  "email" | ||||
|                         _name  (nameof model.Email) | ||||
|                         _id    "email" | ||||
|                         _value model.Email | ||||
|                         _required | ||||
|                         _autofocus ] | ||||
|                 label [ _for (nameof model.Email) ] [ locStr s["E-mail Address"] ] | ||||
|                 inputField "email" (nameof model.Email) model.Email [ _required; _autofocus ] | ||||
|             ] | ||||
|             div [ _inputField ] [ | ||||
|                 label [ _for "password" ] [ locStr s["Password"] ] | ||||
|                 input [ _type        "password" | ||||
|                         _name        (nameof model.Password) | ||||
|                         _id          "password" | ||||
|                         _placeholder $"""({s["Case-Sensitive"].Value.ToLower ()})""" | ||||
|                         _required ] | ||||
|                 label [ _for (nameof model.Password) ] [ locStr s["Password"] ] | ||||
|                 inputField "password" (nameof model.Password) "" [ | ||||
|                     _placeholder $"""({s["Case-Sensitive"].Value.ToLower ()})"""; _required | ||||
|                 ] | ||||
|             ] | ||||
|         ] | ||||
|         div [ _fieldRow ] [ | ||||
| @ -188,7 +170,7 @@ let logOn (model : UserLogOn) groups ctx viewInfo = | ||||
|             ] | ||||
|         ] | ||||
|         div [ _checkboxField ] [ | ||||
|             input [ _type "checkbox"; _name (nameof model.RememberMe); _id "rememberMe"; _value "True" ] | ||||
|             inputField "checkbox" (nameof model.RememberMe) "True" [] | ||||
|             label [ _for "rememberMe" ] [ locStr s["Remember Me"] ] | ||||
|             br [] | ||||
|             small [] [ em [] [ str $"""({s["Requires Cookies"].Value.ToLower ()})""" ] ] | ||||
| @ -210,13 +192,7 @@ let maintain (users : User list) ctx viewInfo = | ||||
|         | [] -> space | ||||
|         | _ -> | ||||
|             table [ _class "pt-table pt-action-table" ] [ | ||||
|                 thead [] [ | ||||
|                     tr [] [ | ||||
|                         th [] [ locStr s["Actions"] ] | ||||
|                         th [] [ locStr s["Name"] ] | ||||
|                         th [] [ locStr s["Admin?"] ] | ||||
|                     ] | ||||
|                 ] | ||||
|                 tableHeadings s [ "Actions"; "Name"; "Admin?" ] | ||||
|                 users | ||||
|                 |> List.map (fun user -> | ||||
|                     let userId    = flatGuid user.userId | ||||
|  | ||||
| @ -73,14 +73,14 @@ module Configure = | ||||
|                 route  "es"       Handlers.Church.maintain | ||||
|                 routef "/%O/edit" Handlers.Church.edit | ||||
|             ] | ||||
|             route  "/class/logon" (redirectTo true "/small-group/log-on") | ||||
|             routef "/error/%s"    Handlers.Home.error | ||||
|             routef "/language/%s" Handlers.Home.language | ||||
|             route    "/class/logon" (redirectTo true "/small-group/log-on") | ||||
|             routef   "/error/%s"    Handlers.Home.error | ||||
|             routef   "/language/%s" Handlers.Home.language | ||||
|             subRoute "/legal" [ | ||||
|                 route "/privacy-policy"   Handlers.Home.privacyPolicy | ||||
|                 route "/terms-of-service" Handlers.Home.tos | ||||
|             ] | ||||
|             route "/log-off" Handlers.Home.logOff | ||||
|             route    "/log-off" Handlers.Home.logOff | ||||
|             subRoute "/prayer-request" [ | ||||
|                 route  "s"           (Handlers.PrayerRequest.maintain true) | ||||
|                 routef "s/email/%s"  Handlers.PrayerRequest.email | ||||
| @ -107,7 +107,7 @@ module Configure = | ||||
|                 route  "/members"        Handlers.SmallGroup.members | ||||
|                 route  "/preferences"    Handlers.SmallGroup.preferences | ||||
|             ] | ||||
|             route "/unauthorized" Handlers.Home.unauthorized | ||||
|             route    "/unauthorized" Handlers.Home.unauthorized | ||||
|             subRoute "/user" [ | ||||
|                 route  "s"                Handlers.User.maintain | ||||
|                 routef "/%O/edit"         Handlers.User.edit | ||||
| @ -116,7 +116,7 @@ module Configure = | ||||
|                 route  "/logon"           (redirectTo true "/user/log-on") | ||||
|                 route  "/password"        Handlers.User.password | ||||
|             ] | ||||
|             route "/" Handlers.Home.homePage | ||||
|             route    "/" Handlers.Home.homePage | ||||
|         ] | ||||
|         POST [ | ||||
|             subRoute "/church" [ | ||||
|  | ||||
| @ -55,6 +55,7 @@ let currentGroup (ctx : HttpContext) = | ||||
| 
 | ||||
| open System | ||||
| open Giraffe | ||||
| open Giraffe.Htmx | ||||
| open PrayerTracker.Cookies | ||||
| open PrayerTracker.ViewModels | ||||
| 
 | ||||
| @ -82,8 +83,8 @@ let viewInfo (ctx : HttpContext) startTicks = | ||||
|                            HttpOnly = true)) | ||||
|     | None -> () | ||||
|     let layout = | ||||
|         match ctx.TryGetRequestHeader "X-Target" with | ||||
|         | Some hdr when hdr = "#pt-body" -> ContentOnly | ||||
|         match ctx.Request.Headers.HxTarget with | ||||
|         | Some hdr when hdr = "pt-body" -> ContentOnly | ||||
|         | Some _ -> PartialPage | ||||
|         | None -> FullPage | ||||
|     { AppViewInfo.fresh with | ||||
|  | ||||
| @ -9,7 +9,7 @@ this.PT = { | ||||
|    * @param {string} url The URL for the help page. | ||||
|    */ | ||||
|   showHelp(url) { | ||||
|     window.open(url, 'helpWindow', 'height=600px,width=450px,toolbar=0,menubar=0,scrollbars=1') | ||||
|     window.open(url, "helpWindow", "height=600px,width=450px,toolbar=0,menubar=0,scrollbars=1") | ||||
|     return false | ||||
|   }, | ||||
| 
 | ||||
| @ -20,8 +20,8 @@ this.PT = { | ||||
|    */ | ||||
|   confirmDelete(action, prompt) { | ||||
|     if (confirm(prompt)) { | ||||
|       let form = document.querySelector('#DeleteForm') | ||||
|       form.setAttribute('action', action) | ||||
|       let form = document.querySelector("#DeleteForm") | ||||
|       form.setAttribute("action", action) | ||||
|       form.submit() | ||||
|     } | ||||
|     return false | ||||
| @ -41,7 +41,7 @@ this.PT = { | ||||
|    * @param {Function} func The function to run once the DOM content is loaded | ||||
|    */ | ||||
|   onLoad(func) { | ||||
|     document.addEventListener('DOMContentLoaded', func) | ||||
|     document.addEventListener("DOMContentLoaded", func) | ||||
|   }, | ||||
| 
 | ||||
|   /** | ||||
| @ -55,7 +55,7 @@ this.PT = { | ||||
|     const field1Value = document.getElementById(field1).value | ||||
|     const field2Element = document.getElementById(field2) | ||||
|     if (field1Value === field2Element.value) { | ||||
|       field2Element.setCustomValidity('') | ||||
|       field2Element.setCustomValidity("") | ||||
|       return true | ||||
|     } | ||||
|     field2Element.setCustomValidity(errorMsg) | ||||
| @ -67,8 +67,8 @@ this.PT = { | ||||
|    * @param {HTMLElement} div The div to be shown | ||||
|    */ | ||||
|   showDiv(div) { | ||||
|     if (div.className.indexOf(' pt-shown') === -1) { | ||||
|       div.className += ' pt-shown' | ||||
|     if (div.className.indexOf(" pt-shown") === -1) { | ||||
|       div.className += " pt-shown" | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
| @ -77,7 +77,7 @@ this.PT = { | ||||
|    * @param {HTMLElement} div The div to be hidden | ||||
|    */ | ||||
|   hideDiv(div) { | ||||
|     div.className = div.className.replace(' pt-shown', '') | ||||
|     div.className = div.className.replace(" pt-shown", "") | ||||
|   }, | ||||
| 
 | ||||
|   /** | ||||
| @ -85,7 +85,7 @@ this.PT = { | ||||
|    */ | ||||
|   initCKEditor() { | ||||
|     ClassicEditor | ||||
|       .create(document.querySelector('#text')) | ||||
|       .create(document.querySelector("#Text")) | ||||
|       .catch(console.error) | ||||
|   }, | ||||
| 
 | ||||
| @ -101,9 +101,9 @@ this.PT = { | ||||
|        * If the interface box is checked, show and require the interface URL field (if not, well... don't) | ||||
|        */ | ||||
|       checkInterface() { | ||||
|         const div  = document.getElementById('divInterfaceAddress') | ||||
|         const addr = document.getElementById('interfaceAddress') | ||||
|         if (document.getElementById('hasInterface').checked) { | ||||
|         const div  = document.getElementById("divInterfaceAddress") | ||||
|         const addr = document.getElementById("InterfaceAddress") | ||||
|         if (document.getElementById("HasInterface").checked) { | ||||
|           PT.showDiv(div) | ||||
|           addr.required = true | ||||
|         } | ||||
| @ -117,8 +117,8 @@ this.PT = { | ||||
|        */ | ||||
|       onPageLoad() { | ||||
|         PT.church.edit.checkInterface() | ||||
|         document.getElementById('hasInterface') | ||||
|           .addEventListener('click', PT.church.edit.checkInterface) | ||||
|         document.getElementById("HasInterface") | ||||
|           .addEventListener("click", PT.church.edit.checkInterface) | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
| @ -154,23 +154,23 @@ this.PT = { | ||||
|        */ | ||||
|       onPageLoad() { | ||||
|         PT.initCKEditor() | ||||
|         const sendNo = document.getElementById('sendN') | ||||
|         const catDiv = document.getElementById('divCategory') | ||||
|         const catSel = document.getElementById('requestType') | ||||
|         const addChk = document.getElementById('addToRequestList') | ||||
|         if (sendNo !== 'undefined') { | ||||
|           const addDiv = document.getElementById('divAddToList') | ||||
|           sendNo.addEventListener('click', () => { | ||||
|         const sendNo = document.getElementById("SendToClass_N") | ||||
|         const catDiv = document.getElementById("divCategory") | ||||
|         const catSel = document.getElementById("RequestType") | ||||
|         const addChk = document.getElementById("AddToRequestList") | ||||
|         if (sendNo !== "undefined") { | ||||
|           const addDiv = document.getElementById("divAddToList") | ||||
|           sendNo.addEventListener("click", () => { | ||||
|             PT.hideDiv(addDiv) | ||||
|             PT.hideDiv(catDiv) | ||||
|             catSel.required = false | ||||
|             addChk.checked = false | ||||
|           }) | ||||
|           document.getElementById('sendY').addEventListener('click', () => { | ||||
|           document.getElementById("SendToClass_Y").addEventListener("click", () => { | ||||
|             PT.showDiv(addDiv) | ||||
|           }) | ||||
|         } | ||||
|         addChk.addEventListener('click', () => { | ||||
|         addChk.addEventListener("click", () => { | ||||
|           if (addChk.checked) { | ||||
|             PT.showDiv(catDiv) | ||||
|             catSel.required = true | ||||
| @ -190,11 +190,11 @@ this.PT = { | ||||
|        * Determine which field should have the focus | ||||
|        */ | ||||
|       onPageLoad() { | ||||
|         const grp = document.getElementById('SmallGroupId') | ||||
|         const grp = document.getElementById("SmallGroupId") | ||||
|         if (grp.options[grp.selectedIndex].value === '') { | ||||
|           grp.focus() | ||||
|         } else { | ||||
|           document.getElementById('Password').focus() | ||||
|           document.getElementById("Password").focus() | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
| @ -225,9 +225,9 @@ this.PT = { | ||||
|        * Show or hide the class password based on the visibility. | ||||
|        */ | ||||
|       checkVisibility() { | ||||
|         const divPw = document.getElementById('divClassPassword') | ||||
|         if (document.getElementById('viz_Public').checked | ||||
|             || document.getElementById('viz_Private').checked) { | ||||
|         const divPw = document.getElementById("divClassPassword") | ||||
|         if (document.getElementById("Visibility_Public").checked | ||||
|             || document.getElementById("Visibility_Private").checked) { | ||||
|           // Disable password
 | ||||
|           PT.hideDiv(divPw) | ||||
|         } else { | ||||
| @ -239,16 +239,16 @@ this.PT = { | ||||
|        * Bind the event handlers | ||||
|        */ | ||||
|       onPageLoad() { | ||||
|         ['Public', 'Private', 'Password'].map(typ => { | ||||
|           document.getElementById(`viz_${typ}`).addEventListener('click', | ||||
|         ["Public", "Private", "Password"].map(typ => { | ||||
|           document.getElementById(`Visibility_${typ}`).addEventListener("click", | ||||
|             PT.smallGroup.preferences.checkVisibility) | ||||
|         }) | ||||
|         PT.smallGroup.preferences.checkVisibility() | ||||
|         ;['lineColor', 'headingColor'].map(name => { | ||||
|           document.getElementById(`${name}Type_Name`).addEventListener('click', () => { | ||||
|         ;["LineColor", "HeadingColor"].map(name => { | ||||
|           document.getElementById(`${name}Type_Name`).addEventListener("click", () => { | ||||
|             PT.smallGroup.preferences.toggleType(name) | ||||
|           }) | ||||
|           document.getElementById(`${name}Type_RGB`).addEventListener('click', () => { | ||||
|           document.getElementById(`${name}Type_RGB`).addEventListener("click", () => { | ||||
|             PT.smallGroup.preferences.toggleType(name) | ||||
|           }) | ||||
|           PT.smallGroup.preferences.toggleType(name) | ||||
| @ -270,9 +270,13 @@ this.PT = { | ||||
|        */ | ||||
|       onPageLoad(isNew) { | ||||
|         if (isNew) { | ||||
|           PT.requireFields(['password', 'passwordConfirm']) | ||||
|           PT.requireFields(["Password", "PasswordConfirm"]) | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
| } | ||||
| } | ||||
| 
 | ||||
| htmx.on("htmx:configRequest", function (e) { | ||||
|   e.detail.headers["X-Target"] = e.detail.target | ||||
| }) | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user