Version 8 #43
| @ -6,7 +6,7 @@ open PrayerTracker.ViewModels | ||||
| 
 | ||||
| /// View for the church edit page | ||||
| let edit (m : EditChurch) ctx vi = | ||||
|     let pageTitle = match m.IsNew with true -> "Add a New Church" | false -> "Edit Church" | ||||
|     let pageTitle = if m.IsNew then "Add a New Church" else "Edit Church" | ||||
|     let s         = I18N.localizer.Force () | ||||
|     [ form [ _action "/church/save"; _method "post"; _class "pt-center-columns" ] [ | ||||
|         style [ _scoped ] [ | ||||
|  | ||||
| @ -27,7 +27,7 @@ let space = rawText " " | ||||
| 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-{size}" ] [ rawText name ] | ||||
| let iconSized size name = i [ _class $"material-icons md-%i{size}" ] [ rawText name ] | ||||
| 
 | ||||
| /// Generate a CSRF prevention token | ||||
| let csrfToken (ctx : HttpContext) = | ||||
| @ -80,13 +80,11 @@ let namedColorList name selected attrs (s : IStringLocalizer) = | ||||
| 
 | ||||
| /// Generate an input[type=radio] that is selected if its value is the current value | ||||
| let radio name domId value current = | ||||
|     input | ||||
|         [ _type "radio" | ||||
|     input [ _type "radio" | ||||
|             _name name | ||||
|             _id domId | ||||
|             _value value | ||||
|           if value = current then _checked | ||||
|         ] | ||||
|             if value = current then _checked ] | ||||
| 
 | ||||
| /// Generate a select list with the current value selected | ||||
| let selectList name selected attrs items = | ||||
| @ -100,7 +98,7 @@ let selectList name selected attrs items = | ||||
|     |> 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 = $"— {text} —" | ||||
| 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 ] | ||||
| @ -108,29 +106,13 @@ let submit attrs ico text = button (_type "submit" :: attrs) [ icon ico; rawText | ||||
| 
 | ||||
| open System | ||||
| 
 | ||||
| // TODO: this is where to implement issue #1 | ||||
| /// Format a GUID with no dashes (used for URLs and forms) | ||||
| let flatGuid (x : Guid) = x.ToString "N" | ||||
| 
 | ||||
| /// An empty GUID string (used for "add" actions) | ||||
| let emptyGuid = flatGuid Guid.Empty | ||||
| 
 | ||||
| 
 | ||||
| /// blockquote tag | ||||
| let blockquote = tag "blockquote" | ||||
| 
 | ||||
| /// role attribute | ||||
| let _role = attr "role" | ||||
| /// aria-* attribute | ||||
| let _aria typ = attr $"aria-{typ}" | ||||
| /// onclick attribute | ||||
| let _onclick = attr "onclick" | ||||
| /// onsubmit attribute | ||||
| let _onsubmit = attr "onsubmit" | ||||
| 
 | ||||
| /// scoped flag (used for <style> tag) | ||||
| let _scoped = flag "scoped" | ||||
| 
 | ||||
| 
 | ||||
| /// The name this function used to have when the view engine was part of Giraffe | ||||
| let renderHtmlNode = RenderView.AsString.htmlNode | ||||
| 
 | ||||
|  | ||||
| @ -2,6 +2,8 @@ | ||||
| module PrayerTracker.Views.Layout | ||||
| 
 | ||||
| open Giraffe.ViewEngine | ||||
| open Giraffe.ViewEngine.Accessibility | ||||
| open Giraffe.ViewEngine.Htmx | ||||
| open PrayerTracker | ||||
| open PrayerTracker.ViewModels | ||||
| open System | ||||
| @ -12,6 +14,16 @@ open System.Globalization | ||||
| let langCode () = if CultureInfo.CurrentCulture.Name.StartsWith "es" then "es" else "en" | ||||
| 
 | ||||
| 
 | ||||
| /// Known htmx targets | ||||
| module Target = | ||||
|      | ||||
|     /// htmx links target the body element | ||||
|     let body = _hxTarget "body" | ||||
|      | ||||
|     /// htmx links target the #pt-body element | ||||
|     let content = _hxTarget "#pt-body" | ||||
| 
 | ||||
|      | ||||
| /// Navigation items | ||||
| module Navigation = | ||||
|    | ||||
| @ -23,39 +35,46 @@ module Navigation = | ||||
|             match m.User with | ||||
|             | Some u -> | ||||
|                 li [ _class "dropdown" ] [ | ||||
|                     a [ _class "dropbtn" | ||||
|                         _role "button" | ||||
|                         _aria "label" s["Requests"].Value | ||||
|                         _title s["Requests"].Value ] | ||||
|                       [ icon "question_answer"; space; locStr s["Requests"]; space; icon "keyboard_arrow_down" ] | ||||
|                     div [ _class "dropdown-content"; _role "menu" ] [ | ||||
|                         a [ _href "/prayer-requests" ] [ icon "compare_arrows"; menuSpacer; locStr s["Maintain"] ] | ||||
|                         a [ _href "/prayer-requests/view" ] [ icon "list"; menuSpacer; locStr s["View List"] ] | ||||
|                     a [ _class "dropbtn"; _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"] | ||||
|                         ] | ||||
|                     ] | ||||
|                 ] | ||||
|                 li [ _class "dropdown" ] [ | ||||
|                     a [ _class "dropbtn"; _role "button"; _aria "label" s["Group"].Value; _title s["Group"].Value ] | ||||
|                       [ icon "group"; space; locStr s["Group"]; space; icon "keyboard_arrow_down" ] | ||||
|                     div [ _class "dropdown-content"; _role "menu" ] [ | ||||
|                         a [ _href "/small-group/members" ] | ||||
|                           [ icon "email"; menuSpacer; locStr s["Maintain Group Members"] ] | ||||
|                         a [ _href "/small-group/announcement" ] | ||||
|                           [ icon "send";  menuSpacer; locStr s["Send Announcement"] ] | ||||
|                         a [ _href "/small-group/preferences" ] | ||||
|                           [ icon "build"; menuSpacer; locStr s["Change Preferences"] ] | ||||
|                     a [ _class "dropbtn"; _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"] | ||||
|                         ] | ||||
|                     ] | ||||
|                 ] | ||||
|                 if u.isAdmin then | ||||
|                     li [ _class "dropdown" ] [ | ||||
|                         a [ _class     "dropbtn" | ||||
|                             _role "button" | ||||
|                             _aria "label" s["Administration"].Value | ||||
|                             _ariaLabel s["Administration"].Value | ||||
|                             _title     s["Administration"].Value | ||||
|                           ] [ icon "settings"; space; locStr s["Administration"]; space; icon "keyboard_arrow_down" ] | ||||
|                         div [ _class "dropdown-content"; _role "menu" ] [ | ||||
|                             a [ _href "/churches" ]     [ icon "home";  menuSpacer; locStr s["Churches"] ] | ||||
|                             a [ _href "/small-groups" ] [ icon "send";  menuSpacer; locStr s["Groups"] ] | ||||
|                             a [ _href "/users" ]        [ icon "build"; menuSpacer; locStr s["Users"] ] | ||||
|                             _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"] ] | ||||
|                         ] | ||||
|                     ] | ||||
|             | None -> | ||||
| @ -63,63 +82,72 @@ module Navigation = | ||||
|                 | Some _ -> | ||||
|                     li [] [ | ||||
|                         a [ _href      "/prayer-requests/view" | ||||
|                             _aria "label" s["View Request List"].Value | ||||
|                             _title s["View Request List"].Value | ||||
|                           ] [ icon "list"; space; locStr s["View Request List"] ] | ||||
|                             _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 [ _class "dropbtn" | ||||
|                             _role "button" | ||||
|                             _aria "label" s["Log On"].Value | ||||
|                             _title s["Log On"].Value | ||||
|                           ] [ icon "security"; space; locStr s["Log On"]; space; icon "keyboard_arrow_down" ] | ||||
|                         div [ _class "dropdown-content"; _role "menu" ] [ | ||||
|                             a [ _href "/user/log-on" ]        [ icon "person"; menuSpacer; locStr s["User"] ] | ||||
|                             a [ _href "/small-group/log-on" ] [ icon "group";  menuSpacer; locStr s["Group"] ] | ||||
|                         a [ _class "dropbtn"; _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"] | ||||
|                             ] | ||||
|                         ] | ||||
|                     ] | ||||
|                     li [] [ | ||||
|                         a [ _href      "/prayer-requests/lists" | ||||
|                             _aria "label" s["View Request List"].Value | ||||
|                             _title s["View Request List"].Value | ||||
|                           ] [ icon "list"; space; locStr s["View Request List"] ] | ||||
|                             _ariaLabel s["View Request List"].Value | ||||
|                             _title     s["View Request List"].Value ] [ | ||||
|                             icon "list"; space; locStr s["View Request List"] | ||||
|                         ] | ||||
|                     ] | ||||
|             li [] [ | ||||
|                 a [ _href      $"https://docs.prayer.bitbadger.solutions/{langCode ()}" | ||||
|                     _aria   "label" s["Help"].Value; | ||||
|                     _ariaLabel s["Help"].Value | ||||
|                     _title     s["View Help"].Value | ||||
|                     _target    "_blank" | ||||
|                   ] [ icon "help"; space; locStr s["Help"] ] | ||||
|                     _rel       "noopener" ] [ | ||||
|                     icon "help"; space; locStr s["Help"] | ||||
|                 ] | ||||
|             ] | ||||
|         ] | ||||
|         let rightLinks = | ||||
|             match m.Group with | ||||
|             | Some _ -> [ | ||||
|                 match m.User with | ||||
|             | Some _ -> | ||||
|                 [   match m.User with | ||||
|                     | Some _ -> | ||||
|                         li [] [ | ||||
|                             a [ _href      "/user/password" | ||||
|                             _aria "label" s["Change Your Password"].Value | ||||
|                             _title s["Change Your Password"].Value | ||||
|                           ] [ icon "lock"; space; locStr s["Change Your 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"; _aria "label" s["Log Off"].Value; _title s["Log Off"].Value ] | ||||
|                       [ icon "power_settings_new"; space; locStr s["Log Off"] ] | ||||
|                         a [ _href      "/log-off" | ||||
|                             _ariaLabel s["Log Off"].Value | ||||
|                             _title     s["Log Off"].Value | ||||
|                             _hxTarget  "body" ] [ | ||||
|                             icon "power_settings_new"; space; locStr s["Log Off"] | ||||
|                         ] | ||||
|                     ] | ||||
|                 ] | ||||
|             | None -> [] | ||||
|         header [ _class "pt-title-bar" ] [ | ||||
|             section [ _class "pt-title-bar-left" ] [ | ||||
|         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"] ] | ||||
|                 ] | ||||
|                 ul [] leftLinks | ||||
|             ] | ||||
|             section [ _class "pt-title-bar-center" ] [] | ||||
|             section [ _class "pt-title-bar-right"; _role "toolbar" ] [ | ||||
|             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" ] [ | ||||
|                 ul [] rightLinks | ||||
|             ] | ||||
|         ] | ||||
| @ -127,7 +155,7 @@ module Navigation = | ||||
|     /// Identity bar (below top nav) | ||||
|     let identity m = | ||||
|         let s = I18N.localizer.Force () | ||||
|         header [ _id "pt-language" ] [ | ||||
|         header [ _id "pt-language"; Target.body ] [ | ||||
|             div [] [ | ||||
|                 span [ _class "u" ] [ locStr s["Language"]; rawText ": " ] | ||||
|                 match langCode () with | ||||
| @ -141,8 +169,8 @@ module Navigation = | ||||
|                     a [ _href "/language/es" ] [ locStr s["Cambie a Español"] ] | ||||
|             ] | ||||
|             match m.Group with | ||||
|             | Some g ->[ | ||||
|                 match m.User with | ||||
|             | Some g -> | ||||
|                 [   match m.User with | ||||
|                     | Some u -> | ||||
|                         span [ _class "u" ] [ locStr s["Currently Logged On"] ] | ||||
|                         rawText "   " | ||||
| @ -155,7 +183,7 @@ module Navigation = | ||||
|                     icon "group" | ||||
|                     space | ||||
|                     match m.User with | ||||
|                 | Some _ -> a [ _href "/small-group" ] [ strong [] [ str g.name ] ] | ||||
|                     | Some _ -> a [ _href "/small-group"; Target.content ] [ strong [] [ str g.name ] ] | ||||
|                     | None -> strong [] [ str g.name ] | ||||
|                     rawText "  " | ||||
|                 ] | ||||
| @ -178,13 +206,12 @@ module Content = | ||||
| let private titleSep = rawText " « " | ||||
| 
 | ||||
| /// Common HTML head tag items | ||||
| let private commonHead = | ||||
|     [ meta [ _name "viewport"; _content "width=device-width, initial-scale=1" ] | ||||
| 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" ] | ||||
|       script [ _src "/js/app.js" ] [] | ||||
|     ] | ||||
| ] | ||||
| 
 | ||||
| /// Render the <head> portion of the page | ||||
| let private htmlHead m pageTitle = | ||||
| @ -203,8 +230,12 @@ let private htmlHead m pageTitle = | ||||
| let private helpLink link = | ||||
|     let s = I18N.localizer.Force () | ||||
|     sup [] [ | ||||
|         a [ _href link; _title s["Click for Help on This Page"].Value; _onclick $"return PT.showHelp('{link}')" ] | ||||
|           [ icon "help_outline" ] | ||||
|         a [ _href    link | ||||
|             _title   s["Click for Help on This Page"].Value | ||||
|             _onclick $"return PT.showHelp('{link}')" | ||||
|             _hxNoBoost ] [ | ||||
|             icon "help_outline" | ||||
|         ] | ||||
|     ] | ||||
| 
 | ||||
| /// Render the page title, and optionally a help link | ||||
| @ -240,7 +271,7 @@ let private messages m = | ||||
| /// Render the <footer> at the bottom of the page | ||||
| let private htmlFooter m = | ||||
|     let s          = I18N.localizer.Force () | ||||
|     let imgText = sprintf "%O %O" s["PrayerTracker"] s["from Bit Badger Solutions"] | ||||
|     let imgText    = $"""%O{s["PrayerTracker"]} %O{s["from Bit Badger Solutions"]}""" | ||||
|     let resultTime = TimeSpan(DateTime.Now.Ticks - m.RequestStart).TotalSeconds | ||||
|     footer [] [ | ||||
|         div [ _id "pt-legal" ] [ | ||||
| @ -251,27 +282,31 @@ let private htmlFooter m = | ||||
|             a [ _href   "https://github.com/bit-badger/PrayerTracker" | ||||
|                 _title  s["View source code and get technical support"].Value | ||||
|                 _target "_blank" | ||||
|                 _rel "noopener" | ||||
|               ] [ locStr s["Source & Support"] | ||||
|                 _rel    "noopener" ] [ | ||||
|                 locStr s["Source & Support"] | ||||
|             ] | ||||
|         ] | ||||
|         div [ _id "pt-footer" ] [ | ||||
|             a [ _href "/"; _style "line-height:28px;" ] | ||||
|               [ img [ _src $"""/img/%O{s["footer_en"]}.png"""; _alt imgText; _title imgText ] ] | ||||
|             a [ _href "/"; _style "line-height:28px;" ] [ | ||||
|                 img [ _src $"""/img/%O{s["footer_en"]}.png"""; _alt imgText; _title imgText ] | ||||
|             ] | ||||
|             str m.Version | ||||
|             space | ||||
|             i [ _title s["This page loaded in {0:N3} seconds", resultTime].Value; _class "material-icons md-18" ] | ||||
|               [ str "schedule" ] | ||||
|             i [ _title s["This page loaded in {0:N3} seconds", resultTime].Value; _class "material-icons md-18" ] [ | ||||
|                 str "schedule" | ||||
|             ] | ||||
|         ] | ||||
|         Script.minified | ||||
|         script [ _src "/js/app.js" ] [] | ||||
|     ] | ||||
| 
 | ||||
| /// The standard layout for PrayerTracker | ||||
| let standard m pageTitle (content : XmlNode) = | ||||
|     let s   = I18N.localizer.Force () | ||||
|     let ttl = s[pageTitle] | ||||
|     html [ _lang "" ] [ | ||||
|     html [ _lang (langCode ()) ] [ | ||||
|         htmlHead m ttl | ||||
|         body [] [ | ||||
|         body [ _hxBoost ] [ | ||||
|             Navigation.top m | ||||
|             div [ _id "pt-body" ] [ | ||||
|                 Navigation.identity m | ||||
| @ -287,7 +322,7 @@ let standard m pageTitle (content : XmlNode) = | ||||
| let bare pageTitle content = | ||||
|     let s   = I18N.localizer.Force () | ||||
|     let ttl = s[pageTitle] | ||||
|     html [ _lang "" ] [ | ||||
|     html [ _lang (langCode ()) ] [ | ||||
|         head [] [ | ||||
|             meta [ _charset "UTF-8" ] | ||||
|             title [] [ locStr ttl; titleSep; locStr s["PrayerTracker"] ] | ||||
|  | ||||
| @ -20,6 +20,7 @@ | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Giraffe" Version="6.0.0" /> | ||||
|     <PackageReference Include="Giraffe.ViewEngine" Version="1.4.0" /> | ||||
|     <PackageReference Include="Giraffe.ViewEngine.Htmx" Version="1.8.0" /> | ||||
|     <PackageReference Include="MailKit" Version="3.3.0" /> | ||||
|     <PackageReference Include="Microsoft.AspNetCore.Html.Abstractions" Version="2.2.0" /> | ||||
|     <PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" /> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user