Add style/onload to render context (#36)

This commit is contained in:
Daniel J. Summers 2022-07-31 21:25:44 -04:00
parent 1547377527
commit f66dd76dfb
9 changed files with 683 additions and 611 deletions

View File

@ -5,57 +5,62 @@ open PrayerTracker.Entities
open PrayerTracker.ViewModels
/// View for the church edit page
let edit (m : EditChurch) ctx vi =
let pageTitle = if m.IsNew then "Add a New Church" else "Edit Church"
let edit (model : EditChurch) ctx viewInfo =
let pageTitle = if model.IsNew then "Add a New Church" else "Edit Church"
let s = I18N.localizer.Force ()
[ form [ _action "/church/save"; _method "post"; _class "pt-center-columns"; Target.content ] [
style [ _scoped ] [
rawText "#name { width: 20rem; } #city { width: 10rem; } #st { width: 3rem; } #interfaceAddress { width: 30rem; }"
]
csrfToken ctx
input [ _type "hidden"; _name (nameof m.ChurchId); _value (flatGuid m.ChurchId) ]
div [ _class "pt-field-row" ] [
div [ _class "pt-field" ] [
label [ _for "name" ] [ locStr s["Church Name"] ]
input [ _type "text"; _name (nameof m.Name); _id "name"; _required; _autofocus; _value m.Name ]
]
div [ _class "pt-field" ] [
label [ _for "City"] [ locStr s["City"] ]
input [ _type "text"; _name (nameof m.City); _id "city"; _required; _value m.City ]
]
div [ _class "pt-field" ] [
label [ _for "state" ] [ locStr s["State or Province"] ]
input [ _type "text"
_name (nameof m.State)
_id "state"
_minlength "2"; _maxlength "2"
_value m.State
_required ]
]
]
div [ _class "pt-field-row" ] [
div [ _class "pt-checkbox-field" ] [
input [ _type "checkbox"
_name (nameof m.HasInterface)
_id "hasInterface"
_value "True"
if defaultArg m.HasInterface false then _checked ]
label [ _for "hasInterface" ] [ locStr s["Has an interface with Virtual Prayer Room"] ]
]
]
div [ _class "pt-field-row pt-fadeable"; _id "divInterfaceAddress" ] [
div [ _class "pt-field" ] [
label [ _for "interfaceAddress" ] [ locStr s["VPR Interface URL"] ]
input [ _type "url"
_name (nameof m.InterfaceAddress)
_id "interfaceAddress";
_value (defaultArg m.InterfaceAddress "") ]
]
]
div [ _class "pt-field-row" ] [ submit [] "save" s["Save Church"] ]
let vi =
viewInfo
|> AppViewInfo.withScopedStyles [
"#name { width: 20rem; }"
"#city { width: 10rem; }"
"#st { width: 3rem; }"
"#interfaceAddress { width: 30rem; }"
]
script [] [ rawText "PT.onLoad(PT.church.edit.onPageLoad)" ]
|> AppViewInfo.withOnLoadScript "PT.church.edit.onPageLoad"
form [ _action "/church/save"; _method "post"; _class "pt-center-columns"; Target.content ] [
csrfToken ctx
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 ]
]
div [ _inputField ] [
label [ _for "City"] [ locStr s["City"] ]
input [ _type "text"; _name (nameof model.City); _id "city"; _required; _value model.City ]
]
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 ]
]
]
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"] ]
]
]
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 "") ]
]
]
div [ _fieldRow ] [ submit [] "save" s["Save Church"] ]
]
|> List.singleton
|> Layout.Content.standard
|> Layout.standard vi pageTitle

View File

@ -119,6 +119,25 @@ let _onsubmit = attr "onsubmit"
/// A "rel='noopener'" attribute
let _relNoOpener = _rel "noopener"
/// A class attribute that designates a row of fields, with the additional classes passed
let _fieldRowWith classes =
let extraClasses = if List.isEmpty classes then "" else $""" {classes |> String.concat " "}"""
_class $"pt-field-row{extraClasses}"
/// The class that designates a row of fields
let _fieldRow = _fieldRowWith []
/// A class attribute that designates an input field, with the additional classes passed
let _inputFieldWith classes =
let extraClasses = if List.isEmpty classes then "" else $""" {classes |> String.concat " "}"""
_class $"pt-field{extraClasses}"
/// The class that designates an input field / label pair
let _inputField = _inputFieldWith []
/// The class that designates a checkbox / label pair
let _checkboxField = _class "pt-checkbox-field"
/// The name this function used to have when the view engine was part of Giraffe
let renderHtmlNode = RenderView.AsString.htmlNode

View File

@ -6,7 +6,7 @@ open Giraffe.ViewEngine
open PrayerTracker.ViewModels
/// The error page
let error code vi =
let error code viewInfo =
let s = I18N.localizer.Force ()
let l = I18N.forView "Home/Error"
use sw = new StringWriter ()
@ -37,7 +37,7 @@ let error code vi =
_alt $"""%A{s["PrayerTracker"]} %A{s["from Bit Badger Solutions"]}"""
_title $"""%A{s["PrayerTracker"]} %A{s["from Bit Badger Solutions"]}"""
_style "vertical-align:text-bottom;" ]
str vi.Version
str viewInfo.Version
]
]
|> div []
@ -45,7 +45,7 @@ let error code vi =
/// The home page
let index vi =
let index viewInfo =
let s = I18N.localizer.Force ()
let l = I18N.forView "Home/Index"
use sw = new StringWriter ()
@ -118,11 +118,11 @@ let index vi =
]
]
|> Layout.Content.standard
|> Layout.standard vi "Welcome!"
|> Layout.standard viewInfo "Welcome!"
/// Privacy Policy page
let privacyPolicy vi =
let privacyPolicy viewInfo =
let s = I18N.localizer.Force ()
let l = I18N.forView "Home/PrivacyPolicy"
use sw = new StringWriter ()
@ -190,11 +190,11 @@ let privacyPolicy vi =
]
]
|> Layout.Content.standard
|> Layout.standard vi "Privacy Policy"
|> Layout.standard viewInfo "Privacy Policy"
/// Terms of Service page
let termsOfService vi =
let termsOfService viewInfo =
let s = I18N.localizer.Force ()
let l = I18N.forView "Home/TermsOfService"
use sw = new StringWriter ()
@ -237,11 +237,11 @@ let termsOfService vi =
p [] [ raw l["You may also wish to review our {0} to learn how we handle your data.", ppLink] ]
]
|> Layout.Content.standard
|> Layout.standard vi "Terms of Service"
|> Layout.standard viewInfo "Terms of Service"
/// View for unauthorized page
let unauthorized vi =
let unauthorized viewInfo =
let s = I18N.localizer.Force ()
let l = I18N.forView "Home/Unauthorized"
use sw = new StringWriter ()
@ -256,4 +256,4 @@ let unauthorized vi =
]
]
|> Layout.Content.standard
|> Layout.standard vi "Unauthorized Access"
|> Layout.standard viewInfo "Unauthorized Access"

View File

@ -293,10 +293,16 @@ let private contentSection viewInfo title (content : XmlNode) = [
Navigation.identity viewInfo
renderPageTitle viewInfo title
yield! messages viewInfo
match viewInfo.ScopedStyle with
| [] -> ()
| styles -> style [ _scoped ] (styles |> List.map (fun it -> rawText $"{it};"))
content
htmlFooter viewInfo
for jsFile in viewInfo.Script do
script [ _src $"/js/{jsFile}.js" ] []
match viewInfo.OnLoadScript with
| Some onLoad -> script [] [ rawText $"PT.onLoad({onLoad}" ]
| None -> ()
]
/// The HTML head element for partial responses

View File

