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 NodaTime | ||||
| open Npgsql | ||||
| open Npgsql.FSharp | ||||
| 
 | ||||
| /// Helper types and functions for the cache | ||||
| [<AutoOpen>] | ||||
|  | ||||
| @ -3,40 +3,45 @@ module PrayerTracker.Views.CommonFunctions | ||||
| 
 | ||||
| open System.IO | ||||
| open System.Text.Encodings.Web | ||||
| open Giraffe | ||||
| open Giraffe.ViewEngine | ||||
| open Microsoft.AspNetCore.Antiforgery | ||||
| open Microsoft.AspNetCore.Http | ||||
| open Microsoft.AspNetCore.Mvc.Localization | ||||
| open Microsoft.Extensions.Localization | ||||
| 
 | ||||
| /// 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 | ||||
| let rawLocText (writer : StringWriter) (text : LocalizedHtmlString) = | ||||
|     text.WriteTo (writer, HtmlEncoder.Default) | ||||
| let rawLocText (writer: StringWriter) (text: LocalizedHtmlString) = | ||||
|     text.WriteTo(writer, HtmlEncoder.Default) | ||||
|     let txt = string writer | ||||
|     writer.GetStringBuilder().Clear () |> ignore | ||||
|     writer.GetStringBuilder().Clear() |> ignore | ||||
|     rawText txt | ||||
| 
 | ||||
| /// A space (used for back-to-back localization string breaks) | ||||
| let space = rawText " " | ||||
| 
 | ||||
| /// 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) | ||||
| 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 | ||||
| let csrfToken (ctx : HttpContext) = | ||||
|     let antiForgery = ctx.GetService<IAntiforgery> () | ||||
| let csrfToken (ctx: HttpContext) = | ||||
|     let antiForgery = ctx.GetService<IAntiforgery>() | ||||
|     let tokenSet    = antiForgery.GetAndStoreTokens ctx | ||||
|     input [ _type "hidden"; _name tokenSet.FormFieldName; _value tokenSet.RequestToken ] | ||||
| 
 | ||||
