diff --git a/src/MyWebLog/Caches.fs b/src/MyWebLog/Caches.fs index de81cf2..5042f55 100644 --- a/src/MyWebLog/Caches.fs +++ b/src/MyWebLog/Caches.fs @@ -172,18 +172,34 @@ module TemplateCache = let get (themeId : ThemeId) (templateName : string) (data : IData) = backgroundTask { let templatePath = $"{ThemeId.toString themeId}/{templateName}" match _cache.ContainsKey templatePath with - | true -> () + | true -> return Ok _cache[templatePath] | false -> match! data.Theme.FindById themeId with | Some theme -> - let mutable text = (theme.Templates |> List.find (fun t -> t.Name = templateName)).Text - while hasInclude.IsMatch text do - let child = hasInclude.Match text - let childText = (theme.Templates |> List.find (fun t -> t.Name = child.Groups[1].Value)).Text - text <- text.Replace (child.Value, childText) - _cache[templatePath] <- Template.Parse (text, SyntaxCompatibility.DotLiquid22) - | None -> () - return _cache[templatePath] + match theme.Templates |> List.tryFind (fun t -> t.Name = templateName) with + | Some template -> + let mutable text = template.Text + let mutable childNotFound = "" + while hasInclude.IsMatch text do + let child = hasInclude.Match text + let childText = + match theme.Templates |> List.tryFind (fun t -> t.Name = child.Groups[1].Value) with + | Some childTemplate -> childTemplate.Text + | None -> + childNotFound <- + if childNotFound = "" then child.Groups[1].Value + else $"{childNotFound}; {child.Groups[1].Value}" + "" + text <- text.Replace (child.Value, childText) + if childNotFound <> "" then + let s = if childNotFound.IndexOf ";" >= 0 then "s" else "" + return Error $"Could not find the child template{s} {childNotFound} required by {templateName}" + else + _cache[templatePath] <- Template.Parse (text, SyntaxCompatibility.DotLiquid22) + return Ok _cache[templatePath] + | None -> + return Error $"Theme ID {ThemeId.toString themeId} does not have a template named {templateName}" + | None -> return Result.Error $"Theme ID {ThemeId.toString themeId} does not exist" } /// Get all theme/template names currently cached diff --git a/src/MyWebLog/Handlers/Admin.fs b/src/MyWebLog/Handlers/Admin.fs index cee5e12..6e5023e 100644 --- a/src/MyWebLog/Handlers/Admin.fs +++ b/src/MyWebLog/Handlers/Admin.fs @@ -32,38 +32,43 @@ let dashboard : HttpHandler = requireAccess Author >=> fun next ctx -> task { |> adminView "dashboard" next ctx } -// GET /admin/dashboard/administration +// GET /admin/administration let adminDashboard : HttpHandler = requireAccess Administrator >=> fun next ctx -> task { - let! themes = ctx.Data.Theme.All () - let! bodyTemplate = TemplateCache.get adminTheme "theme-list-body" ctx.Data - let cachedTemplates = TemplateCache.allNames () - let! hash = - hashForPage "myWebLog Administration" - |> withAntiCsrf ctx - |> addToHash "themes" (themes |> List.map (DisplayTheme.fromTheme WebLogCache.isThemeInUse) |> Array.ofList) - |> addToHash "cached_themes" ( - themes - |> Seq.ofList - |> Seq.map (fun it -> [| - ThemeId.toString it.Id - it.Name - cachedTemplates |> List.filter (fun n -> n.StartsWith (ThemeId.toString it.Id)) |> List.length |> string - |]) - |> Array.ofSeq) - |> addToHash "web_logs" ( - WebLogCache.all () - |> Seq.ofList - |> Seq.sortBy (fun it -> it.Name) - |> Seq.map (fun it -> [| WebLogId.toString it.Id; it.Name; it.UrlBase |]) - |> Array.ofSeq) - |> addViewContext ctx - return! - addToHash "theme_list" (bodyTemplate.Render hash) hash - |> adminView "admin-dashboard" next ctx + match! TemplateCache.get adminTheme "theme-list-body" ctx.Data with + | Ok bodyTemplate -> + let! themes = ctx.Data.Theme.All () + let cachedTemplates = TemplateCache.allNames () + let! hash = + hashForPage "myWebLog Administration" + |> withAntiCsrf ctx + |> addToHash "themes" (themes |> List.map (DisplayTheme.fromTheme WebLogCache.isThemeInUse) |> Array.ofList) + |> addToHash "cached_themes" ( + themes + |> Seq.ofList + |> Seq.map (fun it -> [| + ThemeId.toString it.Id + it.Name + cachedTemplates + |> List.filter (fun n -> n.StartsWith (ThemeId.toString it.Id)) + |> List.length + |> string + |]) + |> Array.ofSeq) + |> addToHash "web_logs" ( + WebLogCache.all () + |> Seq.ofList + |> Seq.sortBy (fun it -> it.Name) + |> Seq.map (fun it -> [| WebLogId.toString it.Id; it.Name; it.UrlBase |]) + |> Array.ofSeq) + |> addViewContext ctx + return! + addToHash "theme_list" (bodyTemplate.Render hash) hash + |> adminView "admin-dashboard" next ctx + | Error message -> return! Error.server message next ctx } /// Redirect the user to the admin dashboard -let toAdminDashboard : HttpHandler = redirectToGet "admin/dashboard/administration" +let toAdminDashboard : HttpHandler = redirectToGet "admin/administration" // ~~ CACHES ~~ @@ -117,14 +122,16 @@ let refreshThemeCache themeId : HttpHandler = requireAccess Administrator >=> fu // GET /admin/categories let listCategories : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { - let! catListTemplate = TemplateCache.get adminTheme "category-list-body" ctx.Data - let! hash = - hashForPage "Categories" - |> withAntiCsrf ctx - |> addViewContext ctx - return! - addToHash "category_list" (catListTemplate.Render hash) hash - |> adminView "category-list" next ctx + match! TemplateCache.get adminTheme "category-list-body" ctx.Data with + | Ok catListTemplate -> + let! hash = + hashForPage "Categories" + |> withAntiCsrf ctx + |> addViewContext ctx + return! + addToHash "category_list" (catListTemplate.Render hash) hash + |> adminView "category-list" next ctx + | Error message -> return! Error.server message next ctx } // GET /admin/categories/bare @@ -204,11 +211,13 @@ let private tagMappingHash (ctx : HttpContext) = task { // GET /admin/settings/tag-mappings let tagMappings : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { - let! hash = tagMappingHash ctx - let! listTemplate = TemplateCache.get adminTheme "tag-mapping-list-body" ctx.Data - return! - addToHash "tag_mapping_list" (listTemplate.Render hash) hash - |> adminView "tag-mapping-list" next ctx + match! TemplateCache.get adminTheme "tag-mapping-list-body" ctx.Data with + | Ok listTemplate -> + let! hash = tagMappingHash ctx + return! + addToHash "tag_mapping_list" (listTemplate.Render hash) hash + |> adminView "tag-mapping-list" next ctx + | Error message -> return! Error.server message next ctx } // GET /admin/settings/tag-mappings/bare @@ -421,31 +430,39 @@ open System.Collections.Generic // GET /admin/settings let settings : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { - let data = ctx.Data - let! allPages = data.Page.All ctx.WebLog.Id - let! themes = data.Theme.All () - return! - hashForPage "Web Log Settings" - |> withAntiCsrf ctx - |> addToHash ViewContext.Model (SettingsModel.fromWebLog ctx.WebLog) - |> addToHash "pages" ( - seq { - KeyValuePair.Create ("posts", "- First Page of Posts -") - yield! allPages - |> List.sortBy (fun p -> p.Title.ToLower ()) - |> List.map (fun p -> KeyValuePair.Create (PageId.toString p.Id, p.Title)) - } - |> Array.ofSeq) - |> addToHash "themes" ( - themes - |> Seq.ofList - |> Seq.map (fun it -> KeyValuePair.Create (ThemeId.toString it.Id, $"{it.Name} (v{it.Version})")) - |> Array.ofSeq) - |> addToHash "upload_values" [| - KeyValuePair.Create (UploadDestination.toString Database, "Database") - KeyValuePair.Create (UploadDestination.toString Disk, "Disk") - |] - |> adminView "settings" next ctx + let data = ctx.Data + match! TemplateCache.get adminTheme "user-list-body" data with + | Ok userTemplate -> + let! allPages = data.Page.All ctx.WebLog.Id + let! themes = data.Theme.All () + let! users = data.WebLogUser.FindByWebLog ctx.WebLog.Id + let! hash = + hashForPage "Web Log Settings" + |> withAntiCsrf ctx + |> addToHash ViewContext.Model (SettingsModel.fromWebLog ctx.WebLog) + |> addToHash "pages" ( + seq { + KeyValuePair.Create ("posts", "- First Page of Posts -") + yield! allPages + |> List.sortBy (fun p -> p.Title.ToLower ()) + |> List.map (fun p -> KeyValuePair.Create (PageId.toString p.Id, p.Title)) + } + |> Array.ofSeq) + |> addToHash "themes" ( + themes + |> Seq.ofList + |> Seq.map (fun it -> KeyValuePair.Create (ThemeId.toString it.Id, $"{it.Name} (v{it.Version})")) + |> Array.ofSeq) + |> addToHash "upload_values" [| + KeyValuePair.Create (UploadDestination.toString Database, "Database") + KeyValuePair.Create (UploadDestination.toString Disk, "Disk") + |] + |> addToHash "users" (users |> List.map (DisplayUser.fromUser ctx.WebLog) |> Array.ofList) + |> addViewContext ctx + return! + addToHash "user_list" (userTemplate.Render hash) hash + |> adminView "settings" next ctx + | Error message -> return! Error.server message next ctx } // POST /admin/settings diff --git a/src/MyWebLog/Handlers/Helpers.fs b/src/MyWebLog/Handlers/Helpers.fs index 05ee4d1..01e62dd 100644 --- a/src/MyWebLog/Handlers/Helpers.fs +++ b/src/MyWebLog/Handlers/Helpers.fs @@ -218,23 +218,6 @@ let addViewContext ctx (hash : Hash) = task { let isHtmx (ctx : HttpContext) = ctx.Request.IsHtmx && not ctx.Request.IsHtmxRefresh -/// Render a view for the specified theme, using the specified template, layout, and hash -let viewForTheme themeId template next ctx (hash : Hash) = task { - let! hash = addViewContext ctx hash - - // NOTE: DotLiquid does not support {% render %} or {% include %} in its templates, so we will do a 2-pass render; - // the net effect is a "layout" capability similar to Razor or Pug - - // Render view content... - let! contentTemplate = TemplateCache.get themeId template ctx.Data - let _ = addToHash ViewContext.Content (contentTemplate.Render hash) hash - - // ...then render that content with its layout - let! layoutTemplate = TemplateCache.get themeId (if isHtmx ctx then "layout-partial" else "layout") ctx.Data - - return! htmlString (layoutTemplate.Render hash) next ctx -} - /// Convert messages to headers (used for htmx responses) let messagesToHeaders (messages : UserMessage array) : HttpHandler = seq { @@ -249,52 +232,12 @@ let messagesToHeaders (messages : UserMessage array) : HttpHandler = } |> Seq.reduce (>=>) -/// Render a bare view for the specified theme, using the specified template and hash -let bareForTheme themeId template next ctx (hash : Hash) = task { - let! hash = addViewContext ctx hash - - if not (hash.ContainsKey ViewContext.Content) then - let! contentTemplate = TemplateCache.get themeId template ctx.Data - addToHash ViewContext.Content (contentTemplate.Render hash) hash |> ignore - - // Bare templates are rendered with layout-bare - let! layoutTemplate = TemplateCache.get themeId "layout-bare" ctx.Data - return! - (messagesToHeaders (hash[ViewContext.Messages] :?> UserMessage[]) - >=> htmlString (layoutTemplate.Render hash)) - next ctx -} - -/// Return a view for the web log's default theme -let themedView template next ctx hash = task { - let! hash = addViewContext ctx hash - return! viewForTheme (hash[ViewContext.WebLog] :?> WebLog).ThemeId template next ctx hash -} - -/// The ID for the admin theme -let adminTheme = ThemeId "admin" - -/// Display a view for the admin theme -let adminView template = - viewForTheme adminTheme template - -/// Display a bare view for the admin theme -let adminBareView template = - bareForTheme adminTheme template - /// Redirect after doing some action; commits session and issues a temporary redirect let redirectToGet url : HttpHandler = fun _ ctx -> task { do! commitSession ctx return! redirectTo false (WebLog.relativeUrl ctx.WebLog (Permalink url)) earlyReturn ctx } -/// Validate the anti cross-site request forgery token in the current request -let validateCsrf : HttpHandler = fun next ctx -> task { - match! ctx.AntiForgery.IsRequestValidAsync ctx with - | true -> return! next ctx - | false -> return! RequestErrors.BAD_REQUEST "CSRF token invalid" earlyReturn ctx -} - /// Handlers for error conditions module Error = @@ -324,9 +267,81 @@ module Error = let messages = [| { UserMessage.error with Message = $"The URL {ctx.Request.Path.Value} was not found" } |] - (messagesToHeaders messages >=> setStatusCode 404) earlyReturn ctx - else - (setStatusCode 404 >=> text "Not found") earlyReturn ctx) + RequestErrors.notFound (messagesToHeaders messages) earlyReturn ctx + else RequestErrors.NOT_FOUND "Not found" earlyReturn ctx) + + let server message : HttpHandler = + handleContext (fun ctx -> + if isHtmx ctx then + let messages = [| { UserMessage.error with Message = message } |] + ServerErrors.internalError (messagesToHeaders messages) earlyReturn ctx + else ServerErrors.INTERNAL_ERROR message earlyReturn ctx) + + +/// Render a view for the specified theme, using the specified template, layout, and hash +let viewForTheme themeId template next ctx (hash : Hash) = task { + let! hash = addViewContext ctx hash + + // NOTE: DotLiquid does not support {% render %} or {% include %} in its templates, so we will do a 2-pass render; + // the net effect is a "layout" capability similar to Razor or Pug + + // Render view content... + match! TemplateCache.get themeId template ctx.Data with + | Ok contentTemplate -> + let _ = addToHash ViewContext.Content (contentTemplate.Render hash) hash + // ...then render that content with its layout + match! TemplateCache.get themeId (if isHtmx ctx then "layout-partial" else "layout") ctx.Data with + | Ok layoutTemplate -> return! htmlString (layoutTemplate.Render hash) next ctx + | Error message -> return! Error.server message next ctx + | Error message -> return! Error.server message next ctx +} + +/// Render a bare view for the specified theme, using the specified template and hash +let bareForTheme themeId template next ctx (hash : Hash) = task { + let! hash = addViewContext ctx hash + let withContent = task { + if hash.ContainsKey ViewContext.Content then return Ok hash + else + match! TemplateCache.get themeId template ctx.Data with + | Ok contentTemplate -> return Ok (addToHash ViewContext.Content (contentTemplate.Render hash) hash) + | Error message -> return Error message + } + match! withContent with + | Ok completeHash -> + // Bare templates are rendered with layout-bare + match! TemplateCache.get themeId "layout-bare" ctx.Data with + | Ok layoutTemplate -> + return! + (messagesToHeaders (hash[ViewContext.Messages] :?> UserMessage[]) + >=> htmlString (layoutTemplate.Render completeHash)) + next ctx + | Error message -> return! Error.server message next ctx + | Error message -> return! Error.server message next ctx +} + +/// Return a view for the web log's default theme +let themedView template next ctx hash = task { + let! hash = addViewContext ctx hash + return! viewForTheme (hash[ViewContext.WebLog] :?> WebLog).ThemeId template next ctx hash +} + +/// The ID for the admin theme +let adminTheme = ThemeId "admin" + +/// Display a view for the admin theme +let adminView template = + viewForTheme adminTheme template + +/// Display a bare view for the admin theme +let adminBareView template = + bareForTheme adminTheme template + +/// Validate the anti cross-site request forgery token in the current request +let validateCsrf : HttpHandler = fun next ctx -> task { + match! ctx.AntiForgery.IsRequestValidAsync ctx with + | true -> return! next ctx + | false -> return! RequestErrors.BAD_REQUEST "CSRF token invalid" earlyReturn ctx +} /// Require a user to be logged on diff --git a/src/MyWebLog/Handlers/Routes.fs b/src/MyWebLog/Handlers/Routes.fs index da9a226..b04bff1 100644 --- a/src/MyWebLog/Handlers/Routes.fs +++ b/src/MyWebLog/Handlers/Routes.fs @@ -106,13 +106,14 @@ let router : HttpHandler = choose [ ] subRoute "/admin" (requireUser >=> choose [ GET_HEAD >=> choose [ + route "/administration" >=> Admin.adminDashboard subRoute "/categor" (choose [ route "ies" >=> Admin.listCategories route "ies/bare" >=> Admin.listCategoriesBare routef "y/%s/edit" Admin.editCategory ]) - route "/dashboard" >=> Admin.dashboard - route "/dashboard/administration" >=> Admin.adminDashboard + route "/dashboard" >=> Admin.dashboard + route "/my-info" >=> User.myInfo subRoute "/page" (choose [ route "s" >=> Page.all 1 routef "s/page/%i" Page.all @@ -134,6 +135,11 @@ let router : HttpHandler = choose [ subRoute "/rss" (choose [ route "" >=> Feed.editSettings routef "/%s/edit" Feed.editCustomFeed + ]) + subRoute "/user" (choose [ + route "s" >=> User.all + routef "/%s/edit" User.edit + ]) subRoute "/tag-mapping" (choose [ route "s" >=> Admin.tagMappings @@ -149,12 +155,6 @@ let router : HttpHandler = choose [ route "s" >=> Upload.list route "/new" >=> Upload.showNew ]) - subRoute "/user" (choose [ - route "s" >=> User.all - route "s/bare" >=> User.bare - route "/my-info" >=> User.myInfo - routef "/%s/edit" User.edit - ]) ] POST >=> validateCsrf >=> choose [ subRoute "/cache" (choose [ @@ -165,6 +165,7 @@ let router : HttpHandler = choose [ route "/save" >=> Admin.saveCategory routef "/%s/delete" Admin.deleteCategory ]) + route "/my-info" >=> User.saveMyInfo subRoute "/page" (choose [ route "/save" >=> Page.save route "/permalinks" >=> Page.savePermalinks @@ -192,6 +193,10 @@ let router : HttpHandler = choose [ route "/save" >=> Admin.saveMapping routef "/%s/delete" Admin.deleteMapping ]) + subRoute "/user" (choose [ + route "/save" >=> User.save + routef "/%s/delete" User.delete + ]) ]) subRoute "/theme" (choose [ route "/new" >=> Admin.saveTheme @@ -202,11 +207,6 @@ let router : HttpHandler = choose [ routexp "/delete/(.*)" Upload.deleteFromDisk routef "/%s/delete" Upload.deleteFromDb ]) - subRoute "/user" (choose [ - route "/my-info" >=> User.saveMyInfo - route "/save" >=> User.save - routef "/%s/delete" User.delete - ]) ] ]) GET_HEAD >=> routexp "/category/(.*)" Post.pageOfCategorizedPosts diff --git a/src/MyWebLog/Handlers/User.fs b/src/MyWebLog/Handlers/User.fs index ffa8b13..6000c15 100644 --- a/src/MyWebLog/Handlers/User.fs +++ b/src/MyWebLog/Handlers/User.fs @@ -72,34 +72,18 @@ let logOff : HttpHandler = fun next ctx -> task { open System.Collections.Generic open Giraffe.Htmx -open Microsoft.AspNetCore.Http -/// Create the hash needed to display the user list -let private userListHash (ctx : HttpContext) = task { +/// Got no time for URL/form manipulators... +let private goAway : HttpHandler = RequestErrors.BAD_REQUEST "really?" + +// GET /admin/settings/users +let all : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { let! users = ctx.Data.WebLogUser.FindByWebLog ctx.WebLog.Id return! hashForPage "User Administration" |> withAntiCsrf ctx |> addToHash "users" (users |> List.map (DisplayUser.fromUser ctx.WebLog) |> Array.ofList) - |> addViewContext ctx -} - -/// Got no time for URL/form manipulators... -let private goAway : HttpHandler = RequestErrors.BAD_REQUEST "really?" - -// GET /admin/users -let all : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { - let! hash = userListHash ctx - let! tmpl = TemplateCache.get adminTheme "user-list-body" ctx.Data - return! - addToHash "user_list" (tmpl.Render hash) hash - |> adminView "user-list" next ctx -} - -// GET /admin/users/bare -let bare : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { - let! hash = userListHash ctx - return! adminBareView "user-list-body" next ctx hash + |> adminBareView "user-list-body" next ctx } /// Show the edit user page @@ -116,7 +100,7 @@ let private showEdit (model : EditUserModel) : HttpHandler = fun next ctx -> |] |> adminBareView "user-edit" next ctx -// GET /admin/user/{id}/edit +// GET /admin/settings/user/{id}/edit let edit usrId : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { let isNew = usrId = "new" let userId = WebLogUserId usrId @@ -128,7 +112,7 @@ let edit usrId : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> tas | None -> return! Error.notFound next ctx } -// POST /admin/user/{id}/delete +// POST /admin/settings/user/{id}/delete let delete userId : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { let data = ctx.Data match! data.WebLogUser.FindById (WebLogUserId userId) ctx.WebLog.Id with @@ -142,14 +126,14 @@ let delete userId : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> { UserMessage.success with Message = $"User {WebLogUser.displayName user} deleted successfully" } - return! bare next ctx + return! all next ctx | Error msg -> do! addMessage ctx { UserMessage.error with Message = $"User {WebLogUser.displayName user} was not deleted" Detail = Some msg } - return! bare next ctx + return! all next ctx | None -> return! Error.notFound next ctx } @@ -164,14 +148,14 @@ let private showMyInfo (model : EditMyInfoModel) (user : WebLogUser) : HttpHandl |> adminView "my-info" next ctx -// GET /admin/user/my-info +// GET /admin/my-info let myInfo : HttpHandler = requireAccess Author >=> fun next ctx -> task { match! ctx.Data.WebLogUser.FindById ctx.UserId ctx.WebLog.Id with | Some user -> return! showMyInfo (EditMyInfoModel.fromUser user) user next ctx | None -> return! Error.notFound next ctx } -// POST /admin/user/my-info +// POST /admin/my-info let saveMyInfo : HttpHandler = requireAccess Author >=> fun next ctx -> task { let! model = ctx.BindFormAsync () let data = ctx.Data @@ -194,7 +178,7 @@ let saveMyInfo : HttpHandler = requireAccess Author >=> fun next ctx -> task { do! data.WebLogUser.Update user let pwMsg = if model.NewPassword = "" then "" else " and updated your password" do! addMessage ctx { UserMessage.success with Message = $"Saved your information{pwMsg} successfully" } - return! redirectToGet "admin/user/my-info" next ctx + return! redirectToGet "admin/my-info" next ctx | Some user -> do! addMessage ctx { UserMessage.error with Message = "Passwords did not match; no updates made" } return! showMyInfo { model with NewPassword = ""; NewPasswordConfirm = "" } user next ctx @@ -204,7 +188,7 @@ let saveMyInfo : HttpHandler = requireAccess Author >=> fun next ctx -> task { // User save is not statically compilable; not sure why, but we'll revisit it at some point #nowarn "3511" -// POST /admin/user/save +// POST /admin/settings/user/save let save : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { let! model = ctx.BindFormAsync () let data = ctx.Data @@ -232,7 +216,7 @@ let save : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { { UserMessage.success with Message = $"""{if model.IsNew then "Add" else "Updat"}ed user successfully""" } - return! bare next ctx + return! all next ctx | Some _ -> do! addMessage ctx { UserMessage.error with Message = "The passwords did not match; nothing saved" } return! diff --git a/src/admin-theme/_layout.liquid b/src/admin-theme/_layout.liquid index 1fa59df..ea5234e 100644 --- a/src/admin-theme/_layout.liquid +++ b/src/admin-theme/_layout.liquid @@ -17,11 +17,10 @@ {%- endif %} {%- if is_web_log_admin %} {{ "admin/categories" | nav_link: "Categories" }} - {{ "admin/users" | nav_link: "Users" }} {{ "admin/settings" | nav_link: "Settings" }} {%- endif %} {%- if is_administrator %} - {{ "admin/dashboard/administration" | nav_link: "Admin" }} + {{ "admin/administration" | nav_link: "Admin" }} {%- endif %} {%- endif %} diff --git a/src/admin-theme/_user-list-columns.liquid b/src/admin-theme/_user-list-columns.liquid new file mode 100644 index 0000000..74b6626 --- /dev/null +++ b/src/admin-theme/_user-list-columns.liquid @@ -0,0 +1,4 @@ +{%- assign user_col = "col-12 col-md-4 col-xl-3" -%} +{%- assign email_col = "col-12 col-md-4 col-xl-4" -%} +{%- assign cre8_col = "d-none d-xl-block col-xl-2" -%} +{%- assign last_col = "col-12 col-md-4 col-xl-3" -%} diff --git a/src/admin-theme/admin-dashboard.liquid b/src/admin-theme/admin-dashboard.liquid index fbede29..65592ed 100644 --- a/src/admin-theme/admin-dashboard.liquid +++ b/src/admin-theme/admin-dashboard.liquid @@ -1,6 +1,6 @@ 

