Tweak admin UI templates (#25)

- Move user management under web log settings
- Move user self-update to my-info
- Return meaningful error if a template does not exist
- Tweak margins/paddings throughout
- Do not show headings on list pages if lists are empty
- Fix pagination styles for page/post list pages
This commit is contained in:
Daniel J. Summers 2022-07-26 16:28:14 -04:00
parent ff9c08842b
commit 3189681021
23 changed files with 652 additions and 586 deletions

View File

@ -172,18 +172,34 @@ module TemplateCache =
let get (themeId : ThemeId) (templateName : string) (data : IData) = backgroundTask { let get (themeId : ThemeId) (templateName : string) (data : IData) = backgroundTask {
let templatePath = $"{ThemeId.toString themeId}/{templateName}" let templatePath = $"{ThemeId.toString themeId}/{templateName}"
match _cache.ContainsKey templatePath with match _cache.ContainsKey templatePath with
| true -> () | true -> return Ok _cache[templatePath]
| false -> | false ->
match! data.Theme.FindById themeId with match! data.Theme.FindById themeId with
| Some theme -> | Some theme ->
let mutable text = (theme.Templates |> List.find (fun t -> t.Name = templateName)).Text match theme.Templates |> List.tryFind (fun t -> t.Name = templateName) with
while hasInclude.IsMatch text do | Some template ->
let child = hasInclude.Match text let mutable text = template.Text
let childText = (theme.Templates |> List.find (fun t -> t.Name = child.Groups[1].Value)).Text let mutable childNotFound = ""
text <- text.Replace (child.Value, childText) while hasInclude.IsMatch text do
_cache[templatePath] <- Template.Parse (text, SyntaxCompatibility.DotLiquid22) let child = hasInclude.Match text
| None -> () let childText =
return _cache[templatePath] 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 /// Get all theme/template names currently cached

View File

@ -32,38 +32,43 @@ let dashboard : HttpHandler = requireAccess Author >=> fun next ctx -> task {
|> adminView "dashboard" next ctx |> adminView "dashboard" next ctx
} }
// GET /admin/dashboard/administration // GET /admin/administration
let adminDashboard : HttpHandler = requireAccess Administrator >=> fun next ctx -> task { let adminDashboard : HttpHandler = requireAccess Administrator >=> fun next ctx -> task {
let! themes = ctx.Data.Theme.All () match! TemplateCache.get adminTheme "theme-list-body" ctx.Data with
let! bodyTemplate = TemplateCache.get adminTheme "theme-list-body" ctx.Data | Ok bodyTemplate ->
let cachedTemplates = TemplateCache.allNames () let! themes = ctx.Data.Theme.All ()
let! hash = let cachedTemplates = TemplateCache.allNames ()
hashForPage "myWebLog Administration" let! hash =
|> withAntiCsrf ctx hashForPage "myWebLog Administration"
|> addToHash "themes" (themes |> List.map (DisplayTheme.fromTheme WebLogCache.isThemeInUse) |> Array.ofList) |> withAntiCsrf ctx
|> addToHash "cached_themes" ( |> addToHash "themes" (themes |> List.map (DisplayTheme.fromTheme WebLogCache.isThemeInUse) |> Array.ofList)
themes |> addToHash "cached_themes" (
|> Seq.ofList themes
|> Seq.map (fun it -> [| |> Seq.ofList
ThemeId.toString it.Id |> Seq.map (fun it -> [|
it.Name ThemeId.toString it.Id
cachedTemplates |> List.filter (fun n -> n.StartsWith (ThemeId.toString it.Id)) |> List.length |> string it.Name
|]) cachedTemplates
|> Array.ofSeq) |> List.filter (fun n -> n.StartsWith (ThemeId.toString it.Id))
|> addToHash "web_logs" ( |> List.length
WebLogCache.all () |> string
|> Seq.ofList |])
|> Seq.sortBy (fun it -> it.Name) |> Array.ofSeq)
|> Seq.map (fun it -> [| WebLogId.toString it.Id; it.Name; it.UrlBase |]) |> addToHash "web_logs" (
|> Array.ofSeq) WebLogCache.all ()
|> addViewContext ctx |> Seq.ofList
return! |> Seq.sortBy (fun it -> it.Name)
addToHash "theme_list" (bodyTemplate.Render hash) hash |> Seq.map (fun it -> [| WebLogId.toString it.Id; it.Name; it.UrlBase |])
|> adminView "admin-dashboard" next ctx |> 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 /// Redirect the user to the admin dashboard
let toAdminDashboard : HttpHandler = redirectToGet "admin/dashboard/administration" let toAdminDashboard : HttpHandler = redirectToGet "admin/administration"
// ~~ CACHES ~~ // ~~ CACHES ~~
@ -117,14 +122,16 @@ let refreshThemeCache themeId : HttpHandler = requireAccess Administrator >=> fu
// GET /admin/categories // GET /admin/categories
let listCategories : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { let listCategories : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task {
let! catListTemplate = TemplateCache.get adminTheme "category-list-body" ctx.Data match! TemplateCache.get adminTheme "category-list-body" ctx.Data with
let! hash = | Ok catListTemplate ->
hashForPage "Categories" let! hash =
|> withAntiCsrf ctx hashForPage "Categories"
|> addViewContext ctx |> withAntiCsrf ctx
return! |> addViewContext ctx
addToHash "category_list" (catListTemplate.Render hash) hash return!
|> adminView "category-list" next ctx addToHash "category_list" (catListTemplate.Render hash) hash
|> adminView "category-list" next ctx
| Error message -> return! Error.server message next ctx
} }
// GET /admin/categories/bare // GET /admin/categories/bare
@ -204,11 +211,13 @@ let private tagMappingHash (ctx : HttpContext) = task {
// GET /admin/settings/tag-mappings // GET /admin/settings/tag-mappings
let tagMappings : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { let tagMappings : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task {
let! hash = tagMappingHash ctx match! TemplateCache.get adminTheme "tag-mapping-list-body" ctx.Data with
let! listTemplate = TemplateCache.get adminTheme "tag-mapping-list-body" ctx.Data | Ok listTemplate ->
return! let! hash = tagMappingHash ctx
addToHash "tag_mapping_list" (listTemplate.Render hash) hash return!
|> adminView "tag-mapping-list" next ctx 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 // GET /admin/settings/tag-mappings/bare
@ -421,31 +430,39 @@ open System.Collections.Generic
// GET /admin/settings // GET /admin/settings
let settings : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { let settings : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task {
let data = ctx.Data let data = ctx.Data
let! allPages = data.Page.All ctx.WebLog.Id match! TemplateCache.get adminTheme "user-list-body" data with
let! themes = data.Theme.All () | Ok userTemplate ->
return! let! allPages = data.Page.All ctx.WebLog.Id
hashForPage "Web Log Settings" let! themes = data.Theme.All ()
|> withAntiCsrf ctx let! users = data.WebLogUser.FindByWebLog ctx.WebLog.Id
|> addToHash ViewContext.Model (SettingsModel.fromWebLog ctx.WebLog) let! hash =
|> addToHash "pages" ( hashForPage "Web Log Settings"
seq { |> withAntiCsrf ctx
KeyValuePair.Create ("posts", "- First Page of Posts -") |> addToHash ViewContext.Model (SettingsModel.fromWebLog ctx.WebLog)
yield! allPages |> addToHash "pages" (
|> List.sortBy (fun p -> p.Title.ToLower ()) seq {
|> List.map (fun p -> KeyValuePair.Create (PageId.toString p.Id, p.Title)) KeyValuePair.Create ("posts", "- First Page of Posts -")
} yield! allPages
|> Array.ofSeq) |> List.sortBy (fun p -> p.Title.ToLower ())
|> addToHash "themes" ( |> List.map (fun p -> KeyValuePair.Create (PageId.toString p.Id, p.Title))
themes }
|> Seq.ofList |> Array.ofSeq)
|> Seq.map (fun it -> KeyValuePair.Create (ThemeId.toString it.Id, $"{it.Name} (v{it.Version})")) |> addToHash "themes" (
|> Array.ofSeq) themes
|> addToHash "upload_values" [| |> Seq.ofList
KeyValuePair.Create (UploadDestination.toString Database, "Database") |> Seq.map (fun it -> KeyValuePair.Create (ThemeId.toString it.Id, $"{it.Name} (v{it.Version})"))
KeyValuePair.Create (UploadDestination.toString Disk, "Disk") |> Array.ofSeq)
|] |> addToHash "upload_values" [|
|> adminView "settings" next ctx 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 // POST /admin/settings

View File

@ -218,23 +218,6 @@ let addViewContext ctx (hash : Hash) = task {
let isHtmx (ctx : HttpContext) = let isHtmx (ctx : HttpContext) =
ctx.Request.IsHtmx && not ctx.Request.IsHtmxRefresh 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) /// Convert messages to headers (used for htmx responses)
let messagesToHeaders (messages : UserMessage array) : HttpHandler = let messagesToHeaders (messages : UserMessage array) : HttpHandler =
seq { seq {
@ -249,52 +232,12 @@ let messagesToHeaders (messages : UserMessage array) : HttpHandler =
} }
|> Seq.reduce (>=>) |> 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 /// Redirect after doing some action; commits session and issues a temporary redirect
let redirectToGet url : HttpHandler = fun _ ctx -> task { let redirectToGet url : HttpHandler = fun _ ctx -> task {
do! commitSession ctx do! commitSession ctx
return! redirectTo false (WebLog.relativeUrl ctx.WebLog (Permalink url)) earlyReturn 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 /// Handlers for error conditions
module Error = module Error =
@ -324,9 +267,81 @@ module Error =
let messages = [| let messages = [|
{ UserMessage.error with Message = $"The URL {ctx.Request.Path.Value} was not found" } { UserMessage.error with Message = $"The URL {ctx.Request.Path.Value} was not found" }
|] |]
(messagesToHeaders messages >=> setStatusCode 404) earlyReturn ctx RequestErrors.notFound (messagesToHeaders messages) earlyReturn ctx
else else RequestErrors.NOT_FOUND "Not found" earlyReturn ctx)
(setStatusCode 404 >=> text "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 /// Require a user to be logged on

View File

@ -106,13 +106,14 @@ let router : HttpHandler = choose [
] ]
subRoute "/admin" (requireUser >=> choose [ subRoute "/admin" (requireUser >=> choose [
GET_HEAD >=> choose [ GET_HEAD >=> choose [
route "/administration" >=> Admin.adminDashboard
subRoute "/categor" (choose [ subRoute "/categor" (choose [
route "ies" >=> Admin.listCategories route "ies" >=> Admin.listCategories
route "ies/bare" >=> Admin.listCategoriesBare route "ies/bare" >=> Admin.listCategoriesBare
routef "y/%s/edit" Admin.editCategory routef "y/%s/edit" Admin.editCategory
]) ])
route "/dashboard" >=> Admin.dashboard route "/dashboard" >=> Admin.dashboard
route "/dashboard/administration" >=> Admin.adminDashboard route "/my-info" >=> User.myInfo
subRoute "/page" (choose [ subRoute "/page" (choose [
route "s" >=> Page.all 1 route "s" >=> Page.all 1
routef "s/page/%i" Page.all routef "s/page/%i" Page.all
@ -134,6 +135,11 @@ let router : HttpHandler = choose [
subRoute "/rss" (choose [ subRoute "/rss" (choose [
route "" >=> Feed.editSettings route "" >=> Feed.editSettings
routef "/%s/edit" Feed.editCustomFeed routef "/%s/edit" Feed.editCustomFeed
])
subRoute "/user" (choose [
route "s" >=> User.all
routef "/%s/edit" User.edit
]) ])
subRoute "/tag-mapping" (choose [ subRoute "/tag-mapping" (choose [
route "s" >=> Admin.tagMappings route "s" >=> Admin.tagMappings
@ -149,12 +155,6 @@ let router : HttpHandler = choose [
route "s" >=> Upload.list route "s" >=> Upload.list
route "/new" >=> Upload.showNew 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 [ POST >=> validateCsrf >=> choose [
subRoute "/cache" (choose [ subRoute "/cache" (choose [
@ -165,6 +165,7 @@ let router : HttpHandler = choose [
route "/save" >=> Admin.saveCategory route "/save" >=> Admin.saveCategory
routef "/%s/delete" Admin.deleteCategory routef "/%s/delete" Admin.deleteCategory
]) ])
route "/my-info" >=> User.saveMyInfo
subRoute "/page" (choose [ subRoute "/page" (choose [
route "/save" >=> Page.save route "/save" >=> Page.save
route "/permalinks" >=> Page.savePermalinks route "/permalinks" >=> Page.savePermalinks
@ -192,6 +193,10 @@ let router : HttpHandler = choose [
route "/save" >=> Admin.saveMapping route "/save" >=> Admin.saveMapping
routef "/%s/delete" Admin.deleteMapping routef "/%s/delete" Admin.deleteMapping
]) ])
subRoute "/user" (choose [
route "/save" >=> User.save
routef "/%s/delete" User.delete
])
]) ])
subRoute "/theme" (choose [ subRoute "/theme" (choose [
route "/new" >=> Admin.saveTheme route "/new" >=> Admin.saveTheme
@ -202,11 +207,6 @@ let router : HttpHandler = choose [
routexp "/delete/(.*)" Upload.deleteFromDisk routexp "/delete/(.*)" Upload.deleteFromDisk
routef "/%s/delete" Upload.deleteFromDb 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 GET_HEAD >=> routexp "/category/(.*)" Post.pageOfCategorizedPosts

View File

@ -72,34 +72,18 @@ let logOff : HttpHandler = fun next ctx -> task {
open System.Collections.Generic open System.Collections.Generic
open Giraffe.Htmx open Giraffe.Htmx
open Microsoft.AspNetCore.Http
/// Create the hash needed to display the user list /// Got no time for URL/form manipulators...
let private userListHash (ctx : HttpContext) = task { 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 let! users = ctx.Data.WebLogUser.FindByWebLog ctx.WebLog.Id
return! return!
hashForPage "User Administration" hashForPage "User Administration"
|> withAntiCsrf ctx |> withAntiCsrf ctx
|> addToHash "users" (users |> List.map (DisplayUser.fromUser ctx.WebLog) |> Array.ofList) |> addToHash "users" (users |> List.map (DisplayUser.fromUser ctx.WebLog) |> Array.ofList)
|> addViewContext ctx |> adminBareView "user-list-body" next 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
} }
/// Show the edit user page /// Show the edit user page
@ -116,7 +100,7 @@ let private showEdit (model : EditUserModel) : HttpHandler = fun next ctx ->
|] |]
|> adminBareView "user-edit" 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 edit usrId : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task {
let isNew = usrId = "new" let isNew = usrId = "new"
let userId = WebLogUserId usrId let userId = WebLogUserId usrId
@ -128,7 +112,7 @@ let edit usrId : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> tas
| None -> return! Error.notFound next ctx | 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 delete userId : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task {
let data = ctx.Data let data = ctx.Data
match! data.WebLogUser.FindById (WebLogUserId userId) ctx.WebLog.Id with 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 { UserMessage.success with
Message = $"User {WebLogUser.displayName user} deleted successfully" Message = $"User {WebLogUser.displayName user} deleted successfully"
} }
return! bare next ctx return! all next ctx
| Error msg -> | Error msg ->
do! addMessage ctx do! addMessage ctx
{ UserMessage.error with { UserMessage.error with
Message = $"User {WebLogUser.displayName user} was not deleted" Message = $"User {WebLogUser.displayName user} was not deleted"
Detail = Some msg Detail = Some msg
} }
return! bare next ctx return! all next ctx
| None -> return! Error.notFound 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 |> adminView "my-info" next ctx
// GET /admin/user/my-info // GET /admin/my-info
let myInfo : HttpHandler = requireAccess Author >=> fun next ctx -> task { let myInfo : HttpHandler = requireAccess Author >=> fun next ctx -> task {
match! ctx.Data.WebLogUser.FindById ctx.UserId ctx.WebLog.Id with match! ctx.Data.WebLogUser.FindById ctx.UserId ctx.WebLog.Id with
| Some user -> return! showMyInfo (EditMyInfoModel.fromUser user) user next ctx | Some user -> return! showMyInfo (EditMyInfoModel.fromUser user) user next ctx
| None -> return! Error.notFound 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 saveMyInfo : HttpHandler = requireAccess Author >=> fun next ctx -> task {
let! model = ctx.BindFormAsync<EditMyInfoModel> () let! model = ctx.BindFormAsync<EditMyInfoModel> ()
let data = ctx.Data let data = ctx.Data
@ -194,7 +178,7 @@ let saveMyInfo : HttpHandler = requireAccess Author >=> fun next ctx -> task {
do! data.WebLogUser.Update user do! data.WebLogUser.Update user
let pwMsg = if model.NewPassword = "" then "" else " and updated your password" let pwMsg = if model.NewPassword = "" then "" else " and updated your password"
do! addMessage ctx { UserMessage.success with Message = $"Saved your information{pwMsg} successfully" } 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 -> | Some user ->
do! addMessage ctx { UserMessage.error with Message = "Passwords did not match; no updates made" } do! addMessage ctx { UserMessage.error with Message = "Passwords did not match; no updates made" }
return! showMyInfo { model with NewPassword = ""; NewPasswordConfirm = "" } user next ctx 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 // User save is not statically compilable; not sure why, but we'll revisit it at some point
#nowarn "3511" #nowarn "3511"
// POST /admin/user/save // POST /admin/settings/user/save
let save : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { let save : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task {
let! model = ctx.BindFormAsync<EditUserModel> () let! model = ctx.BindFormAsync<EditUserModel> ()
let data = ctx.Data let data = ctx.Data
@ -232,7 +216,7 @@ let save : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task {
{ UserMessage.success with { UserMessage.success with
Message = $"""{if model.IsNew then "Add" else "Updat"}ed user successfully""" Message = $"""{if model.IsNew then "Add" else "Updat"}ed user successfully"""
} }
return! bare next ctx return! all next ctx
| Some _ -> | Some _ ->
do! addMessage ctx { UserMessage.error with Message = "The passwords did not match; nothing saved" } do! addMessage ctx { UserMessage.error with Message = "The passwords did not match; nothing saved" }
return! return!

View File

@ -17,11 +17,10 @@
{%- endif %} {%- endif %}
{%- if is_web_log_admin %} {%- if is_web_log_admin %}
{{ "admin/categories" | nav_link: "Categories" }} {{ "admin/categories" | nav_link: "Categories" }}
{{ "admin/users" | nav_link: "Users" }}
{{ "admin/settings" | nav_link: "Settings" }} {{ "admin/settings" | nav_link: "Settings" }}
{%- endif %} {%- endif %}
{%- if is_administrator %} {%- if is_administrator %}
{{ "admin/dashboard/administration" | nav_link: "Admin" }} {{ "admin/administration" | nav_link: "Admin" }}
{%- endif %} {%- endif %}
</ul> </ul>
{%- endif %} {%- endif %}

View File

@ -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" -%}

View File

@ -1,6 +1,6 @@
<h2 class="my-3">{{ page_title }}</h2> <h2 class="my-3">{{ page_title }}</h2>
<article> <article>
<fieldset class="container mb-3"> <fieldset class="container mb-3 pb-0">
<legend>Themes</legend> <legend>Themes</legend>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
@ -8,7 +8,7 @@
hx-target="#theme_new"> hx-target="#theme_new">
Upload a New Theme Upload a New Theme
</a> </a>
<div class="container"> <div class="container g-0">
{% include_template "_theme-list-columns" %} {% include_template "_theme-list-columns" %}
<div class="row mwl-table-heading"> <div class="row mwl-table-heading">
<div class="{{ theme_col }}">Theme</div> <div class="{{ theme_col }}">Theme</div>
@ -21,10 +21,10 @@
</div> </div>
</div> </div>
</fieldset> </fieldset>
<fieldset class="container"> <fieldset class="container mb-3 pb-0">
{%- assign cache_base_url = "admin/cache/" -%} {%- assign cache_base_url = "admin/cache/" -%}
<legend>Caches</legend> <legend>Caches</legend>
<div class="row pb-3"> <div class="row pb-2">
<div class="col"> <div class="col">
<p> <p>
myWebLog uses a few caches to ensure that it serves pages as fast as possible. Normal actions taken within the myWebLog uses a few caches to ensure that it serves pages as fast as possible. Normal actions taken within the
@ -38,31 +38,31 @@
<div class="col-12 col-lg-6 pb-3"> <div class="col-12 col-lg-6 pb-3">
<div class="card"> <div class="card">
<header class="card-header text-white bg-secondary">Web Logs</header> <header class="card-header text-white bg-secondary">Web Logs</header>
<div class="card-body"> <div class="card-body pb-0">
<h6 class="card-subtitle text-muted pb-3"> <h6 class="card-subtitle text-muted pb-3">
These caches include the page list and categories for each web log These caches include the page list and categories for each web log
</h6> </h6>
{%- assign web_log_base_url = cache_base_url | append: "web-log/" -%} {%- assign web_log_base_url = cache_base_url | append: "web-log/" -%}
<form method="post" class="container pb-3" hx-boost="false" hx-target="body" hx-swap="innerHTML show:window:top"> <form method="post" class="container g-0" hx-boost="false" hx-target="body"
hx-swap="innerHTML show:window:top">
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}"> <input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
<button type="submit" class="btn btn-sm btn-primary pb-2" <button type="submit" class="btn btn-sm btn-primary mb-2"
hx-post="{{ web_log_base_url | append: "all/refresh" | relative_link }}"> hx-post="{{ web_log_base_url | append: "all/refresh" | relative_link }}">
Refresh All Refresh All
</button> </button>
<div class="row mwl-table-heading"> <div class="row mwl-table-heading">
<div class="col">Name</div> <div class="col">Web Log</div>
<div class="col">URL Base</div>
</div> </div>
{%- for web_log in web_logs %} {%- for web_log in web_logs %}
<div class="row mwl-table-detail"> <div class="row mwl-table-detail">
<div class="col"> <div class="col">
{{ web_log[1] }}<br> {{ web_log[1] }}<br>
<small> <small>
<span class="text-muted">{{ web_log[2] }}</span><br>
{%- assign refresh_url = web_log_base_url | append: web_log[0] | append: "/refresh" | relative_link -%} {%- assign refresh_url = web_log_base_url | append: web_log[0] | append: "/refresh" | relative_link -%}
<a href="{{ refresh_url }}" hx-post="{{ refresh_url }}">Refresh</a> <a href="{{ refresh_url }}" hx-post="{{ refresh_url }}">Refresh</a>
</small> </small>
</div> </div>
<div class="col">{{ web_log[2] }}</div>
</div> </div>
{%- endfor %} {%- endfor %}
</form> </form>
@ -72,21 +72,22 @@
<div class="col-12 col-lg-6 pb-3"> <div class="col-12 col-lg-6 pb-3">
<div class="card"> <div class="card">
<header class="card-header text-white bg-secondary">Themes</header> <header class="card-header text-white bg-secondary">Themes</header>
<div class="card-body"> <div class="card-body pb-0">
<h6 class="card-subtitle text-muted pb-3"> <h6 class="card-subtitle text-muted pb-3">
The themes template cache is loaded on demand; refresh a cache with 0 templates will still refresh the The theme template cache is filled on demand as pages are displayed; refreshing a theme with no cached
theme asset cache templates will still refresh its asset cache
</h6> </h6>
{%- assign theme_base_url = cache_base_url | append: "theme/" -%} {%- assign theme_base_url = cache_base_url | append: "theme/" -%}
<form method="post" class="container pb-3" hx-boost="false" hx-target="body" hx-swap="innerHTML show:window:top"> <form method="post" class="container g-0" hx-boost="false" hx-target="body"
hx-swap="innerHTML show:window:top">
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}"> <input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
<button type="submit" class="btn btn-sm btn-primary pb-2" <button type="submit" class="btn btn-sm btn-primary mb-2"
hx-post="{{ theme_base_url | append: "all/refresh" | relative_link }}"> hx-post="{{ theme_base_url | append: "all/refresh" | relative_link }}">
Refresh All Refresh All
</button> </button>
<div class="row mwl-table-heading"> <div class="row mwl-table-heading">
<div class="col-8">Name</div> <div class="col-8">Theme</div>
<div class="col-4">Cached Templates</div> <div class="col-4">Cached</div>
</div> </div>
{%- for theme in cached_themes %} {%- for theme in cached_themes %}
{% unless theme[0] == "admin" %} {% unless theme[0] == "admin" %}

View File

@ -1,45 +1,57 @@
<form method="post" id="catList" class="container" hx-target="this" hx-swap="outerHTML show:window:top"> <div id="catList" class="container">
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}"> <div class="row">
<div class="row mwl-table-detail" id="cat_new"></div> <div class="col">
{%- assign cat_count = categories | size -%} {%- assign cat_count = categories | size -%}
{% if cat_count > 0 %} {% if cat_count > 0 %}
{%- assign cat_col = "col-12 col-md-6 col-xl-5 col-xxl-4" -%} {%- 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" -%} {%- assign desc_col = "col-12 col-md-6 col-xl-7 col-xxl-8" -%}
{% for cat in categories -%} <div class="container">
<div class="row mwl-table-detail" id="cat_{{ cat.id }}"> <div class="row mwl-table-heading">
<div class="{{ cat_col }} no-wrap"> <div class="{{ cat_col }}">Category<span class="d-md-none">; Description</span></div>
{%- if cat.parent_names %} <div class="{{ desc_col }} d-none d-md-inline-block">Description</div>
<small class="text-muted">{% for name in cat.parent_names %}{{ name }} &rang; {% endfor %}</small> </div>
{%- endif %}
{{ cat.name }}<br>
<small>
{%- assign cat_url_base = "admin/category/" | append: cat.id -%}
{%- if cat.post_count > 0 %}
<a href="{{ cat | category_link }}" target="_blank">
View {{ cat.post_count }} Post{% unless cat.post_count == 1 %}s{% endunless -%}
</a>
<span class="text-muted"> &bull; </span>
{%- endif %}
<a href="{{ cat_url_base | append: "/edit" | relative_link }}" hx-target="#cat_{{ cat.id }}"
hx-swap="innerHTML show:#cat_{{ cat.id }}:top">
Edit
</a>
<span class="text-muted"> &bull; </span>
{%- assign cat_del_link = cat_url_base | append: "/delete" | relative_link -%}
<a href="{{ cat_del_link }}" hx-post="{{ cat_del_link }}" class="text-danger"
hx-confirm="Are you sure you want to delete the category &ldquo;{{ cat.name }}&rdquo;? This action cannot be undone.">
Delete
</a>
</small>
</div> </div>
<div class="{{ desc_col }}"> <form method="post" class="container" hx-target="#catList" hx-swap="outerHTML show:window:top">
{%- if cat.description %}{{ cat.description.value }}{% else %}<em class="text-muted">none</em>{% endif %} <input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
<div class="row mwl-table-detail" id="cat_new"></div>
{% for cat in categories -%}
<div class="row mwl-table-detail" id="cat_{{ cat.id }}">
<div class="{{ cat_col }} no-wrap">
{%- if cat.parent_names %}
<small class="text-muted">{% for name in cat.parent_names %}{{ name }} &rang; {% endfor %}</small>
{%- endif %}
{{ cat.name }}<br>
<small>
{%- assign cat_url_base = "admin/category/" | append: cat.id -%}
{%- if cat.post_count > 0 %}
<a href="{{ cat | category_link }}" target="_blank">
View {{ cat.post_count }} Post{% unless cat.post_count == 1 %}s{% endunless -%}
</a>
<span class="text-muted"> &bull; </span>
{%- endif %}
<a href="{{ cat_url_base | append: "/edit" | relative_link }}" hx-target="#cat_{{ cat.id }}"
hx-swap="innerHTML show:#cat_{{ cat.id }}:top">
Edit
</a>
<span class="text-muted"> &bull; </span>
{%- assign cat_del_link = cat_url_base | append: "/delete" | relative_link -%}
<a href="{{ cat_del_link }}" hx-post="{{ cat_del_link }}" class="text-danger"
hx-confirm="Are you sure you want to delete the category &ldquo;{{ cat.name }}&rdquo;? This action cannot be undone.">
Delete
</a>
</small>
</div>
<div class="{{ desc_col }}">
{%- if cat.description %}{{ cat.description.value }}{% else %}<em class="text-muted">none</em>{% endif %}
</div>
</div>
{%- endfor %}
</form>
{%- else -%}
<div id="cat_new">
<p class="text-muted fst-italic text-center">This web log has no categores defined</p>
</div> </div>
</div> {%- endif %}
{%- endfor %}
{%- else -%}
<div class="row">
<div class="col-12 text-muted fst-italic text-center">This web log has no categores defined</div>
</div> </div>
{%- endif %} </div>
</form> </div>

View File

@ -4,13 +4,5 @@
hx-target="#cat_new"> hx-target="#cat_new">
Add a New Category Add a New Category
</a> </a>
<div class="container">
{%- 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" -%}
<div class="row mwl-table-heading">
<div class="{{ cat_col }}">Category<span class="d-md-none">; Description</span></div>
<div class="{{ desc_col }} d-none d-md-inline-block">Description</div>
</div>
</div>
{{ category_list }} {{ category_list }}
</article> </article>

View File

@ -1,6 +1,6 @@
<h2 class="my-3">{{ page_title }}</h2> <h2 class="my-3">{{ page_title }}</h2>
<article> <article>
<form action="{{ "admin/user/my-info" | relative_link }}" method="post"> <form action="{{ "admin/my-info" | relative_link }}" method="post">
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}"> <input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
<div class="d-flex flex-row flex-wrap justify-content-around"> <div class="d-flex flex-row flex-wrap justify-content-around">
<div class="text-center mb-3 lh-sm"> <div class="text-center mb-3 lh-sm">

View File

@ -2,19 +2,19 @@
<article> <article>
<a href="{{ "admin/page/new/edit" | relative_link }}" class="btn btn-primary btn-sm mb-3">Create a New Page</a> <a href="{{ "admin/page/new/edit" | relative_link }}" class="btn btn-primary btn-sm mb-3">Create a New Page</a>
{%- assign page_count = pages | size -%} {%- assign page_count = pages | size -%}
{%- assign title_col = "col-12 col-md-5" -%} {% if page_count > 0 %}
{%- assign link_col = "col-12 col-md-5" -%} {%- assign title_col = "col-12 col-md-5" -%}
{%- assign upd8_col = "col-12 col-md-2" -%} {%- assign link_col = "col-12 col-md-5" -%}
<form method="post" class="container" hx-target="body"> {%- assign upd8_col = "col-12 col-md-2" -%}
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}"> <form method="post" class="container" hx-target="body">
<div class="row mwl-table-heading"> <input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
<div class="{{ title_col }}"> <div class="row mwl-table-heading">
<span class="d-none d-md-inline">Title</span><span class="d-md-none">Page</span> <div class="{{ title_col }}">
<span class="d-none d-md-inline">Title</span><span class="d-md-none">Page</span>
</div>
<div class="{{ link_col }} d-none d-md-inline-block">Permalink</div>
<div class="{{ upd8_col }} d-none d-md-inline-block">Updated</div>
</div> </div>
<div class="{{ link_col }} d-none d-md-inline-block">Permalink</div>
<div class="{{ upd8_col }} d-none d-md-inline-block">Updated</div>
</div>
{% if page_count > 0 %}
{% for pg in pages -%} {% for pg in pages -%}
<div class="row mwl-table-detail"> <div class="row mwl-table-detail">
<div class="{{ title_col }}"> <div class="{{ title_col }}">
@ -48,30 +48,30 @@
</div> </div>
</div> </div>
{%- endfor %} {%- endfor %}
{% else %} </form>
<div class="row"> {% if page_nbr > 1 or page_count == 25 %}
<div class="col text-muted fst-italic text-center">This web log has no pages</div> <div class="d-flex justify-content-evenly mb-3">
<div>
{% if page_nbr > 1 %}
<p>
<a class="btn btn-secondary" href="{{ "admin/pages" | append: prev_page | relative_link }}">
&laquo; Previous
</a>
</p>
{% endif %}
</div>
<div class="text-right">
{% if page_count == 25 %}
<p>
<a class="btn btn-secondary" href="{{ "admin/pages" | append: next_page | relative_link }}">
Next &raquo;
</a>
</p>
{% endif %}
</div>
</div> </div>
{% endif %} {% endif %}
</form> {% else %}
{% if page_nbr > 1 or page_count == 25 %} <p class="text-muted fst-italic text-center">This web log has no pages</p>
<div class="d-flex justify-content-evenly pb-3">
<div>
{% if page_nbr > 1 %}
<p>
<a class="btn btn-default" href="{{ "admin/pages" | append: prev_page | relative_link }}">
&laquo; Previous
</a>
</p>
{% endif %}
</div>
<div class="text-right">
{% if page_count == 25 %}
<p>
<a class="btn btn-default" href="{{ "admin/pages" | append: next_page | relative_link }}">Next &raquo;</a>
</p>
{% endif %}
</div>
</div>
{% endif %} {% endif %}
</article> </article>

View File

@ -1,22 +1,22 @@
<h2 class="my-3">{{ page_title }}</h2> <h2 class="my-3">{{ page_title }}</h2>
<article> <article>
<a href="{{ "admin/post/new/edit" | relative_link }}" class="btn btn-primary btn-sm mb-3">Write a New Post</a> <a href="{{ "admin/post/new/edit" | relative_link }}" class="btn btn-primary btn-sm mb-3">Write a New Post</a>
<form method="post" class="container" hx-target="body"> {%- assign post_count = model.posts | size -%}
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}"> {%- if post_count > 0 %}
{%- assign post_count = model.posts | size -%} <form method="post" class="container" hx-target="body">
{%- assign date_col = "col-xs-12 col-md-3 col-lg-2" -%} <input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
{%- assign title_col = "col-xs-12 col-md-7 col-lg-6 col-xl-5 col-xxl-4" -%} {%- assign date_col = "col-xs-12 col-md-3 col-lg-2" -%}
{%- assign author_col = "col-xs-12 col-md-2 col-lg-1" -%} {%- assign title_col = "col-xs-12 col-md-7 col-lg-6 col-xl-5 col-xxl-4" -%}
{%- assign tag_col = "col-lg-3 col-xl-4 col-xxl-5 d-none d-lg-inline-block" -%} {%- assign author_col = "col-xs-12 col-md-2 col-lg-1" -%}
<div class="row mwl-table-heading"> {%- assign tag_col = "col-lg-3 col-xl-4 col-xxl-5 d-none d-lg-inline-block" -%}
<div class="{{ date_col }}"> <div class="row mwl-table-heading">
<span class="d-md-none">Post</span><span class="d-none d-md-inline">Date</span> <div class="{{ date_col }}">
<span class="d-md-none">Post</span><span class="d-none d-md-inline">Date</span>
</div>
<div class="{{ title_col }} d-none d-md-inline-block">Title</div>
<div class="{{ author_col }} d-none d-md-inline-block">Author</div>
<div class="{{ tag_col }}">Tags</div>
</div> </div>
<div class="{{ title_col }} d-none d-md-inline-block">Title</div>
<div class="{{ author_col }} d-none d-md-inline-block">Author</div>
<div class="{{ tag_col }}">Tags</div>
</div>
{%- if post_count > 0 %}
{% for post in model.posts -%} {% for post in model.posts -%}
<div class="row mwl-table-detail"> <div class="row mwl-table-detail">
<div class="{{ date_col }} no-wrap"> <div class="{{ date_col }} no-wrap">
@ -77,24 +77,22 @@
</div> </div>
</div> </div>
{%- endfor %} {%- endfor %}
{% else %} </form>
<div class="row"> {% if model.newer_link or model.older_link %}
<div class="col text-muted fst-italic text-center">This web log has no posts</div> <div class="d-flex justify-content-evenly mb-3">
<div>
{% if model.newer_link %}
<p><a class="btn btn-secondary" href="{{ model.newer_link.value }}">&laquo; Newer Posts</a></p>
{% endif %}
</div>
<div class="text-right">
{% if model.older_link %}
<p><a class="btn btn-secondary" href="{{ model.older_link.value }}">Older Posts &raquo;</a></p>
{% endif %}
</div>
</div> </div>
{% endif %} {% endif %}
</form> {% else %}
{% if model.newer_link or model.older_link %} <p class="text-muted fst-italic text-center">This web log has no posts</p>
<div class="d-flex justify-content-evenly">
<div>
{% if model.newer_link %}
<p><a class="btn btn-default" href="{{ model.newer_link.value }}">&laquo; Newer Posts</a></p>
{% endif %}
</div>
<div class="text-right">
{% if model.older_link %}
<p><a class="btn btn-default" href="{{ model.older_link.value }}">Older Posts &raquo;</a></p>
{% endif %}
</div>
</div>
{% endif %} {% endif %}
</article> </article>

View File

@ -63,50 +63,54 @@
</div> </div>
</div> </div>
</form> </form>
<h3>Custom Feeds</h3> <fieldset class="container mb-3 pb-0">
<a class="btn btn-sm btn-secondary" href="{{ 'admin/settings/rss/new/edit' | relative_link }}"> <legend>Custom Feeds</legend>
Add a New Custom Feed <div class="row">
</a> <div class="col">
<form method="post" class="container" hx-target="body"> <a class="btn btn-sm btn-secondary" href="{{ 'admin/settings/rss/new/edit' | relative_link }}">
{%- assign source_col = "col-12 col-md-6" -%} Add a New Custom Feed
{%- assign path_col = "col-12 col-md-6" -%} </a>
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}"> {%- assign feed_count = custom_feeds | size -%}
<div class="row mwl-table-heading"> {% if feed_count > 0 %}
<div class="{{ source_col }}"> <form method="post" class="container g-0" hx-target="body">
<span class="d-md-none">Feed</span><span class="d-none d-md-inline">Source</span> {%- assign source_col = "col-12 col-md-6" -%}
{%- assign path_col = "col-12 col-md-6" -%}
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
<div class="row mwl-table-heading">
<div class="{{ source_col }}">
<span class="d-md-none">Feed</span><span class="d-none d-md-inline">Source</span>
</div>
<div class="{{ path_col }} d-none d-md-inline-block">Relative Path</div>
</div>
{% for feed in custom_feeds %}
<div class="row mwl-table-detail">
<div class="{{ source_col }}">
{{ feed.source }}
{%- if feed.is_podcast %} &nbsp; <span class="badge bg-primary">PODCAST</span>{% endif %}<br>
<small>
{%- assign feed_url = "admin/settings/rss/" | append: feed.id -%}
<a href="{{ feed.path | relative_link }}" target="_blank">View Feed</a>
<span class="text-muted"> &bull; </span>
<a href="{{ feed_url | append: "/edit" | relative_link }}">Edit</a>
<span class="text-muted"> &bull; </span>
{%- assign feed_del_link = feed_url | append: "/delete" | relative_link -%}
<a href="{{ feed_del_link }}" hx-post="{{ feed_del_link }}" class="text-danger"
hx-confirm="Are you sure you want to delete the custom RSS feed based on {{ feed.source | strip_html | escape }}? This action cannot be undone.">
Delete
</a>
</small>
</div>
<div class="{{ path_col }}">
<small class="d-md-none">Served at {{ feed.path }}</small>
<span class="d-none d-md-inline">{{ feed.path }}</span>
</div>
</div>
{% endfor %}
</form>
{% else %}
<p class="text-muted fst-italic text-center">No custom feeds defined</p>
{% endif %}
</div> </div>
<div class="{{ path_col }} d-none d-md-inline-block">Relative Path</div>
</div> </div>
{%- assign feed_count = custom_feeds | size -%} </fieldset>
{% if feed_count > 0 %}
{% for feed in custom_feeds %}
<div class="row mwl-table-detail">
<div class="{{ source_col }}">
{{ feed.source }}
{%- if feed.is_podcast %} &nbsp; <span class="badge bg-primary">PODCAST</span>{% endif %}<br>
<small>
{%- assign feed_url = "admin/settings/rss/" | append: feed.id -%}
<a href="{{ feed.path | relative_link }}" target="_blank">View Feed</a>
<span class="text-muted"> &bull; </span>
<a href="{{ feed_url | append: "/edit" | relative_link }}">Edit</a>
<span class="text-muted"> &bull; </span>
{%- assign feed_del_link = feed_url | append: "/delete" | relative_link -%}
<a href="{{ feed_del_link }}" hx-post="{{ feed_del_link }}" class="text-danger"
hx-confirm="Are you sure you want to delete the custom RSS feed based on {{ feed.source | strip_html | escape }}? This action cannot be undone.">
Delete
</a>
</small>
</div>
<div class="{{ path_col }}">
<small class="d-md-none">Served at {{ feed.path }}</small>
<span class="d-none d-md-inline">{{ feed.path }}</span>
</div>
</div>
{% endfor %}
{% else %}
<tr>
<td colspan="3" class="text-muted fst-italic text-center">No custom feeds defined</td>
</tr>
{% endif %}
</form>
</article> </article>

View File

@ -1,106 +1,136 @@
<h2 class="my-3">{{ web_log.name }} Settings</h2> <h2 class="my-3">{{ web_log.name }} Settings</h2>
<p class="text-muted">
Other Settings: <a href="{{ "admin/settings/tag-mappings" | relative_link }}">Tag Mappings</a> &bull;
<a href="{{ "admin/settings/rss" | relative_link }}">RSS Settings</a>
</p>
<article> <article>
<form action="{{ "admin/settings" | relative_link }}" method="post"> <p class="text-muted">
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}"> Other Settings: <a href="{{ "admin/settings/tag-mappings" | relative_link }}">Tag Mappings</a> &bull;
<div class="container"> <a href="{{ "admin/settings/rss" | relative_link }}">RSS Settings</a>
<div class="row"> </p>
<div class="col-12 col-md-6 col-xl-4 pb-3"> <fieldset class="container mb-3">
<div class="form-floating"> <legend>Web Log Settings</legend>
<input type="text" name="Name" id="name" class="form-control" placeholder="Name" required autofocus <div class="row">
value="{{ model.name }}"> <div class="col">
<label for="name">Name</label> <form action="{{ "admin/settings" | relative_link }}" method="post">
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
<div class="container">
<div class="row">
<div class="col-12 col-md-6 col-xl-4 pb-3">
<div class="form-floating">
<input type="text" name="Name" id="name" class="form-control" placeholder="Name" required autofocus
value="{{ model.name }}">
<label for="name">Name</label>
</div>
</div>
<div class="col-12 col-md-6 col-xl-4 pb-3">
<div class="form-floating">
<input type="text" name="Slug" id="slug" class="form-control" placeholder="Slug" required
value="{{ model.slug }}">
<label for="slug">Slug</label>
<span class="form-text">
<span class="badge rounded-pill bg-warning text-dark">WARNING</span> changing this value may break
links
(<a href="https://bitbadger.solutions/open-source/myweblog/configuring.html#blog-settings"
target="_blank">more</a>)
</span>
</div>
</div>
<div class="col-12 col-md-6 col-xl-4 pb-3">
<div class="form-floating">
<input type="text" name="Subtitle" id="subtitle" class="form-control" placeholder="Subtitle"
value="{{ model.subtitle }}">
<label for="subtitle">Subtitle</label>
</div>
</div>
<div class="col-12 col-md-6 col-xl-4 offset-xl-1 pb-3">
<div class="form-floating">
<select name="ThemeId" id="themeId" class="form-control" required>
{% for theme in themes -%}
<option value="{{ theme[0] }}"{% if model.theme_id == theme[0] %} selected="selected"{% endif %}>
{{ theme[1] }}
</option>
{%- endfor %}
</select>
<label for="themeId">Theme</label>
</div>
</div>
<div class="col-12 col-md-6 offset-md-1 col-xl-4 offset-xl-0 pb-3">
<div class="form-floating">
<select name="DefaultPage" id="defaultPage" class="form-control" required>
{% for pg in pages -%}
<option value="{{ pg[0] }}"
{%- if pg[0] == model.default_page %} selected="selected"{% endif %}>
{{ pg[1] }}
</option>
{%- endfor %}
</select>
<label for="defaultPage">Default Page</label>
</div>
</div>
<div class="col-12 col-md-4 col-xl-2 pb-3">
<div class="form-floating">
<input type="number" name="PostsPerPage" id="postsPerPage" class="form-control" min="0" max="50"
required value="{{ model.posts_per_page }}">
<label for="postsPerPage">Posts per Page</label>
</div>
</div>
</div>
<div class="row">
<div class="col-12 col-md-4 col-xl-3 offset-xl-2 pb-3">
<div class="form-floating">
<input type="text" name="TimeZone" id="timeZone" class="form-control" placeholder="Time Zone" required
value="{{ model.time_zone }}">
<label for="timeZone">Time Zone</label>
</div>
</div>
<div class="col-12 col-md-4 col-xl-2">
<div class="form-check form-switch">
<input type="checkbox" name="AutoHtmx" id="autoHtmx" class="form-check-input" value="true"
{%- if model.auto_htmx %} checked="checked"{% endif %}>
<label for="autoHtmx" class="form-check-label">Auto-Load htmx</label>
</div>
<span class="form-text fst-italic">
<a href="https://htmx.org" target="_blank" rel="noopener">What is this?</a>
</span>
</div>
<div class="col-12 col-md-4 col-xl-3 pb-3">
<div class="form-floating">
<select name="Uploads" id="uploads" class="form-control">
{%- for it in upload_values %}
<option value="{{ it[0] }}"
{%- if model.uploads == it[0] %} selected{% endif %}>{{ it[1] }}</option>
{%- endfor %}
</select>
<label for="uploads">Default Upload Destination</label>
</div>
</div>
</div>
<div class="row pb-3">
<div class="col text-center">
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</div>
</div> </div>
</div> </form>
<div class="col-12 col-md-6 col-xl-4 pb-3">
<div class="form-floating">
<input type="text" name="Slug" id="slug" class="form-control" placeholder="Slug" required
value="{{ model.slug }}">
<label for="slug">Slug</label>
<span class="form-text">
<span class="badge rounded-pill bg-warning text-dark">WARNING</span> changing this value may break links
(<a href="https://bitbadger.solutions/open-source/myweblog/configuring.html#blog-settings"
target="_blank">more</a>)
</span>
</div>
</div>
<div class="col-12 col-md-6 col-xl-4 pb-3">
<div class="form-floating">
<input type="text" name="Subtitle" id="subtitle" class="form-control" placeholder="Subtitle"
value="{{ model.subtitle }}">
<label for="subtitle">Subtitle</label>
</div>
</div>
<div class="col-12 col-md-6 col-xl-4 offset-xl-1 pb-3">
<div class="form-floating">
<select name="ThemeId" id="themeId" class="form-control" required>
{% for theme in themes -%}
<option value="{{ theme[0] }}"{% if model.theme_id == theme[0] %} selected="selected"{% endif %}>
{{ theme[1] }}
</option>
{%- endfor %}
</select>
<label for="themeId">Theme</label>
</div>
</div>
<div class="col-12 col-md-6 offset-md-1 col-xl-4 offset-xl-0 pb-3">
<div class="form-floating">
<select name="DefaultPage" id="defaultPage" class="form-control" required>
{% for pg in pages -%}
<option value="{{ pg[0] }}"
{%- if pg[0] == model.default_page %} selected="selected"{% endif %}>
{{ pg[1] }}
</option>
{%- endfor %}
</select>
<label for="defaultPage">Default Page</label>
</div>
</div>
<div class="col-12 col-md-4 col-xl-2 pb-3">
<div class="form-floating">
<input type="number" name="PostsPerPage" id="postsPerPage" class="form-control" min="0" max="50" required
value="{{ model.posts_per_page }}">
<label for="postsPerPage">Posts per Page</label>
</div>
</div>
</div>
<div class="row">
<div class="col-12 col-md-4 col-xl-3 offset-xl-2 pb-3">
<div class="form-floating">
<input type="text" name="TimeZone" id="timeZone" class="form-control" placeholder="Time Zone" required
value="{{ model.time_zone }}">
<label for="timeZone">Time Zone</label>
</div>
</div>
<div class="col-12 col-md-4 col-xl-2">
<div class="form-check form-switch">
<input type="checkbox" name="AutoHtmx" id="autoHtmx" class="form-check-input" value="true"
{%- if model.auto_htmx %} checked="checked"{% endif %}>
<label for="autoHtmx" class="form-check-label">Auto-Load htmx</label>
</div>
<span class="form-text fst-italic">
<a href="https://htmx.org" target="_blank" rel="noopener">What is this?</a>
</span>
</div>
<div class="col-12 col-md-4 col-xl-3 pb-3">
<div class="form-floating">
<select name="Uploads" id="uploads" class="form-control">
{%- for it in upload_values %}
<option value="{{ it[0] }}"{% if model.uploads == it[0] %} selected{% endif %}>{{ it[1] }}</option>
{%- endfor %}
</select>
<label for="uploads">Default Upload Destination</label>
</div>
</div>
</div>
<div class="row pb-3">
<div class="col text-center">
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</div> </div>
</div> </div>
</form> </fieldset>
<fieldset class="container mb-3 pb-0">
<legend>Users</legend>
<div class="row">
<div class="col">
{% include_template "_user-list-columns" %}
<a href="{{ "admin/settings/user/new/edit" | relative_link }}" class="btn btn-primary btn-sm mb-3"
hx-target="#user_new">
Add a New User
</a>
<div class="container g-0">
<div class="row mwl-table-heading">
<div class="{{ user_col }}">User<span class="d-md-none">; Full Name / E-mail; Last Log On</span></div>
<div class="{{ email_col }} d-none d-md-inline-block">Full Name / E-mail</div>
<div class="{{ cre8_col }}">Created</div>
<div class="{{ last_col }} d-none d-md-block">Last Log On</div>
</div>
</div>
{{ user_list }}
</div>
</div>
</fieldset>
</article> </article>

View File

@ -1,33 +1,45 @@
<form method="post" class="container" id="tagList" hx-target="this" hx-swap="outerHTML show:window:top"> <div id="tagList" class="container">
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}"> <div class="row">
<div class="row mwl-table-detail" id="tag_new"></div> <div class="col">
{%- assign map_count = mappings | size -%} {%- assign map_count = mappings | size -%}
{% if map_count > 0 -%} {% if map_count > 0 -%}
{% for map in mappings -%} <div class="container">
{%- assign map_id = mapping_ids | value: map.tag -%} <div class="row mwl-table-heading">
<div class="row mwl-table-detail" id="tag_{{ map_id }}"> <div class="col">Tag</div>
<div class="col no-wrap"> <div class="col">URL Value</div>
{{ map.tag }}<br> </div>
<small>
{%- assign map_url = "admin/settings/tag-mapping/" | append: map_id -%}
<a href="{{ map_url | append: "/edit" | relative_link }}" hx-target="#tag_{{ map_id }}"
hx-swap="innerHTML show:#tag_{{ map_id }}:top">
Edit
</a>
<span class="text-muted"> &bull; </span>
{%- assign map_del_link = map_url | append: "/delete" | relative_link -%}
<a href="{{ map_del_link }}" hx-post="{{ map_del_link }}" class="text-danger"
hx-confirm="Are you sure you want to delete the mapping for &ldquo;{{ map.tag }}&rdquo;? This action cannot be undone.">
Delete
</a>
</small>
</div> </div>
<div class="col">{{ map.url_value }}</div> <form method="post" class="container" hx-target="#tagList" hx-swap="outerHTML show:window:top">
</div> <input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
{%- endfor %} <div class="row mwl-table-detail" id="tag_new"></div>
{%- else -%} {% for map in mappings -%}
<div class="row"> {%- assign map_id = mapping_ids | value: map.tag -%}
<div class="col text-muted text-center fst-italic">This web log has no tag mappings</div> <div class="row mwl-table-detail" id="tag_{{ map_id }}">
<div class="col no-wrap">
{{ map.tag }}<br>
<small>
{%- assign map_url = "admin/settings/tag-mapping/" | append: map_id -%}
<a href="{{ map_url | append: "/edit" | relative_link }}" hx-target="#tag_{{ map_id }}"
hx-swap="innerHTML show:#tag_{{ map_id }}:top">
Edit
</a>
<span class="text-muted"> &bull; </span>
{%- assign map_del_link = map_url | append: "/delete" | relative_link -%}
<a href="{{ map_del_link }}" hx-post="{{ map_del_link }}" class="text-danger"
hx-confirm="Are you sure you want to delete the mapping for &ldquo;{{ map.tag }}&rdquo;? This action cannot be undone.">
Delete
</a>
</small>
</div>
<div class="col">{{ map.url_value }}</div>
</div>
{%- endfor %}
</form>
{%- else -%}
<div id="tag_new">
<p class="text-muted text-center fst-italic">This web log has no tag mappings</p>
</div>
{%- endif %}
</div> </div>
{%- endif %} </div>
</form> </div>

View File

@ -4,11 +4,5 @@
hx-target="#tag_new"> hx-target="#tag_new">
Add a New Tag Mapping Add a New Tag Mapping
</a> </a>
<div class="container">
<div class="row mwl-table-heading">
<div class="col">Tag</div>
<div class="col">URL Value</div>
</div>
</div>
{{ tag_mapping_list }} {{ tag_mapping_list }}
</article> </article>

View File

@ -1,4 +1,4 @@
<form method="post" id="themeList" class="container" hx-target="this" hx-swap="outerHTML show:window:top"> <form method="post" id="themeList" class="container g-0" hx-target="this" hx-swap="outerHTML show:window:top">
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}"> <input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
{% include_template "_theme-list-columns" %} {% include_template "_theme-list-columns" %}
{% for theme in themes -%} {% for theme in themes -%}

View File

@ -1,5 +1,5 @@
<div class="col"> <div class="col">
<h5>{{ page_title }}</h5> <h5 class="mt-2">{{ page_title }}</h5>
<form action="{{ "admin/theme/new" | relative_link }}" method="post" class="container" enctype="multipart/form-data" <form action="{{ "admin/theme/new" | relative_link }}" method="post" class="container" enctype="multipart/form-data"
hx-boost="false"> hx-boost="false">
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}"> <input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">

View File

@ -7,15 +7,15 @@
<form method="post" class="container" hx-target="body"> <form method="post" class="container" hx-target="body">
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}"> <input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
<div class="row"> <div class="row">
<div class="col text-muted text-center"><em>Uploaded files served from</em><br>{{ upload_base }}</div> <div class="col text-center"><em class="text-muted">Uploaded files served from</em><br>{{ upload_base }}</div>
</div>
<div class="row mwl-table-heading">
<div class="col-6">File Name</div>
<div class="col-3">Path</div>
<div class="col-3">File Date/Time</div>
</div> </div>
{%- assign file_count = files | size -%} {%- assign file_count = files | size -%}
{%- if file_count > 0 %} {%- if file_count > 0 %}
<div class="row mwl-table-heading">
<div class="col-6">File Name</div>
<div class="col-3">Path</div>
<div class="col-3">File Date/Time</div>
</div>
{% for file in files %} {% for file in files %}
<div class="row mwl-table-detail"> <div class="row mwl-table-detail">
<div class="col-6"> <div class="col-6">
@ -69,7 +69,7 @@
{% endfor %} {% endfor %}
{%- else -%} {%- else -%}
<div class="row"> <div class="row">
<div class="col text-muted fst-italic text-center">This web log has uploaded files</div> <div class="col text-muted fst-italic text-center"><br>This web log has uploaded files</div>
</div> </div>
{%- endif %} {%- endif %}
</form> </form>

View File

@ -1,13 +1,13 @@
<div class="col-12"> <div class="col-12">
<h5 class="my-3">{{ page_title }}</h5> <h5 class="my-3">{{ page_title }}</h5>
<form hx-post="{{ "admin/user/save" | relative_link }}" method="post" class="container" <form hx-post="{{ "admin/settings/user/save" | relative_link }}" method="post" class="container"
hx-target="#userList" hx-swap="outerHTML show:window:top"> hx-target="#userList" hx-swap="outerHTML show:window:top">
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}"> <input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
<input type="hidden" name="Id" value="{{ model.id }}"> <input type="hidden" name="Id" value="{{ model.id }}">
<div class="row"> <div class="row">
<div class="col-12 col-md-5 col-lg-3 col-xxl-2 offset-xxl-1 mb-3"> <div class="col-12 col-md-5 col-lg-3 col-xxl-2 offset-xxl-1 mb-3">
<div class="form-floating"> <div class="form-floating">
<select name="AccessLevel" id="accessLevel" class="form-control" required> <select name="AccessLevel" id="accessLevel" class="form-control" required autofocus>
{%- for level in access_levels %} {%- for level in access_levels %}
<option value="{{ level[0] }}"{% if model.access_level == level[0] %} selected{% endif %}> <option value="{{ level[0] }}"{% if model.access_level == level[0] %} selected{% endif %}>
{{ level[1] }} {{ level[1] }}
@ -88,7 +88,14 @@
<div class="row mb-3"> <div class="row mb-3">
<div class="col text-center"> <div class="col text-center">
<button type="submit" class="btn btn-sm btn-primary">Save Changes</button> <button type="submit" class="btn btn-sm btn-primary">Save Changes</button>
<a href="{{ "admin/users/bare" | relative_link }}" class="btn btn-sm btn-secondary ms-3">Cancel</a> {% if model.is_new %}
<button type="button" class="btn btn-sm btn-secondary ms-3"
onclick="document.getElementById('user_new').innerHTML = ''">
Cancel
</button>
{% else %}
<a href="{{ "admin/settings/users" | relative_link }}" class="btn btn-sm btn-secondary ms-3">Cancel</a>
{% endif %}
</div> </div>
</div> </div>
</form> </form>

View File

@ -1,60 +1,61 @@
<form method="post" id="userList" class="container" hx-target="this" hx-swap="outerHTML show:window:top"> <div id="userList">
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}"> <div class="container g-0">
<div class="row mwl-table-detail" id="user_new"></div> <div class="row mwl-table-detail" id="user_new"></div>
{%- assign user_col = "col-12 col-md-4 col-xl-3" -%} </div>
{%- assign email_col = "col-12 col-md-4 col-xl-4" -%} <form method="post" id="userList" class="container g-0" hx-target="this" hx-swap="outerHTML show:window:top">
{%- assign cre8_col = "d-none d-xl-block col-xl-2" -%} <input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
{%- assign last_col = "col-12 col-md-4 col-xl-3" -%} {% include_template "_user-list-columns" %}
{%- assign badge = "ms-2 badge bg" -%} {%- assign badge = "ms-2 badge bg" -%}
{% for user in users -%} {% for user in users -%}
<div class="row mwl-table-detail" id="user_{{ user.id }}"> <div class="row mwl-table-detail" id="user_{{ user.id }}">
<div class="{{ user_col }} no-wrap"> <div class="{{ user_col }} no-wrap">
{{ user.preferred_name }} {{ user.preferred_name }}
{%- if user.access_level == "Administrator" %} {%- if user.access_level == "Administrator" %}
<span class="{{ badge }}-success">ADMINISTRATOR</span> <span class="{{ badge }}-success">ADMINISTRATOR</span>
{%- elsif user.access_level == "WebLogAdmin" %} {%- elsif user.access_level == "WebLogAdmin" %}
<span class="{{ badge }}-primary">WEB LOG ADMIN</span> <span class="{{ badge }}-primary">WEB LOG ADMIN</span>
{%- elsif user.access_level == "Editor" %} {%- elsif user.access_level == "Editor" %}
<span class="{{ badge }}-secondary">EDITOR</span> <span class="{{ badge }}-secondary">EDITOR</span>
{%- elsif user.access_level == "Author" %} {%- elsif user.access_level == "Author" %}
<span class="{{ badge }}-dark">AUTHOR</span> <span class="{{ badge }}-dark">AUTHOR</span>
{%- endif %}<br> {%- endif %}<br>
{%- unless is_administrator == false and user.access_level == "Administrator" %} {%- unless is_administrator == false and user.access_level == "Administrator" %}
<small> <small>
{%- assign user_url_base = "admin/user/" | append: user.id -%} {%- assign user_url_base = "admin/settings/user/" | append: user.id -%}
<a href="{{ user_url_base | append: "/edit" | relative_link }}" hx-target="#user_{{ user.id }}" <a href="{{ user_url_base | append: "/edit" | relative_link }}" hx-target="#user_{{ user.id }}"
hx-swap="innerHTML show:#user_{{ user.id }}:top"> hx-swap="innerHTML show:#user_{{ user.id }}:top">
Edit Edit
</a>
{% unless user_id == user.id %}
<span class="text-muted"> &bull; </span>
{%- assign user_del_link = user_url_base | append: "/delete" | relative_link -%}
<a href="{{ user_del_link }}" hx-post="{{ user_del_link }}" class="text-danger"
hx-confirm="Are you sure you want to delete the user &ldquo;{{ user.preferred_name }}&rdquo;? This action cannot be undone. (This action will not succeed if the user has authored any posts or pages.)">
Delete
</a> </a>
{% endunless %} {% unless user_id == user.id %}
<span class="text-muted"> &bull; </span>
{%- assign user_del_link = user_url_base | append: "/delete" | relative_link -%}
<a href="{{ user_del_link }}" hx-post="{{ user_del_link }}" class="text-danger"
hx-confirm="Are you sure you want to delete the user &ldquo;{{ user.preferred_name }}&rdquo;? This action cannot be undone. (This action will not succeed if the user has authored any posts or pages.)">
Delete
</a>
{% endunless %}
</small>
{%- endunless %}
</div>
<div class="{{ email_col }}">
{{ user.first_name }} {{ user.last_name }}<br>
<small class="text-muted">
{{ user.email }}
{%- unless user.url == "" %}<br>{{ user.url }}{% endunless %}
</small> </small>
{%- endunless %} </div>
<div class="{{ cre8_col }}">
{{ user.created_on | date: "MMMM d, yyyy" }}
</div>
<div class="{{ last_col }}">
{% if user.last_seen_on %}
{{ user.last_seen_on | date: "MMMM d, yyyy" }} at
{{ user.last_seen_on | date: "h:mmtt" | downcase }}
{% else %}
--
{% endif %}
</div>
</div> </div>
<div class="{{ email_col }}"> {%- endfor %}
{{ user.first_name }} {{ user.last_name }}<br> </form>
<small class="text-muted"> </div>
{{ user.email }}
{%- unless user.url == "" %}<br>{{ user.url }}{% endunless %}
</small>
</div>
<div class="{{ cre8_col }}">
{{ user.created_on | date: "MMMM d, yyyy" }}
</div>
<div class="{{ last_col }}">
{% if user.last_seen_on %}
{{ user.last_seen_on | date: "MMMM d, yyyy" }} at
{{ user.last_seen_on | date: "h:mmtt" | downcase }}
{% else %}
--
{% endif %}
</div>
</div>
{%- endfor %}
</form>

View File

@ -1,20 +0,0 @@
<h2 class="my-3">{{ page_title }}</h2>
<article>
<a href="{{ "admin/user/new/edit" | relative_link }}" class="btn btn-primary btn-sm mb-3"
hx-target="#user_new">
Add a New User
</a>
<div class="container">
{%- 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" -%}
<div class="row mwl-table-heading">
<div class="{{ user_col }}">User<span class="d-md-none">; Details; Last Log On</span></div>
<div class="{{ email_col }} d-none d-md-inline-block">Details</div>
<div class="{{ cre8_col }}">Created</div>
<div class="{{ last_col }} d-none d-md-block">Last Log On</div>
</div>
</div>
{{ user_list }}
</article>