From ae29a6c03b7dc84fa4d0c3133afec68cef5346f6 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Sun, 14 Aug 2022 19:46:37 -0400 Subject: [PATCH] Fix request/announcement text (#36) - Update defaults for small group - Add support for native font stack (#38) - Fix color picker error when named color is selected - Use invariant culture for URL formatting (#41) --- src/PrayerTracker.Data/Entities.fs | 11 ++++++++-- src/PrayerTracker.Tests/Data/EntitiesTests.fs | 11 +++++++++- src/PrayerTracker.UI/CommonFunctions.fs | 22 +++++++++++++++++++ src/PrayerTracker.UI/PrayerRequest.fs | 18 +++++++++------ src/PrayerTracker.UI/SmallGroup.fs | 10 ++++++--- src/PrayerTracker.UI/ViewModels.fs | 6 ++--- src/PrayerTracker/SmallGroup.fs | 2 +- src/PrayerTracker/wwwroot/js/app.js | 8 +++++++ src/names-to-lower.sql | 6 +++-- 9 files changed, 75 insertions(+), 19 deletions(-) diff --git a/src/PrayerTracker.Data/Entities.fs b/src/PrayerTracker.Data/Entities.fs index f037225..29bcfd1 100644 --- a/src/PrayerTracker.Data/Entities.fs +++ b/src/PrayerTracker.Data/Entities.fs @@ -312,6 +312,13 @@ type ListPreferences = /// How the as-of date should be automatically displayed AsOfDateDisplay : AsOfDateDisplay } +with + + /// The list of fonts to use when displaying request lists (converts "native" to native font stack) + member this.FontStack = + if this.Fonts = "native" then + """system-ui,-apple-system,"Segoe UI",Roboto,Ubuntu,"Liberation Sans",Cantarell,"Helvetica Neue",sans-serif""" + else this.Fonts /// Functions to support list preferences module ListPreferences = @@ -323,8 +330,8 @@ module ListPreferences = DaysToKeepNew = 7 LongTermUpdateWeeks = 4 EmailFromName = "PrayerTracker" - EmailFromAddress = "prayer@djs-consulting.com" - Fonts = "Century Gothic,Tahoma,Luxi Sans,sans-serif" + EmailFromAddress = "prayer@bitbadger.solutions" + Fonts = "native" HeadingColor = "maroon" LineColor = "navy" HeadingFontSize = 16 diff --git a/src/PrayerTracker.Tests/Data/EntitiesTests.fs b/src/PrayerTracker.Tests/Data/EntitiesTests.fs index 67a3b85..2168d44 100644 --- a/src/PrayerTracker.Tests/Data/EntitiesTests.fs +++ b/src/PrayerTracker.Tests/Data/EntitiesTests.fs @@ -98,6 +98,15 @@ let expirationTests = [] let listPreferencesTests = testList "ListPreferences" [ + test "FontStack is correct for native fonts" { + Expect.equal ListPreferences.empty.FontStack + """system-ui,-apple-system,"Segoe UI",Roboto,Ubuntu,"Liberation Sans",Cantarell,"Helvetica Neue",sans-serif""" + "The expected native font stack was incorrect" + } + test "FontStack is correct for specific fonts" { + Expect.equal { ListPreferences.empty with Fonts = "Arial,sans-serif" }.FontStack "Arial,sans-serif" + "The specified fonts were not returned correctly" + } test "empty is as expected" { let mt = ListPreferences.empty Expect.equal mt.SmallGroupId.Value Guid.Empty "The small group ID should have been an empty GUID" @@ -107,7 +116,7 @@ let listPreferencesTests = Expect.equal mt.EmailFromName "PrayerTracker" "The default e-mail from name should have been PrayerTracker" Expect.equal mt.EmailFromAddress "prayer@djs-consulting.com" "The default e-mail from address should have been prayer@djs-consulting.com" - Expect.equal mt.Fonts "Century Gothic,Tahoma,Luxi Sans,sans-serif" "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.LineColor "navy" "The default heding line color should have been navy" Expect.equal mt.HeadingFontSize 16 "The default heading font size should have been 16" diff --git a/src/PrayerTracker.UI/CommonFunctions.fs b/src/PrayerTracker.UI/CommonFunctions.fs index 8dabb0c..c31f8c5 100644 --- a/src/PrayerTracker.UI/CommonFunctions.fs +++ b/src/PrayerTracker.UI/CommonFunctions.fs @@ -78,6 +78,28 @@ let namedColorList name selected attrs (s : IStringLocalizer) = |> List.ofSeq |> select (_name name :: attrs) +/// Convert a named color to its hex notation +let colorToHex (color : string) = + match color with + | it when it.StartsWith "#" -> color + | "aqua" -> "#00ffff" + | "black" -> "#000000" + | "blue" -> "#0000ff" + | "fuchsia" -> "#ff00ff" + | "gray" -> "#808080" + | "green" -> "#008000" + | "lime" -> "#00ff00" + | "maroon" -> "#800000" + | "navy" -> "#000080" + | "olive" -> "#808000" + | "purple" -> "#800080" + | "red" -> "#ff0000" + | "silver" -> "#c0c0c0" + | "teal" -> "#008080" + | "white" -> "#ffffff" + | "yellow" -> "#ffff00" + | it -> it + /// Generate an input[type=radio] that is selected if its value is the current value let radio name domId value current = input [ _type "radio" diff --git a/src/PrayerTracker.UI/PrayerRequest.fs b/src/PrayerTracker.UI/PrayerRequest.fs index 8e2b6ad..3823a19 100644 --- a/src/PrayerTracker.UI/PrayerRequest.fs +++ b/src/PrayerTracker.UI/PrayerRequest.fs @@ -1,6 +1,6 @@ module PrayerTracker.Views.PrayerRequest -open System +open System.Globalization open System.IO open Giraffe open Giraffe.ViewEngine @@ -17,7 +17,11 @@ let edit (model : EditRequest) today ctx viewInfo = let s = I18N.localizer.Force () 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 ] [ + form [ _action "/prayer-request/save" + _method "post" + _class "pt-center-columns" + _onsubmit "PT.updateCKEditor()" + Target.content ] [ csrfToken ctx inputField "hidden" (nameof model.RequestId) model.RequestId [] div [ _fieldRow ] [ @@ -79,7 +83,7 @@ let email model viewInfo = let pageTitle = $"""{s["Prayer Requests"].Value} • {model.SmallGroup.Name}""" let prefs = model.SmallGroup.Preferences let addresses = model.Recipients |> List.map (fun mbr -> $"{mbr.Name} <{mbr.Email}>") |> String.concat ", " - [ p [ _style $"font-family:{prefs.Fonts};font-size:%i{prefs.TextFontSize}pt;" ] [ + [ p [ _style $"font-family:{prefs.FontStack};font-size:%i{prefs.TextFontSize}pt;" ] [ locStr s["The request list was sent to the following people, via individual e-mails"] rawText ":" br [] @@ -202,7 +206,7 @@ let maintain (model : MaintainRequests) (ctx : HttpContext) viewInfo = ] ] div [ updateClass ] [ - str (req.UpdatedDate.ToString(s["MMMM d, yyyy"].Value, Globalization.CultureInfo.CurrentUICulture)) + str (req.UpdatedDate.ToString(s["MMMM d, yyyy"].Value, CultureInfo.CurrentUICulture)) ] div [ _class "cell" ] [ locStr types[req.RequestType] ] div [ expiredClass ] [ str (match req.Requestor with Some r -> r | None -> " ") ] @@ -300,7 +304,7 @@ let print model version = rawText (model.AsHtml s) br [] hr [] - div [ _style $"font-size:70%%;font-family:{model.SmallGroup.Preferences.Fonts};" ] [ + div [ _style $"font-size:70%%;font-family:{model.SmallGroup.Preferences.FontStack};" ] [ img [ _src $"""/img/{s["footer_en"].Value}.png""" _style "vertical-align:text-bottom;" _alt imgAlt @@ -317,7 +321,7 @@ let view model viewInfo = let s = I18N.localizer.Force () let pageTitle = $"""{s["Prayer Requests"].Value} • {model.SmallGroup.Name}""" let spacer = rawText "       " - let dtString = model.Date.ToString ("yyyy-MM-dd", null) // TODO: this should be invariant + let dtString = model.Date.ToString ("yyyy-MM-dd", CultureInfo.InvariantCulture) [ div [ _class "pt-center-text" ] [ br [] a [ _class "pt-icon-link" @@ -333,7 +337,7 @@ let view model viewInfo = if date.DayOfWeek = IsoDayOfWeek.Sunday then date else findSunday (date.PlusDays 1) let sunday = findSunday model.Date a [ _class "pt-icon-link" - _href $"""/prayer-requests/view/{sunday.ToString ("yyyy-MM-dd", null)}""" // TODO: make invariant + _href $"""/prayer-requests/view/{sunday.ToString ("yyyy-MM-dd", CultureInfo.InvariantCulture)}""" _title s["List for Next Sunday"].Value ] [ icon "update"; rawText "  "; locStr s["List for Next Sunday"] ] diff --git a/src/PrayerTracker.UI/SmallGroup.fs b/src/PrayerTracker.UI/SmallGroup.fs index 3ecd603..7210903 100644 --- a/src/PrayerTracker.UI/SmallGroup.fs +++ b/src/PrayerTracker.UI/SmallGroup.fs @@ -14,7 +14,11 @@ let announcement isAdmin ctx viewInfo = let model = { SendToClass = ""; Text = ""; AddToRequestList = None; RequestType = None } let reqTypes = ReferenceList.requestTypeList s let vi = AppViewInfo.withOnLoadScript "PT.smallGroup.announcement.onPageLoad" viewInfo - form [ _action "/small-group/announcement/send"; _method "post"; _class "pt-center-columns"; Target.content ] [ + form [ _action "/small-group/announcement/send" + _method "post" + _class "pt-center-columns" + _onsubmit "PT.updateCKEditor()" + Target.content ] [ csrfToken ctx div [ _fieldRow ] [ div [ _inputFieldWith [ "pt-editor" ] ] [ @@ -461,7 +465,7 @@ let preferences (model : EditPreferences) ctx viewInfo = input [ _type "color" _name (nameof model.LineColor) _id $"{nameof model.LineColor}_Color" - _value model.LineColor // TODO: convert to hex or skip if named + _value (colorToHex model.LineColor) if not (model.LineColor.StartsWith "#") then _disabled ] ] ] @@ -483,7 +487,7 @@ let preferences (model : EditPreferences) ctx viewInfo = input [ _type "color" _name (nameof model.HeadingColor) _id $"{nameof model.HeadingColor}_Color" - _value model.HeadingColor // TODO: convert to hex or skip if named + _value (colorToHex model.HeadingColor) if not (model.HeadingColor.StartsWith "#") then _disabled ] ] ] diff --git a/src/PrayerTracker.UI/ViewModels.fs b/src/PrayerTracker.UI/ViewModels.fs index 0cf709a..4f9597c 100644 --- a/src/PrayerTracker.UI/ViewModels.fs +++ b/src/PrayerTracker.UI/ViewModels.fs @@ -778,7 +778,7 @@ with let p = this.SmallGroup.Preferences let asOfSize = Math.Round (float p.TextFontSize * 0.8, 2) [ if this.ShowHeader then - div [ _style $"text-align:center;font-family:{p.Fonts}" ] [ + div [ _style $"text-align:center;font-family:{p.FontStack}" ] [ span [ _style $"font-size:%i{p.HeadingFontSize}pt;" ] [ strong [] [ str s["Prayer Requests"].Value ] ] @@ -792,7 +792,7 @@ with br [] for _, name, reqs in this.RequestsByType s do div [ _style "padding-left:10px;padding-bottom:.5em;" ] [ - table [ _style $"font-family:{p.Fonts};page-break-inside:avoid;" ] [ + table [ _style $"font-family:{p.FontStack};page-break-inside:avoid;" ] [ tr [] [ td [ _style $"font-size:%i{p.HeadingFontSize}pt;color:{p.HeadingColor};padding:3px 0;border-top:solid 3px {p.LineColor};border-bottom:solid 3px {p.LineColor};font-weight:bold;" ] [ rawText "    "; str name.Value; rawText "    " @@ -804,7 +804,7 @@ with reqs |> List.map (fun req -> let bullet = if this.IsNew req then "circle" else "disc" - li [ _style $"list-style-type:{bullet};font-family:{p.Fonts};font-size:%i{p.TextFontSize}pt;padding-bottom:.25em;" ] [ + li [ _style $"list-style-type:{bullet};font-family:{p.FontStack};font-size:%i{p.TextFontSize}pt;padding-bottom:.25em;" ] [ match req.Requestor with | Some r when r <> "" -> strong [] [ str r ] diff --git a/src/PrayerTracker/SmallGroup.fs b/src/PrayerTracker/SmallGroup.fs index 6a2c552..9779148 100644 --- a/src/PrayerTracker/SmallGroup.fs +++ b/src/PrayerTracker/SmallGroup.fs @@ -260,7 +260,7 @@ let sendAnnouncement : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> // Reformat the text to use the class's font stylings let requestText = ckEditorToText model.Text let htmlText = - p [ _style $"font-family:{pref.Fonts};font-size:%d{pref.TextFontSize}pt;" ] [ rawText requestText ] + p [ _style $"font-family:{pref.FontStack};font-size:%d{pref.TextFontSize}pt;" ] [ rawText requestText ] |> renderHtmlNode let plainText = (htmlToPlainText >> wordWrap 74) htmlText // Send the e-mails diff --git a/src/PrayerTracker/wwwroot/js/app.js b/src/PrayerTracker/wwwroot/js/app.js index a0c0bdb..5244a0b 100644 --- a/src/PrayerTracker/wwwroot/js/app.js +++ b/src/PrayerTracker/wwwroot/js/app.js @@ -86,9 +86,17 @@ this.PT = { initCKEditor() { ClassicEditor .create(document.querySelector("#Text")) + .then(editor => window.ckEditor = editor) .catch(console.error) }, + /** + * Instruct the current CKEditor element to update its source (needed as htmx does not fire the submit event) + */ + updateCKEditor() { + window.ckEditor.updateElement() + }, + /** * Scripts for pages served by the Church controller */ diff --git a/src/names-to-lower.sql b/src/names-to-lower.sql index 87522e8..013ee3b 100644 --- a/src/names-to-lower.sql +++ b/src/names-to-lower.sql @@ -29,11 +29,14 @@ ALTER TABLE pt."ListPreference" RENAME COLUMN "PageSize" TO page_size; ALTER TABLE pt."ListPreference" RENAME COLUMN "AsOfDateDisplay" TO as_of_date_display; ALTER TABLE pt."ListPreference" RENAME CONSTRAINT "PK_ListPreference" TO pk_list_preference; ALTER TABLE pt."ListPreference" RENAME CONSTRAINT "FK_ListPreference_SmallGroup_SmallGroupId" TO fk_list_preference_small_group_id; -ALTER TABLE pt."ListPreference" RENAME CONSTRAINT "FK_ListPreference_TimeZone_TimeZoneId" TO fk_list_preference_time_zone_id; +ALTER TABLE pt."ListPreference" DROP CONSTRAINT "FK_ListPreference_TimeZone_TimeZoneId"; ALTER TABLE pt."ListPreference" RENAME TO list_preference; ALTER INDEX pt."IX_ListPreference_TimeZoneId" RENAME TO ix_list_preference_time_zone_id; +ALTER TABLE pt.list_preference ALTER COLUMN email_from_address SET DEFAULT 'prayer@bitbadger.solutions'; +ALTER TABLE pt.list_preference ALTER COLUMN fonts SET DEFAULT 'native'; + -- Small Group Member ALTER TABLE pt."Member" RENAME COLUMN "MemberId" TO id; ALTER TABLE pt."Member" RENAME COLUMN "SmallGroupId" TO small_group_id; @@ -77,7 +80,6 @@ ALTER TABLE pt."SmallGroup" RENAME TO small_group; ALTER INDEX pt."IX_SmallGroup_ChurchId" RENAME TO ix_small_group_church_id; -- Time Zone (goes away) -ALTER TABLE pt.list_preference DROP CONSTRAINT fk_list_preference_time_zone_id; DROP TABLE pt."TimeZone"; -- User