diff --git a/src/MyWebLog.Domain/DataTypes.fs b/src/MyWebLog.Domain/DataTypes.fs index 8f33973..7c1cb1f 100644 --- a/src/MyWebLog.Domain/DataTypes.fs +++ b/src/MyWebLog.Domain/DataTypes.fs @@ -264,7 +264,7 @@ type WebLog = /// The number of posts to display on pages of posts postsPerPage : int - /// The path of the theme (within /views/themes) + /// The path of the theme (within /themes) themePath : string /// The URL base diff --git a/src/MyWebLog/Handlers/Admin.fs b/src/MyWebLog/Handlers/Admin.fs index 7f6e6ad..e3a8e64 100644 --- a/src/MyWebLog/Handlers/Admin.fs +++ b/src/MyWebLog/Handlers/Admin.fs @@ -49,15 +49,28 @@ let dashboard : HttpHandler = fun next ctx -> task { // GET /admin/categories let listCategories : HttpHandler = fun next ctx -> task { + let! catListTemplate = TemplateCache.get "admin" "category-list-body" + let hash = Hash.FromAnonymousObject {| + web_log = ctx.WebLog + categories = CategoryCache.get ctx + page_title = "Categories" + csrf = csrfToken ctx + |} + hash.Add ("category_list", catListTemplate.Render hash) + return! viewForTheme "admin" "category-list" next ctx hash +} + +// GET /admin/categories/bare +let listCategoriesBare : HttpHandler = fun next ctx -> task { return! Hash.FromAnonymousObject {| categories = CategoryCache.get ctx - page_title = "Categories" csrf = csrfToken ctx |} - |> viewForTheme "admin" "category-list" next ctx + |> bareForTheme "admin" "category-list-body" next ctx } + // GET /admin/category/{id}/edit let editCategory catId : HttpHandler = fun next ctx -> task { let! result = task { @@ -77,7 +90,7 @@ let editCategory catId : HttpHandler = fun next ctx -> task { page_title = title categories = CategoryCache.get ctx |} - |> viewForTheme "admin" "category-edit" next ctx + |> bareForTheme "admin" "category-edit" next ctx | None -> return! Error.notFound next ctx } @@ -103,9 +116,7 @@ let saveCategory : HttpHandler = fun next ctx -> task { do! (match model.categoryId with "new" -> Data.Category.add | _ -> Data.Category.update) cat conn do! CategoryCache.update ctx do! addMessage ctx { UserMessage.success with message = "Category saved successfully" } - return! - redirectToGet (WebLog.relativeUrl webLog (Permalink $"admin/category/{CategoryId.toString cat.id}/edit")) - next ctx + return! listCategoriesBare next ctx | None -> return! Error.notFound next ctx } @@ -117,7 +128,7 @@ let deleteCategory catId : HttpHandler = fun next ctx -> task { do! CategoryCache.update ctx do! addMessage ctx { UserMessage.success with message = "Category deleted successfully" } | false -> do! addMessage ctx { UserMessage.error with message = "Category not found; cannot delete" } - return! redirectToGet (WebLog.relativeUrl webLog (Permalink "admin/categories")) next ctx + return! listCategoriesBare next ctx } // -- PAGES -- @@ -304,20 +315,37 @@ let saveSettings : HttpHandler = fun next ctx -> task { // -- TAG MAPPINGS -- -// GET /admin/tag-mappings -let tagMappings : HttpHandler = fun next ctx -> task { +open Microsoft.AspNetCore.Http + +/// Get the hash necessary to render the tag mapping list +let private tagMappingHash (ctx : HttpContext) = task { let! mappings = Data.TagMap.findByWebLogId ctx.WebLog.id ctx.Conn - return! - Hash.FromAnonymousObject - {| csrf = csrfToken ctx - mappings = mappings - mapping_ids = mappings |> List.map (fun it -> { name = it.tag; value = TagMapId.toString it.id }) - page_title = "Tag Mappings" - |} - |> viewForTheme "admin" "tag-mapping-list" next ctx + return Hash.FromAnonymousObject {| + web_log = ctx.WebLog + csrf = csrfToken ctx + mappings = mappings + mapping_ids = mappings |> List.map (fun it -> { name = it.tag; value = TagMapId.toString it.id }) + |} } -// GET /admin/tag-mapping/{id}/edit +// GET /admin/settings/tag-mappings +let tagMappings : HttpHandler = fun next ctx -> task { + let! hash = tagMappingHash ctx + let! listTemplate = TemplateCache.get "admin" "tag-mapping-list-body" + + hash.Add ("tag_mapping_list", listTemplate.Render hash) + hash.Add ("page_title", "Tag Mappings") + + return! viewForTheme "admin" "tag-mapping-list" next ctx hash +} + +// GET /admin/settings/tag-mappings/bare +let tagMappingsBare : HttpHandler = fun next ctx -> task { + let! hash = tagMappingHash ctx + return! bareForTheme "admin" "tag-mapping-list-body" next ctx hash +} + +// GET /admin/settings/tag-mapping/{id}/edit let editMapping tagMapId : HttpHandler = fun next ctx -> task { let isNew = tagMapId = "new" let tagMap = @@ -333,11 +361,11 @@ let editMapping tagMapId : HttpHandler = fun next ctx -> task { model = EditTagMapModel.fromMapping tm page_title = if isNew then "Add Tag Mapping" else $"Mapping for {tm.tag} Tag" |} - |> viewForTheme "admin" "tag-mapping-edit" next ctx + |> bareForTheme "admin" "tag-mapping-edit" next ctx | None -> return! Error.notFound next ctx } -// POST /admin/tag-mapping/save +// POST /admin/settings/tag-mapping/save let saveMapping : HttpHandler = fun next ctx -> task { let webLog = ctx.WebLog let conn = ctx.Conn @@ -351,17 +379,15 @@ let saveMapping : HttpHandler = fun next ctx -> task { | Some tm -> do! Data.TagMap.save { tm with tag = model.tag.ToLower (); urlValue = model.urlValue.ToLower () } conn do! addMessage ctx { UserMessage.success with message = "Tag mapping saved successfully" } - return! - redirectToGet (WebLog.relativeUrl webLog (Permalink $"admin/tag-mapping/{TagMapId.toString tm.id}/edit")) - next ctx + return! tagMappingsBare next ctx | None -> return! Error.notFound next ctx } -// POST /admin/tag-mapping/{id}/delete +// POST /admin/settings/tag-mapping/{id}/delete let deleteMapping tagMapId : HttpHandler = fun next ctx -> task { let webLog = ctx.WebLog match! Data.TagMap.delete (TagMapId tagMapId) webLog.id ctx.Conn with | true -> do! addMessage ctx { UserMessage.success with message = "Tag mapping deleted successfully" } | false -> do! addMessage ctx { UserMessage.error with message = "Tag mapping not found; nothing deleted" } - return! redirectToGet (WebLog.relativeUrl webLog (Permalink "admin/tag-mappings")) next ctx + return! tagMappingsBare next ctx } diff --git a/src/MyWebLog/Handlers/Helpers.fs b/src/MyWebLog/Handlers/Helpers.fs index b6f63a6..f63e838 100644 --- a/src/MyWebLog/Handlers/Helpers.fs +++ b/src/MyWebLog/Handlers/Helpers.fs @@ -85,8 +85,8 @@ open Giraffe.ViewEngine /// htmx script tag let private htmxScript = RenderView.AsString.htmlNode Htmx.Script.minified -/// Render a view for the specified theme, using the specified template, layout, and hash -let viewForTheme theme template next ctx = fun (hash : Hash) -> task { +/// Populate the DotLiquid hash with standard information +let private populateHash hash ctx = task { // Don't need the web log, but this adds it to the hash if the function is called directly let _ = deriveWebLogFromHash hash ctx let! messages = messages ctx @@ -98,6 +98,11 @@ let viewForTheme theme template next ctx = fun (hash : Hash) -> task { hash.Add ("htmx_script", htmxScript) do! commitSession ctx +} + +/// Render a view for the specified theme, using the specified template, layout, and hash +let viewForTheme theme template next ctx = fun (hash : Hash) -> task { + do! populateHash hash ctx // 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 @@ -108,12 +113,38 @@ let viewForTheme theme template next ctx = fun (hash : Hash) -> task { // ...then render that content with its layout let isHtmx = ctx.Request.IsHtmx && not ctx.Request.IsHtmxRefresh - let layout = if isHtmx then "layout-partial" else "layout" - let! layoutTemplate = TemplateCache.get theme layout + let! layoutTemplate = TemplateCache.get theme (if isHtmx then "layout-partial" else "layout") return! htmlString (layoutTemplate.Render hash) next ctx } +/// Render a bare view for the specified theme, using the specified template and hash +let bareForTheme theme template next ctx = fun (hash : Hash) -> task { + do! populateHash hash ctx + + // Bare templates are rendered with layout-bare + let! contentTemplate = TemplateCache.get theme template + hash.Add ("content", contentTemplate.Render hash) + + let! layoutTemplate = TemplateCache.get theme "layout-bare" + + // add messages as HTTP headers + let messages = hash["messages"] :?> UserMessage[] + let actions = seq { + yield! + messages + |> Array.map (fun m -> + match m.detail with + | Some detail -> $"{m.level}|||{m.message}|||{detail}" + | None -> $"{m.level}|||{m.message}" + |> setHttpHeader "X-Message") + withHxNoPush + htmlString (layoutTemplate.Render hash) + } + + return! (actions |> Seq.reduce (>=>)) next ctx +} + /// Return a view for the web log's default theme let themedView template next ctx = fun (hash : Hash) -> task { return! viewForTheme (deriveWebLogFromHash hash ctx).themePath template next ctx hash diff --git a/src/MyWebLog/Handlers/Routes.fs b/src/MyWebLog/Handlers/Routes.fs index 98e6725..75ac4c3 100644 --- a/src/MyWebLog/Handlers/Routes.fs +++ b/src/MyWebLog/Handlers/Routes.fs @@ -98,6 +98,7 @@ let router : HttpHandler = choose [ GET >=> choose [ subRoute "/categor" (choose [ route "ies" >=> Admin.listCategories + route "ies/bare" >=> Admin.listCategoriesBare routef "y/%s/edit" Admin.editCategory ]) route "/dashboard" >=> Admin.dashboard @@ -121,6 +122,7 @@ let router : HttpHandler = choose [ ]) subRoute "/tag-mapping" (choose [ route "s" >=> Admin.tagMappings + route "s/bare" >=> Admin.tagMappingsBare routef "/%s/edit" Admin.editMapping ]) ]) diff --git a/src/MyWebLog/appsettings.json b/src/MyWebLog/appsettings.json index 9129ee1..29f11b0 100644 --- a/src/MyWebLog/appsettings.json +++ b/src/MyWebLog/appsettings.json @@ -3,7 +3,7 @@ "hostname": "data02.bitbadger.solutions", "database": "myWebLog_dev" }, - "Generator": "myWebLog 2.0-alpha25", + "Generator": "myWebLog 2.0-alpha26", "Logging": { "LogLevel": { "MyWebLog.Handlers": "Debug" diff --git a/src/MyWebLog/themes/admin/category-edit.liquid b/src/MyWebLog/themes/admin/category-edit.liquid index 5a2fe39..cb402fa 100644 --- a/src/MyWebLog/themes/admin/category-edit.liquid +++ b/src/MyWebLog/themes/admin/category-edit.liquid @@ -1,27 +1,27 @@ -