| /// Create a summary for a table of items | ||||
| let tableSummary itemCount (s : IStringLocalizer) = | ||||
| let tableSummary itemCount (s: IStringLocalizer) = | ||||
|     div [ _class "pt-center-text" ] [ | ||||
|         small [] [ | ||||
|             match itemCount with | ||||
| @ -48,7 +53,7 @@ let tableSummary itemCount (s : IStringLocalizer) = | ||||
|     ] | ||||
|       | ||||
| /// 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) | ||||
|     seq { | ||||
|         ("aqua",    s["Aqua"],    "black") | ||||
| @ -79,7 +84,7 @@ let namedColorList name selected attrs (s : IStringLocalizer) = | ||||
|     |> select (_name name :: attrs) | ||||
| 
 | ||||
| /// Convert a named color to its hex notation | ||||
| let colorToHex (color : string) = | ||||
| let colorToHex (color: string) = | ||||
|     match color with | ||||
|     | it when it.StartsWith "#" -> color | ||||
|     | "aqua"    -> "#00ffff" | ||||
| @ -100,7 +105,7 @@ let colorToHex (color : string) = | ||||
|     | "yellow"  -> "#ffff00" | ||||
|     | 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 = | ||||
|     input [ _type "radio" | ||||
|             _name name | ||||
| @ -108,7 +113,7 @@ let radio name domId value current = | ||||
|             _value value | ||||
|             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 = | ||||
|     items | ||||
|     |> Seq.map (fun (value, text) -> | ||||
| @ -119,16 +124,18 @@ let selectList name selected attrs items = | ||||
|     |> List.ofSeq | ||||
|     |> select (List.concat [ [ _name name; _id name ]; attrs ]) | ||||
| 
 | ||||
| /// Generate the text for a default entry at the top of a select list | ||||
| let selectDefault text = $"— %s{text} —" | ||||
| /// <summary>Generate the text for a default entry at the top of a <c>select</c> list</summary> | ||||
| let selectDefault text = | ||||
|     $"— %s{text} —" | ||||
| 
 | ||||
| /// Generate a standard submit button with icon and text | ||||
| let submit attrs ico text = button (_type "submit" :: attrs) [ icon ico; rawText "  "; locStr 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 ] | ||||
| 
 | ||||
| /// Create an HTML onsubmit event handler | ||||
| let _onsubmit = attr "onsubmit" | ||||
| 
 | ||||
| /// A "rel='noopener'" attribute | ||||
| /// <summary>A <c>rel="noopener"</c> attribute</summary> | ||||
| let _relNoOpener = _rel "noopener" | ||||
| 
 | ||||
| /// 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 | ||||
| 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 = | ||||
|     List.concat [ [ _type typ; _name name; _id name; if value <> "" then _value value ]; attrs ] |> input | ||||
| 
 | ||||
| /// Generate a table heading with the given localized column names | ||||
| let tableHeadings (s : IStringLocalizer) (headings : string list) = | ||||
| let tableHeadings (s: IStringLocalizer) (headings: string list) = | ||||
|     headings | ||||
|     |> List.map (fun heading -> th [ _scope "col" ] [ locStr s[heading] ]) | ||||
|     |> tr [] | ||||
| @ -166,12 +175,20 @@ let tableHeadings (s : IStringLocalizer) (headings : string list) = | ||||
|     |> thead [] | ||||
| 
 | ||||
| /// For a list of strings, prepend a pound sign and string them together with commas (CSS selector by ID) | ||||
| let toHtmlIds it = it |> List.map (fun x -> $"#%s{x}") |> String.concat ", " | ||||
| let toHtmlIds it = | ||||
|     it |> List.map (fun x -> $"#%s{x}") |> String.concat ", " | ||||
| 
 | ||||
| /// The name this function used to have when the view engine was part of Giraffe | ||||
| let renderHtmlNode = RenderView.AsString.htmlNode | ||||
| 
 | ||||
| 
 | ||||
| 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 | ||||
| 
 | ||||
| /// 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 | ||||
|     let name timeZoneId (s : IStringLocalizer) = | ||||
|     let name timeZoneId (s: IStringLocalizer) = | ||||
|         match xref |> List.tryFind (fun it -> fst it = timeZoneId) with | ||||
|         | Some tz -> s[snd tz] | ||||
|         | None -> | ||||
|  | ||||
| @ -12,11 +12,11 @@ let private resAsmName = typeof<Common>.Assembly.GetName().Name | ||||
| /// Set up the string and HTML localizer factories | ||||
| let setUpFactories fac = | ||||
|     stringLocFactory <- fac | ||||
|     htmlLocFactory <- HtmlLocalizerFactory stringLocFactory | ||||
|     htmlLocFactory   <- HtmlLocalizerFactory stringLocFactory | ||||
| 
 | ||||
| /// 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 | ||||
| let forView (view : string) = | ||||
|     htmlLocFactory.Create ($"Views.{view.Replace ('/', '.')}", resAsmName) | ||||
| let forView (view: string) = | ||||
|     htmlLocFactory.Create($"Views.{view.Replace('/', '.')}", resAsmName) | ||||
|  | ||||
| @ -25,20 +25,25 @@ module Navigation = | ||||
|                     a [ _dropdown; _ariaLabel s["Requests"].Value; _title s["Requests"].Value; _roleButton ] [ | ||||
|                         icon "question_answer"; space; locStr s["Requests"]; space; icon "keyboard_arrow_down" ] | ||||
|                     div [ _class "dropdown-content"; _roleMenuBar ] [ | ||||
|                         a [ _href "/prayer-requests"; _roleMenuItem ] [ | ||||
|                             icon "compare_arrows"; menuSpacer; locStr s["Maintain"] ] | ||||
|                         a [ _href "/prayer-requests/view"; _roleMenuItem ] [ | ||||
|                             icon "list"; menuSpacer; locStr s["View List"] ] ] ] | ||||
|                         pageLink "/prayer-requests" | ||||
|                             [ _roleMenuItem ] | ||||
|                             [ icon "compare_arrows"; menuSpacer; locStr s["Maintain"] ] | ||||
|                         pageLink "/prayer-requests/view" | ||||
|                             [ _roleMenuItem ] | ||||
|                             [ icon "list"; menuSpacer; locStr s["View List"] ] ] ] | ||||
|                 li [ _class "dropdown" ] [ | ||||
|                     a [ _dropdown; _ariaLabel s["Group"].Value; _title s["Group"].Value; _roleButton ] [ | ||||
|                         icon "group"; space; locStr s["Group"]; space; icon "keyboard_arrow_down" ] | ||||
|                     div [ _class "dropdown-content"; _roleMenuBar ] [ | ||||
|                         a [ _href "/small-group/members"; _roleMenuItem ] [ | ||||
|                             icon "email"; menuSpacer; locStr s["Maintain Group Members"] ] | ||||
|                         a [ _href "/small-group/announcement"; _roleMenuItem ] [ | ||||
|                             icon "send";  menuSpacer; locStr s["Send Announcement"] ] | ||||
|                         a [ _href "/small-group/preferences"; _roleMenuItem ] [ | ||||
|                             icon "build"; menuSpacer; locStr s["Change Preferences"] ] ] ] | ||||
|                         pageLink "/small-group/members" | ||||
|                             [ _roleMenuItem ] | ||||
|                             [ icon "email"; menuSpacer; locStr s["Maintain Group Members"] ] | ||||
|                         pageLink "/small-group/announcement" | ||||
|                             [ _roleMenuItem ] | ||||
|                             [ icon "send";  menuSpacer; locStr s["Send Announcement"] ] | ||||
|                         pageLink "/small-group/preferences" | ||||
|                             [ _roleMenuItem ] | ||||
|                             [ icon "build"; menuSpacer; locStr s["Change Preferences"] ] ] ] | ||||
|                 if u.IsAdmin then | ||||
|                     li [ _class "dropdown" ] [ | ||||
|                         a [ _dropdown | ||||
| @ -47,31 +52,31 @@ module Navigation = | ||||
|                             _roleButton ] [ | ||||
|                             icon "settings"; space; locStr s["Administration"]; space; icon "keyboard_arrow_down" ] | ||||
|                         div [ _class "dropdown-content"; _roleMenuBar ] [ | ||||
|                             a [ _href "/churches"; _roleMenuItem ] [ icon "home";  menuSpacer; locStr s["Churches"] ] | ||||
|                             a [ _href "/small-groups"; _roleMenuItem ] [ | ||||
|                                 icon "send";  menuSpacer; locStr s["Groups"] ] | ||||
|                             a [ _href "/users"; _roleMenuItem ] [ icon "build"; menuSpacer; locStr s["Users"] ] ] ] | ||||
|                             pageLink "/churches" [ _roleMenuItem ] [ icon "home";  menuSpacer; locStr s["Churches"] ] | ||||
|                             pageLink "/small-groups" | ||||
|                                 [ _roleMenuItem ] | ||||
|                                 [ icon "send";  menuSpacer; locStr s["Groups"] ] | ||||
|                             pageLink "/users" [ _roleMenuItem ] [ icon "build"; menuSpacer; locStr s["Users"] ] ] ] | ||||
|             | None -> | ||||
|                 match m.Group with | ||||
|                 | Some _ -> | ||||
|                     li [] [ | ||||
|                         a [ _href      "/prayer-requests/view" | ||||
|                             _ariaLabel s["View Request List"].Value | ||||
|                             _title     s["View Request List"].Value ] [ | ||||
|                             icon "list"; space; locStr s["View Request List"] ] ] | ||||
|                         pageLink "/prayer-requests/view" | ||||
|                             [ _ariaLabel s["View Request List"].Value; _title s["View Request List"].Value ] | ||||
|                             [ icon "list"; space; locStr s["View Request List"] ] ] | ||||
|                 | None -> | ||||
|                     li [ _class "dropdown" ] [ | ||||
|                         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" ] | ||||
|                         div [ _class "dropdown-content"; _roleMenuBar ] [ | ||||
|                             a [ _href "/user/log-on"; _roleMenuItem ] [ icon "person"; menuSpacer; locStr s["User"] ] | ||||
|                             a [ _href "/small-group/log-on"; _roleMenuItem ] [ | ||||
|                                 icon "group";  menuSpacer; locStr s["Group"] ] ] ] | ||||
|                             pageLink "/user/log-on" [ _roleMenuItem ] [ icon "person"; menuSpacer; locStr s["User"] ] | ||||
|                             pageLink "/small-group/log-on" | ||||
|                                 [ _roleMenuItem ] | ||||
|                                 [ icon "group";  menuSpacer; locStr s["Group"] ] ] ] | ||||
|                     li [] [ | ||||
|                         a [ _href      "/prayer-requests/lists" | ||||
|                             _ariaLabel s["View Request List"].Value | ||||
|                             _title     s["View Request List"].Value ] [ | ||||
|                             icon "list"; space; locStr s["View Request List"] ] ] | ||||
|                         pageLink "/prayer-requests/lists" | ||||
|                             [ _ariaLabel s["View Request List"].Value; _title s["View Request List"].Value ] | ||||
|                             [ icon "list"; space; locStr s["View Request List"] ] ] | ||||
|             li [] [ | ||||
|                 a [ _href "/help"; _ariaLabel s["Help"].Value; _title s["View Help"].Value; _target "_blank" ] [ | ||||
|                     icon "help"; space; locStr s["Help"] ] ] ] | ||||
| @ -81,19 +86,19 @@ module Navigation = | ||||
|                 match m.User with | ||||
|                 | Some _ -> | ||||
|                     li [] [ | ||||
|                         a [ _href      "/user/password" | ||||
|                             _ariaLabel s["Change Your Password"].Value | ||||
|                             _title     s["Change Your Password"].Value ] [ | ||||
|                             icon "lock"; space; locStr s["Change Your Password"] ] ] | ||||
|                         pageLink "/user/password" | ||||
|                             [ _ariaLabel s["Change Your Password"].Value; _title s["Change Your Password"].Value ] | ||||
|                             [ icon "lock"; space; locStr s["Change Your Password"] ] ] | ||||
|                 | None -> () | ||||
|                 li [] [ | ||||
|                     a [ _href "/log-off"; _ariaLabel s["Log Off"].Value; _title s["Log Off"].Value; Target.body ] [ | ||||
|                         icon "power_settings_new"; space; locStr s["Log Off"] ] ] ] | ||||
|                     pageLink "/log-off" | ||||
|                         [ _ariaLabel s["Log Off"].Value; _title s["Log Off"].Value; Target.body ] | ||||
|                         [ icon "power_settings_new"; space; locStr s["Log Off"] ] ] ] | ||||
|             | None -> [] | ||||
|         header [ _class "pt-title-bar"; Target.content ] [ | ||||
|             section [ _class "pt-title-bar-left"; _ariaLabel "Left side of top menu" ] [ | ||||
|                 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 ] | ||||
|             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" ] [ | ||||
| @ -109,11 +114,11 @@ module Navigation = | ||||
|                 | "es" ->  | ||||
|                     strong [] [ locStr s["Spanish"] ] | ||||
|                     rawText "     " | ||||
|                     a [ _href "/language/en" ] [ locStr s["Change to English"] ] | ||||
|                     pageLink "/language/en" [] [ locStr s["Change to English"] ] | ||||
|                 | _ -> | ||||
|                     strong [] [ locStr s["English"] ] | ||||
|                     rawText "     " | ||||
|                     a [ _href "/language/es" ] [ locStr s["Cambie a Español"] ] ] | ||||
|                     pageLink "/language/es" [] [ locStr s["Cambie a Español"] ] ] | ||||
|             match m.Group with | ||||
|             | Some g -> | ||||
|                 [ match m.User with | ||||
| @ -129,7 +134,7 @@ module Navigation = | ||||
|                   icon "group" | ||||
|                   space | ||||
|                   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 -> [] | ||||
|             |> div [] ] | ||||
| @ -153,7 +158,7 @@ let private commonHead = [ | ||||
|     meta [ _name "viewport"; _content "width=device-width, initial-scale=1" ] | ||||
|     meta [ _name "generator"; _content "Giraffe" ] | ||||
|     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 | ||||
| let private htmlHead viewInfo pgTitle = | ||||
| @ -163,19 +168,16 @@ let private htmlHead viewInfo pgTitle = | ||||
|         title [] [ locStr pgTitle; titleSep; locStr s["PrayerTracker"] ] | ||||
|         yield! commonHead | ||||
|         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 | ||||
| let private helpLink link = | ||||
|     let s = I18N.localizer.Force() | ||||
|     sup [ _class "pt-help-link" ] [ | ||||
|         a [ _href    link | ||||
|             _title   s["Click for Help on This Page"].Value | ||||
|             _onclick $"return PT.showHelp('{link}')" | ||||
|             _hxNoBoost ] [ iconSized 18 "help_outline" ] ] | ||||
|             _onclick $"return PT.showHelp('{link}')" ] [ iconSized 18 "help_outline" ] ] | ||||
| 
 | ||||