{{ page_title }}

-
+
Themes
@@ -8,7 +8,7 @@ hx-target="#theme_new"> Upload a New Theme -
+
{% include_template "_theme-list-columns" %}
Theme
@@ -21,10 +21,10 @@
-
+
{%- assign cache_base_url = "admin/cache/" -%} Caches -
+

myWebLog uses a few caches to ensure that it serves pages as fast as possible. Normal actions taken within the @@ -38,31 +38,31 @@

Web Logs
-
+
These caches include the page list and categories for each web log
{%- assign web_log_base_url = cache_base_url | append: "web-log/" -%} -
+ -
-
Name
-
URL Base
+
Web Log
{%- for web_log in web_logs %}
{{ web_log[1] }}
+ {{ web_log[2] }}
{%- assign refresh_url = web_log_base_url | append: web_log[0] | append: "/refresh" | relative_link -%} Refresh
-
{{ web_log[2] }}
{%- endfor %}
@@ -72,21 +72,22 @@
Themes
-
+
- The themes template cache is loaded on demand; refresh a cache with 0 templates will still refresh the - theme asset cache + The theme template cache is filled on demand as pages are displayed; refreshing a theme with no cached + templates will still refresh its asset cache
{%- assign theme_base_url = cache_base_url | append: "theme/" -%} -
+ -
-
Name
-
Cached Templates
+
Theme
+
Cached
{%- for theme in cached_themes %} {% unless theme[0] == "admin" %} diff --git a/src/admin-theme/category-list-body.liquid b/src/admin-theme/category-list-body.liquid index 9c9dc2f..a3e822a 100644 --- a/src/admin-theme/category-list-body.liquid +++ b/src/admin-theme/category-list-body.liquid @@ -1,45 +1,57 @@ - - -
- {%- assign cat_count = categories | size -%} - {% if cat_count > 0 %} - {%- assign cat_col = "col-12 col-md-6 col-xl-5 col-xxl-4" -%} - {%- assign desc_col = "col-12 col-md-6 col-xl-7 col-xxl-8" -%} - {% for cat in categories -%} -
-
- {%- if cat.parent_names %} - {% for name in cat.parent_names %}{{ name }} ⟩ {% endfor %} - {%- endif %} - {{ cat.name }}
- - {%- assign cat_url_base = "admin/category/" | append: cat.id -%} - {%- if cat.post_count > 0 %} - - View {{ cat.post_count }} Post{% unless cat.post_count == 1 %}s{% endunless -%} - - - {%- endif %} - - Edit - - - {%- assign cat_del_link = cat_url_base | append: "/delete" | relative_link -%} - - Delete - - +
+
+
+ {%- assign cat_count = categories | size -%} + {% if cat_count > 0 %} + {%- assign cat_col = "col-12 col-md-6 col-xl-5 col-xxl-4" -%} + {%- assign desc_col = "col-12 col-md-6 col-xl-7 col-xxl-8" -%} +
+
+
Category; Description
+
Description
+
-
- {%- if cat.description %}{{ cat.description.value }}{% else %}none{% endif %} + + +
+ {% for cat in categories -%} +
+
+ {%- if cat.parent_names %} + {% for name in cat.parent_names %}{{ name }} ⟩ {% endfor %} + {%- endif %} + {{ cat.name }}
+ + {%- assign cat_url_base = "admin/category/" | append: cat.id -%} + {%- if cat.post_count > 0 %} + + View {{ cat.post_count }} Post{% unless cat.post_count == 1 %}s{% endunless -%} + + + {%- endif %} + + Edit + + + {%- assign cat_del_link = cat_url_base | append: "/delete" | relative_link -%} + + Delete + + +
+
+ {%- if cat.description %}{{ cat.description.value }}{% else %}none{% endif %} +
+
+ {%- endfor %} + + {%- else -%} +
+

