WIP on fixi implementation
This commit is contained in:
parent
7fb1eca2a3
commit
5240b78487
@ -5,7 +5,6 @@ open System.Threading.Tasks
|
|||||||
open Microsoft.Extensions.Caching.Distributed
|
open Microsoft.Extensions.Caching.Distributed
|
||||||
open NodaTime
|
open NodaTime
|
||||||
open Npgsql
|
open Npgsql
|
||||||
open Npgsql.FSharp
|
|
||||||
|
|
||||||
/// Helper types and functions for the cache
|
/// Helper types and functions for the cache
|
||||||
[<AutoOpen>]
|
[<AutoOpen>]
|
||||||
|
@ -3,40 +3,45 @@ module PrayerTracker.Views.CommonFunctions
|
|||||||
|
|
||||||
open System.IO
|
open System.IO
|
||||||
open System.Text.Encodings.Web
|
open System.Text.Encodings.Web
|
||||||
open Giraffe
|
|
||||||
open Giraffe.ViewEngine
|
open Giraffe.ViewEngine
|
||||||
open Microsoft.AspNetCore.Antiforgery
|
|
||||||
open Microsoft.AspNetCore.Http
|
|
||||||
open Microsoft.AspNetCore.Mvc.Localization
|
open Microsoft.AspNetCore.Mvc.Localization
|
||||||
open Microsoft.Extensions.Localization
|
open Microsoft.Extensions.Localization
|
||||||
|
|
||||||
/// Encoded text for a localized string
|
/// Encoded text for a localized string
|
||||||
let locStr (text : LocalizedString) = str text.Value
|
let locStr (text: LocalizedString) =
|
||||||
|
str text.Value
|
||||||
|
|
||||||
/// Raw text for a localized HTML string
|
/// Raw text for a localized HTML string
|
||||||
let rawLocText (writer : StringWriter) (text : LocalizedHtmlString) =
|
let rawLocText (writer: StringWriter) (text: LocalizedHtmlString) =
|
||||||
text.WriteTo (writer, HtmlEncoder.Default)
|
text.WriteTo(writer, HtmlEncoder.Default)
|
||||||
let txt = string writer
|
let txt = string writer
|
||||||
writer.GetStringBuilder().Clear () |> ignore
|
writer.GetStringBuilder().Clear() |> ignore
|
||||||
rawText txt
|
rawText txt
|
||||||
|
|
||||||
/// A space (used for back-to-back localization string breaks)
|
/// A space (used for back-to-back localization string breaks)
|
||||||
let space = rawText " "
|
let space = rawText " "
|
||||||
|
|
||||||
/// Generate a Material Design icon
|
/// Generate a Material Design icon
|
||||||
let icon name = i [ _class "material-icons" ] [ rawText name ]
|
let icon name =
|
||||||
|
i [ _class "material-icons" ] [ rawText name ]
|
||||||
|
|
||||||
/// Generate a Material Design icon, specifying the point size (must be defined in CSS)
|
/// Generate a Material Design icon, specifying the point size (must be defined in CSS)
|
||||||
let iconSized size name = i [ _class $"material-icons md-%i{size}" ] [ rawText name ]
|
let iconSized size name =
|
||||||
|
i [ _class $"material-icons md-%i{size}" ] [ rawText name ]
|
||||||
|
|
||||||
|
|
||||||
|
open Giraffe
|
||||||
|
open Microsoft.AspNetCore.Antiforgery
|
||||||
|
open Microsoft.AspNetCore.Http
|
||||||
|
|
||||||
/// Generate a CSRF prevention token
|
/// Generate a CSRF prevention token
|
||||||
let csrfToken (ctx : HttpContext) =
|
let csrfToken (ctx: HttpContext) =
|
||||||
let antiForgery = ctx.GetService<IAntiforgery> ()
|
let antiForgery = ctx.GetService<IAntiforgery>()
|
||||||
let tokenSet = antiForgery.GetAndStoreTokens ctx
|
let tokenSet = antiForgery.GetAndStoreTokens ctx
|
||||||
input [ _type "hidden"; _name tokenSet.FormFieldName; _value tokenSet.RequestToken ]
|
input [ _type "hidden"; _name tokenSet.FormFieldName; _value tokenSet.RequestToken ]
|
||||||
|
|
||||||
/// Create a summary for a table of items
|
/// Create a summary for a table of items
|
||||||
let tableSummary itemCount (s : IStringLocalizer) =
|
let tableSummary itemCount (s: IStringLocalizer) =
|
||||||
div [ _class "pt-center-text" ] [
|
div [ _class "pt-center-text" ] [
|
||||||
small [] [
|
small [] [
|
||||||
match itemCount with
|
match itemCount with
|
||||||
@ -48,7 +53,7 @@ let tableSummary itemCount (s : IStringLocalizer) =
|
|||||||
]
|
]
|
||||||
|
|
||||||
/// Generate a list of named HTML colors
|
/// Generate a list of named HTML colors
|
||||||
let namedColorList name selected attrs (s : IStringLocalizer) =
|
let namedColorList name selected attrs (s: IStringLocalizer) =
|
||||||
// The list of HTML named colors (name, display, text color)
|
// The list of HTML named colors (name, display, text color)
|
||||||
seq {
|
seq {
|
||||||
("aqua", s["Aqua"], "black")
|
("aqua", s["Aqua"], "black")
|
||||||
@ -79,7 +84,7 @@ let namedColorList name selected attrs (s : IStringLocalizer) =
|
|||||||
|> select (_name name :: attrs)
|
|> select (_name name :: attrs)
|
||||||
|
|
||||||
/// Convert a named color to its hex notation
|
/// Convert a named color to its hex notation
|
||||||
let colorToHex (color : string) =
|
let colorToHex (color: string) =
|
||||||
match color with
|
match color with
|
||||||
| it when it.StartsWith "#" -> color
|
| it when it.StartsWith "#" -> color
|
||||||
| "aqua" -> "#00ffff"
|
| "aqua" -> "#00ffff"
|
||||||
@ -100,7 +105,7 @@ let colorToHex (color : string) =
|
|||||||
| "yellow" -> "#ffff00"
|
| "yellow" -> "#ffff00"
|
||||||
| it -> it
|
| it -> it
|
||||||
|
|
||||||
/// Generate an input[type=radio] that is selected if its value is the current value
|
/// <summary>Generate an <c>input type=radio</c> that is selected if its value is the current value</summary>
|
||||||
let radio name domId value current =
|
let radio name domId value current =
|
||||||
input [ _type "radio"
|
input [ _type "radio"
|
||||||
_name name
|
_name name
|
||||||
@ -108,7 +113,7 @@ let radio name domId value current =
|
|||||||
_value value
|
_value value
|
||||||
if value = current then _checked ]
|
if value = current then _checked ]
|
||||||
|
|
||||||
/// Generate a select list with the current value selected
|
/// <summary>Generate a <c>select</c> list with the current value selected</summary>
|
||||||
let selectList name selected attrs items =
|
let selectList name selected attrs items =
|
||||||
items
|
items
|
||||||
|> Seq.map (fun (value, text) ->
|
|> Seq.map (fun (value, text) ->
|
||||||
@ -119,16 +124,18 @@ let selectList name selected attrs items =
|
|||||||
|> List.ofSeq
|
|> List.ofSeq
|
||||||
|> select (List.concat [ [ _name name; _id name ]; attrs ])
|
|> select (List.concat [ [ _name name; _id name ]; attrs ])
|
||||||
|
|
||||||
/// Generate the text for a default entry at the top of a select list
|
/// <summary>Generate the text for a default entry at the top of a <c>select</c> list</summary>
|
||||||
let selectDefault text = $"— %s{text} —"
|
let selectDefault text =
|
||||||
|
$"— %s{text} —"
|
||||||
|
|
||||||
/// Generate a standard submit button with icon and text
|
/// <summary>Generate a standard <c>button type=submit</c> with icon and text</summary>
|
||||||
let submit attrs ico text = button (_type "submit" :: attrs) [ icon ico; rawText " "; locStr text ]
|
let submit attrs ico text =
|
||||||
|
button (_type "submit" :: attrs) [ icon ico; rawText " "; locStr text ]
|
||||||
|
|
||||||
/// Create an HTML onsubmit event handler
|
/// Create an HTML onsubmit event handler
|
||||||
let _onsubmit = attr "onsubmit"
|
let _onsubmit = attr "onsubmit"
|
||||||
|
|
||||||
/// A "rel='noopener'" attribute
|
/// <summary>A <c>rel="noopener"</c> attribute</summary>
|
||||||
let _relNoOpener = _rel "noopener"
|
let _relNoOpener = _rel "noopener"
|
||||||
|
|
||||||
/// A class attribute that designates a row of fields, with the additional classes passed
|
/// A class attribute that designates a row of fields, with the additional classes passed
|
||||||
@ -153,12 +160,14 @@ let _checkboxField = _class "pt-checkbox-field"
|
|||||||
/// A group of related fields, inputs, links, etc., displayed in a row
|
/// A group of related fields, inputs, links, etc., displayed in a row
|
||||||
let _group = _class "pt-group"
|
let _group = _class "pt-group"
|
||||||
|
|
||||||
/// Create an input field of the given type, with matching name and ID and the given value
|
/// <summary>
|
||||||
|
/// Create an <c>input</c> field of the given <c>type</c>, with matching name and ID and the given value
|
||||||
|
/// </summary>
|
||||||
let inputField typ name value attrs =
|
let inputField typ name value attrs =
|
||||||
List.concat [ [ _type typ; _name name; _id name; if value <> "" then _value value ]; attrs ] |> input
|
List.concat [ [ _type typ; _name name; _id name; if value <> "" then _value value ]; attrs ] |> input
|
||||||
|
|
||||||
/// Generate a table heading with the given localized column names
|
/// Generate a table heading with the given localized column names
|
||||||
let tableHeadings (s : IStringLocalizer) (headings : string list) =
|
let tableHeadings (s: IStringLocalizer) (headings: string list) =
|
||||||
headings
|
headings
|
||||||
|> List.map (fun heading -> th [ _scope "col" ] [ locStr s[heading] ])
|
|> List.map (fun heading -> th [ _scope "col" ] [ locStr s[heading] ])
|
||||||
|> tr []
|
|> tr []
|
||||||
@ -166,12 +175,20 @@ let tableHeadings (s : IStringLocalizer) (headings : string list) =
|
|||||||
|> thead []
|
|> thead []
|
||||||
|
|
||||||
/// For a list of strings, prepend a pound sign and string them together with commas (CSS selector by ID)
|
/// For a list of strings, prepend a pound sign and string them together with commas (CSS selector by ID)
|
||||||
let toHtmlIds it = it |> List.map (fun x -> $"#%s{x}") |> String.concat ", "
|
let toHtmlIds it =
|
||||||
|
it |> List.map (fun x -> $"#%s{x}") |> String.concat ", "
|
||||||
|
|
||||||
/// The name this function used to have when the view engine was part of Giraffe
|
/// The name this function used to have when the view engine was part of Giraffe
|
||||||
let renderHtmlNode = RenderView.AsString.htmlNode
|
let renderHtmlNode = RenderView.AsString.htmlNode
|
||||||
|
|
||||||
|
|
||||||
|
open Giraffe.Fixi
|
||||||
|
|
||||||
|
/// Create a page link that will make the request with fixi
|
||||||
|
let pageLink href attrs content =
|
||||||
|
a (List.append [ _href href; _fxGet; _fxAction href; _fxTarget "#pt-body" ] attrs) content
|
||||||
|
|
||||||
|
|
||||||
open Microsoft.AspNetCore.Html
|
open Microsoft.AspNetCore.Html
|
||||||
|
|
||||||
/// Render an HTML node, then return the value as an HTML string
|
/// Render an HTML node, then return the value as an HTML string
|
||||||
@ -194,7 +211,7 @@ module TimeZones =
|
|||||||
]
|
]
|
||||||
|
|
||||||
/// Get the name of a time zone, given its Id
|
/// Get the name of a time zone, given its Id
|
||||||
let name timeZoneId (s : IStringLocalizer) =
|
let name timeZoneId (s: IStringLocalizer) =
|
||||||
match xref |> List.tryFind (fun it -> fst it = timeZoneId) with
|
match xref |> List.tryFind (fun it -> fst it = timeZoneId) with
|
||||||
| Some tz -> s[snd tz]
|
| Some tz -> s[snd tz]
|
||||||
| None ->
|
| None ->
|
||||||
|
@ -12,11 +12,11 @@ let private resAsmName = typeof<Common>.Assembly.GetName().Name
|
|||||||
/// Set up the string and HTML localizer factories
|
/// Set up the string and HTML localizer factories
|
||||||
let setUpFactories fac =
|
let setUpFactories fac =
|
||||||
stringLocFactory <- fac
|
stringLocFactory <- fac
|
||||||
htmlLocFactory <- HtmlLocalizerFactory stringLocFactory
|
htmlLocFactory <- HtmlLocalizerFactory stringLocFactory
|
||||||
|
|
||||||
/// An instance of the common string localizer
|
/// An instance of the common string localizer
|
||||||
let localizer = lazy (stringLocFactory.Create ("Common", resAsmName))
|
let localizer = lazy stringLocFactory.Create("Common", resAsmName)
|
||||||
|
|
||||||
/// Get a view localizer
|
/// Get a view localizer
|
||||||
let forView (view : string) =
|
let forView (view: string) =
|
||||||
htmlLocFactory.Create ($"Views.{view.Replace ('/', '.')}", resAsmName)
|
htmlLocFactory.Create($"Views.{view.Replace('/', '.')}", resAsmName)
|
||||||
|
@ -25,20 +25,25 @@ module Navigation =
|
|||||||
a [ _dropdown; _ariaLabel s["Requests"].Value; _title s["Requests"].Value; _roleButton ] [
|
a [ _dropdown; _ariaLabel s["Requests"].Value; _title s["Requests"].Value; _roleButton ] [
|
||||||
icon "question_answer"; space; locStr s["Requests"]; space; icon "keyboard_arrow_down" ]
|
icon "question_answer"; space; locStr s["Requests"]; space; icon "keyboard_arrow_down" ]
|
||||||
div [ _class "dropdown-content"; _roleMenuBar ] [
|
div [ _class "dropdown-content"; _roleMenuBar ] [
|
||||||
a [ _href "/prayer-requests"; _roleMenuItem ] [
|
pageLink "/prayer-requests"
|
||||||
icon "compare_arrows"; menuSpacer; locStr s["Maintain"] ]
|
[ _roleMenuItem ]
|
||||||
a [ _href "/prayer-requests/view"; _roleMenuItem ] [
|
[ icon "compare_arrows"; menuSpacer; locStr s["Maintain"] ]
|
||||||
icon "list"; menuSpacer; locStr s["View List"] ] ] ]
|
pageLink "/prayer-requests/view"
|
||||||
|
[ _roleMenuItem ]
|
||||||
|
[ icon "list"; menuSpacer; locStr s["View List"] ] ] ]
|
||||||
li [ _class "dropdown" ] [
|
li [ _class "dropdown" ] [
|
||||||
a [ _dropdown; _ariaLabel s["Group"].Value; _title s["Group"].Value; _roleButton ] [
|
a [ _dropdown; _ariaLabel s["Group"].Value; _title s["Group"].Value; _roleButton ] [
|
||||||
icon "group"; space; locStr s["Group"]; space; icon "keyboard_arrow_down" ]
|
icon "group"; space; locStr s["Group"]; space; icon "keyboard_arrow_down" ]
|
||||||
div [ _class "dropdown-content"; _roleMenuBar ] [
|
div [ _class "dropdown-content"; _roleMenuBar ] [
|
||||||
a [ _href "/small-group/members"; _roleMenuItem ] [
|
pageLink "/small-group/members"
|
||||||
icon "email"; menuSpacer; locStr s["Maintain Group Members"] ]
|
[ _roleMenuItem ]
|
||||||
a [ _href "/small-group/announcement"; _roleMenuItem ] [
|
[ icon "email"; menuSpacer; locStr s["Maintain Group Members"] ]
|
||||||
icon "send"; menuSpacer; locStr s["Send Announcement"] ]
|
pageLink "/small-group/announcement"
|
||||||
a [ _href "/small-group/preferences"; _roleMenuItem ] [
|
[ _roleMenuItem ]
|
||||||
icon "build"; menuSpacer; locStr s["Change Preferences"] ] ] ]
|
[ icon "send"; menuSpacer; locStr s["Send Announcement"] ]
|
||||||
|
pageLink "/small-group/preferences"
|
||||||
|
[ _roleMenuItem ]
|
||||||
|
[ icon "build"; menuSpacer; locStr s["Change Preferences"] ] ] ]
|
||||||
if u.IsAdmin then
|
if u.IsAdmin then
|
||||||
li [ _class "dropdown" ] [
|
li [ _class "dropdown" ] [
|
||||||
a [ _dropdown
|
a [ _dropdown
|
||||||
@ -47,31 +52,31 @@ module Navigation =
|
|||||||
_roleButton ] [
|
_roleButton ] [
|
||||||
icon "settings"; space; locStr s["Administration"]; space; icon "keyboard_arrow_down" ]
|
icon "settings"; space; locStr s["Administration"]; space; icon "keyboard_arrow_down" ]
|
||||||
div [ _class "dropdown-content"; _roleMenuBar ] [
|
div [ _class "dropdown-content"; _roleMenuBar ] [
|
||||||
a [ _href "/churches"; _roleMenuItem ] [ icon "home"; menuSpacer; locStr s["Churches"] ]
|
pageLink "/churches" [ _roleMenuItem ] [ icon "home"; menuSpacer; locStr s["Churches"] ]
|
||||||
a [ _href "/small-groups"; _roleMenuItem ] [
|
pageLink "/small-groups"
|
||||||
icon "send"; menuSpacer; locStr s["Groups"] ]
|
[ _roleMenuItem ]
|
||||||
a [ _href "/users"; _roleMenuItem ] [ icon "build"; menuSpacer; locStr s["Users"] ] ] ]
|
[ icon "send"; menuSpacer; locStr s["Groups"] ]
|
||||||
|
pageLink "/users" [ _roleMenuItem ] [ icon "build"; menuSpacer; locStr s["Users"] ] ] ]
|
||||||
| None ->
|
| None ->
|
||||||
match m.Group with
|
match m.Group with
|
||||||
| Some _ ->
|
| Some _ ->
|
||||||
li [] [
|
li [] [
|
||||||
a [ _href "/prayer-requests/view"
|
pageLink "/prayer-requests/view"
|
||||||
_ariaLabel s["View Request List"].Value
|
[ _ariaLabel s["View Request List"].Value; _title s["View Request List"].Value ]
|
||||||
_title s["View Request List"].Value ] [
|
[ icon "list"; space; locStr s["View Request List"] ] ]
|
||||||
icon "list"; space; locStr s["View Request List"] ] ]
|
|
||||||
| None ->
|
| None ->
|
||||||
li [ _class "dropdown" ] [
|
li [ _class "dropdown" ] [
|
||||||
a [ _dropdown; _ariaLabel s["Log On"].Value; _title s["Log On"].Value; _roleButton ] [
|
a [ _dropdown; _ariaLabel s["Log On"].Value; _title s["Log On"].Value; _roleButton ] [
|
||||||
icon "security"; space; locStr s["Log On"]; space; icon "keyboard_arrow_down" ]
|
icon "security"; space; locStr s["Log On"]; space; icon "keyboard_arrow_down" ]
|
||||||
div [ _class "dropdown-content"; _roleMenuBar ] [
|
div [ _class "dropdown-content"; _roleMenuBar ] [
|
||||||
a [ _href "/user/log-on"; _roleMenuItem ] [ icon "person"; menuSpacer; locStr s["User"] ]
|
pageLink "/user/log-on" [ _roleMenuItem ] [ icon "person"; menuSpacer; locStr s["User"] ]
|
||||||
a [ _href "/small-group/log-on"; _roleMenuItem ] [
|
pageLink "/small-group/log-on"
|
||||||
icon "group"; menuSpacer; locStr s["Group"] ] ] ]
|
[ _roleMenuItem ]
|
||||||
|
[ icon "group"; menuSpacer; locStr s["Group"] ] ] ]
|
||||||
li [] [
|
li [] [
|
||||||
a [ _href "/prayer-requests/lists"
|
pageLink "/prayer-requests/lists"
|
||||||
_ariaLabel s["View Request List"].Value
|
[ _ariaLabel s["View Request List"].Value; _title s["View Request List"].Value ]
|
||||||
_title s["View Request List"].Value ] [
|
[ icon "list"; space; locStr s["View Request List"] ] ]
|
||||||
icon "list"; space; locStr s["View Request List"] ] ]
|
|
||||||
li [] [
|
li [] [
|
||||||
a [ _href "/help"; _ariaLabel s["Help"].Value; _title s["View Help"].Value; _target "_blank" ] [
|
a [ _href "/help"; _ariaLabel s["Help"].Value; _title s["View Help"].Value; _target "_blank" ] [
|
||||||
icon "help"; space; locStr s["Help"] ] ] ]
|
icon "help"; space; locStr s["Help"] ] ] ]
|
||||||
@ -81,19 +86,19 @@ module Navigation =
|
|||||||
match m.User with
|
match m.User with
|
||||||
| Some _ ->
|
| Some _ ->
|
||||||
li [] [
|
li [] [
|
||||||
a [ _href "/user/password"
|
pageLink "/user/password"
|
||||||
_ariaLabel s["Change Your Password"].Value
|
[ _ariaLabel s["Change Your Password"].Value; _title s["Change Your Password"].Value ]
|
||||||
_title s["Change Your Password"].Value ] [
|
[ icon "lock"; space; locStr s["Change Your Password"] ] ]
|
||||||
icon "lock"; space; locStr s["Change Your Password"] ] ]
|
|
||||||
| None -> ()
|
| None -> ()
|
||||||
li [] [
|
li [] [
|
||||||
a [ _href "/log-off"; _ariaLabel s["Log Off"].Value; _title s["Log Off"].Value; Target.body ] [
|
pageLink "/log-off"
|
||||||
icon "power_settings_new"; space; locStr s["Log Off"] ] ] ]
|
[ _ariaLabel s["Log Off"].Value; _title s["Log Off"].Value; Target.body ]
|
||||||
|
[ icon "power_settings_new"; space; locStr s["Log Off"] ] ] ]
|
||||||
| None -> []
|
| None -> []
|
||||||
header [ _class "pt-title-bar"; Target.content ] [
|
header [ _class "pt-title-bar"; Target.content ] [
|
||||||
section [ _class "pt-title-bar-left"; _ariaLabel "Left side of top menu" ] [
|
section [ _class "pt-title-bar-left"; _ariaLabel "Left side of top menu" ] [
|
||||||
span [ _class "pt-title-bar-home" ] [
|
span [ _class "pt-title-bar-home" ] [
|
||||||
a [ _href "/"; _title s["Home"].Value ] [ locStr s["PrayerTracker"] ] ]
|
pageLink "/" [ _title s["Home"].Value ] [ locStr s["PrayerTracker"] ] ]
|
||||||
ul [] leftLinks ]
|
ul [] leftLinks ]
|
||||||
section [ _class "pt-title-bar-center"; _ariaLabel "Empty center space in top menu" ] []
|
section [ _class "pt-title-bar-center"; _ariaLabel "Empty center space in top menu" ] []
|
||||||
section [ _class "pt-title-bar-right"; _roleToolBar; _ariaLabel "Right side of top menu" ] [
|
section [ _class "pt-title-bar-right"; _roleToolBar; _ariaLabel "Right side of top menu" ] [
|
||||||
@ -109,11 +114,11 @@ module Navigation =
|
|||||||
| "es" ->
|
| "es" ->
|
||||||
strong [] [ locStr s["Spanish"] ]
|
strong [] [ locStr s["Spanish"] ]
|
||||||
rawText " "
|
rawText " "
|
||||||
a [ _href "/language/en" ] [ locStr s["Change to English"] ]
|
pageLink "/language/en" [] [ locStr s["Change to English"] ]
|
||||||
| _ ->
|
| _ ->
|
||||||
strong [] [ locStr s["English"] ]
|
strong [] [ locStr s["English"] ]
|
||||||
rawText " "
|
rawText " "
|
||||||
a [ _href "/language/es" ] [ locStr s["Cambie a Español"] ] ]
|
pageLink "/language/es" [] [ locStr s["Cambie a Español"] ] ]
|
||||||
match m.Group with
|
match m.Group with
|
||||||
| Some g ->
|
| Some g ->
|
||||||
[ match m.User with
|
[ match m.User with
|
||||||
@ -129,7 +134,7 @@ module Navigation =
|
|||||||
icon "group"
|
icon "group"
|
||||||
space
|
space
|
||||||
match m.User with
|
match m.User with
|
||||||
| Some _ -> a [ _href "/small-group"; Target.content ] [ strong [] [ str g.Name ] ]
|
| Some _ -> pageLink "/small-group" [] [ strong [] [ str g.Name ] ]
|
||||||
| None -> strong [] [ str g.Name ] ]
|
| None -> strong [] [ str g.Name ] ]
|
||||||
| None -> []
|
| None -> []
|
||||||
|> div [] ]
|
|> div [] ]
|
||||||
@ -153,7 +158,7 @@ let private commonHead = [
|
|||||||
meta [ _name "viewport"; _content "width=device-width, initial-scale=1" ]
|
meta [ _name "viewport"; _content "width=device-width, initial-scale=1" ]
|
||||||
meta [ _name "generator"; _content "Giraffe" ]
|
meta [ _name "generator"; _content "Giraffe" ]
|
||||||
link [ _rel "stylesheet"; _href "https://fonts.googleapis.com/icon?family=Material+Icons" ]
|
link [ _rel "stylesheet"; _href "https://fonts.googleapis.com/icon?family=Material+Icons" ]
|
||||||
link [ _rel "stylesheet"; _href "/css/app.css" ] ]
|
link [ _rel "stylesheet"; _href "/_/app.css" ] ]
|
||||||
|
|
||||||
/// Render the <head> portion of the page
|
/// Render the <head> portion of the page
|
||||||
let private htmlHead viewInfo pgTitle =
|
let private htmlHead viewInfo pgTitle =
|
||||||
@ -163,19 +168,16 @@ let private htmlHead viewInfo pgTitle =
|
|||||||
title [] [ locStr pgTitle; titleSep; locStr s["PrayerTracker"] ]
|
title [] [ locStr pgTitle; titleSep; locStr s["PrayerTracker"] ]
|
||||||
yield! commonHead
|
yield! commonHead
|
||||||
for cssFile in viewInfo.Style do
|
for cssFile in viewInfo.Style do
|
||||||
link [ _rel "stylesheet"; _href $"/css/{cssFile}.css"; _type "text/css" ] ]
|
link [ _rel "stylesheet"; _href $"/_/{cssFile}.css"; _type "text/css" ] ]
|
||||||
|
|
||||||
|
|
||||||
open Giraffe.ViewEngine.Htmx
|
|
||||||
|
|
||||||
/// Render a link to the help page for the current page
|
/// Render a link to the help page for the current page
|
||||||
let private helpLink link =
|
let private helpLink link =
|
||||||
let s = I18N.localizer.Force()
|
let s = I18N.localizer.Force()
|
||||||
sup [ _class "pt-help-link" ] [
|
sup [ _class "pt-help-link" ] [
|
||||||
a [ _href link
|
a [ _href link
|
||||||
_title s["Click for Help on This Page"].Value
|
_title s["Click for Help on This Page"].Value
|
||||||
_onclick $"return PT.showHelp('{link}')"
|
_onclick $"return PT.showHelp('{link}')" ] [ iconSized 18 "help_outline" ] ]
|
||||||
_hxNoBoost ] [ iconSized 18 "help_outline" ] ]
|
|
||||||
|
|
||||||
/// Render the page title, and optionally a help link
|
/// Render the page title, and optionally a help link
|
||||||
let private renderPageTitle viewInfo pgTitle =
|
let private renderPageTitle viewInfo pgTitle =
|
||||||
@ -217,9 +219,9 @@ let private htmlFooter viewInfo =
|
|||||||
let resultTime = (SystemClock.Instance.GetCurrentInstant() - viewInfo.RequestStart).TotalSeconds
|
let resultTime = (SystemClock.Instance.GetCurrentInstant() - viewInfo.RequestStart).TotalSeconds
|
||||||
footer [ _class "pt-footer" ] [
|
footer [ _class "pt-footer" ] [
|
||||||
div [ _id "pt-legal" ] [
|
div [ _id "pt-legal" ] [
|
||||||
a [ _href "/legal/privacy-policy" ] [ locStr s["Privacy Policy"] ]
|
pageLink "/legal/privacy-policy" [] [ locStr s["Privacy Policy"] ]
|
||||||
rawText " "
|
rawText " "
|
||||||
a [ _href "/legal/terms-of-service" ] [ locStr s["Terms of Service"] ]
|
pageLink "/legal/terms-of-service" [] [ locStr s["Terms of Service"] ]
|
||||||
rawText " "
|
rawText " "
|
||||||
a [ _href "https://git.bitbadger.solutions/bit-badger/PrayerTracker"
|
a [ _href "https://git.bitbadger.solutions/bit-badger/PrayerTracker"
|
||||||
_title s["View source code and get technical support"].Value
|
_title s["View source code and get technical support"].Value
|
||||||
@ -227,7 +229,7 @@ let private htmlFooter viewInfo =
|
|||||||
_relNoOpener ] [
|
_relNoOpener ] [
|
||||||
locStr s["Source & Support"] ] ]
|
locStr s["Source & Support"] ] ]
|
||||||
div [ _id "pt-footer" ] [
|
div [ _id "pt-footer" ] [
|
||||||
a [ _href "/"; _style "line-height:28px;" ] [
|
pageLink "/" [ _style "line-height:28px;" ] [
|
||||||
img [ _src $"""/img/%O{s["footer_en"]}.png"""
|
img [ _src $"""/img/%O{s["footer_en"]}.png"""
|
||||||
_alt imgText
|
_alt imgText
|
||||||
_title imgText
|
_title imgText
|
||||||
@ -268,19 +270,16 @@ let private partialHead pgTitle =
|
|||||||
meta [ _charset "UTF-8" ]
|
meta [ _charset "UTF-8" ]
|
||||||
title [] [ locStr pgTitle; titleSep; locStr s["PrayerTracker"] ] ]
|
title [] [ locStr pgTitle; titleSep; locStr s["PrayerTracker"] ] ]
|
||||||
|
|
||||||
open Giraffe.Htmx.Common
|
|
||||||
|
|
||||||
/// The body of the PrayerTracker layout
|
/// The body of the PrayerTracker layout
|
||||||
let private pageLayout viewInfo pgTitle content =
|
let private pageLayout viewInfo pgTitle content =
|
||||||
body [ _hxBoost ] [
|
body [] [
|
||||||
Navigation.top viewInfo
|
Navigation.top viewInfo
|
||||||
div [ _id "pt-body"; Target.content; _hxSwap $"{HxSwap.InnerHtml} show:window:top" ]
|
div [ _id "pt-body" ] (contentSection viewInfo pgTitle content)
|
||||||
(contentSection viewInfo pgTitle content)
|
|
||||||
match viewInfo.Layout with
|
match viewInfo.Layout with
|
||||||
| FullPage ->
|
| FullPage ->
|
||||||
Script.minified
|
|
||||||
script [ _src "/js/ckeditor/ckeditor.js" ] []
|
script [ _src "/js/ckeditor/ckeditor.js" ] []
|
||||||
script [ _src "/js/app.js" ] []
|
script [ _src "/_/fixi-0.5.7.js" ] []
|
||||||
|
script [ _src "/_/app.js" ] []
|
||||||
| _ -> () ]
|
| _ -> () ]
|
||||||
|
|
||||||
/// The standard layout(s) for PrayerTracker
|
/// The standard layout(s) for PrayerTracker
|
||||||
@ -316,8 +315,8 @@ let help pageTitle isHome content =
|
|||||||
meta [ _name "viewport"; _content "width=device-width, initial-scale=1" ]
|
meta [ _name "viewport"; _content "width=device-width, initial-scale=1" ]
|
||||||
title [] [ locStr pgTitle; titleSep; locStr s["PrayerTracker Help"] ]
|
title [] [ locStr pgTitle; titleSep; locStr s["PrayerTracker Help"] ]
|
||||||
link [ _href "https://fonts.googleapis.com/icon?family=Material+Icons"; _rel "stylesheet" ]
|
link [ _href "https://fonts.googleapis.com/icon?family=Material+Icons"; _rel "stylesheet" ]
|
||||||
link [ _href "/css/app.css"; _rel "stylesheet" ]
|
link [ _href "/_/app.css"; _rel "stylesheet" ]
|
||||||
link [ _href "/css/help.css"; _rel "stylesheet" ] ]
|
link [ _href "/_/help.css"; _rel "stylesheet" ] ]
|
||||||
body [] [
|
body [] [
|
||||||
header [ _class "pt-title-bar" ] [
|
header [ _class "pt-title-bar" ] [
|
||||||
section [ _class "pt-title-bar-left" ] [
|
section [ _class "pt-title-bar-left" ] [
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Giraffe.Fixi" Version="0.5.7" />
|
||||||
<PackageReference Include="Giraffe.ViewEngine" Version="1.4.0" />
|
<PackageReference Include="Giraffe.ViewEngine" Version="1.4.0" />
|
||||||
<PackageReference Include="Giraffe.ViewEngine.Htmx" Version="2.0.4" />
|
<PackageReference Include="Giraffe.ViewEngine.Htmx" Version="2.0.4" />
|
||||||
<PackageReference Include="MailKit" Version="4.9.0" />
|
<PackageReference Include="MailKit" Version="4.9.0" />
|
||||||
|
@ -19,10 +19,12 @@ let emptyGuid = shortGuid Guid.Empty
|
|||||||
module String =
|
module String =
|
||||||
|
|
||||||
/// string.Trim()
|
/// string.Trim()
|
||||||
let trim (str: string) = str.Trim()
|
let trim (str: string) =
|
||||||
|
str.Trim()
|
||||||
|
|
||||||
/// string.Replace()
|
/// string.Replace()
|
||||||
let replace (find: string) repl (str: string) = str.Replace(find, repl)
|
let replace (find: string) repl (str: string) =
|
||||||
|
str.Replace(find, repl)
|
||||||
|
|
||||||
/// Replace the first occurrence of a string with a second string within a given string
|
/// Replace the first occurrence of a string with a second string within a given string
|
||||||
let replaceFirst (needle: string) replacement (haystack: string) =
|
let replaceFirst (needle: string) replacement (haystack: string) =
|
||||||
@ -51,7 +53,7 @@ let stripTags allowedTags input =
|
|||||||
allowedTags
|
allowedTags
|
||||||
|> List.fold (fun acc t ->
|
|> List.fold (fun acc t ->
|
||||||
acc
|
acc
|
||||||
|| htmlTag.IndexOf $"<{t}>" = 0
|
|| htmlTag.IndexOf $"<%s{t}>" = 0
|
||||||
|| htmlTag.IndexOf $"<{t} " = 0
|
|| htmlTag.IndexOf $"<{t} " = 0
|
||||||
|| htmlTag.IndexOf $"</{t}" = 0) false
|
|| htmlTag.IndexOf $"</{t}" = 0) false
|
||||||
|> not
|
|> not
|
||||||
@ -60,7 +62,7 @@ let stripTags allowedTags input =
|
|||||||
|
|
||||||
|
|
||||||
/// Wrap a string at the specified number of characters
|
/// Wrap a string at the specified number of characters
|
||||||
let wordWrap charPerLine (input : string) =
|
let wordWrap charPerLine (input: string) =
|
||||||
match input.Length with
|
match input.Length with
|
||||||
| len when len <= charPerLine -> input
|
| len when len <= charPerLine -> input
|
||||||
| _ ->
|
| _ ->
|
||||||
@ -92,7 +94,7 @@ let wordWrap charPerLine (input : string) =
|
|||||||
|> String.concat "\n"
|
|> String.concat "\n"
|
||||||
|
|
||||||
/// Modify the text returned by CKEditor into the format we need for request and announcement text
|
/// Modify the text returned by CKEditor into the format we need for request and announcement text
|
||||||
let ckEditorToText (text : string) =
|
let ckEditorToText (text: string) =
|
||||||
[ "\n\t", ""
|
[ "\n\t", ""
|
||||||
" ", " "
|
" ", " "
|
||||||
" ", "  "
|
" ", "  "
|
||||||
@ -119,8 +121,8 @@ let htmlToPlainText html =
|
|||||||
|> String.replace "\u00a0" " "
|
|> String.replace "\u00a0" " "
|
||||||
|
|
||||||
/// Get the second portion of a tuple as a string
|
/// Get the second portion of a tuple as a string
|
||||||
let sndAsString x = (snd >> string) x
|
let sndAsString x =
|
||||||
|
(snd >> string) x
|
||||||
|
|
||||||
/// Make a URL with query string parameters
|
/// Make a URL with query string parameters
|
||||||
let makeUrl url qs =
|
let makeUrl url qs =
|
||||||
|
@ -10,14 +10,14 @@ open PrayerTracker.Entities
|
|||||||
module ReferenceList =
|
module ReferenceList =
|
||||||
|
|
||||||
/// A localized list of the AsOfDateDisplay DU cases
|
/// A localized list of the AsOfDateDisplay DU cases
|
||||||
let asOfDateList (s : IStringLocalizer) = [
|
let asOfDateList (s: IStringLocalizer) = [
|
||||||
AsOfDateDisplay.toCode NoDisplay, s["Do not display the “as of” date"]
|
AsOfDateDisplay.toCode NoDisplay, s["Do not display the “as of” date"]
|
||||||
AsOfDateDisplay.toCode ShortDate, s["Display a short “as of” date"]
|
AsOfDateDisplay.toCode ShortDate, s["Display a short “as of” date"]
|
||||||
AsOfDateDisplay.toCode LongDate, s["Display a full “as of” date"]
|
AsOfDateDisplay.toCode LongDate, s["Display a full “as of” date"]
|
||||||
]
|
]
|
||||||
|
|
||||||
/// A list of e-mail type options
|
/// A list of e-mail type options
|
||||||
let emailTypeList def (s : IStringLocalizer) =
|
let emailTypeList def (s: IStringLocalizer) =
|
||||||
// Localize the default type
|
// Localize the default type
|
||||||
let defaultType =
|
let defaultType =
|
||||||
s[match def with HtmlFormat -> "HTML Format" | PlainTextFormat -> "Plain-Text Format"].Value
|
s[match def with HtmlFormat -> "HTML Format" | PlainTextFormat -> "Plain-Text Format"].Value
|
||||||
@ -28,14 +28,14 @@ module ReferenceList =
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A list of expiration options
|
/// A list of expiration options
|
||||||
let expirationList (s : IStringLocalizer) includeExpireNow = [
|
let expirationList (s: IStringLocalizer) includeExpireNow = [
|
||||||
Expiration.toCode Automatic, s["Expire Normally"]
|
Expiration.toCode Automatic, s["Expire Normally"]
|
||||||
Expiration.toCode Manual, s["Request Never Expires"]
|
Expiration.toCode Manual, s["Request Never Expires"]
|
||||||
if includeExpireNow then Expiration.toCode Forced, s["Expire Immediately"]
|
if includeExpireNow then Expiration.toCode Forced, s["Expire Immediately"]
|
||||||
]
|
]
|
||||||
|
|
||||||
/// A list of request types
|
/// A list of request types
|
||||||
let requestTypeList (s : IStringLocalizer) = [
|
let requestTypeList (s: IStringLocalizer) = [
|
||||||
CurrentRequest, s["Current Requests"]
|
CurrentRequest, s["Current Requests"]
|
||||||
LongTermRequest, s["Long-Term Requests"]
|
LongTermRequest, s["Long-Term Requests"]
|
||||||
PraiseReport, s["Praise Reports"]
|
PraiseReport, s["Praise Reports"]
|
||||||
@ -63,7 +63,7 @@ module MessageLevel =
|
|||||||
| Warning -> "WARNING"
|
| Warning -> "WARNING"
|
||||||
| Error -> "ERROR"
|
| Error -> "ERROR"
|
||||||
|
|
||||||
let toCssClass level = (toString level).ToLowerInvariant ()
|
let toCssClass level = (toString level).ToLowerInvariant()
|
||||||
|
|
||||||
|
|
||||||
/// This is used to create a message that is displayed to the user
|
/// This is used to create a message that is displayed to the user
|
||||||
@ -217,7 +217,7 @@ type AssignGroups =
|
|||||||
module AssignGroups =
|
module AssignGroups =
|
||||||
|
|
||||||
/// Create an instance of this form from an existing user
|
/// Create an instance of this form from an existing user
|
||||||
let fromUser (user : User) =
|
let fromUser (user: User) =
|
||||||
{ UserId = shortGuid user.Id.Value
|
{ UserId = shortGuid user.Id.Value
|
||||||
UserName = user.Name
|
UserName = user.Name
|
||||||
SmallGroups = ""
|
SmallGroups = ""
|
||||||
@ -265,7 +265,7 @@ with
|
|||||||
member this.IsNew = emptyGuid = this.ChurchId
|
member this.IsNew = emptyGuid = this.ChurchId
|
||||||
|
|
||||||
/// Populate a church from this form
|
/// Populate a church from this form
|
||||||
member this.PopulateChurch (church : Church) =
|
member this.PopulateChurch (church: Church) =
|
||||||
{ church with
|
{ church with
|
||||||
Name = this.Name
|
Name = this.Name
|
||||||
City = this.City
|
City = this.City
|
||||||
@ -278,7 +278,7 @@ with
|
|||||||
module EditChurch =
|
module EditChurch =
|
||||||
|
|
||||||
/// Create an instance from an existing church
|
/// Create an instance from an existing church
|
||||||
let fromChurch (church : Church) =
|
let fromChurch (church: Church) =
|
||||||
{ ChurchId = shortGuid church.Id.Value
|
{ ChurchId = shortGuid church.Id.Value
|
||||||
Name = church.Name
|
Name = church.Name
|
||||||
City = church.City
|
City = church.City
|
||||||
@ -322,7 +322,7 @@ with
|
|||||||
module EditMember =
|
module EditMember =
|
||||||
|
|
||||||
/// Create an instance from an existing member
|
/// Create an instance from an existing member
|
||||||
let fromMember (mbr : Member) =
|
let fromMember (mbr: Member) =
|
||||||
{ MemberId = shortGuid mbr.Id.Value
|
{ MemberId = shortGuid mbr.Id.Value
|
||||||
Name = mbr.Name
|
Name = mbr.Name
|
||||||
Email = mbr.Email
|
Email = mbr.Email
|
||||||
@ -404,7 +404,7 @@ type EditPreferences =
|
|||||||
with
|
with
|
||||||
|
|
||||||
/// Set the properties of a small group based on the form's properties
|
/// Set the properties of a small group based on the form's properties
|
||||||
member this.PopulatePreferences (prefs : ListPreferences) =
|
member this.PopulatePreferences (prefs: ListPreferences) =
|
||||||
let isPublic, grpPw =
|
let isPublic, grpPw =
|
||||||
if this.Visibility = GroupVisibility.PublicList then true, ""
|
if this.Visibility = GroupVisibility.PublicList then true, ""
|
||||||
elif this.Visibility = GroupVisibility.HasPassword then false, (defaultArg this.GroupPassword "")
|
elif this.Visibility = GroupVisibility.HasPassword then false, (defaultArg this.GroupPassword "")
|
||||||
@ -432,7 +432,7 @@ with
|
|||||||
/// Support for the EditPreferences type
|
/// Support for the EditPreferences type
|
||||||
module EditPreferences =
|
module EditPreferences =
|
||||||
/// Populate an edit form from existing preferences
|
/// Populate an edit form from existing preferences
|
||||||
let fromPreferences (prefs : ListPreferences) =
|
let fromPreferences (prefs: ListPreferences) =
|
||||||
let setType (x : string) = match x.StartsWith "#" with true -> "RGB" | false -> "Name"
|
let setType (x : string) = match x.StartsWith "#" with true -> "RGB" | false -> "Name"
|
||||||
{ ExpireDays = prefs.DaysToExpire
|
{ ExpireDays = prefs.DaysToExpire
|
||||||
DaysToKeepNew = prefs.DaysToKeepNew
|
DaysToKeepNew = prefs.DaysToKeepNew
|
||||||
@ -504,7 +504,7 @@ module EditRequest =
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create an instance from an existing request
|
/// Create an instance from an existing request
|
||||||
let fromRequest (req : PrayerRequest) =
|
let fromRequest (req: PrayerRequest) =
|
||||||
{ empty with
|
{ empty with
|
||||||
RequestId = shortGuid req.Id.Value
|
RequestId = shortGuid req.Id.Value
|
||||||
RequestType = PrayerRequestType.toCode req.RequestType
|
RequestType = PrayerRequestType.toCode req.RequestType
|
||||||
@ -532,7 +532,7 @@ with
|
|||||||
member this.IsNew = emptyGuid = this.SmallGroupId
|
member this.IsNew = emptyGuid = this.SmallGroupId
|
||||||
|
|
||||||
/// Populate a small group from this form
|
/// Populate a small group from this form
|
||||||
member this.populateGroup (grp : SmallGroup) =
|
member this.populateGroup (grp: SmallGroup) =
|
||||||
{ grp with
|
{ grp with
|
||||||
Name = this.Name
|
Name = this.Name
|
||||||
ChurchId = idFromShort ChurchId this.ChurchId
|
ChurchId = idFromShort ChurchId this.ChurchId
|
||||||
@ -542,7 +542,7 @@ with
|
|||||||
module EditSmallGroup =
|
module EditSmallGroup =
|
||||||
|
|
||||||
/// Create an instance from an existing small group
|
/// Create an instance from an existing small group
|
||||||
let fromGroup (grp : SmallGroup) =
|
let fromGroup (grp: SmallGroup) =
|
||||||
{ SmallGroupId = shortGuid grp.Id.Value
|
{ SmallGroupId = shortGuid grp.Id.Value
|
||||||
Name = grp.Name
|
Name = grp.Name
|
||||||
ChurchId = shortGuid grp.ChurchId.Value
|
ChurchId = shortGuid grp.ChurchId.Value
|
||||||
@ -586,7 +586,7 @@ with
|
|||||||
member this.IsNew = emptyGuid = this.UserId
|
member this.IsNew = emptyGuid = this.UserId
|
||||||
|
|
||||||
/// Populate a user from the form
|
/// Populate a user from the form
|
||||||
member this.PopulateUser (user : User) hasher =
|
member this.PopulateUser (user: User) hasher =
|
||||||
{ user with
|
{ user with
|
||||||
FirstName = this.FirstName
|
FirstName = this.FirstName
|
||||||
LastName = this.LastName
|
LastName = this.LastName
|
||||||
@ -612,7 +612,7 @@ module EditUser =
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create an instance from an existing user
|
/// Create an instance from an existing user
|
||||||
let fromUser (user : User) =
|
let fromUser (user: User) =
|
||||||
{ empty with
|
{ empty with
|
||||||
UserId = shortGuid user.Id.Value
|
UserId = shortGuid user.Id.Value
|
||||||
FirstName = user.FirstName
|
FirstName = user.FirstName
|
||||||
@ -756,13 +756,13 @@ type RequestList =
|
|||||||
with
|
with
|
||||||
|
|
||||||
/// Group requests by their type, along with the type and its localized string
|
/// Group requests by their type, along with the type and its localized string
|
||||||
member this.RequestsByType (s : IStringLocalizer) =
|
member this.RequestsByType (s: IStringLocalizer) =
|
||||||
ReferenceList.requestTypeList s
|
ReferenceList.requestTypeList s
|
||||||
|> List.map (fun (typ, name) ->
|
|> List.map (fun (typ, name) ->
|
||||||
let sort =
|
let sort =
|
||||||
match this.SmallGroup.Preferences.RequestSort with
|
match this.SmallGroup.Preferences.RequestSort with
|
||||||
| SortByDate -> Seq.sortByDescending (fun req -> req.UpdatedDate)
|
| SortByDate -> Seq.sortByDescending _.UpdatedDate
|
||||||
| SortByRequestor -> Seq.sortBy (fun req -> req.Requestor)
|
| SortByRequestor -> Seq.sortBy _.Requestor
|
||||||
let reqs =
|
let reqs =
|
||||||
this.Requests
|
this.Requests
|
||||||
|> Seq.ofList
|
|> Seq.ofList
|
||||||
@ -773,12 +773,12 @@ with
|
|||||||
|> List.filter (fun (_, _, reqs) -> not (List.isEmpty reqs))
|
|> List.filter (fun (_, _, reqs) -> not (List.isEmpty reqs))
|
||||||
|
|
||||||
/// Is this request new?
|
/// Is this request new?
|
||||||
member this.IsNew (req : PrayerRequest) =
|
member this.IsNew (req: PrayerRequest) =
|
||||||
let reqDate = req.UpdatedDate.InZone(SmallGroup.timeZone this.SmallGroup).Date
|
let reqDate = req.UpdatedDate.InZone(SmallGroup.timeZone this.SmallGroup).Date
|
||||||
Period.Between(reqDate, this.Date, PeriodUnits.Days).Days <= this.SmallGroup.Preferences.DaysToKeepNew
|
Period.Between(reqDate, this.Date, PeriodUnits.Days).Days <= this.SmallGroup.Preferences.DaysToKeepNew
|
||||||
|
|
||||||
/// Generate this list as HTML
|
/// Generate this list as HTML
|
||||||
member this.AsHtml (s : IStringLocalizer) =
|
member this.AsHtml (s: IStringLocalizer) =
|
||||||
let p = this.SmallGroup.Preferences
|
let p = this.SmallGroup.Preferences
|
||||||
let asOfSize = Math.Round (float p.TextFontSize * 0.8, 2)
|
let asOfSize = Math.Round (float p.TextFontSize * 0.8, 2)
|
||||||
[ if this.ShowHeader then
|
[ if this.ShowHeader then
|
||||||
@ -822,8 +822,8 @@ with
|
|||||||
| LongDate ->
|
| LongDate ->
|
||||||
let dt =
|
let dt =
|
||||||
match p.AsOfDateDisplay with
|
match p.AsOfDateDisplay with
|
||||||
| ShortDate -> req.UpdatedDate.InZone(tz).Date.ToString ("d", null)
|
| ShortDate -> req.UpdatedDate.InZone(tz).Date.ToString("d", null)
|
||||||
| LongDate -> req.UpdatedDate.InZone(tz).Date.ToString ("D", null)
|
| LongDate -> req.UpdatedDate.InZone(tz).Date.ToString("D", null)
|
||||||
| _ -> ""
|
| _ -> ""
|
||||||
i [ _style $"font-size:%.2f{asOfSize}pt" ] [
|
i [ _style $"font-size:%.2f{asOfSize}pt" ] [
|
||||||
rawText " ("; str s["as of"].Value; str " "; str dt; rawText ")"
|
rawText " ("; str s["as of"].Value; str " "; str dt; rawText ")"
|
||||||
@ -835,17 +835,17 @@ with
|
|||||||
|> RenderView.AsString.htmlNodes
|
|> RenderView.AsString.htmlNodes
|
||||||
|
|
||||||
/// Generate this list as plain text
|
/// Generate this list as plain text
|
||||||
member this.AsText (s : IStringLocalizer) =
|
member this.AsText (s: IStringLocalizer) =
|
||||||
let tz = SmallGroup.timeZone this.SmallGroup
|
let tz = SmallGroup.timeZone this.SmallGroup
|
||||||
seq {
|
seq {
|
||||||
this.SmallGroup.Name
|
this.SmallGroup.Name
|
||||||
s["Prayer Requests"].Value
|
s["Prayer Requests"].Value
|
||||||
this.Date.ToString (s["MMMM d, yyyy"].Value, null)
|
this.Date.ToString(s["MMMM d, yyyy"].Value, null)
|
||||||
" "
|
" "
|
||||||
for _, name, reqs in this.RequestsByType s do
|
for _, name, reqs in this.RequestsByType s do
|
||||||
let dashes = String.replicate (name.Value.Length + 4) "-"
|
let dashes = String.replicate (name.Value.Length + 4) "-"
|
||||||
dashes
|
dashes
|
||||||
$" {name.Value.ToUpper ()}"
|
$" {name.Value.ToUpper()}"
|
||||||
dashes
|
dashes
|
||||||
for req in reqs do
|
for req in reqs do
|
||||||
let bullet = if this.IsNew req then "+" else "-"
|
let bullet = if this.IsNew req then "+" else "-"
|
||||||
@ -855,8 +855,8 @@ with
|
|||||||
| _ ->
|
| _ ->
|
||||||
let dt =
|
let dt =
|
||||||
match this.SmallGroup.Preferences.AsOfDateDisplay with
|
match this.SmallGroup.Preferences.AsOfDateDisplay with
|
||||||
| ShortDate -> req.UpdatedDate.InZone(tz).Date.ToString ("d", null)
|
| ShortDate -> req.UpdatedDate.InZone(tz).Date.ToString("d", null)
|
||||||
| LongDate -> req.UpdatedDate.InZone(tz).Date.ToString ("D", null)
|
| LongDate -> req.UpdatedDate.InZone(tz).Date.ToString("D", null)
|
||||||
| _ -> ""
|
| _ -> ""
|
||||||
$""" ({s["as of"].Value} {dt})"""
|
$""" ({s["as of"].Value} {dt})"""
|
||||||
|> sprintf " %s %s%s%s" bullet requestor (htmlToPlainText req.Text)
|
|> sprintf " %s %s%s%s" bullet requestor (htmlToPlainText req.Text)
|
||||||
|
@ -5,7 +5,7 @@ open Microsoft.AspNetCore.Http
|
|||||||
/// Middleware to add the starting ticks for the request
|
/// Middleware to add the starting ticks for the request
|
||||||
type RequestStartMiddleware (next: RequestDelegate) =
|
type RequestStartMiddleware (next: RequestDelegate) =
|
||||||
|
|
||||||
member this.InvokeAsync (ctx: HttpContext) = task {
|
member this.InvokeAsync(ctx: HttpContext) = task {
|
||||||
ctx.Items[Key.startTime] <- ctx.Now
|
ctx.Items[Key.startTime] <- ctx.Now
|
||||||
return! next.Invoke ctx
|
return! next.Invoke ctx
|
||||||
}
|
}
|
||||||
@ -45,14 +45,14 @@ module Configure =
|
|||||||
open PrayerTracker.Data
|
open PrayerTracker.Data
|
||||||
|
|
||||||
/// Configure ASP.NET Core's service collection (dependency injection container)
|
/// Configure ASP.NET Core's service collection (dependency injection container)
|
||||||
let services (svc : IServiceCollection) =
|
let services (svc: IServiceCollection) =
|
||||||
let _ = svc.AddOptions()
|
let _ = svc.AddOptions()
|
||||||
let _ = svc.AddLocalization(fun options -> options.ResourcesPath <- "Resources")
|
let _ = svc.AddLocalization(fun options -> options.ResourcesPath <- "Resources")
|
||||||
let _ =
|
let _ =
|
||||||
svc.Configure<RequestLocalizationOptions>(fun (opts: RequestLocalizationOptions) ->
|
svc.Configure<RequestLocalizationOptions>(fun (opts: RequestLocalizationOptions) ->
|
||||||
let supportedCultures = [|
|
let supportedCultures =
|
||||||
CultureInfo "en-US"; CultureInfo "en-GB"; CultureInfo "en-AU"; CultureInfo "en"
|
[| CultureInfo "en-US"; CultureInfo "en-GB"; CultureInfo "en-AU"; CultureInfo "en"
|
||||||
CultureInfo "es-MX"; CultureInfo "es-ES"; CultureInfo "es" |]
|
CultureInfo "es-MX"; CultureInfo "es-ES"; CultureInfo "es" |]
|
||||||
opts.DefaultRequestCulture <- RequestCulture("en-US", "en-US")
|
opts.DefaultRequestCulture <- RequestCulture("en-US", "en-US")
|
||||||
opts.SupportedCultures <- supportedCultures
|
opts.SupportedCultures <- supportedCultures
|
||||||
opts.SupportedUICultures <- supportedCultures)
|
opts.SupportedUICultures <- supportedCultures)
|
||||||
@ -190,7 +190,7 @@ module Configure =
|
|||||||
open Microsoft.Extensions.Options
|
open Microsoft.Extensions.Options
|
||||||
|
|
||||||
/// Configure the application
|
/// Configure the application
|
||||||
let app (app : IApplicationBuilder) =
|
let app (app: IApplicationBuilder) =
|
||||||
let env = app.ApplicationServices.GetRequiredService<IWebHostEnvironment>()
|
let env = app.ApplicationServices.GetRequiredService<IWebHostEnvironment>()
|
||||||
if env.IsDevelopment() then
|
if env.IsDevelopment() then
|
||||||
app.UseDeveloperExceptionPage()
|
app.UseDeveloperExceptionPage()
|
||||||
|
@ -56,19 +56,18 @@ type ClaimsPrincipal with
|
|||||||
|
|
||||||
/// The ID of the currently logged on small group
|
/// The ID of the currently logged on small group
|
||||||
member this.SmallGroupId =
|
member this.SmallGroupId =
|
||||||
if this.HasClaim (fun c -> c.Type = ClaimTypes.GroupSid) then
|
this.FindFirstValue ClaimTypes.GroupSid
|
||||||
Some (idFromShort SmallGroupId (this.FindFirst(fun c -> c.Type = ClaimTypes.GroupSid).Value))
|
|> Option.ofObj
|
||||||
else None
|
|> Option.map (idFromShort SmallGroupId)
|
||||||
|
|
||||||
/// The ID of the currently signed in user
|
/// The ID of the currently signed-in user
|
||||||
member this.UserId =
|
member this.UserId =
|
||||||
if this.HasClaim (fun c -> c.Type = ClaimTypes.NameIdentifier) then
|
this.FindFirstValue ClaimTypes.NameIdentifier
|
||||||
Some (idFromShort UserId (this.FindFirst(fun c -> c.Type = ClaimTypes.NameIdentifier).Value))
|
|> Option.ofObj
|
||||||
else None
|
|> Option.map (idFromShort UserId)
|
||||||
|
|
||||||
|
|
||||||
open Giraffe
|
open Giraffe
|
||||||
open Npgsql
|
|
||||||
|
|
||||||
/// Extensions on the ASP.NET Core HTTP context
|
/// Extensions on the ASP.NET Core HTTP context
|
||||||
type HttpContext with
|
type HttpContext with
|
||||||
@ -83,7 +82,7 @@ type HttpContext with
|
|||||||
member _.Strings = Views.I18N.localizer.Force()
|
member _.Strings = Views.I18N.localizer.Force()
|
||||||
|
|
||||||
/// The currently logged on small group (sets the value in the session if it is missing)
|
/// The currently logged on small group (sets the value in the session if it is missing)
|
||||||
member this.CurrentGroup () = task {
|
member this.CurrentGroup() = task {
|
||||||
match this.Session.CurrentGroup with
|
match this.Session.CurrentGroup with
|
||||||
| Some group -> return Some group
|
| Some group -> return Some group
|
||||||
| None ->
|
| None ->
|
||||||
@ -98,7 +97,7 @@ type HttpContext with
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The currently logged on user (sets the value in the session if it is missing)
|
/// The currently logged on user (sets the value in the session if it is missing)
|
||||||
member this.CurrentUser () = task {
|
member this.CurrentUser() = task {
|
||||||
match this.Session.CurrentUser with
|
match this.Session.CurrentUser with
|
||||||
| Some user -> return Some user
|
| Some user -> return Some user
|
||||||
| None ->
|
| None ->
|
||||||
|
87
src/PrayerTracker/wwwroot/_/fixi-0.5.7.js
Normal file
87
src/PrayerTracker/wwwroot/_/fixi-0.5.7.js
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
(()=>{
|
||||||
|
let send = (elt, type, detail, bub)=>elt.dispatchEvent(new CustomEvent("fx:" + type, {detail, cancelable:true, bubbles:bub !== false, composed:true}))
|
||||||
|
let attr = (elt, name, defaultVal)=>elt.getAttribute(name) || defaultVal
|
||||||
|
let ignore = (elt)=>elt.matches("[fx-ignore]") || elt.closest("[fx-ignore]") != null
|
||||||
|
let init = (elt)=>{
|
||||||
|
let options = {}
|
||||||
|
if (elt.__fixi || ignore(elt) || !send(elt, "init", {options})) return
|
||||||
|
elt.__fixi = async(evt)=>{
|
||||||
|
let reqs = elt.__fixi.requests ||= new Set()
|
||||||
|
let form = elt.form || elt.closest("form")
|
||||||
|
let body = new FormData(form ?? undefined, evt.submitter)
|
||||||
|
if (!form && elt.name) body.append(elt.name, elt.value)
|
||||||
|
let ac = new AbortController()
|
||||||
|
let cfg = {
|
||||||
|
trigger:evt,
|
||||||
|
action:attr(elt, "fx-action"),
|
||||||
|
method:attr(elt, "fx-method", "GET").toUpperCase(),
|
||||||
|
target: document.querySelector(attr(elt, "fx-target")) ?? elt,
|
||||||
|
swap:attr(elt, "fx-swap", "outerHTML"),
|
||||||
|
body,
|
||||||
|
drop:reqs.size,
|
||||||
|
headers:{"FX-Request":"true"},
|
||||||
|
abort:ac.abort.bind(ac),
|
||||||
|
signal:ac.signal,
|
||||||
|
preventTrigger:true,
|
||||||
|
transition:document.startViewTransition?.bind(document),
|
||||||
|
fetch:fetch.bind(window)
|
||||||
|
}
|
||||||
|
let go = send(elt, "config", {cfg, requests:reqs})
|
||||||
|
if (cfg.preventTrigger) evt.preventDefault()
|
||||||
|
if (!go || cfg.drop) return
|
||||||
|
if (/GET|DELETE/.test(cfg.method)){
|
||||||
|
let params = new URLSearchParams(cfg.body)
|
||||||
|
if (params.size)
|
||||||
|
cfg.action += (/\?/.test(cfg.action) ? "&" : "?") + params
|
||||||
|
cfg.body = null
|
||||||
|
}
|
||||||
|
reqs.add(cfg)
|
||||||
|
try {
|
||||||
|
if (cfg.confirm){
|
||||||
|
let result = await cfg.confirm()
|
||||||
|
if (!result) return
|
||||||
|
}
|
||||||
|
if (!send(elt, "before", {cfg, requests:reqs})) return
|
||||||
|
cfg.response = await cfg.fetch(cfg.action, cfg)
|
||||||
|
cfg.text = await cfg.response.text()
|
||||||
|
if (!send(elt, "after", {cfg})) return
|
||||||
|
} catch(error) {
|
||||||
|
send(elt, "error", {cfg, error})
|
||||||
|
return
|
||||||
|
} finally {
|
||||||
|
reqs.delete(cfg)
|
||||||
|
send(elt, "finally", {cfg})
|
||||||
|
}
|
||||||
|
let doSwap = ()=>{
|
||||||
|
if (cfg.swap instanceof Function)
|
||||||
|
return cfg.swap(cfg)
|
||||||
|
else if (/(before|after)(start|end)/.test(cfg.swap))
|
||||||
|
cfg.target.insertAdjacentHTML(cfg.swap, cfg.text)
|
||||||
|
else if(cfg.swap in cfg.target)
|
||||||
|
cfg.target[cfg.swap] = cfg.text
|
||||||
|
else throw cfg.swap
|
||||||
|
}
|
||||||
|
if (cfg.transition)
|
||||||
|
await cfg.transition(doSwap).finished
|
||||||
|
else
|
||||||
|
await doSwap()
|
||||||
|
send(elt, "swapped", {cfg})
|
||||||
|
}
|
||||||
|
elt.__fixi.evt = attr(elt, "fx-trigger", elt.matches("form") ? "submit" : elt.matches("input:not([type=button]),select,textarea") ? "change" : "click")
|
||||||
|
elt.addEventListener(elt.__fixi.evt, elt.__fixi, options)
|
||||||
|
send(elt, "inited", {}, false)
|
||||||
|
}
|
||||||
|
let process = (elt)=>{
|
||||||
|
if (elt instanceof Element){
|
||||||
|
if (ignore(elt)) return
|
||||||
|
if (elt.matches("[fx-action]")) init(elt)
|
||||||
|
elt.querySelectorAll("[fx-action]").forEach(init)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener("fx:process", (evt)=>process(evt.target))
|
||||||
|
document.addEventListener("DOMContentLoaded", ()=>{
|
||||||
|
document.__fixi_mo = new MutationObserver((recs)=>recs.forEach((r)=>r.type === "childList" && r.addedNodes.forEach((n)=>process(n))))
|
||||||
|
document.__fixi_mo.observe(document.body, {childList:true, subtree:true})
|
||||||
|
process(document.body)
|
||||||
|
})
|
||||||
|
})()
|
Loading…
x
Reference in New Issue
Block a user