Add native font stack to pref page (#38)

- Update font verbiage to explain native fonts
- Sync/update missing/outdated resources
This commit is contained in:
Daniel J. Summers 2022-08-15 18:05:28 -04:00
parent 24f5b5a741
commit 1b48acd66a
17 changed files with 159 additions and 79 deletions

View File

@ -11,7 +11,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Giraffe" Version="6.0.0" /> <PackageReference Include="Giraffe" Version="6.0.0" />
<PackageReference Include="Microsoft.FSharpLu" Version="0.11.7" />
<PackageReference Include="NodaTime" Version="3.1.1" /> <PackageReference Include="NodaTime" Version="3.1.1" />
<PackageReference Update="FSharp.Core" Version="6.0.5" /> <PackageReference Update="FSharp.Core" Version="6.0.5" />
<PackageReference Include="Npgsql.FSharp" Version="5.3.0" /> <PackageReference Include="Npgsql.FSharp" Version="5.3.0" />

View File

@ -114,11 +114,11 @@ let listPreferencesTests =
Expect.equal mt.DaysToKeepNew 7 "The default days to keep new should have been 7" Expect.equal mt.DaysToKeepNew 7 "The default days to keep new should have been 7"
Expect.equal mt.LongTermUpdateWeeks 4 "The default long term update weeks should have been 4" Expect.equal mt.LongTermUpdateWeeks 4 "The default long term update weeks should have been 4"
Expect.equal mt.EmailFromName "PrayerTracker" "The default e-mail from name should have been PrayerTracker" Expect.equal mt.EmailFromName "PrayerTracker" "The default e-mail from name should have been PrayerTracker"
Expect.equal mt.EmailFromAddress "prayer@djs-consulting.com" Expect.equal mt.EmailFromAddress "prayer@bitbadger.solutions"
"The default e-mail from address should have been prayer@djs-consulting.com" "The default e-mail from address should have been prayer@bitbadger.solutions"
Expect.equal mt.Fonts "native" "The default list fonts were incorrect" Expect.equal mt.Fonts "native" "The default list fonts were incorrect"
Expect.equal mt.HeadingColor "maroon" "The default heading text color should have been maroon" Expect.equal mt.HeadingColor "maroon" "The default heading text color should have been maroon"
Expect.equal mt.LineColor "navy" "The default heding line color should have been navy" Expect.equal mt.LineColor "navy" "The default heading line color should have been navy"
Expect.equal mt.HeadingFontSize 16 "The default heading font size should have been 16" Expect.equal mt.HeadingFontSize 16 "The default heading font size should have been 16"
Expect.equal mt.TextFontSize 12 "The default text font size should have been 12" Expect.equal mt.TextFontSize 12 "The default text font size should have been 12"
Expect.equal mt.RequestSort SortByDate "The default request sort should have been by date" Expect.equal mt.RequestSort SortByDate "The default request sort should have been by date"

View File

@ -16,7 +16,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Expecto" Version="9.0.4" /> <PackageReference Include="Expecto" Version="9.0.4" />
<PackageReference Include="NodaTime.Testing" Version="3.1.0" /> <PackageReference Include="NodaTime.Testing" Version="3.1.1" />
<PackageReference Update="FSharp.Core" Version="6.0.5" /> <PackageReference Update="FSharp.Core" Version="6.0.5" />
</ItemGroup> </ItemGroup>

View File

@ -261,7 +261,7 @@ let editMemberTests =
[<Tests>] [<Tests>]
let editPreferencesTests = let editPreferencesTests =
testList "EditPreferences" [ testList "EditPreferences" [
test "fromPreferences succeeds for named colors and private list" { test "fromPreferences succeeds for native fonts, named colors, and private list" {
let prefs = ListPreferences.empty let prefs = ListPreferences.empty
let edit = EditPreferences.fromPreferences prefs let edit = EditPreferences.fromPreferences prefs
Expect.equal edit.ExpireDays prefs.DaysToExpire "The expiration days were not filled correctly" Expect.equal edit.ExpireDays prefs.DaysToExpire "The expiration days were not filled correctly"
@ -278,7 +278,8 @@ let editPreferencesTests =
Expect.equal edit.LineColor prefs.LineColor "The heading line color was not filled correctly" Expect.equal edit.LineColor prefs.LineColor "The heading line color was not filled correctly"
Expect.equal edit.HeadingColorType "Name" "The heading text color type was not derived correctly" Expect.equal edit.HeadingColorType "Name" "The heading text color type was not derived correctly"
Expect.equal edit.HeadingColor prefs.HeadingColor "The heading text color was not filled correctly" Expect.equal edit.HeadingColor prefs.HeadingColor "The heading text color was not filled correctly"
Expect.equal edit.Fonts prefs.Fonts "The list fonts were not filled correctly" Expect.isTrue edit.IsNative "The IsNative flag should have been true (default value)"
Expect.isNone edit.Fonts "The list fonts should not exist for native font stack"
Expect.equal edit.HeadingFontSize prefs.HeadingFontSize "The heading font size was not filled correctly" Expect.equal edit.HeadingFontSize prefs.HeadingFontSize "The heading font size was not filled correctly"
Expect.equal edit.ListFontSize prefs.TextFontSize "The list text font size was not filled correctly" Expect.equal edit.ListFontSize prefs.TextFontSize "The list text font size was not filled correctly"
Expect.equal edit.TimeZone (TimeZoneId.toString prefs.TimeZoneId) "The time zone was not filled correctly" Expect.equal edit.TimeZone (TimeZoneId.toString prefs.TimeZoneId) "The time zone was not filled correctly"
@ -310,6 +311,13 @@ let editPreferencesTests =
Expect.equal edit.Visibility GroupVisibility.PublicList Expect.equal edit.Visibility GroupVisibility.PublicList
"The list visibility was not derived correctly" "The list visibility was not derived correctly"
} }
test "fromPreferences succeeds for non-native fonts" {
let prefs = { ListPreferences.empty with Fonts = "Arial,sans-serif" }
let edit = EditPreferences.fromPreferences prefs
Expect.isFalse edit.IsNative "The IsNative flag should have been false"
Expect.isSome edit.Fonts "The fonts should have been filled for non-native fonts"
Expect.equal edit.Fonts.Value prefs.Fonts "The fonts were not filled correctly"
}
] ]
[<Tests>] [<Tests>]
@ -529,11 +537,12 @@ let requestListTests =
"AsHtml succeeds without header or as-of date", "AsHtml succeeds without header or as-of date",
fun reqList -> fun reqList ->
let htmlList = { reqList with SmallGroup = { reqList.SmallGroup with Name = "Test HTML Group" } } let htmlList = { reqList with SmallGroup = { reqList.SmallGroup with Name = "Test HTML Group" } }
let html = htmlList.AsHtml _s let html = htmlList.AsHtml _s
let fonts = reqList.SmallGroup.Preferences.FontStack.Replace ("\"", "&quot;")
Expect.equal -1 (html.IndexOf "Test HTML Group") Expect.equal -1 (html.IndexOf "Test HTML Group")
"The small group name should not have existed (no header)" "The small group name should not have existed (no header)"
let curReqHeading = let curReqHeading =
[ """<table style="font-family:Century Gothic,Tahoma,Luxi Sans,sans-serif;page-break-inside:avoid;">""" [ $"""<table style="font-family:{fonts};page-break-inside:avoid;">"""
"<tr>" "<tr>"
"""<td style="font-size:16pt;color:maroon;padding:3px 0;border-top:solid 3px navy;border-bottom:solid 3px navy;font-weight:bold;">""" """<td style="font-size:16pt;color:maroon;padding:3px 0;border-top:solid 3px navy;border-bottom:solid 3px navy;font-weight:bold;">"""
"&nbsp; &nbsp; Current Requests&nbsp; &nbsp; </td></tr></table>" "&nbsp; &nbsp; Current Requests&nbsp; &nbsp; </td></tr></table>"
@ -541,16 +550,16 @@ let requestListTests =
|> String.concat "" |> String.concat ""
Expect.stringContains html curReqHeading """Heading for category "Current Requests" not found""" Expect.stringContains html curReqHeading """Heading for category "Current Requests" not found"""
let curReqHtml = let curReqHtml =
[ "<ul>" [ $"""<ul style="font-family:{fonts};font-size:12pt">"""
"""<li style="list-style-type:circle;font-family:Century Gothic,Tahoma,Luxi Sans,sans-serif;font-size:12pt;padding-bottom:.25em;">""" """<li style="list-style-type:circle;padding-bottom:.25em;">"""
"<strong>Zeb</strong> &ndash; zyx</li>" "<strong>Zeb</strong> &ndash; zyx</li>"
"""<li style="list-style-type:disc;font-family:Century Gothic,Tahoma,Luxi Sans,sans-serif;font-size:12pt;padding-bottom:.25em;">""" """<li style="list-style-type:disc;padding-bottom:.25em;">"""
"<strong>Aaron</strong> &ndash; abc</li></ul>" "<strong>Aaron</strong> &ndash; abc</li></ul>"
] ]
|> String.concat "" |> String.concat ""
Expect.stringContains html curReqHtml """Expected HTML for "Current Requests" requests not found""" Expect.stringContains html curReqHtml """Expected HTML for "Current Requests" requests not found"""
let praiseHeading = let praiseHeading =
[ """<table style="font-family:Century Gothic,Tahoma,Luxi Sans,sans-serif;page-break-inside:avoid;">""" [ $"""<table style="font-family:{fonts};page-break-inside:avoid;">"""
"<tr>" "<tr>"
"""<td style="font-size:16pt;color:maroon;padding:3px 0;border-top:solid 3px navy;border-bottom:solid 3px navy;font-weight:bold;">""" """<td style="font-size:16pt;color:maroon;padding:3px 0;border-top:solid 3px navy;border-bottom:solid 3px navy;font-weight:bold;">"""
"&nbsp; &nbsp; Praise Reports&nbsp; &nbsp; </td></tr></table>" "&nbsp; &nbsp; Praise Reports&nbsp; &nbsp; </td></tr></table>"
@ -558,8 +567,8 @@ let requestListTests =
|> String.concat "" |> String.concat ""
Expect.stringContains html praiseHeading """Heading for category "Praise Reports" not found""" Expect.stringContains html praiseHeading """Heading for category "Praise Reports" not found"""
let praiseHtml = let praiseHtml =
[ "<ul>" [ $"""<ul style="font-family:{fonts};font-size:12pt">"""
"""<li style="list-style-type:circle;font-family:Century Gothic,Tahoma,Luxi Sans,sans-serif;font-size:12pt;padding-bottom:.25em;">""" """<li style="list-style-type:circle;padding-bottom:.25em;">"""
"nmo</li></ul>" "nmo</li></ul>"
] ]
|> String.concat "" |> String.concat ""
@ -571,9 +580,10 @@ let requestListTests =
SmallGroup = { reqList.SmallGroup with Name = "Test HTML Group" } SmallGroup = { reqList.SmallGroup with Name = "Test HTML Group" }
ShowHeader = true ShowHeader = true
} }
let html = htmlList.AsHtml _s let html = htmlList.AsHtml _s
let fonts = reqList.SmallGroup.Preferences.FontStack.Replace ("\"", "&quot;")
let lstHeading = let lstHeading =
[ """<div style="text-align:center;font-family:Century Gothic,Tahoma,Luxi Sans,sans-serif">""" [ $"""<div style="text-align:center;font-family:{fonts}">"""
"""<span style="font-size:16pt;"><strong>Prayer Requests</strong></span><br>""" """<span style="font-size:16pt;"><strong>Prayer Requests</strong></span><br>"""
"""<span style="font-size:12pt;"><strong>Test HTML Group</strong><br>""" """<span style="font-size:12pt;"><strong>Test HTML Group</strong><br>"""
htmlList.Date.ToString ("MMMM d, yyyy", null) htmlList.Date.ToString ("MMMM d, yyyy", null)

View File

@ -41,12 +41,14 @@ let edit (model : EditChurch) ctx viewInfo =
div [ _checkboxField ] [ div [ _checkboxField ] [
inputField "checkbox" (nameof model.HasInterface) "True" inputField "checkbox" (nameof model.HasInterface) "True"
[ if defaultArg model.HasInterface false then _checked ] [ if defaultArg model.HasInterface false then _checked ]
label [ _for (nameof model.HasInterface) ] [ locStr s["Has an interface with Virtual Prayer Room"] ] label [ _for (nameof model.HasInterface) ] [
locStr s["Has an Interface with “{0}”", "Virtual Prayer Space"]
]
] ]
] ]
div [ _fieldRowWith [ "pt-fadeable" ]; _id "divInterfaceAddress" ] [ div [ _fieldRowWith [ "pt-fadeable" ]; _id "divInterfaceAddress" ] [
div [ _inputField ] [ div [ _inputField ] [
label [ _for (nameof model.InterfaceAddress) ] [ locStr s["VPR Interface URL"] ] label [ _for (nameof model.InterfaceAddress) ] [ locStr s["Interface URL"] ]
inputField "url" (nameof model.InterfaceAddress) (defaultArg model.InterfaceAddress "") [] inputField "url" (nameof model.InterfaceAddress) (defaultArg model.InterfaceAddress "") []
] ]
] ]

View File

@ -302,11 +302,12 @@ let private contentSection viewInfo pgTitle (content : XmlNode) = [
htmlFooter viewInfo htmlFooter viewInfo
match viewInfo.OnLoadScript with match viewInfo.OnLoadScript with
| Some onLoad -> | Some onLoad ->
let doCall = if onLoad.EndsWith ")" then "" else "()"
script [] [ script [] [
rawText $""" rawText $"""
window.doOnLoad = () => {{ window.doOnLoad = () => {{
if (window.PT) {{ if (window.PT) {{
{onLoad}() {onLoad}{doCall}
delete window.doOnLoad delete window.doOnLoad
}} else {{ setTimeout(window.doOnLoad, 500) }} }} else {{ setTimeout(window.doOnLoad, 500) }}
}} }}

View File

@ -54,15 +54,10 @@ let edit (model : EditRequest) today ctx viewInfo =
div [ _fieldRow ] [ div [ _fieldRow ] [
div [ _inputField ] [ div [ _inputField ] [
label [] [ locStr s["Expiration"] ] label [] [ locStr s["Expiration"] ]
ReferenceList.expirationList s (not model.IsNew) span [ _class "pt-radio-group" ] [
|> List.map (fun exp -> for code, name in ReferenceList.expirationList s (not model.IsNew) do
let radioId = String.concat "_" [ nameof model.Expiration; fst exp ] label [] [ radio (nameof model.Expiration) "" code model.Expiration; locStr name ]
span [ _class "text-nowrap" ] [ ]
radio (nameof model.Expiration) radioId (fst exp) model.Expiration
label [ _for radioId ] [ space; locStr (snd exp) ]
rawText " &nbsp; &nbsp; "
])
|> div [ _class "pt-center-text" ]
] ]
] ]
div [ _fieldRow ] [ div [ _fieldRow ] [

View File

@ -396,8 +396,8 @@
<data name="The group member “{0}” was deleted successfully" xml:space="preserve"> <data name="The group member “{0}” was deleted successfully" xml:space="preserve">
<value>El miembro del grupo “{0}” se eliminó con éxito</value> <value>El miembro del grupo “{0}” se eliminó con éxito</value>
</data> </data>
<data name="The group {0} and its {1} prayer request(s) was deleted successfully (revoked access from {2} user(s))" xml:space="preserve"> <data name="The group “{0}” and its {1} prayer request(s) were deleted successfully; revoked access from {2} user(s)" xml:space="preserve">
<value>El grupo {0} y sus {1} peticion(es) de oración se ha eliminado correctamente (acceso revocada por {2} usuario(s))</value> <value>El grupo {0} y sus {1} peticion(es) de oración se ha eliminado correctamente; acceso revocada por {2} usuario(s)</value>
</data> </data>
<data name="The old password was incorrect - your password was NOT changed" xml:space="preserve"> <data name="The old password was incorrect - your password was NOT changed" xml:space="preserve">
<value>La contraseña antigua es incorrecta - la contraseña NO ha cambiado</value> <value>La contraseña antigua es incorrecta - la contraseña NO ha cambiado</value>
@ -742,7 +742,7 @@
<value>Este</value> <value>Este</value>
</data> </data>
<data name="MMMM d, yyyy" xml:space="preserve"> <data name="MMMM d, yyyy" xml:space="preserve">
<value>d \de MMMM yyyy</value> <value>d \d\e MMMM yyyy</value>
</data> </data>
<data name="Mountain" xml:space="preserve"> <data name="Mountain" xml:space="preserve">
<value>Montaña</value> <value>Montaña</value>
@ -831,4 +831,55 @@
<data name="Administrators" xml:space="preserve"> <data name="Administrators" xml:space="preserve">
<value>Administradores</value> <value>Administradores</value>
</data> </data>
<data name="Native Fonts" xml:space="preserve">
<value>Fuentes Nativas</value>
</data>
<data name="Named Fonts" xml:space="preserve">
<value>Fuentes con Nombre</value>
</data>
<data name="Select Church" xml:space="preserve">
<value>Seleccione una Iglesia</value>
</data>
<data name="Select Group" xml:space="preserve">
<value>Seleccione un Grupo</value>
</data>
<data name="Member Name" xml:space="preserve">
<value>Nombre de Miembro</value>
</data>
<data name="Custom Color" xml:space="preserve">
<value>Color Personalizado</value>
</data>
<data name="Church Name" xml:space="preserve">
<value>Nombre de la Iglesia</value>
</data>
<data name="City" xml:space="preserve">
<value>Ciudad</value>
</data>
<data name="Has an Interface with “{0}”" xml:space="preserve">
<value>Tiene una Interfaz con “{0}”</value>
</data>
<data name="Interface URL" xml:space="preserve">
<value>URL de la Interfaz</value>
</data>
<data name="Successfully {0} church “{1}”" xml:space="preserve">
<value>Iglesia “{1}” {0} con éxito</value>
</data>
<data name="The church “{0}” and its {1} small group(s) (with {2} prayer request(s)) were deleted successfully; revoked access from {3} user(s)" xml:space="preserve">
<value>La iglesia "{0}" y sus {1} grupo(s) (con {2} peticion(es) de oración) se eliminaron correctamente; acceso revocado de {3} usuario(s)</value>
</data>
<data name="Successfully {0} group “{1}”" xml:space="preserve">
<value>El grupo “{1}” {0} con éxito</value>
</data>
<data name="First Name" xml:space="preserve">
<value>Primer Nombre</value>
</data>
<data name="Last Name" xml:space="preserve">
<value>Apellido</value>
</data>
<data name="Password Again" xml:space="preserve">
<value>Contraseña otra Vez</value>
</data>
<data name="This User Is a {0} Administrator" xml:space="preserve">
<value>Este Usuario Es un Administrador de {0}</value>
</data>
</root> </root>

View File

@ -117,16 +117,16 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="Ending with either “serif” or “sans-serif” will cause the user's browser to use the default “serif” font (“Times New Roman” on Windows) or “sans-serif” font (“Arial” on Windows) if no other fonts in the list are found." xml:space="preserve"> <data name="Ending this list with either “serif” or “sans-serif” will cause the user's browser to use the default “serif” font (“Times New Roman” on Windows) or “sans-serif” font (“Arial” on Windows) if no other fonts in the list are found." xml:space="preserve">
<value>Con la extensión “serif” o el “sans-serif” hará que el navegador del usuario para utilizar el valor predeterminado “serif” de la fuente (“Times New Roman” en Windows) o “sans-serif” de la fuente (“Arial” en Windows) si no otras fuentes en la lista se encuentran.</value> <value>Terminar esta lista con “serif” o “sans-serif” hará que el navegador del usuario utilice la fuente predeterminado “serif” (“Times New Roman” en Windows) o “sans-serif” (“Arial” en Windows) si no se encuentran otras fuentes en la lista.</value>
</data> </data>
<data name="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 &lt;a href=&quot;http://www.w3schools.com/html/html_colornames.asp&quot; title=&quot;HTML Color List - W3 School&quot;&gt;HTML color name list&lt;/a&gt;." xml:space="preserve"> <data name="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 &lt;a href=&quot;http://www.w3schools.com/html/html_colornames.asp&quot; title=&quot;HTML Color List - W3 School&quot;&gt;HTML color name list&lt;/a&gt;." xml:space="preserve">
<value>Si desea un color personalizado, que puede ser capaz de obtener algunas ideas (y una lista de valores RGB para los colores) de la lista de la Escuela de W3 de &lt;a href="http://www.w3schools.com/html/html_colornames.asp" title="La Lista de Nombres de Colores HTML - La Escuela de W3"&gt;nombres de colores HTML&lt;/a&gt;.</value> <value>Si desea un color personalizado, que puede ser capaz de obtener algunas ideas (y una lista de valores RGB para los colores) de la lista de la Escuela de W3 de &lt;a href="http://www.w3schools.com/html/html_colornames.asp" title="La Lista de Nombres de Colores HTML - La Escuela de W3"&gt;nombres de colores HTML&lt;/a&gt;.</value>
</data> </data>
<data name="List font names, separated by commas." xml:space="preserve"> <data name="Native fonts match the default font based on the user's device (computer, phone, tablet, etc.)." xml:space="preserve">
<value>Lista de nombres de fuentes separados por comas.</value> <value>Las fuentes nativas coinciden con la fuente predeterminada según el dispositivo del usuario (computadora, teléfono, tableta, etc.).</value>
</data> </data>
<data name="The first font that is matched is the one that is used." xml:space="preserve"> <data name="Named fonts should be separated by commas, and will be displayed using the first one the user has in the list." xml:space="preserve">
<value>La primera fuente que se corresponde es el que se utiliza.</value> <value>Las fuentes con nombre deben estar separadas por comas y se mostrarán usando la primera que el usuario tenga en la lista.</value>
</data> </data>
</root> </root>

View File

@ -30,13 +30,15 @@ let announcement isAdmin ctx viewInfo =
div [ _fieldRow ] [ div [ _fieldRow ] [
div [ _inputField ] [ div [ _inputField ] [
label [] [ locStr s["Send Announcement to"]; rawText ":" ] label [] [ locStr s["Send Announcement to"]; rawText ":" ]
div [ _class "pt-center-text" ] [ div [ _class "pt-radio-group" ] [
radio (nameof model.SendToClass) $"{nameof model.SendToClass}_Y" "Y" "Y" label [] [
label [ _for $"{nameof model.SendToClass}_Y" ] [ radio (nameof model.SendToClass) $"{nameof model.SendToClass}_Y" "Y" "Y"
locStr s["This Group"]; rawText " &nbsp; &nbsp; " locStr s["This Group"]
]
label [] [
radio (nameof model.SendToClass) $"{nameof model.SendToClass}_N" "N" "Y"
locStr s["All {0} Users", s["PrayerTracker"]]
] ]
radio (nameof model.SendToClass) $"{nameof model.SendToClass}_N" "N" "Y"
label [ _for $"{nameof model.SendToClass}_N" ] [ locStr s["All {0} Users", s["PrayerTracker"]] ]
] ]
] ]
] ]
@ -519,7 +521,21 @@ let preferences (model : EditPreferences) ctx viewInfo =
legend [] [ strong [] [ icon "font_download"; rawText " &nbsp;"; locStr s["Fonts"] ] ] legend [] [ strong [] [ icon "font_download"; rawText " &nbsp;"; locStr s["Fonts"] ] ]
div [ _inputField ] [ div [ _inputField ] [
label [ _for (nameof model.Fonts) ] [ locStr s["Fonts** for List"] ] label [ _for (nameof model.Fonts) ] [ locStr s["Fonts** for List"] ]
inputField "text" (nameof model.Fonts) model.Fonts [ _required ] let value = if model.IsNative then "True" else "False"
span [ _class "pt-radio-group" ] [
label [] [
radio (nameof model.IsNative) $"{nameof model.IsNative}_Y" "True" value
locStr s["Native Fonts"]
]
inputField "text" "nativeFontSpacer" "" [ _style "visibility:hidden" ]
]
span [ _class "pt-radio-group" ] [
label [] [
radio (nameof model.IsNative) $"{nameof model.IsNative}_N" "False" value
locStr s["Named Fonts"]
]
inputField "text" (nameof model.Fonts) (defaultArg model.Fonts "") [ _required ]
]
] ]
div [ _fieldRow ] [ div [ _fieldRow ] [
div [ _inputField ] [ div [ _inputField ] [
@ -595,11 +611,11 @@ let preferences (model : EditPreferences) ctx viewInfo =
] ]
p [] [ p [] [
rawText "** " rawText "** "
raw l["List font names, separated by commas."] raw l["Native fonts match the default font based on the user's device (computer, phone, tablet, etc.)."]
space space
raw l["The first font that is matched is the one that is used."] raw l["Named fonts should be separated by commas, and will be displayed using the first one the user has in the list."]
space space
raw l["Ending with either “serif” or “sans-serif” will cause the user's browser to use the default “serif” font (“Times New Roman” on Windows) or “sans-serif” font (“Arial” on Windows) if no other fonts in the list are found."] raw l["Ending this list with either “serif” or “sans-serif” will cause the user's browser to use the default “serif” font (“Times New Roman” on Windows) or “sans-serif” font (“Arial” on Windows) if no other fonts in the list are found."]
] ]
p [] [ p [] [
rawText "*** " rawText "*** "

View File

@ -133,7 +133,7 @@ let edit (model : EditUser) ctx viewInfo =
] ]
div [ _checkboxField ] [ div [ _checkboxField ] [
inputField "checkbox" (nameof model.IsAdmin) "True" [ if defaultArg model.IsAdmin false then _checked ] 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"] ] label [ _for (nameof model.IsAdmin) ] [ locStr s["This User Is a {0} Administrator", s["PrayerTracker"]] ]
] ]
div [ _fieldRow ] [ submit [] "save" s["Save User"] ] div [ _fieldRow ] [ submit [] "save" s["Save User"] ]
] ]

View File

@ -374,8 +374,11 @@ type EditPreferences =
/// The named color for the heading text /// The named color for the heading text
HeadingColor : string HeadingColor : string
/// Whether the class uses the native font stack
IsNative : bool
/// The fonts to use for the list /// The fonts to use for the list
Fonts : string Fonts : string option
/// The font size for the heading text /// The font size for the heading text
HeadingFontSize : int HeadingFontSize : int
@ -416,7 +419,7 @@ with
DefaultEmailType = EmailFormat.fromCode this.DefaultEmailType DefaultEmailType = EmailFormat.fromCode this.DefaultEmailType
LineColor = this.LineColor LineColor = this.LineColor
HeadingColor = this.HeadingColor HeadingColor = this.HeadingColor
Fonts = this.Fonts Fonts = if this.IsNative || Option.isNone this.Fonts then "native" else this.Fonts.Value
HeadingFontSize = this.HeadingFontSize HeadingFontSize = this.HeadingFontSize
TextFontSize = this.ListFontSize TextFontSize = this.ListFontSize
TimeZoneId = TimeZoneId this.TimeZone TimeZoneId = TimeZoneId this.TimeZone
@ -442,7 +445,8 @@ module EditPreferences =
LineColor = prefs.LineColor LineColor = prefs.LineColor
HeadingColorType = setType prefs.HeadingColor HeadingColorType = setType prefs.HeadingColor
HeadingColor = prefs.HeadingColor HeadingColor = prefs.HeadingColor
Fonts = prefs.Fonts IsNative = (prefs.Fonts = "native")
Fonts = if prefs.Fonts = "native" then None else Some prefs.Fonts
HeadingFontSize = prefs.HeadingFontSize HeadingFontSize = prefs.HeadingFontSize
ListFontSize = prefs.TextFontSize ListFontSize = prefs.TextFontSize
TimeZone = TimeZoneId.toString prefs.TimeZoneId TimeZone = TimeZoneId.toString prefs.TimeZoneId
@ -804,7 +808,7 @@ with
reqs reqs
|> List.map (fun req -> |> List.map (fun req ->
let bullet = if this.IsNew req then "circle" else "disc" let bullet = if this.IsNew req then "circle" else "disc"
li [ _style $"list-style-type:{bullet};font-family:{p.FontStack};font-size:%i{p.TextFontSize}pt;padding-bottom:.25em;" ] [ li [ _style $"list-style-type:{bullet};padding-bottom:.25em;" ] [
match req.Requestor with match req.Requestor with
| Some r when r <> "" -> | Some r when r <> "" ->
strong [] [ str r ] strong [] [ str r ]
@ -825,7 +829,7 @@ with
rawText "&nbsp; ("; str s["as of"].Value; str " "; str dt; rawText ")" rawText "&nbsp; ("; str s["as of"].Value; str " "; str dt; rawText ")"
] ]
]) ])
|> ul [] |> ul [ _style $"font-family:{p.FontStack};font-size:%i{p.TextFontSize}pt" ]
br [] br []
] ]
|> RenderView.AsString.htmlNodes |> RenderView.AsString.htmlNodes

View File

@ -24,7 +24,7 @@ let delete chId : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun
let! _, stats = findStats churchId conn let! _, stats = findStats churchId conn
do! Churches.deleteById churchId conn do! Churches.deleteById churchId conn
addInfo ctx addInfo ctx
ctx.Strings["The church {0} and its {1} small groups (with {2} prayer request(s)) were deleted successfully; revoked access from {3} user(s)", ctx.Strings["The church {0} and its {1} small group(s) (with {2} prayer request(s)) were deleted successfully; revoked access from {3} user(s)",
church.Name, stats.SmallGroups, stats.PrayerRequests, stats.Users] church.Name, stats.SmallGroups, stats.PrayerRequests, stats.Users]
return! redirectTo false "/churches" next ctx return! redirectTo false "/churches" next ctx
| None -> return! fourOhFour ctx | None -> return! fourOhFour ctx

View File

@ -2,7 +2,6 @@
module PrayerTracker.Extensions module PrayerTracker.Extensions
open Microsoft.AspNetCore.Http open Microsoft.AspNetCore.Http
open Microsoft.FSharpLu
open Newtonsoft.Json open Newtonsoft.Json
open NodaTime open NodaTime
open NodaTime.Serialization.JsonNet open NodaTime.Serialization.JsonNet
@ -17,18 +16,18 @@ let private jsonSettings = JsonSerializerSettings().ConfigureForNodaTime DateTim
type ISession with type ISession with
/// Set an object in the session /// Set an object in the session
member this.SetObject key value = member this.SetObject<'T> key (value : 'T) =
this.SetString (key, JsonConvert.SerializeObject (value, jsonSettings)) this.SetString (key, JsonConvert.SerializeObject (value, jsonSettings))
/// Get an object from the session /// Get an object from the session
member this.GetObject<'T> key = member this.TryGetObject<'T> key =
match this.GetString key with match this.GetString key with
| null -> Unchecked.defaultof<'T> | null -> None
| v -> JsonConvert.DeserializeObject<'T> (v, jsonSettings) | v -> Some (JsonConvert.DeserializeObject<'T> (v, jsonSettings))
/// The currently logged on small group /// The currently logged on small group
member this.CurrentGroup member this.CurrentGroup
with get () = this.GetObject<SmallGroup> Key.Session.currentGroup |> Option.fromObject with get () = this.TryGetObject<SmallGroup> Key.Session.currentGroup
and set (v : SmallGroup option) = and set (v : SmallGroup option) =
match v with match v with
| Some group -> this.SetObject Key.Session.currentGroup group | Some group -> this.SetObject Key.Session.currentGroup group
@ -36,7 +35,7 @@ type ISession with
/// The currently logged on user /// The currently logged on user
member this.CurrentUser member this.CurrentUser
with get () = this.GetObject<User> Key.Session.currentUser |> Option.fromObject with get () = this.TryGetObject<User> Key.Session.currentUser
and set (v : User option) = and set (v : User option) =
match v with match v with
| Some user -> this.SetObject Key.Session.currentUser { user with PasswordHash = "" } | Some user -> this.SetObject Key.Session.currentUser { user with PasswordHash = "" }
@ -45,9 +44,8 @@ type ISession with
/// Current messages for the session /// Current messages for the session
member this.Messages member this.Messages
with get () = with get () =
match box (this.GetObject<UserMessage list> Key.Session.userMessages) with this.TryGetObject<UserMessage list> Key.Session.userMessages
| null -> List.empty<UserMessage> |> Option.defaultValue List.empty<UserMessage>
| msgs -> unbox msgs
and set (v : UserMessage list) = this.SetObject Key.Session.userMessages v and set (v : UserMessage list) = this.SetObject Key.Session.userMessages v
@ -69,28 +67,18 @@ type ClaimsPrincipal with
else None else None
open System.Threading.Tasks
open Giraffe open Giraffe
open Microsoft.Extensions.Configuration
open Npgsql open Npgsql
/// Extensions on the ASP.NET Core HTTP context /// Extensions on the ASP.NET Core HTTP context
type HttpContext with type HttpContext with
// TODO: is this disposed? /// The system clock (via DI)
member private this.LazyConn : Lazy<Task<NpgsqlConnection>> = lazy (backgroundTask { member this.Clock = this.GetService<IClock> ()
let cfg = this.GetService<IConfiguration> ()
let conn = new NpgsqlConnection (cfg.GetConnectionString "PrayerTracker")
do! conn.OpenAsync ()
return conn
})
/// The PostgreSQL connection (configured via DI) /// The PostgreSQL connection (configured via DI)
member this.Conn = this.GetService<NpgsqlConnection> () member this.Conn = this.GetService<NpgsqlConnection> ()
/// The system clock (via DI)
member this.Clock = this.GetService<IClock> ()
/// The current instant /// The current instant
member this.Now = this.Clock.GetCurrentInstant () member this.Now = this.Clock.GetCurrentInstant ()

View File

@ -23,7 +23,7 @@ let delete grpId : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fu
let! users = Users.countByGroup groupId conn let! users = Users.countByGroup groupId conn
do! SmallGroups.deleteById groupId conn do! SmallGroups.deleteById groupId conn
addInfo ctx addInfo ctx
ctx.Strings["The group {0} and its {1} prayer request(s) were deleted successfully; revoked access from {2} user(s)", ctx.Strings["The group {0} and its {1} prayer request(s) were deleted successfully; revoked access from {2} user(s)",
grp.Name, reqs, users] grp.Name, reqs, users]
return! redirectTo false "/small-groups" next ctx return! redirectTo false "/small-groups" next ctx
| None -> return! fourOhFour ctx | None -> return! fourOhFour ctx

View File

@ -257,7 +257,8 @@ footer a:hover {
.pt-radio-group { .pt-radio-group {
display: flex; display: flex;
flex-flow: row; flex-flow: row;
gap: 2rem; gap: 1.5rem;
align-items: baseline;
} }
.pt-center-text { .pt-center-text {
text-align: center; text-align: center;

View File

@ -243,6 +243,13 @@ this.PT = {
} }
}, },
/**
* Enable or disable the font list based on whether the native font stack is selected or not
*/
checkFonts() {
document.getElementById("Fonts").disabled = document.getElementById("IsNative_Y").checked
},
/** /**
* Bind the event handlers * Bind the event handlers
*/ */
@ -261,6 +268,12 @@ this.PT = {
}) })
PT.smallGroup.preferences.toggleType(name) PT.smallGroup.preferences.toggleType(name)
}) })
;["Y", "N"].map(name => {
document.getElementById(`IsNative_${name}`).addEventListener("click", () => {
PT.smallGroup.preferences.checkFonts()
})
})
PT.smallGroup.preferences.checkFonts()
}, },
}, },
}, },