{{ page_title }}

-
-
+
+
{{ page_title }}
+ -
-
-
+
+
- +
-
+
-
-
+
- @@ -36,21 +36,19 @@
-
-
-
+
-
-
- +
+ + Cancel
-
-
+ diff --git a/src/MyWebLog/themes/admin/category-list-body.liquid b/src/MyWebLog/themes/admin/category-list-body.liquid new file mode 100644 index 0000000..6f050a4 --- /dev/null +++ b/src/MyWebLog/themes/admin/category-list-body.liquid @@ -0,0 +1,46 @@ +
+ +
+ {%- assign cat_count = categories | size -%} + {% if cat_count > 0 %} + {%- assign cat_col = "col-12 col-md-6 col-xl-5 col-xxl-4" -%} + {%- assign desc_col = "col-12 col-md-6 col-xl-7 col-xxl-8" -%} + {% for cat in categories -%} +
+
+ {%- if cat.parent_names %} + {% for name in cat.parent_names %}{{ name }} ⟩ {% endfor %} + {%- endif %} + {{ cat.name }}
+ + {%- if cat.post_count > 0 %} + + View {{ cat.post_count }} Post{% unless cat.post_count == 1 %}s{% endunless -%} + + + {%- endif %} + {%- capture cat_edit %}admin/category/{{ cat.id }}/edit{% endcapture -%} + + Edit + + + {%- capture cat_del %}admin/category/{{ cat.id }}/delete{% endcapture -%} + {%- capture cat_del_link %}{{ cat_del | relative_link }}{% endcapture -%} + + Delete + + +
+
+ {%- if cat.description %}{{ cat.description.value }}{% else %}none{% endif %} +
+
+ {%- endfor %} + {%- else -%} +
+
This web log has no categores defined
+
+ {%- endif %} +
diff --git a/src/MyWebLog/themes/admin/category-list.liquid b/src/MyWebLog/themes/admin/category-list.liquid index 71d2c9f..1cfce71 100644 --- a/src/MyWebLog/themes/admin/category-list.liquid +++ b/src/MyWebLog/themes/admin/category-list.liquid @@ -1,54 +1,16 @@ 