This web log has no categores defined

-
- {%- endfor %} - {%- else -%} -
-
This web log has no categores defined
+ {%- endif %}
- {%- endif %} - +
+
diff --git a/src/admin-theme/category-list.liquid b/src/admin-theme/category-list.liquid index 1cfce71..d6eef09 100644 --- a/src/admin-theme/category-list.liquid +++ b/src/admin-theme/category-list.liquid @@ -4,13 +4,5 @@ hx-target="#cat_new"> Add a New Category -
- {%- assign cat_col = "col-12 col-md-6 col-xl-5 col-xxl-4" -%} - {%- assign desc_col = "col-12 col-md-6 col-xl-7 col-xxl-8" -%} -
-
Category; Description
-
Description
-
-
{{ category_list }}
diff --git a/src/admin-theme/my-info.liquid b/src/admin-theme/my-info.liquid index 22faf5f..b022874 100644 --- a/src/admin-theme/my-info.liquid +++ b/src/admin-theme/my-info.liquid @@ -1,6 +1,6 @@

{{ page_title }}

-
+
diff --git a/src/admin-theme/page-list.liquid b/src/admin-theme/page-list.liquid index b4165da..1a38b51 100644 --- a/src/admin-theme/page-list.liquid +++ b/src/admin-theme/page-list.liquid @@ -2,19 +2,19 @@
Create a New Page {%- assign page_count = pages | size -%} - {%- assign title_col = "col-12 col-md-5" -%} - {%- assign link_col = "col-12 col-md-5" -%} - {%- assign upd8_col = "col-12 col-md-2" -%} - - -
-
- TitlePage + {% if page_count > 0 %} + {%- assign title_col = "col-12 col-md-5" -%} + {%- assign link_col = "col-12 col-md-5" -%} + {%- assign upd8_col = "col-12 col-md-2" -%} + + +
+
+ TitlePage +
+ +
Updated
- -
Updated
-
- {% if page_count > 0 %} {% for pg in pages -%}
@@ -48,30 +48,30 @@
{%- endfor %} - {% else %} -
-
This web log has no pages
+ + {% if page_nbr > 1 or page_count == 25 %} +
+
+ {% if page_nbr > 1 %} +

