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:
parent
ff9c08842b
commit
3189681021
|
@ -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
|
||||||
|
| Some template ->
|
||||||
|
let mutable text = template.Text
|
||||||
|
let mutable childNotFound = ""
|
||||||
while hasInclude.IsMatch text do
|
while hasInclude.IsMatch text do
|
||||||
let child = hasInclude.Match text
|
let child = hasInclude.Match text
|
||||||
let childText = (theme.Templates |> List.find (fun t -> t.Name = child.Groups[1].Value)).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)
|
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)
|
_cache[templatePath] <- Template.Parse (text, SyntaxCompatibility.DotLiquid22)
|
||||||
| None -> ()
|
return Ok _cache[templatePath]
|
||||||
return _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
|
||||||
|
|
|
@ -32,10 +32,11 @@ 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 {
|
||||||
|
match! TemplateCache.get adminTheme "theme-list-body" ctx.Data with
|
||||||
|
| Ok bodyTemplate ->
|
||||||
let! themes = ctx.Data.Theme.All ()
|
let! themes = ctx.Data.Theme.All ()
|
||||||
let! bodyTemplate = TemplateCache.get adminTheme "theme-list-body" ctx.Data
|
|
||||||
let cachedTemplates = TemplateCache.allNames ()
|
let cachedTemplates = TemplateCache.allNames ()
|
||||||
let! hash =
|
let! hash =
|
||||||
hashForPage "myWebLog Administration"
|
hashForPage "myWebLog Administration"
|
||||||
|
@ -47,7 +48,10 @@ let adminDashboard : HttpHandler = requireAccess Administrator >=> fun next ctx
|
||||||
|> Seq.map (fun it -> [|
|
|> Seq.map (fun it -> [|
|
||||||
ThemeId.toString it.Id
|
ThemeId.toString it.Id
|
||||||
it.Name
|
it.Name
|
||||||
cachedTemplates |> List.filter (fun n -> n.StartsWith (ThemeId.toString it.Id)) |> List.length |> string
|
cachedTemplates
|
||||||
|
|> List.filter (fun n -> n.StartsWith (ThemeId.toString it.Id))
|
||||||
|
|> List.length
|
||||||
|
|> string
|
||||||
|])
|
|])
|
||||||
|> Array.ofSeq)
|
|> Array.ofSeq)
|
||||||
|> addToHash "web_logs" (
|
|> addToHash "web_logs" (
|
||||||
|
@ -60,10 +64,11 @@ let adminDashboard : HttpHandler = requireAccess Administrator >=> fun next ctx
|
||||||
return!
|
return!
|
||||||
addToHash "theme_list" (bodyTemplate.Render hash) hash
|
addToHash "theme_list" (bodyTemplate.Render hash) hash
|
||||||
|> adminView "admin-dashboard" next ctx
|
|> 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,7 +122,8 @@ 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
|
||||||
|
| Ok catListTemplate ->
|
||||||
let! hash =
|
let! hash =
|
||||||
hashForPage "Categories"
|
hashForPage "Categories"
|
||||||
|> withAntiCsrf ctx
|
|> withAntiCsrf ctx
|
||||||
|
@ -125,6 +131,7 @@ let listCategories : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx ->
|
||||||
return!
|
return!
|
||||||
addToHash "category_list" (catListTemplate.Render hash) hash
|
addToHash "category_list" (catListTemplate.Render hash) hash
|
||||||
|> adminView "category-list" next ctx
|
|> 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 {
|
||||||
|
match! TemplateCache.get adminTheme "tag-mapping-list-body" ctx.Data with
|
||||||
|
| Ok listTemplate ->
|
||||||
let! hash = tagMappingHash ctx
|
let! hash = tagMappingHash ctx
|
||||||
let! listTemplate = TemplateCache.get adminTheme "tag-mapping-list-body" ctx.Data
|
|
||||||
return!
|
return!
|
||||||
addToHash "tag_mapping_list" (listTemplate.Render hash) hash
|
addToHash "tag_mapping_list" (listTemplate.Render hash) hash
|
||||||
|> adminView "tag-mapping-list" next ctx
|
|> 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
|
||||||
|
@ -422,9 +431,12 @@ 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
|
||||||
|
match! TemplateCache.get adminTheme "user-list-body" data with
|
||||||
|
| Ok userTemplate ->
|
||||||
let! allPages = data.Page.All ctx.WebLog.Id
|
let! allPages = data.Page.All ctx.WebLog.Id
|
||||||
let! themes = data.Theme.All ()
|
let! themes = data.Theme.All ()
|
||||||
return!
|
let! users = data.WebLogUser.FindByWebLog ctx.WebLog.Id
|
||||||
|
let! hash =
|
||||||
hashForPage "Web Log Settings"
|
hashForPage "Web Log Settings"
|
||||||
|> withAntiCsrf ctx
|
|> withAntiCsrf ctx
|
||||||
|> addToHash ViewContext.Model (SettingsModel.fromWebLog ctx.WebLog)
|
|> addToHash ViewContext.Model (SettingsModel.fromWebLog ctx.WebLog)
|
||||||
|
@ -445,7 +457,12 @@ let settings : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task
|
||||||
KeyValuePair.Create (UploadDestination.toString Database, "Database")
|
KeyValuePair.Create (UploadDestination.toString Database, "Database")
|
||||||
KeyValuePair.Create (UploadDestination.toString Disk, "Disk")
|
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
|
|> adminView "settings" next ctx
|
||||||
|
| Error message -> return! Error.server message next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /admin/settings
|
// POST /admin/settings
|
||||||
|
|
|
@ -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 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
|
else
|
||||||
(setStatusCode 404 >=> text "Not found") earlyReturn ctx)
|
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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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!
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
4
src/admin-theme/_user-list-columns.liquid
Normal file
4
src/admin-theme/_user-list-columns.liquid
Normal 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" -%}
|
|
@ -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" %}
|
||||||
|
|
|
@ -1,10 +1,19 @@
|
||||||
<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" -%}
|
||||||
|
<div class="container">
|
||||||
|
<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>
|
||||||
|
<form method="post" class="container" hx-target="#catList" hx-swap="outerHTML show:window:top">
|
||||||
|
<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 -%}
|
{% for cat in categories -%}
|
||||||
<div class="row mwl-table-detail" id="cat_{{ cat.id }}">
|
<div class="row mwl-table-detail" id="cat_{{ cat.id }}">
|
||||||
<div class="{{ cat_col }} no-wrap">
|
<div class="{{ cat_col }} no-wrap">
|
||||||
|
@ -37,9 +46,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
|
</form>
|
||||||
{%- else -%}
|
{%- else -%}
|
||||||
<div class="row">
|
<div id="cat_new">
|
||||||
<div class="col-12 text-muted fst-italic text-center">This web log has no categores defined</div>
|
<p class="text-muted fst-italic text-center">This web log has no categores defined</p>
|
||||||
</div>
|
</div>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
</form>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<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 -%}
|
||||||
|
{% if page_count > 0 %}
|
||||||
{%- assign title_col = "col-12 col-md-5" -%}
|
{%- assign title_col = "col-12 col-md-5" -%}
|
||||||
{%- assign link_col = "col-12 col-md-5" -%}
|
{%- assign link_col = "col-12 col-md-5" -%}
|
||||||
{%- assign upd8_col = "col-12 col-md-2" -%}
|
{%- assign upd8_col = "col-12 col-md-2" -%}
|
||||||
|
@ -14,7 +15,6 @@
|
||||||
<div class="{{ link_col }} d-none d-md-inline-block">Permalink</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 class="{{ upd8_col }} d-none d-md-inline-block">Updated</div>
|
||||||
</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,18 +48,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
{% else %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col text-muted fst-italic text-center">This web log has no pages</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
</form>
|
||||||
{% if page_nbr > 1 or page_count == 25 %}
|
{% if page_nbr > 1 or page_count == 25 %}
|
||||||
<div class="d-flex justify-content-evenly pb-3">
|
<div class="d-flex justify-content-evenly mb-3">
|
||||||
<div>
|
<div>
|
||||||
{% if page_nbr > 1 %}
|
{% if page_nbr > 1 %}
|
||||||
<p>
|
<p>
|
||||||
<a class="btn btn-default" href="{{ "admin/pages" | append: prev_page | relative_link }}">
|
<a class="btn btn-secondary" href="{{ "admin/pages" | append: prev_page | relative_link }}">
|
||||||
« Previous
|
« Previous
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
@ -68,10 +63,15 @@
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
{% if page_count == 25 %}
|
{% if page_count == 25 %}
|
||||||
<p>
|
<p>
|
||||||
<a class="btn btn-default" href="{{ "admin/pages" | append: next_page | relative_link }}">Next »</a>
|
<a class="btn btn-secondary" href="{{ "admin/pages" | append: next_page | relative_link }}">
|
||||||
|
Next »
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<p class="text-muted fst-italic text-center">This web log has no pages</p>
|
||||||
|
{% endif %}
|
||||||
</article>
|
</article>
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
<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>
|
||||||
|
{%- assign post_count = model.posts | size -%}
|
||||||
|
{%- if post_count > 0 %}
|
||||||
<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 }}">
|
||||||
{%- assign post_count = model.posts | size -%}
|
|
||||||
{%- assign date_col = "col-xs-12 col-md-3 col-lg-2" -%}
|
{%- 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 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 author_col = "col-xs-12 col-md-2 col-lg-1" -%}
|
||||||
|
@ -16,7 +17,6 @@
|
||||||
<div class="{{ author_col }} d-none d-md-inline-block">Author</div>
|
<div class="{{ author_col }} d-none d-md-inline-block">Author</div>
|
||||||
<div class="{{ tag_col }}">Tags</div>
|
<div class="{{ tag_col }}">Tags</div>
|
||||||
</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 %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col text-muted fst-italic text-center">This web log has no posts</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
</form>
|
||||||
{% if model.newer_link or model.older_link %}
|
{% if model.newer_link or model.older_link %}
|
||||||
<div class="d-flex justify-content-evenly">
|
<div class="d-flex justify-content-evenly mb-3">
|
||||||
<div>
|
<div>
|
||||||
{% if model.newer_link %}
|
{% if model.newer_link %}
|
||||||
<p><a class="btn btn-default" href="{{ model.newer_link.value }}">« Newer Posts</a></p>
|
<p><a class="btn btn-secondary" href="{{ model.newer_link.value }}">« Newer Posts</a></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
{% if model.older_link %}
|
{% if model.older_link %}
|
||||||
<p><a class="btn btn-default" href="{{ model.older_link.value }}">Older Posts »</a></p>
|
<p><a class="btn btn-secondary" href="{{ model.older_link.value }}">Older Posts »</a></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<p class="text-muted fst-italic text-center">This web log has no posts</p>
|
||||||
|
{% endif %}
|
||||||
</article>
|
</article>
|
||||||
|
|
|
@ -63,11 +63,16 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<h3>Custom Feeds</h3>
|
<fieldset class="container mb-3 pb-0">
|
||||||
|
<legend>Custom Feeds</legend>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
<a class="btn btn-sm btn-secondary" href="{{ 'admin/settings/rss/new/edit' | relative_link }}">
|
<a class="btn btn-sm btn-secondary" href="{{ 'admin/settings/rss/new/edit' | relative_link }}">
|
||||||
Add a New Custom Feed
|
Add a New Custom Feed
|
||||||
</a>
|
</a>
|
||||||
<form method="post" class="container" hx-target="body">
|
{%- assign feed_count = custom_feeds | size -%}
|
||||||
|
{% if feed_count > 0 %}
|
||||||
|
<form method="post" class="container g-0" hx-target="body">
|
||||||
{%- assign source_col = "col-12 col-md-6" -%}
|
{%- assign source_col = "col-12 col-md-6" -%}
|
||||||
{%- assign path_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 }}">
|
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
|
||||||
|
@ -77,8 +82,6 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="{{ path_col }} d-none d-md-inline-block">Relative Path</div>
|
<div class="{{ path_col }} d-none d-md-inline-block">Relative Path</div>
|
||||||
</div>
|
</div>
|
||||||
{%- assign feed_count = custom_feeds | size -%}
|
|
||||||
{% if feed_count > 0 %}
|
|
||||||
{% for feed in custom_feeds %}
|
{% for feed in custom_feeds %}
|
||||||
<div class="row mwl-table-detail">
|
<div class="row mwl-table-detail">
|
||||||
<div class="{{ source_col }}">
|
<div class="{{ source_col }}">
|
||||||
|
@ -103,10 +106,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="3" class="text-muted fst-italic text-center">No custom feeds defined</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-muted fst-italic text-center">No custom feeds defined</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
</article>
|
</article>
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
<h2 class="my-3">{{ web_log.name }} Settings</h2>
|
<h2 class="my-3">{{ web_log.name }} Settings</h2>
|
||||||
|
<article>
|
||||||
<p class="text-muted">
|
<p class="text-muted">
|
||||||
Other Settings: <a href="{{ "admin/settings/tag-mappings" | relative_link }}">Tag Mappings</a> •
|
Other Settings: <a href="{{ "admin/settings/tag-mappings" | relative_link }}">Tag Mappings</a> •
|
||||||
<a href="{{ "admin/settings/rss" | relative_link }}">RSS Settings</a>
|
<a href="{{ "admin/settings/rss" | relative_link }}">RSS Settings</a>
|
||||||
</p>
|
</p>
|
||||||
<article>
|
<fieldset class="container mb-3">
|
||||||
|
<legend>Web Log Settings</legend>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
<form action="{{ "admin/settings" | relative_link }}" method="post">
|
<form action="{{ "admin/settings" | 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="container">
|
<div class="container">
|
||||||
|
@ -21,7 +25,8 @@
|
||||||
value="{{ model.slug }}">
|
value="{{ model.slug }}">
|
||||||
<label for="slug">Slug</label>
|
<label for="slug">Slug</label>
|
||||||
<span class="form-text">
|
<span class="form-text">
|
||||||
<span class="badge rounded-pill bg-warning text-dark">WARNING</span> changing this value may break links
|
<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"
|
(<a href="https://bitbadger.solutions/open-source/myweblog/configuring.html#blog-settings"
|
||||||
target="_blank">more</a>)
|
target="_blank">more</a>)
|
||||||
</span>
|
</span>
|
||||||
|
@ -61,8 +66,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-4 col-xl-2 pb-3">
|
<div class="col-12 col-md-4 col-xl-2 pb-3">
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<input type="number" name="PostsPerPage" id="postsPerPage" class="form-control" min="0" max="50" required
|
<input type="number" name="PostsPerPage" id="postsPerPage" class="form-control" min="0" max="50"
|
||||||
value="{{ model.posts_per_page }}">
|
required value="{{ model.posts_per_page }}">
|
||||||
<label for="postsPerPage">Posts per Page</label>
|
<label for="postsPerPage">Posts per Page</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -89,7 +94,8 @@
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<select name="Uploads" id="uploads" class="form-control">
|
<select name="Uploads" id="uploads" class="form-control">
|
||||||
{%- for it in upload_values %}
|
{%- for it in upload_values %}
|
||||||
<option value="{{ it[0] }}"{% if model.uploads == it[0] %} selected{% endif %}>{{ it[1] }}</option>
|
<option value="{{ it[0] }}"
|
||||||
|
{%- if model.uploads == it[0] %} selected{% endif %}>{{ it[1] }}</option>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
</select>
|
</select>
|
||||||
<label for="uploads">Default Upload Destination</label>
|
<label for="uploads">Default Upload Destination</label>
|
||||||
|
@ -103,4 +109,28 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</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>
|
||||||
|
|
|
@ -1,8 +1,17 @@
|
||||||
<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 -%}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mwl-table-heading">
|
||||||
|
<div class="col">Tag</div>
|
||||||
|
<div class="col">URL Value</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form method="post" class="container" hx-target="#tagList" hx-swap="outerHTML show:window:top">
|
||||||
|
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
|
||||||
|
<div class="row mwl-table-detail" id="tag_new"></div>
|
||||||
{% for map in mappings -%}
|
{% for map in mappings -%}
|
||||||
{%- assign map_id = mapping_ids | value: map.tag -%}
|
{%- assign map_id = mapping_ids | value: map.tag -%}
|
||||||
<div class="row mwl-table-detail" id="tag_{{ map_id }}">
|
<div class="row mwl-table-detail" id="tag_{{ map_id }}">
|
||||||
|
@ -25,9 +34,12 @@
|
||||||
<div class="col">{{ map.url_value }}</div>
|
<div class="col">{{ map.url_value }}</div>
|
||||||
</div>
|
</div>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
|
</form>
|
||||||
{%- else -%}
|
{%- else -%}
|
||||||
<div class="row">
|
<div id="tag_new">
|
||||||
<div class="col text-muted text-center fst-italic">This web log has no tag mappings</div>
|
<p class="text-muted text-center fst-italic">This web log has no tag mappings</p>
|
||||||
</div>
|
</div>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
</form>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 -%}
|
||||||
|
|
|
@ -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 }}">
|
||||||
|
|
|
@ -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>
|
||||||
|
{%- assign file_count = files | size -%}
|
||||||
|
{%- if file_count > 0 %}
|
||||||
<div class="row mwl-table-heading">
|
<div class="row mwl-table-heading">
|
||||||
<div class="col-6">File Name</div>
|
<div class="col-6">File Name</div>
|
||||||
<div class="col-3">Path</div>
|
<div class="col-3">Path</div>
|
||||||
<div class="col-3">File Date/Time</div>
|
<div class="col-3">File Date/Time</div>
|
||||||
</div>
|
</div>
|
||||||
{%- assign file_count = files | size -%}
|
|
||||||
{%- if file_count > 0 %}
|
|
||||||
{% 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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<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 }}">
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
{%- 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
|
||||||
|
@ -58,3 +58,4 @@
|
||||||
</div>
|
</div>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
|
|
@ -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>
|
|
Loading…
Reference in New Issue
Block a user