@ -11,79 +11,80 @@ open PrayerTracker.Entities
open PrayerTracker.ViewModels
/// View for the prayer request edit page
let edit (m : EditRequest) today ctx vi =
let edit (model : EditRequest) today ctx viewInfo =
let s = I18N.localizer.Force ()
let pageTitle = if m.IsNew then "Add a New Request" else "Edit Request"
[ form [ _action "/prayer-request/save"; _method "post"; _class "pt-center-columns"; Target.content ] [
csrfToken ctx
input [ _type "hidden"; _name (nameof m.RequestId); _value (flatGuid m.RequestId) ]
div [ _class "pt-field-row" ] [
div [ _class "pt-field" ] [
label [ _for (nameof m.RequestType) ] [ locStr s["Request Type"] ]
ReferenceList.requestTypeList s
|> Seq.ofList
|> Seq.map (fun (typ, desc) -> typ.code, desc.Value)
|> selectList (nameof m.RequestType) m.RequestType [ _required; _autofocus ]
let pageTitle = if model.IsNew then "Add a New Request" else "Edit Request"
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) ]
div [ _fieldRow ] [
div [ _inputField ] [
label [ _for (nameof model.RequestType) ] [ locStr s["Request Type"] ]
ReferenceList.requestTypeList s
|> Seq.ofList
|> Seq.map (fun (typ, desc) -> typ.code, desc.Value)
|> 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 "") ]
]
if model.IsNew then
div [ _inputField ] [
label [ _for "enteredDate" ] [ locStr s["Date"] ]
input [ _type "date"; _name (nameof model.EnteredDate); _id "enteredDate"; _placeholder today ]
]
div [ _class "pt-field" ] [
label [ _for "requestor" ] [ locStr s["Requestor / Subject"] ]
input [ _type "text"
_id "requestor"
_name (nameof m.Requestor)
_value (defaultArg m.Requestor "") ]
]
if m.IsNew then
div [ _class "pt-field" ] [
label [ _for "enteredDate" ] [ locStr s["Date"] ]
input [ _type "date"; _name (nameof m.EnteredDate); _id "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"] ]
br []
small [] [ em [] [ str (s["Typo Corrections"].Value.ToLower ()); rawText ", etc." ] ]
]
else
div [ _class "pt-field" ] [
div [ _class "pt-checkbox-field" ] [
br []
input [ _type "checkbox"
_name (nameof m.SkipDateUpdate)
_id "skipDateUpdate"
_value "True" ]
label [ _for "skipDateUpdate" ] [ locStr s["Check to not update the date"] ]
br []
small [] [ em [] [ str (s["Typo Corrections"].Value.ToLower ()); rawText ", etc." ] ]
]
]
]
div [ _class "pt-field-row" ] [
div [ _class "pt-field" ] [
label [] [ locStr s["Expiration"] ]
ReferenceList.expirationList s (not m.IsNew)
|> List.map (fun exp ->
let radioId = $"expiration_{fst exp}"
span [ _class "text-nowrap" ] [
radio (nameof m.Expiration) radioId (fst exp) m.Expiration
label [ _for radioId ] [ locStr (snd exp) ]
rawText "     "
])
|> div [ _class "pt-center-text" ]
]
]
div [ _class "pt-field-row" ] [
div [ _class "pt-field pt-editor" ] [
label [ _for "text" ] [ locStr s["Request"] ]
textarea [ _name (nameof m.Text); _id "text" ] [ str m.Text ]
]
]
div [ _class "pt-field-row" ] [ submit [] "save" s["Save Request"] ]
]
script [] [ rawText "PT.onLoad(PT.initCKEditor)" ]
div [ _fieldRow ] [
div [ _inputField ] [
label [] [ locStr s["Expiration"] ]
ReferenceList.expirationList s (not model.IsNew)
|> List.map (fun exp ->
let radioId = $"expiration_{fst exp}"
span [ _class "text-nowrap" ] [
radio (nameof model.Expiration) radioId (fst exp) model.Expiration
label [ _for radioId ] [ locStr (snd exp) ]
rawText "     "
])
|> div [ _class "pt-center-text" ]
]
]
div [ _fieldRow ] [
div [ _inputFieldWith [ "pt-editor" ] ] [
label [ _for "text" ] [ locStr s["Request"] ]
textarea [ _name (nameof model.Text); _id "text" ] [ str model.Text ]
]
]
div [ _fieldRow ] [ submit [] "save" s["Save Request"] ]
]
|> List.singleton
|> Layout.Content.standard
|> Layout.standard vi pageTitle
/// View for the request e-mail results page
let email m vi =
let email model viewInfo =
let s = I18N.localizer.Force ()
let pageTitle = $"""{s["Prayer Requests"].Value} {m.SmallGroup.name}"""
let prefs = m.SmallGroup.preferences
let addresses = m.Recipients |> List.map (fun mbr -> $"{mbr.memberName} <{mbr.email}>") |> String.concat ", "
let pageTitle = $"""{s["Prayer Requests"].Value} {model.SmallGroup.name}"""
let prefs = model.SmallGroup.preferences
let addresses = model.Recipients |> List.map (fun mbr -> $"{mbr.memberName} <{mbr.email}>") |> String.concat ", "
[ p [ _style $"font-family:{prefs.listFonts};font-size:%i{prefs.textFontSize}pt;" ] [
locStr s["The request list was sent to the following people, via individual e-mails"]
rawText ":"
@ -91,27 +92,27 @@ let email m vi =
small [] [ str addresses ]
]
span [ _class "pt-email-heading" ] [ locStr s["HTML Format"]; rawText ":" ]
div [ _class "pt-email-canvas" ] [ rawText (m.AsHtml s) ]
div [ _class "pt-email-canvas" ] [ rawText (model.AsHtml s) ]
br []
br []
span [ _class "pt-email-heading" ] [ locStr s["Plain-Text Format"]; rawText ":" ]
div [ _class "pt-email-canvas" ] [ pre [] [ str (m.AsText s) ] ]
div [ _class "pt-email-canvas" ] [ pre [] [ str (model.AsText s) ] ]
]
|> Layout.Content.standard
|> Layout.standard vi pageTitle
|> Layout.standard viewInfo pageTitle
/// View for a small group's public prayer request list
let list (m : RequestList) vi =
let list (model : RequestList) viewInfo =
[ br []
I18N.localizer.Force () |> (m.AsHtml >> rawText)
I18N.localizer.Force () |> (model.AsHtml >> rawText)
]
|> Layout.Content.standard
|> Layout.standard vi "View Request List"
|> Layout.standard viewInfo "View Request List"
/// View for the prayer request lists page
let lists (groups : SmallGroup list) vi =
let lists (groups : SmallGroup list) viewInfo =
let s = I18N.localizer.Force ()
let l = I18N.forView "Requests/Lists"
use sw = new StringWriter ()
@ -154,17 +155,17 @@ let lists (groups : SmallGroup list) vi =
]
]
|> Layout.Content.standard
|> Layout.standard vi "Request Lists"
|> Layout.standard viewInfo "Request Lists"
/// View for the prayer request maintenance page
let maintain (m : MaintainRequests) (ctx : HttpContext) vi =
let maintain (model : MaintainRequests) (ctx : HttpContext) viewInfo =
let s = I18N.localizer.Force ()
let l = I18N.forView "Requests/Maintain"
use sw = new StringWriter ()
let raw = rawLocText sw
let now = m.SmallGroup.localDateNow (ctx.GetService<IClock> ())
let prefs = m.SmallGroup.preferences
let now = model.SmallGroup.localDateNow (ctx.GetService<IClock> ())
let prefs = model.SmallGroup.preferences
let types = ReferenceList.requestTypeList s |> Map.ofList
let updReq (req : PrayerRequest) =
if req.updateRequired now prefs.daysToExpire prefs.longTermUpdateWeeks then "pt-request-update" else ""
@ -173,7 +174,7 @@ let maintain (m : MaintainRequests) (ctx : HttpContext) vi =
_class (if req.isExpired now prefs.daysToExpire then "pt-request-expired" else "")
/// Iterate the sequence once, before we render, so we can get the count of it at the top of the table
let requests =
m.Requests
model.Requests
|> List.map (fun req ->
let reqId = flatGuid req.prayerRequestId
let reqText = htmlToPlainText req.text
@ -228,7 +229,7 @@ let maintain (m : MaintainRequests) (ctx : HttpContext) vi =
a [ _href "/prayer-requests/view"; _title s["View Prayer Request List"].Value ] [
icon "list"; rawText " &nbsp;"; locStr s["View Prayer Request List"]
]
match m.SearchTerm with
match model.SearchTerm with
| Some _ ->
rawText " &nbsp; &nbsp; &nbsp; "
a [ _href "/prayer-requests"; _title l["Clear Search Criteria"].Value ] [
@ -240,7 +241,7 @@ let maintain (m : MaintainRequests) (ctx : HttpContext) vi =
input [ _type "text"
_name "search"
_placeholder l["Search requests..."].Value
_value (defaultArg m.SearchTerm "")
_value (defaultArg model.SearchTerm "")
]
space
submit [] "search" s["Search"]
@ -264,22 +265,22 @@ let maintain (m : MaintainRequests) (ctx : HttpContext) vi =
]
div [ _class "pt-center-text" ] [
br []
match m.OnlyActive with
match model.OnlyActive with
| Some true ->
raw l["Inactive requests are currently not shown"]
br []
a [ _href "/prayer-requests/inactive" ] [ raw l["Show Inactive Requests"] ]
| _ ->
if defaultArg m.OnlyActive false then
if defaultArg model.OnlyActive false then
raw l["Inactive requests are currently shown"]
br []
a [ _href "/prayer-requests" ] [ raw l["Do Not Show Inactive Requests"] ]
br []
br []
let search = [ match m.SearchTerm with Some s -> "search", s | None -> () ]
let pg = defaultArg m.PageNbr 1
let search = [ match model.SearchTerm with Some s -> "search", s | None -> () ]
let pg = defaultArg model.PageNbr 1
let url =
match m.OnlyActive with Some true | None -> "" | _ -> "/inactive"
match model.OnlyActive with Some true | None -> "" | _ -> "/inactive"
|> sprintf "/prayer-requests%s"
match pg with
| 1 -> ()
@ -288,7 +289,7 @@ let maintain (m : MaintainRequests) (ctx : HttpContext) vi =
let withPage = match pg with 2 -> search | _ -> ("page", string (pg - 1)) :: search
a [ _href (makeUrl url withPage) ] [ icon "keyboard_arrow_left"; space; raw l["Previous Page"] ]
rawText " &nbsp; &nbsp; "
match requests.Length = m.SmallGroup.preferences.pageSize with
match requests.Length = model.SmallGroup.preferences.pageSize with
| true ->
a [ _href (makeUrl url (("page", string (pg + 1)) :: search)) ] [
raw l["Next Page"]; space; icon "keyboard_arrow_right"
@ -298,19 +299,19 @@ let maintain (m : MaintainRequests) (ctx : HttpContext) vi =
form [ _id "DeleteForm"; _action ""; _method "post" ] [ csrfToken ctx ]
]
|> Layout.Content.wide
|> Layout.standard vi (match m.SearchTerm with Some _ -> "Search Results" | None -> "Maintain Requests")
|> Layout.standard viewInfo (match model.SearchTerm with Some _ -> "Search Results" | None -> "Maintain Requests")
/// View for the printable prayer request list
let print m version =
let print model version =
let s = I18N.localizer.Force ()
let pageTitle = $"""{s["Prayer Requests"].Value} {m.SmallGroup.name}"""
let pageTitle = $"""{s["Prayer Requests"].Value} {model.SmallGroup.name}"""
let imgAlt = $"""{s["PrayerTracker"].Value} {s["from Bit Badger Solutions"].Value}"""
article [] [
rawText (m.AsHtml s)
rawText (model.AsHtml s)
br []
hr []
div [ _style $"font-size:70%%;font-family:{m.SmallGroup.preferences.listFonts};" ] [
div [ _style $"font-size:70%%;font-family:{model.SmallGroup.preferences.listFonts};" ] [
img [ _src $"""/img/{s["footer_en"].Value}.png"""
_style "vertical-align:text-bottom;"
_alt imgAlt
@ -323,45 +324,44 @@ let print m version =
/// View for the prayer request list
let view m vi =
let view model viewInfo =
let s = I18N.localizer.Force ()
let pageTitle = $"""{s["Prayer Requests"].Value} {m.SmallGroup.name}"""
let pageTitle = $"""{s["Prayer Requests"].Value} {model.SmallGroup.name}"""
let spacer = rawText " &nbsp; &nbsp; &nbsp; "
let dtString = m.Date.ToString "yyyy-MM-dd"
[ div [ _class "pt-center-text" ] [
br []
a [ _class "pt-icon-link"
_href $"/prayer-requests/print/{dtString}"
_target "_blank"
_title s["View Printable"].Value ] [
icon "print"; rawText " &nbsp;"; locStr s["View Printable"]
]
if m.CanEmail then
spacer
if m.Date.DayOfWeek <> DayOfWeek.Sunday then
let rec findSunday (date : DateTime) =
if date.DayOfWeek = DayOfWeek.Sunday then date else findSunday (date.AddDays 1.)
let sunday = findSunday m.Date
a [ _class "pt-icon-link"
_href $"""/prayer-requests/view/{sunday.ToString "yyyy-MM-dd"}"""
_title s["List for Next Sunday"].Value ] [
icon "update"; rawText " &nbsp;"; locStr s["List for Next Sunday"]
]
spacer
let emailPrompt = s["This will e-mail the current list to every member of your group, without further prompting. Are you sure this is what you are ready to do?"].Value
a [ _class "pt-icon-link"
_href $"/prayer-requests/email/{dtString}"
_title s["Send via E-mail"].Value
_onclick $"return PT.requests.view.promptBeforeEmail('{emailPrompt}')" ] [
icon "mail_outline"; rawText " &nbsp;"; locStr s["Send via E-mail"]
]
spacer
a [ _class "pt-icon-link"; _href "/prayer-requests"; _title s["Maintain Prayer Requests"].Value ] [
icon "compare_arrows"; rawText " &nbsp;"; locStr s["Maintain Prayer Requests"]
]
]
let dtString = model.Date.ToString "yyyy-MM-dd"
div [ _class "pt-center-text" ] [
br []
rawText (m.AsHtml s)
a [ _class "pt-icon-link"
_href $"/prayer-requests/print/{dtString}"
_target "_blank"
_title s["View Printable"].Value ] [
icon "print"; rawText " &nbsp;"; locStr s["View Printable"]
]
if model.CanEmail then
spacer
if model.Date.DayOfWeek <> DayOfWeek.Sunday then
let rec findSunday (date : DateTime) =
if date.DayOfWeek = DayOfWeek.Sunday then date else findSunday (date.AddDays 1.)
let sunday = findSunday model.Date
a [ _class "pt-icon-link"
_href $"""/prayer-requests/view/{sunday.ToString "yyyy-MM-dd"}"""
_title s["List for Next Sunday"].Value ] [
icon "update"; rawText " &nbsp;"; locStr s["List for Next Sunday"]
]
spacer
let emailPrompt = s["This will e-mail the current list to every member of your group, without further prompting. Are you sure this is what you are ready to do?"].Value
a [ _class "pt-icon-link"
_href $"/prayer-requests/email/{dtString}"
_title s["Send via E-mail"].Value
_onclick $"return PT.requests.view.promptBeforeEmail('{emailPrompt}')" ] [
icon "mail_outline"; rawText " &nbsp;"; locStr s["Send via E-mail"]
]
spacer
a [ _class "pt-icon-link"; _href "/prayer-requests"; _title s["Maintain Prayer Requests"].Value ] [
icon "compare_arrows"; rawText " &nbsp;"; locStr s["Maintain Prayer Requests"]
]
]
|> List.singleton
|> List.append [ br []; rawText (model.AsHtml s) ]
|> Layout.Content.standard
|> Layout.standard vi pageTitle
|> Layout.standard viewInfo pageTitle

View File

@ -6,125 +6,125 @@ open PrayerTracker.Entities
open PrayerTracker.ViewModels
/// View for the announcement page
let announcement isAdmin ctx vi =
let announcement isAdmin ctx viewInfo =
let s = I18N.localizer.Force ()
let m = { SendToClass = ""; Text = ""; AddToRequestList = None; RequestType = None }
let model = { SendToClass = ""; Text = ""; AddToRequestList = None; RequestType = None }
let reqTypes = ReferenceList.requestTypeList s
[ form [ _action "/small-group/announcement/send"; _method "post"; _class "pt-center-columns"; Target.content ] [
csrfToken ctx
div [ _class "pt-field-row" ] [
div [ _class "pt-field pt-editor" ] [
label [ _for "text" ] [ locStr s["Announcement Text"] ]
textarea [ _name (nameof m.Text); _id "text"; _autofocus ] []
]
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 ] []
]
if isAdmin then
div [ _class "pt-field-row" ] [
div [ _class "pt-field" ] [
label [] [ locStr s["Send Announcement to"]; rawText ":" ]
div [ _class "pt-center-text" ] [
radio (nameof m.SendToClass) "sendY" "Y" "Y"
label [ _for "sendY" ] [ locStr s["This Group"]; rawText " &nbsp; &nbsp; " ]
radio (nameof m.SendToClass) "sendN" "N" "Y"
label [ _for "sendN" ] [ locStr s["All {0} Users", s["PrayerTracker"]] ]
]
]
if isAdmin then
div [ _fieldRow ] [
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 " &nbsp; &nbsp; " ]
radio (nameof model.SendToClass) "sendN" "N" "Y"
label [ _for "sendN" ] [ locStr s["All {0} Users", s["PrayerTracker"]] ]
]
]
else input [ _type "hidden"; _name (nameof m.SendToClass); _value "Y" ]
div [ _class "pt-field-row pt-fadeable pt-shown"; _id "divAddToList" ] [
div [ _class "pt-checkbox-field" ] [
input [ _type "checkbox"; _name (nameof m.AddToRequestList); _id "addToRequestList"; _value "True" ]
label [ _for "addToRequestList" ] [ locStr s["Add to Request List"] ]
]
]
div [ _class "pt-field-row pt-fadeable"; _id "divCategory" ] [
div [ _class "pt-field" ] [
label [ _for (nameof m.RequestType) ] [ locStr s["Request Type"] ]
reqTypes
|> Seq.ofList
|> Seq.map (fun (typ, desc) -> typ.code, desc.Value)
|> selectList (nameof m.RequestType) Announcement.code []
]
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"] ]
]
div [ _class "pt-field-row" ] [ submit [] "send" s["Send Announcement"] ]
]
script [] [ rawText "PT.onLoad(PT.smallGroup.announcement.onPageLoad)" ]
div [ _fieldRowWith [ "pt-fadeable" ]; _id "divCategory" ] [
div [ _inputField ] [
label [ _for (nameof model.RequestType) ] [ locStr s["Request Type"] ]
reqTypes
|> Seq.ofList
|> Seq.map (fun (typ, desc) -> typ.code, desc.Value)
|> selectList (nameof model.RequestType) Announcement.code []
]
]
div [ _fieldRow ] [ submit [] "send" s["Send Announcement"] ]
]
|> List.singleton
|> Layout.Content.standard
|> Layout.standard vi "Send Announcement"
/// View for once an announcement has been sent
let announcementSent (m : Announcement) vi =
let announcementSent (model : Announcement) viewInfo =
let s = I18N.localizer.Force ()
[ span [ _class "pt-email-heading" ] [ locStr s["HTML Format"]; rawText ":" ]
div [ _class "pt-email-canvas" ] [ rawText m.Text ]
div [ _class "pt-email-canvas" ] [ rawText model.Text ]
br []
br []
span [ _class "pt-email-heading" ] [ locStr s["Plain-Text Format"]; rawText ":" ]
div [ _class "pt-email-canvas" ] [ pre [] [ str m.PlainText ] ]
div [ _class "pt-email-canvas" ] [ pre [] [ str model.PlainText ] ]
]
|> Layout.Content.standard
|> Layout.standard vi "Announcement Sent"
|> Layout.standard viewInfo "Announcement Sent"
/// View for the small group add/edit page
let edit (m : EditSmallGroup) (churches : Church list) ctx vi =
let edit (model : EditSmallGroup) (churches : Church list) ctx viewInfo =
let s = I18N.localizer.Force ()
let pageTitle = if m.IsNew then "Add a New Group" else "Edit Group"
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 m.SmallGroupId); _value (flatGuid m.SmallGroupId) ]
div [ _class "pt-field-row" ] [
div [ _class "pt-field" ] [
input [ _type "hidden"; _name (nameof model.SmallGroupId); _value (flatGuid model.SmallGroupId) ]
div [ _fieldRow ] [
div [ _inputField ] [
label [ _for "name" ] [ locStr s["Group Name"] ]
input [ _type "text"; _name (nameof m.Name); _id "name"; _value m.Name; _required; _autofocus ]
input [ _type "text"; _name (nameof model.Name); _id "name"; _value model.Name; _required; _autofocus ]
]
]
div [ _class "pt-field-row" ] [
div [ _class "pt-field" ] [
label [ _for (nameof m.ChurchId) ] [ locStr s["Church"] ]
div [ _fieldRow ] [
div [ _inputField ] [
label [ _for (nameof model.ChurchId) ] [ locStr s["Church"] ]
seq {
"", selectDefault s["Select Church"].Value
yield! churches |> List.map (fun c -> flatGuid c.churchId, c.name)
}
|> selectList (nameof m.ChurchId) (flatGuid m.ChurchId) [ _required ]
"", selectDefault s["Select Church"].Value
yield! churches |> List.map (fun c -> flatGuid c.churchId, c.name)
}
|> selectList (nameof model.ChurchId) (flatGuid model.ChurchId) [ _required ]
]
]
div [ _class "pt-field-row" ] [ submit [] "save" s["Save Group"] ]
div [ _fieldRow ] [ submit [] "save" s["Save Group"] ]
]
|> List.singleton
|> Layout.Content.standard
|> Layout.standard vi pageTitle
|> Layout.standard viewInfo pageTitle
/// View for the member edit page
let editMember (m : EditMember) (types : (string * LocalizedString) seq) ctx vi =
let editMember (model : EditMember) (types : (string * LocalizedString) seq) ctx viewInfo =
let s = I18N.localizer.Force ()
let pageTitle = if m.IsNew then "Add a New Group Member" else "Edit Group Member"
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
form [ _action "/small-group/member/save"; _method "post"; _class "pt-center-columns"; Target.content ] [
style [ _scoped ] [ rawText "#name { width: 15rem; } #email { width: 20rem; }" ]
csrfToken ctx
input [ _type "hidden"; _name (nameof m.MemberId); _value (flatGuid m.MemberId) ]
div [ _class "pt-field-row" ] [
div [ _class "pt-field" ] [
input [ _type "hidden"; _name (nameof model.MemberId); _value (flatGuid model.MemberId) ]
div [ _fieldRow ] [
div [ _inputField ] [
label [ _for "name" ] [ locStr s["Member Name"] ]
input [ _type "text"; _name (nameof m.Name); _id "name"; _required; _autofocus; _value m.Name ]
input [ _type "text"; _name (nameof model.Name); _id "name"; _required; _autofocus; _value model.Name ]
]
div [ _class "pt-field" ] [
div [ _inputField ] [
label [ _for "email" ] [ locStr s["E-mail Address"] ]
input [ _type "email"; _name (nameof m.Email); _id "email"; _required; _value m.Email ]
input [ _type "email"; _name (nameof model.Email); _id "email"; _required; _value model.Email ]
]
]
div [ _class "pt-field-row" ] [
div [ _class "pt-field" ] [
label [ _for (nameof m.Format) ] [ locStr s["E-mail Format"] ]
div [ _fieldRow ] [
div [ _inputField ] [
label [ _for (nameof model.Format) ] [ locStr s["E-mail Format"] ]
types
|> Seq.map (fun typ -> fst typ, (snd typ).Value)
|> selectList (nameof m.Format) m.Format []
|> selectList (nameof model.Format) model.Format []
]
]
div [ _class "pt-field-row" ] [ submit [] "save" s["Save"] ]
div [ _fieldRow ] [ submit [] "save" s["Save"] ]
]
|> List.singleton
|> Layout.Content.standard
@ -132,49 +132,49 @@ let editMember (m : EditMember) (types : (string * LocalizedString) seq) ctx vi
/// View for the small group log on page
let logOn (groups : SmallGroup list) grpId ctx vi =
let s = I18N.localizer.Force ()
let m = { SmallGroupId = System.Guid.Empty; Password = ""; RememberMe = None }
[ form [ _action "/small-group/log-on/submit"; _method "post"; _class "pt-center-columns"; Target.body ] [
csrfToken ctx
div [ _class "pt-field-row" ] [
div [ _class "pt-field" ] [
label [ _for (nameof m.SmallGroupId) ] [ locStr s["Group"] ]
seq {
if groups.Length = 0 then "", s["There are no classes with passwords defined"].Value
else
"", selectDefault s["Select Group"].Value
yield!
groups
|> List.map (fun grp -> flatGuid grp.smallGroupId, $"{grp.church.name} | {grp.name}")
}
|> selectList (nameof m.SmallGroupId) grpId [ _required ]
]
div [ _class "pt-field" ] [
label [ _for "password" ] [ locStr s["Password"] ]
input [ _type "password"
_name (nameof m.Password)
_id "password"
_placeholder (s["Case-Sensitive"].Value.ToLower ())
_required ]
]
let logOn (groups : SmallGroup list) grpId ctx viewInfo =
let s = I18N.localizer.Force ()
let model = { SmallGroupId = System.Guid.Empty; Password = ""; RememberMe = None }
let vi = AppViewInfo.withOnLoadScript "PT.smallGroup.logOn.onPageLoad" viewInfo
form [ _action "/small-group/log-on/submit"; _method "post"; _class "pt-center-columns"; Target.body ] [
csrfToken ctx
div [ _fieldRow ] [
div [ _inputField ] [
label [ _for (nameof model.SmallGroupId) ] [ locStr s["Group"] ]
seq {
if groups.Length = 0 then "", s["There are no classes with passwords defined"].Value
else
"", selectDefault s["Select Group"].Value
yield!
groups
|> List.map (fun grp -> flatGuid grp.smallGroupId, $"{grp.church.name} | {grp.name}")
}
|> selectList (nameof model.SmallGroupId) grpId [ _required ]
]
div [ _class "pt-checkbox-field" ] [
input [ _type "checkbox"; _name (nameof m.RememberMe); _id "rememberMe"; _value "True" ]
label [ _for "rememberMe" ] [ locStr s["Remember Me"] ]
br []
small [] [ em [] [ str (s["Requires Cookies"].Value.ToLower ()) ] ]
div [ _inputField ] [
label [ _for "password" ] [ locStr s["Password"] ]
input [ _type "password"
_name (nameof model.Password)
_id "password"
_placeholder (s["Case-Sensitive"].Value.ToLower ())
_required ]
]
div [ _class "pt-field-row" ] [ submit [] "account_circle" s["Log On"] ]
]
script [] [ rawText "PT.onLoad(PT.smallGroup.logOn.onPageLoad)" ]
div [ _checkboxField ] [
input [ _type "checkbox"; _name (nameof model.RememberMe); _id "rememberMe"; _value "True" ]
label [ _for "rememberMe" ] [ locStr s["Remember Me"] ]
br []
small [] [ em [] [ str (s["Requires Cookies"].Value.ToLower ()) ] ]
]
div [ _fieldRow ] [ submit [] "account_circle" s["Log On"] ]
]
|> List.singleton
|> Layout.Content.standard
|> Layout.standard vi "Group Log On"
/// View for the small group maintenance page
let maintain (groups : SmallGroup list) ctx vi =
let maintain (groups : SmallGroup list) ctx viewInfo =
let s = I18N.localizer.Force ()
let grpTbl =
match groups with
@ -223,11 +223,11 @@ let maintain (groups : SmallGroup list) ctx vi =
form [ _id "DeleteForm"; _action ""; _method "post" ] [ csrfToken ctx ]
]
|> Layout.Content.standard
|> Layout.standard vi "Maintain Groups"
|> Layout.standard viewInfo "Maintain Groups"
/// View for the member maintenance page
let members (members : Member list) (emailTyps : Map<string, LocalizedString>) ctx vi =
let members (members : Member list) (emailTypes : Map<string, LocalizedString>) ctx viewInfo =
let s = I18N.localizer.Force ()
let mbrTbl =
match members with
@ -262,7 +262,7 @@ let members (members : Member list) (emailTyps : Map<string, LocalizedString>) c
]
td [] [ str mbr.memberName ]
td [] [ str mbr.email ]
td [] [ locStr emailTyps[defaultArg mbr.format ""] ]
td [] [ locStr emailTypes[defaultArg mbr.format ""] ]
])
|> tbody []
]
@ -279,13 +279,13 @@ let members (members : Member list) (emailTyps : Map<string, LocalizedString>) c
form [ _id "DeleteForm"; _action ""; _method "post" ] [ csrfToken ctx ]
]
|> Layout.Content.standard
|> Layout.standard vi "Maintain Group Members"
|> Layout.standard viewInfo "Maintain Group Members"
open Giraffe.ViewEngine.Accessibility
/// View for the small group overview page
let overview m vi =
let overview model viewInfo =
let s = I18N.localizer.Force ()
let linkSpacer = rawText "&nbsp; "
let types = ReferenceList.requestTypeList s |> dict
@ -304,16 +304,16 @@ let overview m vi =
header [ _roleHeading ] [ iconSized 72 "question_answer"; locStr s["Prayer Requests"] ]
div [] [
p [ _class "pt-center-text" ] [
strong [] [ str (m.TotalActiveReqs.ToString "N0"); space; locStr s["Active Requests"] ]
strong [] [ str (model.TotalActiveReqs.ToString "N0"); space; locStr s["Active Requests"] ]
]
hr []
for cat in m.ActiveReqsByType do
for cat in model.ActiveReqsByType do
str (cat.Value.ToString "N0")
space
locStr types[cat.Key]
br []
br []
str (m.AllReqs.ToString "N0")
str (model.AllReqs.ToString "N0")
space
locStr s["Total Requests"]
hr []
@ -325,7 +325,7 @@ let overview m vi =
section [ _ariaLabel "Small group members" ] [
header [ _roleHeading ] [ iconSized 72 "people_outline"; locStr s["Group Members"] ]
div [ _class "pt-center-text" ] [
strong [] [ str (m.TotalMembers.ToString "N0"); space; locStr s["Members"] ]
strong [] [ str (model.TotalMembers.ToString "N0"); space; locStr s["Members"] ]
hr []
a [ _href "/small-group/members" ] [ icon "email"; linkSpacer; locStr s["Maintain Group Members"] ]
]
@ -333,236 +333,244 @@ let overview m vi =
]
|> List.singleton
|> Layout.Content.wide
|> Layout.standard vi "Small Group Overview"
|> Layout.standard viewInfo "Small Group Overview"
open System.IO
open PrayerTracker
/// View for the small group preferences page
let preferences (m : EditPreferences) (tzs : TimeZone list) ctx vi =
let preferences (model : EditPreferences) (tzs : TimeZone list) ctx viewInfo =
let s = I18N.localizer.Force ()
let l = I18N.forView "SmallGroup/Preferences"
use sw = new StringWriter ()
let raw = rawLocText sw
[ style [ _scoped ] [
rawText "#expireDays, #daysToKeepNew, #longTermUpdateWeeks, #headingFontSize, #listFontSize, #pageSize { width: 3rem; } #emailFromAddress { width: 20rem; } #fonts { width: 40rem; } @media screen and (max-width: 40rem) { #fonts { width: 100%; } }"
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%; } }"
]
form [ _action "/small-group/preferences/save"; _method "post"; _class "pt-center-columns"; Target.content ] [
csrfToken ctx
fieldset [] [
legend [] [ strong [] [ icon "date_range"; rawText " &nbsp;"; locStr s["Dates"] ] ]
div [ _class "pt-field-row" ] [
div [ _class "pt-field" ] [
label [ _for "expireDays" ] [ locStr s["Requests Expire After"] ]
span [] [
input [ _type "number"
_name (nameof m.ExpireDays)
_id "expireDays"
_value (string m.ExpireDays)
_min "1"; _max "30"
_required
_autofocus ]
space
str (s["Days"].Value.ToLower ())
]
]
div [ _class "pt-field" ] [
label [ _for "daysToKeepNew" ] [ locStr s["Requests “New” For"] ]
span [] [
input [ _type "number"
_name (nameof m.DaysToKeepNew)
_id "daysToKeepNew"
_min "1"; _max "30"
_value (string m.DaysToKeepNew)
_required ]
space; str (s["Days"].Value.ToLower ())
]
]
div [ _class "pt-field" ] [
label [ _for "longTermUpdateWeeks" ] [ locStr s["Long-Term Requests Alerted for Update"] ]
span [] [
input [ _type "number"
_name (nameof m.LongTermUpdateWeeks)
_id "longTermUpdateWeeks"
_min "1"; _max "30"
_value (string m.LongTermUpdateWeeks)
_required ]
space; str (s["Weeks"].Value.ToLower ())
]
]
]
]
fieldset [] [
legend [] [ strong [] [ icon "sort"; rawText " &nbsp;"; locStr s["Request Sorting"] ] ]
radio (nameof m.RequestSort) "requestSort_D" "D" m.RequestSort
label [ _for "requestSort_D" ] [ locStr s["Sort by Last Updated Date"] ]
rawText " &nbsp; "
radio (nameof m.RequestSort) "requestSort_R" "R" m.RequestSort
label [ _for "requestSort_R" ] [ locStr s["Sort by Requestor Name"] ]
]
fieldset [] [
legend [] [ strong [] [ icon "mail_outline"; rawText " &nbsp;"; locStr s["E-mail"] ] ]
div [ _class "pt-field-row" ] [
div [ _class "pt-field" ] [
label [ _for "emailFromName" ] [ locStr s["From Name"] ]
input [ _type "text"
_name (nameof m.EmailFromName)
_id "emailFromName"
_value m.EmailFromName
_required ]
]
div [ _class "pt-field" ] [
label [ _for "emailFromAddress" ] [ locStr s["From Address"] ]
input [ _type "email"
_name (nameof m.EmailFromAddress)
_id "emailFromAddress"
_value m.EmailFromAddress
_required ]
]
]
div [ _class "pt-field-row" ] [
div [ _class "pt-field" ] [
label [ _for (nameof m.DefaultEmailType) ] [ locStr s["E-mail Format"] ]
seq {
"", selectDefault s["Select"].Value
yield!
ReferenceList.emailTypeList HtmlFormat s
|> Seq.skip 1
|> Seq.map (fun typ -> fst typ, (snd typ).Value)
}
|> selectList (nameof m.DefaultEmailType) m.DefaultEmailType [ _required ]
]
]
]
fieldset [] [
legend [] [ strong [] [ icon "color_lens"; rawText " &nbsp;"; locStr s["Colors"] ]; rawText " ***" ]
div [ _class "pt-field-row" ] [
div [ _class "pt-field" ] [
label [ _class "pt-center-text" ] [ locStr s["Color of Heading Lines"] ]
span [] [
radio (nameof m.LineColorType) "lineColorType_Name" "Name" m.LineColorType
label [ _for "lineColorType_Name" ] [ locStr s["Named Color"] ]
namedColorList (nameof m.LineColor) m.LineColor [
_id "lineColor_Select"
if m.LineColor.StartsWith "#" then _disabled ] s
rawText "&nbsp; &nbsp; "; str (s["or"].Value.ToUpper ())
radio (nameof m.LineColorType) "lineColorType_RGB" "RGB" m.LineColorType
label [ _for "lineColorType_RGB" ] [ locStr s["Custom Color"] ]
input [ _type "color"
_name (nameof m.LineColor)
_id "lineColor_Color"
_value m.LineColor // TODO: convert to hex or skip if named
if not (m.LineColor.StartsWith "#") then _disabled ]
]
]
]
div [ _class "pt-field-row" ] [
div [ _class "pt-field" ] [
label [ _class "pt-center-text" ] [ locStr s["Color of Heading Text"] ]
span [] [
radio (nameof m.HeadingColorType) "headingColorType_Name" "Name" m.HeadingColorType
label [ _for "headingColorType_Name" ] [ locStr s["Named Color"] ]
namedColorList (nameof m.HeadingColor) m.HeadingColor [
_id "headingColor_Select"
if m.HeadingColor.StartsWith "#" then _disabled ] s
rawText "&nbsp; &nbsp; "; str (s["or"].Value.ToUpper ())
radio (nameof m.HeadingColorType) "headingColorType_RGB" "RGB" m.HeadingColorType
label [ _for "headingColorType_RGB" ] [ locStr s["Custom Color"] ]
input [ _type "color"
_name (nameof m.HeadingColor)
_id "headingColor_Color"
_value m.HeadingColor // TODO: convert to hex or skip if named
if not (m.HeadingColor.StartsWith "#") then _disabled ]
]
]
]
]
fieldset [] [
legend [] [ strong [] [ icon "font_download"; rawText " &nbsp;"; locStr s["Fonts"] ] ]
div [ _class "pt-field" ] [
label [ _for "fonts" ] [ locStr s["Fonts** for List"] ]
input [ _type "text"; _name (nameof m.Fonts); _id "fonts"; _required; _value m.Fonts ]
]
div [ _class "pt-field-row" ] [
div [ _class "pt-field" ] [
label [ _for "headingFontSize" ] [ locStr s["Heading Text Size"] ]
input [ _type "number"
_name (nameof m.HeadingFontSize)
_id "headingFontSize"
_min "8"; _max "24"
_value (string m.HeadingFontSize)
_required ]
]
div [ _class "pt-field" ] [
label [ _for "listFontSize" ] [ locStr s["List Text Size"] ]
input [ _type "number"
_name (nameof m.ListFontSize)
_id "listFontSize"
_min "8"; _max "24"
_value (string m.ListFontSize)
_required ]
]
]
]
fieldset [] [
legend [] [ strong [] [ icon "settings"; rawText " &nbsp;"; locStr s["Other Settings"] ] ]
div [ _class "pt-field-row" ] [
div [ _class "pt-field" ] [
label [ _for (nameof m.TimeZone) ] [ locStr s["Time Zone"] ]
seq {
"", selectDefault s["Select"].Value
yield! tzs |> List.map (fun tz -> tz.timeZoneId, (TimeZones.name tz.timeZoneId s).Value)
}
|> selectList (nameof m.TimeZone) m.TimeZone [ _required ]
]
]
div [ _class "pt-field" ] [
label [] [ locStr s["Request List Visibility"] ]
|> AppViewInfo.withOnLoadScript "PT.smallGroup.preferences.onPageLoad"
form [ _action "/small-group/preferences/save"; _method "post"; _class "pt-center-columns"; Target.content ] [
csrfToken ctx
fieldset [] [
legend [] [ strong [] [ icon "date_range"; rawText " &nbsp;"; locStr s["Dates"] ] ]
div [ _fieldRow ] [
div [ _inputField ] [
label [ _for "expireDays" ] [ locStr s["Requests Expire After"] ]
span [] [
radio (nameof m.Visibility) "viz_Public" (string RequestVisibility.``public``)
(string m.Visibility)
label [ _for "viz_Public" ] [ locStr s["Public"] ]
rawText " &nbsp;"
radio (nameof m.Visibility) "viz_Private" (string RequestVisibility.``private``)
(string m.Visibility)
label [ _for "viz_Private" ] [ locStr s["Private"] ]
rawText " &nbsp;"
radio (nameof m.Visibility) "viz_Password" (string RequestVisibility.passwordProtected)
(string m.Visibility)
label [ _for "viz_Password" ] [ locStr s["Password Protected"] ]
]
]
let classSuffix = if m.Visibility = RequestVisibility.passwordProtected then " pt-show" else ""
div [ _id "divClassPassword"; _class $"pt-field-row pt-fadeable{classSuffix}" ] [
div [ _class "pt-field" ] [
label [ _for "groupPassword" ] [ locStr s["Group Password (Used to Read Online)"] ]
input [ _type "text"
_name (nameof m.GroupPassword)
_id "groupPassword"
_value (defaultArg m.GroupPassword "") ]
]
]
div [ _class "pt-field-row" ] [
div [ _class "pt-field" ] [
label [ _for "pageSize" ] [ locStr s["Page Size"] ]
input [ _type "number"
_name (nameof m.PageSize)
_id "pageSize"
_min "10"; _max "255"
_value (string m.PageSize)
_required ]
_name (nameof model.ExpireDays)
_id "expireDays"
_value (string model.ExpireDays)
_min "1"; _max "30"
_required
_autofocus ]
space
str (s["Days"].Value.ToLower ())
]
div [ _class "pt-field" ] [
label [ _for (nameof m.AsOfDate) ] [ locStr s["“As of” Date Display"] ]
ReferenceList.asOfDateList s
|> List.map (fun (code, desc) -> code, desc.Value)
|> selectList (nameof m.AsOfDate) m.AsOfDate [ _required ]
]
div [ _inputField ] [
label [ _for "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 ]
space; str (s["Days"].Value.ToLower ())
]
]
div [ _inputField ] [
label [ _for "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 ]
space; str (s["Weeks"].Value.ToLower ())
]
]
]
div [ _class "pt-field-row" ] [ submit [] "save" s["Save Preferences"] ]
]
fieldset [] [
legend [] [ strong [] [ icon "sort"; rawText " &nbsp;"; locStr s["Request Sorting"] ] ]
radio (nameof model.RequestSort) "requestSort_D" "D" model.RequestSort
label [ _for "requestSort_D" ] [ locStr s["Sort by Last Updated Date"] ]
rawText " &nbsp; "
radio (nameof model.RequestSort) "requestSort_R" "R" model.RequestSort
label [ _for "requestSort_R" ] [ locStr s["Sort by Requestor Name"] ]
]
fieldset [] [
legend [] [ strong [] [ icon "mail_outline"; rawText " &nbsp;"; 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 ]
]
div [ _inputField ] [
label [ _for "emailFromAddress" ] [ locStr s["From Address"] ]
input [ _type "email"
_name (nameof model.EmailFromAddress)
_id "emailFromAddress"
_value model.EmailFromAddress
_required ]
]
]
div [ _fieldRow ] [
div [ _inputField ] [
label [ _for (nameof model.DefaultEmailType) ] [ locStr s["E-mail Format"] ]
seq {
"", selectDefault s["Select"].Value
yield!
ReferenceList.emailTypeList HtmlFormat s
|> Seq.skip 1
|> Seq.map (fun typ -> fst typ, (snd typ).Value)
}
|> selectList (nameof model.DefaultEmailType) model.DefaultEmailType [ _required ]
]
]
]
fieldset [] [
legend [] [ strong [] [ icon "color_lens"; rawText " &nbsp;"; locStr s["Colors"] ]; rawText " ***" ]
div [ _fieldRow ] [
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"] ]
namedColorList (nameof model.LineColor) model.LineColor [
_id "lineColor_Select"
if model.LineColor.StartsWith "#" then _disabled ] s
rawText "&nbsp; &nbsp; "; str (s["or"].Value.ToUpper ())
radio (nameof model.LineColorType) "lineColorType_RGB" "RGB" model.LineColorType
label [ _for "lineColorType_RGB" ] [ locStr s["Custom Color"] ]
input [ _type "color"
_name (nameof model.LineColor)
_id "lineColor_Color"
_value model.LineColor // TODO: convert to hex or skip if named
if not (model.LineColor.StartsWith "#") then _disabled ]
]
]
]
div [ _fieldRow ] [
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"] ]
namedColorList (nameof model.HeadingColor) model.HeadingColor [
_id "headingColor_Select"
if model.HeadingColor.StartsWith "#" then _disabled ] s
rawText "&nbsp; &nbsp; "; str (s["or"].Value.ToUpper ())
radio (nameof model.HeadingColorType) "headingColorType_RGB" "RGB" model.HeadingColorType
label [ _for "headingColorType_RGB" ] [ locStr s["Custom Color"] ]
input [ _type "color"
_name (nameof model.HeadingColor)
_id "headingColor_Color"
_value model.HeadingColor // TODO: convert to hex or skip if named
if not (model.HeadingColor.StartsWith "#") then _disabled ]
]
]
]
]
fieldset [] [
legend [] [ strong [] [ icon "font_download"; rawText " &nbsp;"; 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 ]
]
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 ]
]
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 ]
]
]
]
fieldset [] [
legend [] [ strong [] [ icon "settings"; rawText " &nbsp;"; locStr s["Other Settings"] ] ]
div [ _fieldRow ] [
div [ _inputField ] [
label [ _for (nameof model.TimeZone) ] [ locStr s["Time Zone"] ]
seq {
"", selectDefault s["Select"].Value
yield! tzs |> List.map (fun tz -> tz.timeZoneId, (TimeZones.name tz.timeZoneId s).Value)
}
|> selectList (nameof model.TimeZone) model.TimeZone [ _required ]
]
]
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"] ]
rawText " &nbsp;"
radio (nameof model.Visibility) "viz_Private" (string RequestVisibility.``private``)
(string model.Visibility)
label [ _for "viz_Private" ] [ locStr s["Private"] ]
rawText " &nbsp;"
radio (nameof model.Visibility) "viz_Password" (string RequestVisibility.passwordProtected)
(string model.Visibility)
label [ _for "viz_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 "") ]
]
]
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 ]
]
div [ _inputField ] [
label [ _for (nameof model.AsOfDate) ] [ locStr s["“As of” Date Display"] ]
ReferenceList.asOfDateList s
|> List.map (fun (code, desc) -> code, desc.Value)
|> selectList (nameof model.AsOfDate) model.AsOfDate [ _required ]
]
]
]
div [ _fieldRow ] [ submit [] "save" s["Save Preferences"] ]
]
|> List.singleton
|> List.append [
p [] [
rawText "** "
raw l["List font names, separated by commas."]
@ -575,7 +583,6 @@ let preferences (m : EditPreferences) (tzs : TimeZone list) ctx vi =
rawText "*** "
raw l["If you want a custom color, you may be able to get some ideas (and a list of RGB values for those colors) from the W3 School's <a href=\"http://www.w3schools.com/html/html_colornames.asp\" title=\"HTML Color List - W3 School\">HTML color name list</a>."]
]
script [] [ rawText "PT.onLoad(PT.smallGroup.preferences.onPageLoad)" ]
]
|> Layout.Content.standard
|> Layout.standard vi "Group Preferences"