+ + « Previous + +

+ {% endif %} +
+
+ {% if page_count == 25 %} +

+ + Next » + +

+ {% endif %} +
{% endif %} - - {% if page_nbr > 1 or page_count == 25 %} -
-
- {% if page_nbr > 1 %} -

- - « Previous - -

- {% endif %} -
-
- {% if page_count == 25 %} -

- Next » -

- {% endif %} -
-
+ {% else %} +

This web log has no pages

{% endif %}
diff --git a/src/admin-theme/post-list.liquid b/src/admin-theme/post-list.liquid index 842d725..7b46939 100644 --- a/src/admin-theme/post-list.liquid +++ b/src/admin-theme/post-list.liquid @@ -1,22 +1,22 @@

{{ page_title }}

Write a New Post -
- - {%- assign post_count = model.posts | size -%} - {%- assign date_col = "col-xs-12 col-md-3 col-lg-2" -%} - {%- assign title_col = "col-xs-12 col-md-7 col-lg-6 col-xl-5 col-xxl-4" -%} - {%- assign author_col = "col-xs-12 col-md-2 col-lg-1" -%} - {%- assign tag_col = "col-lg-3 col-xl-4 col-xxl-5 d-none d-lg-inline-block" -%} -
-
- PostDate + {%- assign post_count = model.posts | size -%} + {%- if post_count > 0 %} + + + {%- assign date_col = "col-xs-12 col-md-3 col-lg-2" -%} + {%- assign title_col = "col-xs-12 col-md-7 col-lg-6 col-xl-5 col-xxl-4" -%} + {%- assign author_col = "col-xs-12 col-md-2 col-lg-1" -%} + {%- assign tag_col = "col-lg-3 col-xl-4 col-xxl-5 d-none d-lg-inline-block" -%} +
+
+ PostDate +
+
Title
+
Author
+
Tags
-
Title
-
Author
-
Tags
-
- {%- if post_count > 0 %} {% for post in model.posts -%}
@@ -77,24 +77,22 @@
{%- endfor %} - {% else %} -
-
This web log has no posts
+ + {% if model.newer_link or model.older_link %} +
+
+ {% if model.newer_link %} +