| /// Render the page title, and optionally a help link | ||||
| let private renderPageTitle viewInfo pgTitle = | ||||
| @ -217,9 +219,9 @@ let private htmlFooter viewInfo = | ||||
|     let resultTime = (SystemClock.Instance.GetCurrentInstant() - viewInfo.RequestStart).TotalSeconds | ||||
|     footer [ _class "pt-footer" ] [ | ||||
|         div [ _id "pt-legal" ] [ | ||||
|             a [ _href "/legal/privacy-policy" ] [ locStr s["Privacy Policy"] ] | ||||
|             pageLink "/legal/privacy-policy" [] [ locStr s["Privacy Policy"] ] | ||||
|             rawText "   " | ||||
|             a [ _href "/legal/terms-of-service" ] [ locStr s["Terms of Service"] ] | ||||
|             pageLink "/legal/terms-of-service" [] [ locStr s["Terms of Service"] ] | ||||
|             rawText "   " | ||||
|             a [ _href   "https://git.bitbadger.solutions/bit-badger/PrayerTracker" | ||||
|                 _title  s["View source code and get technical support"].Value | ||||
| @ -227,7 +229,7 @@ let private htmlFooter viewInfo = | ||||
|                 _relNoOpener ] [ | ||||
|                 locStr s["Source & Support"] ] ] | ||||
|         div [ _id "pt-footer" ] [ | ||||
|             a [ _href "/"; _style "line-height:28px;" ] [ | ||||
|             pageLink "/" [ _style "line-height:28px;" ] [ | ||||
|                 img [ _src   $"""/img/%O{s["footer_en"]}.png""" | ||||
|                       _alt   imgText | ||||
|                       _title imgText | ||||
| @ -268,19 +270,16 @@ let private partialHead pgTitle = | ||||
|         meta [ _charset "UTF-8" ] | ||||
|         title [] [ locStr pgTitle; titleSep; locStr s["PrayerTracker"] ] ] | ||||
| 
 | ||||
| open Giraffe.Htmx.Common | ||||
| 
 | ||||
| /// The body of the PrayerTracker layout | ||||
| let private pageLayout viewInfo pgTitle content = | ||||
|     body [ _hxBoost ] [ | ||||
|     body [] [ | ||||
|         Navigation.top viewInfo | ||||
|         div [ _id "pt-body"; Target.content; _hxSwap $"{HxSwap.InnerHtml} show:window:top" ] | ||||
|             (contentSection viewInfo pgTitle content) | ||||
|         div [ _id "pt-body" ] (contentSection viewInfo pgTitle content) | ||||
|         match viewInfo.Layout with | ||||
|         | FullPage -> | ||||
|             Script.minified | ||||
|             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 | ||||
| @ -316,8 +315,8 @@ let help pageTitle isHome content = | ||||
|             meta [ _name "viewport"; _content "width=device-width, initial-scale=1" ] | ||||
|             title [] [ locStr pgTitle; titleSep; locStr s["PrayerTracker Help"] ] | ||||
|             link [ _href "https://fonts.googleapis.com/icon?family=Material+Icons"; _rel "stylesheet" ] | ||||
|             link [ _href "/css/app.css"; _rel "stylesheet" ] | ||||
|             link [ _href "/css/help.css"; _rel "stylesheet" ] ] | ||||
|             link [ _href "/_/app.css"; _rel "stylesheet" ] | ||||
|             link [ _href "/_/help.css"; _rel "stylesheet" ] ] | ||||
|         body [] [ | ||||
|             header [ _class "pt-title-bar" ] [ | ||||
|                 section [ _class "pt-title-bar-left" ] [ | ||||
|  | ||||
| @ -15,6 +15,7 @@ | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Giraffe.Fixi" Version="0.5.7" /> | ||||
|     <PackageReference Include="Giraffe.ViewEngine" Version="1.4.0" /> | ||||
|     <PackageReference Include="Giraffe.ViewEngine.Htmx" Version="2.0.4" /> | ||||
|     <PackageReference Include="MailKit" Version="4.9.0" /> | ||||
|  | ||||
| @ -19,10 +19,12 @@ let emptyGuid = shortGuid Guid.Empty | ||||
| module String = | ||||
|    | ||||
|     /// string.Trim() | ||||
|     let trim (str: string) = str.Trim() | ||||
|     let trim (str: string) = | ||||
|         str.Trim() | ||||
| 
 | ||||
|     /// 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 | ||||
|     let replaceFirst (needle: string) replacement (haystack: string) = | ||||
| @ -51,7 +53,7 @@ let stripTags allowedTags input = | ||||
|             allowedTags | ||||
|             |> List.fold (fun acc t -> | ||||
|                    acc | ||||
|                 || htmlTag.IndexOf $"<{t}>" = 0 | ||||
|                 || htmlTag.IndexOf $"<%s{t}>" = 0 | ||||
|                 || htmlTag.IndexOf $"<{t} " = 0 | ||||
|                 || htmlTag.IndexOf $"</{t}" = 0) false | ||||
|             |> not | ||||
| @ -60,7 +62,7 @@ let stripTags allowedTags input = | ||||
| 
 | ||||
| 
 | ||||
| /// Wrap a string at the specified number of characters | ||||
| let wordWrap charPerLine (input : string) = | ||||
| let wordWrap charPerLine (input: string) = | ||||
|     match input.Length with | ||||
|     | len when len <= charPerLine -> input | ||||
|     | _ -> | ||||
| @ -92,7 +94,7 @@ let wordWrap charPerLine (input : string) = | ||||
|         |> String.concat "\n" | ||||
| 
 | ||||
| /// 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",    "" | ||||
|       " ",  " " | ||||
|       "  ",      "  " | ||||
| @ -119,8 +121,8 @@ let htmlToPlainText html = | ||||
|         |> String.replace "\u00a0" " " | ||||
| 
 | ||||
| /// 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 | ||||
| let makeUrl url qs = | ||||
|  | ||||
| @ -10,14 +10,14 @@ open PrayerTracker.Entities | ||||
| module ReferenceList = | ||||
| 
 | ||||
|     /// 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 ShortDate, s["Display a short “as of” date"] | ||||
|         AsOfDateDisplay.toCode LongDate,  s["Display a full “as of” date"] | ||||
|     ] | ||||
| 
 | ||||
|     /// A list of e-mail type options | ||||
|     let emailTypeList def (s : IStringLocalizer) = | ||||
|     let emailTypeList def (s: IStringLocalizer) = | ||||
|         // Localize the default type | ||||
|         let defaultType = | ||||
|             s[match def with HtmlFormat -> "HTML Format" | PlainTextFormat -> "Plain-Text Format"].Value | ||||
| @ -28,14 +28,14 @@ module ReferenceList = | ||||
|           } | ||||
| 
 | ||||
|     /// A list of expiration options | ||||
|     let expirationList (s : IStringLocalizer) includeExpireNow = [ | ||||
|     let expirationList (s: IStringLocalizer) includeExpireNow = [ | ||||
|         Expiration.toCode Automatic, s["Expire Normally"] | ||||
|         Expiration.toCode Manual,    s["Request Never Expires"] | ||||
|         if includeExpireNow then Expiration.toCode Forced, s["Expire Immediately"] | ||||
|     ] | ||||
| 
 | ||||
|     /// A list of request types | ||||
|     let requestTypeList (s : IStringLocalizer) = [ | ||||
|     let requestTypeList (s: IStringLocalizer) = [ | ||||
|         CurrentRequest,  s["Current Requests"] | ||||
|         LongTermRequest, s["Long-Term Requests"] | ||||
|         PraiseReport,    s["Praise Reports"] | ||||
| @ -63,7 +63,7 @@ module MessageLevel = | ||||
|         | Warning -> "WARNING" | ||||
|         | 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 | ||||
| @ -217,7 +217,7 @@ type AssignGroups = | ||||
| module AssignGroups = | ||||
|      | ||||
|     /// Create an instance of this form from an existing user | ||||
|     let fromUser (user : User) = | ||||
|     let fromUser (user: User) = | ||||
|         {   UserId      = shortGuid user.Id.Value | ||||
|             UserName    = user.Name | ||||
|             SmallGroups = "" | ||||
| @ -265,7 +265,7 @@ with | ||||
|     member this.IsNew = emptyGuid = this.ChurchId | ||||
|      | ||||
|     /// Populate a church from this form | ||||
|     member this.PopulateChurch (church : Church) = | ||||
|     member this.PopulateChurch (church: Church) = | ||||
|         { church with | ||||
|             Name             = this.Name | ||||
|             City             = this.City | ||||
| @ -278,7 +278,7 @@ with | ||||
| module EditChurch = | ||||
|      | ||||
|     /// Create an instance from an existing church | ||||
|     let fromChurch (church : Church) = | ||||
|     let fromChurch (church: Church) = | ||||
|         {   ChurchId         = shortGuid church.Id.Value | ||||
|             Name             = church.Name | ||||
|             City             = church.City | ||||
| @ -322,7 +322,7 @@ with | ||||
| module EditMember = | ||||
|      | ||||
|     /// Create an instance from an existing member | ||||
|     let fromMember (mbr : Member) = | ||||
|     let fromMember (mbr: Member) = | ||||
|         {   MemberId = shortGuid mbr.Id.Value | ||||
|             Name     = mbr.Name | ||||
|             Email    = mbr.Email | ||||
| @ -404,7 +404,7 @@ type EditPreferences = | ||||
| with | ||||
|    | ||||
|     /// 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 = | ||||
|             if   this.Visibility = GroupVisibility.PublicList  then true, "" | ||||
|             elif this.Visibility = GroupVisibility.HasPassword then false, (defaultArg this.GroupPassword "") | ||||
| @ -432,7 +432,7 @@ with | ||||
| /// Support for the EditPreferences type | ||||
| module EditPreferences = | ||||
|     /// 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" | ||||
|         {   ExpireDays          = prefs.DaysToExpire | ||||
|             DaysToKeepNew       = prefs.DaysToKeepNew | ||||
| @ -504,7 +504,7 @@ module EditRequest = | ||||
|         } | ||||
|      | ||||
|     /// Create an instance from an existing request | ||||
|     let fromRequest (req : PrayerRequest) = | ||||
|     let fromRequest (req: PrayerRequest) = | ||||
|         { empty with | ||||
|             RequestId   = shortGuid req.Id.Value | ||||
|             RequestType = PrayerRequestType.toCode req.RequestType | ||||
| @ -532,7 +532,7 @@ with | ||||
|     member this.IsNew = emptyGuid = this.SmallGroupId | ||||
|      | ||||
|     /// Populate a small group from this form | ||||
|     member this.populateGroup (grp : SmallGroup) = | ||||
|     member this.populateGroup (grp: SmallGroup) = | ||||
|         { grp with | ||||
|             Name     = this.Name | ||||
|             ChurchId = idFromShort ChurchId this.ChurchId | ||||
| @ -542,7 +542,7 @@ with | ||||
| module EditSmallGroup = | ||||
|      | ||||
|     /// Create an instance from an existing small group | ||||
|     let fromGroup (grp : SmallGroup) = | ||||
|     let fromGroup (grp: SmallGroup) = | ||||
|         {   SmallGroupId = shortGuid grp.Id.Value | ||||
|             Name         = grp.Name | ||||
|             ChurchId     = shortGuid grp.ChurchId.Value | ||||
| @ -586,7 +586,7 @@ with | ||||
|     member this.IsNew = emptyGuid = this.UserId | ||||
|    | ||||
|     /// Populate a user from the form | ||||
|     member this.PopulateUser (user : User) hasher = | ||||
|     member this.PopulateUser (user: User) hasher = | ||||
|         { user with | ||||
|             FirstName = this.FirstName | ||||
|             LastName  = this.LastName | ||||
| @ -612,7 +612,7 @@ module EditUser = | ||||
|         } | ||||
|      | ||||
|     /// Create an instance from an existing user | ||||
|     let fromUser (user : User) = | ||||
|     let fromUser (user: User) = | ||||
|         { empty with | ||||
|             UserId    = shortGuid user.Id.Value | ||||
|             FirstName = user.FirstName | ||||
| @ -756,13 +756,13 @@ type RequestList = | ||||
| with | ||||
| 
 | ||||
|     /// 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 | ||||
|         |> List.map (fun (typ, name) -> | ||||
|             let sort = | ||||
|                 match this.SmallGroup.Preferences.RequestSort with | ||||
|                 | SortByDate -> Seq.sortByDescending (fun req -> req.UpdatedDate) | ||||
|                 | SortByRequestor -> Seq.sortBy (fun req -> req.Requestor) | ||||
|                 | SortByDate -> Seq.sortByDescending _.UpdatedDate | ||||
|                 | SortByRequestor -> Seq.sortBy _.Requestor | ||||
|             let reqs = | ||||
|                 this.Requests | ||||
|                 |> Seq.ofList | ||||
| @ -773,12 +773,12 @@ with | ||||
|         |> List.filter (fun (_, _, reqs) -> not (List.isEmpty reqs)) | ||||
|      | ||||
|     /// Is this request new? | ||||
|     member this.IsNew (req : PrayerRequest) = | ||||
|     member this.IsNew (req: PrayerRequest) = | ||||
|         let reqDate = req.UpdatedDate.InZone(SmallGroup.timeZone this.SmallGroup).Date | ||||
|         Period.Between(reqDate, this.Date, PeriodUnits.Days).Days <= this.SmallGroup.Preferences.DaysToKeepNew | ||||
|      | ||||
|     /// Generate this list as HTML | ||||
|     member this.AsHtml (s : IStringLocalizer) = | ||||
|     member this.AsHtml (s: IStringLocalizer) = | ||||
|         let p        = this.SmallGroup.Preferences | ||||
|         let asOfSize = Math.Round (float p.TextFontSize * 0.8, 2) | ||||
|         [   if this.ShowHeader then | ||||
| @ -822,8 +822,8 @@ with | ||||
|                         | LongDate -> | ||||
|                             let dt = | ||||
|                                 match p.AsOfDateDisplay with | ||||
|                                 | ShortDate -> req.UpdatedDate.InZone(tz).Date.ToString ("d", null) | ||||
|                                 | LongDate -> 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) | ||||
|                                 | _ -> "" | ||||
|                             i [ _style $"font-size:%.2f{asOfSize}pt" ] [ | ||||
|                                 rawText "  ("; str s["as of"].Value; str " "; str dt; rawText ")" | ||||
| @ -835,17 +835,17 @@ with | ||||
|         |> RenderView.AsString.htmlNodes | ||||
| 
 | ||||
|     /// Generate this list as plain text | ||||
|     member this.AsText (s : IStringLocalizer) = | ||||
|     member this.AsText (s: IStringLocalizer) = | ||||
|         let tz = SmallGroup.timeZone this.SmallGroup | ||||
|         seq { | ||||
|             this.SmallGroup.Name | ||||
|             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 | ||||
|                 let dashes = String.replicate (name.Value.Length + 4) "-" | ||||
|                 dashes | ||||
|                 $"  {name.Value.ToUpper ()}" | ||||
|                 $"  {name.Value.ToUpper()}" | ||||
|                 dashes | ||||
|                 for req in reqs do | ||||
|                     let bullet    = if this.IsNew req then "+" else "-" | ||||
| @ -855,8 +855,8 @@ with | ||||
|                     | _ -> | ||||
|                         let dt = | ||||
|                             match this.SmallGroup.Preferences.AsOfDateDisplay with | ||||
|                             | ShortDate -> req.UpdatedDate.InZone(tz).Date.ToString ("d", null) | ||||
|                             | LongDate -> 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) | ||||
|                             | _ -> "" | ||||
|                         $"""  ({s["as of"].Value} {dt})""" | ||||
|                     |> 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 | ||||
| type RequestStartMiddleware (next: RequestDelegate) = | ||||
|      | ||||
|     member this.InvokeAsync (ctx: HttpContext) = task { | ||||
|     member this.InvokeAsync(ctx: HttpContext) = task { | ||||
|         ctx.Items[Key.startTime] <- ctx.Now | ||||
|         return! next.Invoke ctx | ||||
|     } | ||||
| @ -45,14 +45,14 @@ module Configure = | ||||
|     open PrayerTracker.Data | ||||
|      | ||||
|     /// Configure ASP.NET Core's service collection (dependency injection container) | ||||
|     let services (svc : IServiceCollection) = | ||||
|     let services (svc: IServiceCollection) = | ||||
|         let _ = svc.AddOptions() | ||||
|         let _ = svc.AddLocalization(fun options -> options.ResourcesPath <- "Resources") | ||||
|         let _ = | ||||
|             svc.Configure<RequestLocalizationOptions>(fun (opts: RequestLocalizationOptions) -> | ||||
|                 let supportedCultures = [| | ||||
|                     CultureInfo "en-US"; CultureInfo "en-GB"; CultureInfo "en-AU"; CultureInfo "en" | ||||
|                     CultureInfo "es-MX"; CultureInfo "es-ES"; CultureInfo "es" |] | ||||
|                 let supportedCultures = | ||||
|                     [| CultureInfo "en-US"; CultureInfo "en-GB"; CultureInfo "en-AU"; CultureInfo "en" | ||||
|                        CultureInfo "es-MX"; CultureInfo "es-ES"; CultureInfo "es" |] | ||||
|                 opts.DefaultRequestCulture <- RequestCulture("en-US", "en-US") | ||||
|                 opts.SupportedCultures     <- supportedCultures | ||||
|                 opts.SupportedUICultures   <- supportedCultures) | ||||
| @ -190,7 +190,7 @@ module Configure = | ||||
|     open Microsoft.Extensions.Options | ||||
|      | ||||
|     /// Configure the application | ||||
|     let app (app : IApplicationBuilder) = | ||||
|     let app (app: IApplicationBuilder) = | ||||
|         let env = app.ApplicationServices.GetRequiredService<IWebHostEnvironment>() | ||||
|         if env.IsDevelopment() then | ||||
|             app.UseDeveloperExceptionPage() | ||||
|  | ||||
| @ -56,19 +56,18 @@ type ClaimsPrincipal with | ||||
|      | ||||
|     /// The ID of the currently logged on small group     | ||||
|     member this.SmallGroupId = | ||||
|         if this.HasClaim (fun c -> c.Type = ClaimTypes.GroupSid) then | ||||
|             Some (idFromShort SmallGroupId (this.FindFirst(fun c -> c.Type = ClaimTypes.GroupSid).Value)) | ||||
|         else None | ||||
|         this.FindFirstValue ClaimTypes.GroupSid | ||||
|         |> Option.ofObj | ||||
|         |> Option.map (idFromShort SmallGroupId) | ||||
|      | ||||
|     /// The ID of the currently signed in user     | ||||
|     /// The ID of the currently signed-in user     | ||||
|     member this.UserId = | ||||
|         if this.HasClaim (fun c -> c.Type = ClaimTypes.NameIdentifier) then | ||||
|             Some (idFromShort UserId (this.FindFirst(fun c -> c.Type = ClaimTypes.NameIdentifier).Value)) | ||||
|         else None | ||||
|         this.FindFirstValue ClaimTypes.NameIdentifier | ||||
|         |> Option.ofObj | ||||
|         |> Option.map (idFromShort UserId) | ||||
| 
 | ||||
| 
 | ||||
| open Giraffe | ||||
| open Npgsql | ||||
| 
 | ||||
| /// Extensions on the ASP.NET Core HTTP context | ||||
| type HttpContext with | ||||
| @ -83,7 +82,7 @@ type HttpContext with | ||||
|     member _.Strings = Views.I18N.localizer.Force() | ||||
|      | ||||
|     /// 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 | ||||
|         | Some group -> return Some group | ||||
|         | None -> | ||||
| @ -98,7 +97,7 @@ type HttpContext with | ||||
|     } | ||||
| 
 | ||||
|     /// 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 | ||||
|         | Some user -> return Some user | ||||
|         | 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