{{ page_title }}

- Add a New Category - - - - - - - - - {%- assign cat_count = categories | size -%} - {% if cat_count > 0 %} - {% for cat in categories -%} - - - - - {%- endfor %} - {%- else -%} - - - - {%- endif %} - -
CategoryDescription
- {%- if cat.parent_names %} - {% for name in cat.parent_names %}{{ name }} ⟩ {% endfor %} - {%- endif %} - {{ cat.name }}
- - {%- if cat.post_count > 0 %} - - View {{ cat.post_count }} Post{% unless cat.post_count == 1 %}s{% endunless -%} - - - {%- endif %} - {%- capture cat_edit %}admin/category/{{ cat.id }}/edit{% endcapture -%} - Edit - - {%- capture cat_del %}admin/category/{{ cat.id }}/delete{% endcapture -%} - {%- capture cat_del_link %}{{ cat_del | relative_link }}{% endcapture -%} - - Delete - - -
- {%- if cat.description %}{{ cat.description.value }}{% else %}none{% endif %} -
This web log has no categores defined
-
- -
+ + Add a New Category + +
+ {%- assign cat_col = "col-12 col-md-6 col-xl-5 col-xxl-4" -%} + {%- assign desc_col = "col-12 col-md-6 col-xl-7 col-xxl-8" -%} +
+
Category; Description
+
Description
+
+
+ {{ category_list }}
diff --git a/src/MyWebLog/themes/admin/layout-bare.liquid b/src/MyWebLog/themes/admin/layout-bare.liquid new file mode 100644 index 0000000..a343dd8 --- /dev/null +++ b/src/MyWebLog/themes/admin/layout-bare.liquid @@ -0,0 +1,5 @@ + + + +{{ content }} + diff --git a/src/MyWebLog/themes/admin/layout-partial.liquid b/src/MyWebLog/themes/admin/layout-partial.liquid index 4ae3606..83f7057 100644 --- a/src/MyWebLog/themes/admin/layout-partial.liquid +++ b/src/MyWebLog/themes/admin/layout-partial.liquid @@ -34,21 +34,19 @@ -
- {% if messages %} -
- {% for msg in messages %} - - {% endfor %} -
- {% endif %} +
+
+ {% for msg in messages %} + + {% endfor %} +
{{ content }}
+ diff --git a/src/MyWebLog/themes/admin/layout.liquid b/src/MyWebLog/themes/admin/layout.liquid index 69cb7a1..239eaa1 100644 --- a/src/MyWebLog/themes/admin/layout.liquid +++ b/src/MyWebLog/themes/admin/layout.liquid @@ -1,7 +1,7 @@ - + {{ page_title | escape }} « Admin « {{ web_log.name | escape }} -
- {% if messages %} -
- {% for msg in messages %} - - {% endfor %} -
- {% endif %} +
+
+ {% for msg in messages %} + + {% endfor %} +
{{ content }}