« Newer Posts

+ {% endif %} +
+
+ {% if model.older_link %} +

Older Posts »

+ {% endif %} +
{% endif %} - - {% if model.newer_link or model.older_link %} -
-
- {% if model.newer_link %} -

« Newer Posts

- {% endif %} -
-
- {% if model.older_link %} -

Older Posts »

- {% endif %} -
-
+ {% else %} +

This web log has no posts

{% endif %}
diff --git a/src/admin-theme/rss-settings.liquid b/src/admin-theme/rss-settings.liquid index 0889309..5efb3c4 100644 --- a/src/admin-theme/rss-settings.liquid +++ b/src/admin-theme/rss-settings.liquid @@ -63,50 +63,54 @@
-

Custom Feeds

- - Add a New Custom Feed - -
- {%- assign source_col = "col-12 col-md-6" -%} - {%- assign path_col = "col-12 col-md-6" -%} - -
-
- FeedSource +
+ Custom Feeds +
+
+ + Add a New Custom Feed + + {%- assign feed_count = custom_feeds | size -%} + {% if feed_count > 0 %} + + {%- assign source_col = "col-12 col-md-6" -%} + {%- assign path_col = "col-12 col-md-6" -%} + +
+
+ FeedSource +
+
Relative Path
+
+ {% for feed in custom_feeds %} +
+
+ {{ feed.source }} + {%- if feed.is_podcast %}   PODCAST{% endif %}
+ + {%- assign feed_url = "admin/settings/rss/" | append: feed.id -%} + View Feed + + Edit + + {%- assign feed_del_link = feed_url | append: "/delete" | relative_link -%} + + Delete + + +
+
+ Served at {{ feed.path }} + {{ feed.path }} +
+
+ {% endfor %} + + {% else %} +