View File

@ -4,13 +4,13 @@ open Giraffe.ViewEngine
open PrayerTracker.ViewModels
/// View for the group assignment page
let assignGroups m groups curGroups ctx vi =
let assignGroups model groups curGroups ctx viewInfo =
let s = I18N.localizer.Force ()
let pageTitle = sprintf "%s %A" m.UserName s["Assign Groups"]
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 m.UserId); _value (flatGuid m.UserId) ]
input [ _type "hidden"; _name (nameof m.UserName); _value m.UserName ]
input [ _type "hidden"; _name (nameof model.UserId); _value (flatGuid model.UserId) ]
input [ _type "hidden"; _name (nameof model.UserName); _value model.UserName ]
table [ _class "pt-table" ] [
thead [] [
tr [] [
@ -24,7 +24,7 @@ let assignGroups m groups curGroups ctx vi =
tr [] [
td [] [
input [ _type "checkbox"
_name (nameof m.SmallGroups)
_name (nameof model.SmallGroups)
_id inputId
_value grpId
if List.contains grpId curGroups then _checked ]
@ -33,43 +33,53 @@ let assignGroups m groups curGroups ctx vi =
])
|> tbody []
]
div [ _class "pt-field-row" ] [ submit [] "save" s["Save Group Assignments"] ]
div [ _fieldRow ] [ submit [] "save" s["Save Group Assignments"] ]
]
|> List.singleton
|> Layout.Content.standard
|> Layout.standard vi pageTitle
|> Layout.standard viewInfo pageTitle
/// View for the password change page
let changePassword ctx vi =
let changePassword ctx viewInfo =
let s = I18N.localizer.Force ()
let m = { OldPassword = ""; NewPassword = ""; NewPasswordConfirm = "" }
[ 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."]
]
style [ _scoped ] [ rawText "#oldPassword, #newPassword, #newPasswordConfirm { width: 10rem; } "]
let model = { OldPassword = ""; NewPassword = ""; NewPasswordConfirm = "" }
let vi =
AppViewInfo.withScopedStyles [ "#oldPassword, #newPassword, #newPasswordConfirm { width: 10rem; }"] viewInfo
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."]
]
|> List.singleton
|> List.append [
form [ _action "/user/password/change"
_method "post"
_onsubmit $"""return PT.compareValidation('newPassword','newPasswordConfirm','%A{s["The passwords do not match"]}')"""
Target.content ] [
csrfToken ctx
div [ _class "pt-field-row" ] [
div [ _class "pt-field" ] [
div [ _fieldRow ] [
div [ _inputField ] [
label [ _for "oldPassword" ] [ locStr s["Current Password"] ]
input [ _type "password"; _name (nameof m.OldPassword); _id "oldPassword"; _required; _autofocus ]
input [ _type "password"
_name (nameof model.OldPassword)
_id "oldPassword"
_required
_autofocus ]
]
]
div [ _class "pt-field-row" ] [
div [ _class "pt-field" ] [
div [ _fieldRow ] [
div [ _inputField ] [
label [ _for "newPassword" ] [ locStr s["New Password Twice"] ]
input [ _type "password"; _name (nameof m.NewPassword); _id "newPassword"; _required ]
input [ _type "password"; _name (nameof model.NewPassword); _id "newPassword"; _required ]
]
div [ _class "pt-field" ] [
div [ _inputField ] [
label [] [ rawText "&nbsp;" ]
input [ _type "password"; _name (nameof m.NewPasswordConfirm); _id "newPasswordConfirm"; _required ]
input [ _type "password"
_name (nameof model.NewPasswordConfirm)
_id "newPasswordConfirm"
_required ]
]
]
div [ _class "pt-field-row" ] [
div [ _fieldRow ] [
submit [ _onclick "document.getElementById('newPasswordConfirm').setCustomValidity('')" ] "done"
s["Change Your Password"]
]
@ -80,102 +90,110 @@ let changePassword ctx vi =
/// View for the edit user page
let edit (m : EditUser) ctx vi =
let edit (model : EditUser) ctx viewInfo =
let s = I18N.localizer.Force ()
let pageTitle = if m.IsNew then "Add a New User" else "Edit User"
let pwPlaceholder = s[if m.IsNew then "" else "No change"].Value
[ style [ _scoped ]
[ rawText "#firstName, #lastName, #password, #passwordConfirm { width: 10rem; } #email { width: 20rem; } " ]
form [ _action "/user/edit/save"
_method "post"
_class "pt-center-columns"
_onsubmit $"""return PT.compareValidation('password','passwordConfirm','%A{s["The passwords do not match"]}')"""
Target.content ] [
csrfToken ctx
input [ _type "hidden"; _name (nameof m.UserId); _value (flatGuid m.UserId) ]
div [ _class "pt-field-row" ] [
div [ _class "pt-field" ] [
label [ _for "firstName" ] [ locStr s["First Name"] ]
input [ _type "text"
_name (nameof m.FirstName)
_id "firstName"
_value m.FirstName
_required
_autofocus ]
]
div [ _class "pt-field" ] [
label [ _for "lastName" ] [ locStr s["Last Name"] ]
input [ _type "text"; _name (nameof m.LastName); _id "lastName"; _value m.LastName; _required ]
]
div [ _class "pt-field" ] [
label [ _for "email" ] [ locStr s["E-mail Address"] ]
input [ _type "email"; _name (nameof m.Email); _id "email"; _value m.Email; _required ]
]
]
div [ _class "pt-field-row" ] [
div [ _class "pt-field" ] [
label [ _for "password" ] [ locStr s["Password"] ]
input [ _type "password"; _name (nameof m.Password); _id "password"; _placeholder pwPlaceholder ]
]
div [ _class "pt-field" ] [
label [ _for "passwordConfirm" ] [ locStr s["Password Again"] ]
input [ _type "password"
_name (nameof m.PasswordConfirm)
_id "passwordConfirm"
_placeholder pwPlaceholder ]
]
]
div [ _class "pt-checkbox-field" ] [
input [ _type "checkbox"
_name (nameof m.IsAdmin)
_id "isAdmin"
_value "True"
if defaultArg m.IsAdmin false then _checked ]
label [ _for "isAdmin" ] [ locStr s["This user is a PrayerTracker administrator"] ]
]
div [ _class "pt-field-row" ] [ submit [] "save" s["Save User"] ]
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 =
viewInfo
|> AppViewInfo.withScopedStyles [
"#firstName, #lastName, #password, #passwordConfirm { width: 10rem; }"
"#email { width: 20rem; }"
]
script [] [ rawText $"PT.onLoad(PT.user.edit.onPageLoad({(string m.IsNew).ToLowerInvariant ()}))" ]
|> 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"]}')"""
Target.content ] [
csrfToken ctx
input [ _type "hidden"; _name (nameof model.UserId); _value (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 ]
]
div [ _inputField ] [
label [ _for "lastName" ] [ locStr s["Last Name"] ]
input [ _type "text"; _name (nameof model.LastName); _id "lastName"; _value 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 ]
]
]
div [ _fieldRow ] [
div [ _inputField ] [
label [ _for "password" ] [ locStr s["Password"] ]
input [ _type "password"; _name (nameof model.Password); _id "password"; _placeholder pwPlaceholder ]
]
div [ _inputField ] [
label [ _for "passwordConfirm" ] [ locStr s["Password Again"] ]
input [ _type "password"
_name (nameof model.PasswordConfirm)
_id "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"] ]
]
div [ _fieldRow ] [ submit [] "save" s["Save User"] ]
]
|> List.singleton
|> Layout.Content.standard
|> Layout.standard vi pageTitle
/// View for the user log on page
let logOn (m : UserLogOn) groups ctx vi =
let s = I18N.localizer.Force ()
let logOn (model : UserLogOn) groups ctx viewInfo =
let s = I18N.localizer.Force ()
let vi = AppViewInfo.withScopedStyles [ "#email { width: 20rem; }" ] viewInfo
form [ _action "/user/log-on"; _method "post"; _class "pt-center-columns"; Target.body ] [
style [ _scoped ] [ rawText "#email { width: 20rem; }" ]
csrfToken ctx
input [ _type "hidden"; _name (nameof m.RedirectUrl); _value (defaultArg m.RedirectUrl "") ]
div [ _class "pt-field-row" ] [
div [ _class "pt-field" ] [
input [ _type "hidden"; _name (nameof model.RedirectUrl); _value (defaultArg model.RedirectUrl "") ]
div [ _fieldRow ] [
div [ _inputField ] [
label [ _for "email"] [ locStr s["E-mail Address"] ]
input [ _type "email"; _name (nameof m.Email); _id "email"; _value m.Email; _required; _autofocus ]
input [ _type "email"
_name (nameof model.Email)
_id "email"
_value model.Email
_required
_autofocus ]
]
div [ _class "pt-field" ] [
div [ _inputField ] [
label [ _for "password" ] [ locStr s["Password"] ]
input [ _type "password"
_name (nameof m.Password)
_name (nameof model.Password)
_id "password"
_placeholder $"""({s["Case-Sensitive"].Value.ToLower ()})"""
_required ]
]
]
div [ _class "pt-field-row" ] [
div [ _class "pt-field" ] [
label [ _for (nameof m.SmallGroupId) ] [ locStr s["Group"] ]
div [ _fieldRow ] [
div [ _inputField ] [
label [ _for (nameof model.SmallGroupId) ] [ locStr s["Group"] ]
seq { "", selectDefault s["Select Group"].Value; yield! groups }
|> selectList (nameof m.SmallGroupId) "" [ _required ]
|> selectList (nameof model.SmallGroupId) "" [ _required ]
]
]
div [ _class "pt-checkbox-field" ] [
input [ _type "checkbox"; _name (nameof m.RememberMe); _id "rememberMe"; _value "True" ]
div [ _checkboxField ] [
input [ _type "checkbox"; _name (nameof model.RememberMe); _id "rememberMe"; _value "True" ]
label [ _for "rememberMe" ] [ locStr s["Remember Me"] ]
br []
small [] [ em [] [ str $"""({s["Requires Cookies"].Value.ToLower ()})""" ] ]
]
div [ _class "pt-field-row" ] [ submit [] "account_circle" s["Log On"] ]
div [ _fieldRow ] [ submit [] "account_circle" s["Log On"] ]
]
|> List.singleton
|> Layout.Content.standard
@ -185,7 +203,7 @@ let logOn (m : UserLogOn) groups ctx vi =
open PrayerTracker.Entities
/// View for the user maintenance page
let maintain (users : User list) ctx vi =
let maintain (users : User list) ctx viewInfo =
let s = I18N.localizer.Force ()
let usrTbl =
match users with
@ -237,4 +255,4 @@ let maintain (users : User list) ctx vi =
form [ _id "DeleteForm"; _action ""; _method "post" ] [ csrfToken ctx ]
]
|> Layout.Content.standard
|> Layout.standard vi "Maintain Users"
|> Layout.standard viewInfo "Maintain Users"

View File

@ -147,24 +147,41 @@ type AppViewInfo =
/// The layout with which the content will be rendered
Layout : LayoutType
/// Scoped styles for this view
ScopedStyle : string list
/// A JavaScript function to run on page load
OnLoadScript : string option
}
// TODO: add onload script option to this, modify layout to add it
/// Support for the AppViewInfo type
module AppViewInfo =
/// A fresh version that can be populated to process the current request
let fresh =
{ Style = []
Script = []
HelpLink = None
Messages = []
Version = ""
RequestStart = DateTime.Now.Ticks
User = None
Group = None
Layout = FullPage
{ Style = []
Script = []
HelpLink = None
Messages = []
Version = ""
RequestStart = DateTime.Now.Ticks
User = None
Group = None
Layout = FullPage
ScopedStyle = []
OnLoadScript = None
}
/// Add scoped styles to the given view info object
let withScopedStyles styles viewInfo =
{ viewInfo with ScopedStyle = styles }
/// Add an onload action to the given view info object
let withOnLoadScript script viewInfo =
{ viewInfo with OnLoadScript = Some script }
/// Form for sending a small group or system-wide announcement
[<CLIMutable; NoComparison; NoEquality>]

View File

@ -2,7 +2,7 @@
* This file contains a library of common functions used throughout the PrayerTracker website, as well as specific
* functions used on pages throughout the site.
*/
const PT = {
this.PT = {
/**
* Open a window with help