No custom feeds defined

+ {% endif %}
-
Relative Path
- {%- assign feed_count = custom_feeds | size -%} - {% if feed_count > 0 %} - {% for feed in custom_feeds %} -
-
- {{ feed.source }} - {%- if feed.is_podcast %}   PODCAST{% endif %}
- - {%- assign feed_url = "admin/settings/rss/" | append: feed.id -%} - View Feed - - Edit - - {%- assign feed_del_link = feed_url | append: "/delete" | relative_link -%} - - Delete - - -
-
- Served at {{ feed.path }} - {{ feed.path }} -
-
- {% endfor %} - {% else %} - - No custom feeds defined - - {% endif %} - +
diff --git a/src/admin-theme/settings.liquid b/src/admin-theme/settings.liquid index c545eac..896f23b 100644 --- a/src/admin-theme/settings.liquid +++ b/src/admin-theme/settings.liquid @@ -1,106 +1,136 @@ 

{{ web_log.name }} Settings

-

- Other Settings: Tag Mappings • - RSS Settings -

-
- -
-
-
-
- - +

+ Other Settings: Tag Mappings • + RSS Settings +

+
+ Web Log Settings +
+
+ + +
+
+
+
+ + +
+
+
+
+ + + + WARNING changing this value may break + links + (more) + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+ + +
+ + What is this? + +
+
+
+ + +
+
+
+
+
+ +
+
-
-
-
- - - - WARNING changing this value may break links - (more) - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
-
-
- - -
-
-
-
- - -
- - What is this? - -
-
-
- - -
-
-
-
-
- -
+
- + +
+ Users +
+
+ {% include_template "_user-list-columns" %} + + Add a New User + +
+
+
User; Full Name / E-mail; Last Log On
+ +
Created
+
Last Log On
+
+
+ {{ user_list }} +
+
+
diff --git a/src/admin-theme/tag-mapping-list-body.liquid b/src/admin-theme/tag-mapping-list-body.liquid index 1951c80..9e8b73c 100644 --- a/src/admin-theme/tag-mapping-list-body.liquid +++ b/src/admin-theme/tag-mapping-list-body.liquid @@ -1,33 +1,45 @@ -
- -
- {%- assign map_count = mappings | size -%} - {% if map_count > 0 -%} - {% for map in mappings -%} - {%- assign map_id = mapping_ids | value: map.tag -%} -
-
- {{ map.tag }}
- - {%- assign map_url = "admin/settings/tag-mapping/" | append: map_id -%} - - Edit - - - {%- assign map_del_link = map_url | append: "/delete" | relative_link -%} - - Delete - - +
+
+
+ {%- assign map_count = mappings | size -%} + {% if map_count > 0 -%} +
+
+
Tag
+
URL Value
+
-
{{ map.url_value }}
-
- {%- endfor %} - {%- else -%} -
-
This web log has no tag mappings
+ + +
+ {% for map in mappings -%} + {%- assign map_id = mapping_ids | value: map.tag -%} +
+
+ {{ map.tag }}
+ + {%- assign map_url = "admin/settings/tag-mapping/" | append: map_id -%} + + Edit + + + {%- assign map_del_link = map_url | append: "/delete" | relative_link -%} + + Delete + + +
+
{{ map.url_value }}
+
+ {%- endfor %} + + {%- else -%} +
+

This web log has no tag mappings

+
+ {%- endif %}
- {%- endif %} - +
+
diff --git a/src/admin-theme/tag-mapping-list.liquid b/src/admin-theme/tag-mapping-list.liquid index 0b583c4..af76d02 100644 --- a/src/admin-theme/tag-mapping-list.liquid +++ b/src/admin-theme/tag-mapping-list.liquid @@ -4,11 +4,5 @@ hx-target="#tag_new"> Add a New Tag Mapping -
-
-
Tag
-
URL Value
-
-
{{ tag_mapping_list }} diff --git a/src/admin-theme/theme-list-body.liquid b/src/admin-theme/theme-list-body.liquid index c48d54c..aad7eb5 100644 --- a/src/admin-theme/theme-list-body.liquid +++ b/src/admin-theme/theme-list-body.liquid @@ -1,4 +1,4 @@ -
+ {% include_template "_theme-list-columns" %} {% for theme in themes -%} diff --git a/src/admin-theme/theme-upload.liquid b/src/admin-theme/theme-upload.liquid index 95932fe..6deaef1 100644 --- a/src/admin-theme/theme-upload.liquid +++ b/src/admin-theme/theme-upload.liquid @@ -1,5 +1,5 @@
-
{{ page_title }}
+
{{ page_title }}
diff --git a/src/admin-theme/upload-list.liquid b/src/admin-theme/upload-list.liquid index a6f9f73..cedc570 100644 --- a/src/admin-theme/upload-list.liquid +++ b/src/admin-theme/upload-list.liquid @@ -7,15 +7,15 @@
-
Uploaded files served from
{{ upload_base }}
-
-
-
File Name
-
Path
-
File Date/Time
+
Uploaded files served from
{{ upload_base }}
{%- assign file_count = files | size -%} {%- if file_count > 0 %} +
+
File Name
+
Path
+
File Date/Time
+
{% for file in files %}
@@ -69,7 +69,7 @@ {% endfor %} {%- else -%}
-
This web log has uploaded files
+

This web log has uploaded files
{%- endif %} diff --git a/src/admin-theme/user-edit.liquid b/src/admin-theme/user-edit.liquid index 1519960..1bd4835 100644 --- a/src/admin-theme/user-edit.liquid +++ b/src/admin-theme/user-edit.liquid @@ -1,13 +1,13 @@
{{ page_title }}
-
- {%- for level in access_levels %} diff --git a/src/admin-theme/user-list-body.liquid b/src/admin-theme/user-list-body.liquid index 5ea9ae6..7faf9c2 100644 --- a/src/admin-theme/user-list-body.liquid +++ b/src/admin-theme/user-list-body.liquid @@ -1,60 +1,61 @@ -
- -
- {%- assign user_col = "col-12 col-md-4 col-xl-3" -%} - {%- assign email_col = "col-12 col-md-4 col-xl-4" -%} - {%- assign cre8_col = "d-none d-xl-block col-xl-2" -%} - {%- assign last_col = "col-12 col-md-4 col-xl-3" -%} - {%- assign badge = "ms-2 badge bg" -%} - {% for user in users -%} -
-
- {{ user.preferred_name }} - {%- if user.access_level == "Administrator" %} - ADMINISTRATOR - {%- elsif user.access_level == "WebLogAdmin" %} - WEB LOG ADMIN - {%- elsif user.access_level == "Editor" %} - EDITOR - {%- elsif user.access_level == "Author" %} - AUTHOR - {%- endif %}
- {%- unless is_administrator == false and user.access_level == "Administrator" %} - - {%- assign user_url_base = "admin/user/" | append: user.id -%} - - Edit - - {% unless user_id == user.id %} - - {%- assign user_del_link = user_url_base | append: "/delete" | relative_link -%} - - Delete +
+
+
+
+ + + {% include_template "_user-list-columns" %} + {%- assign badge = "ms-2 badge bg" -%} + {% for user in users -%} +
+ + +
+ {{ user.created_on | date: "MMMM d, yyyy" }} +
+
+ {% if user.last_seen_on %} + {{ user.last_seen_on | date: "MMMM d, yyyy" }} at + {{ user.last_seen_on | date: "h:mmtt" | downcase }} + {% else %} + -- + {% endif %} +
- -
- {{ user.created_on | date: "MMMM d, yyyy" }} -
-
- {% if user.last_seen_on %} - {{ user.last_seen_on | date: "MMMM d, yyyy" }} at - {{ user.last_seen_on | date: "h:mmtt" | downcase }} - {% else %} - -- - {% endif %} -
-
- {%- endfor %} - + {%- endfor %} + +
diff --git a/src/admin-theme/user-list.liquid b/src/admin-theme/user-list.liquid deleted file mode 100644 index 067953a..0000000 --- a/src/admin-theme/user-list.liquid +++ /dev/null @@ -1,20 +0,0 @@ -

{{ page_title }}

-
- - Add a New User - -
- {%- assign user_col = "col-12 col-md-4 col-xl-3" -%} - {%- assign email_col = "col-12 col-md-4 col-xl-4" -%} - {%- assign cre8_col = "d-none d-xl-block col-xl-2" -%} - {%- assign last_col = "col-12 col-md-4 col-xl-3" -%} -
-
User; Details; Last Log On
- -
Created
-
Last Log On
-
-
- {{ user_list }} -