Eliminate blog settings page helpers — pure .sx defpages with service data
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m32s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m32s
Convert 6 blog settings pages (settings-home, cache, snippets, menu-items, tag-groups, tag-group-edit) from Python page helpers to .sx defpages with (service "blog-page" ...) IO primitives. Create data-driven defcomps that handle iteration via (map ...) instead of Python loops. Post-related page helpers (editor, post-admin/data/preview/entries/settings/edit) remain as Python helpers — they depend on _ensure_post_data and sx_components rendering functions that need separate conversion. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -76,3 +76,6 @@ def register_domain_services() -> None:
|
|||||||
if not services.has("federation"):
|
if not services.has("federation"):
|
||||||
from shared.services.federation_impl import SqlFederationService
|
from shared.services.federation_impl import SqlFederationService
|
||||||
services.federation = SqlFederationService()
|
services.federation = SqlFederationService()
|
||||||
|
|
||||||
|
from .blog_page import BlogPageService
|
||||||
|
services.register("blog_page", BlogPageService())
|
||||||
|
|||||||
234
blog/services/blog_page.py
Normal file
234
blog/services/blog_page.py
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
"""Blog page data service — provides serialized dicts for .sx defpages."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
|
class BlogPageService:
|
||||||
|
"""Service for blog page data, callable via (service "blog-page" ...)."""
|
||||||
|
|
||||||
|
async def cache_data(self, session, **kw):
|
||||||
|
from quart import url_for as qurl
|
||||||
|
from shared.browser.app.csrf import generate_csrf_token
|
||||||
|
return {
|
||||||
|
"clear_url": qurl("settings.cache_clear"),
|
||||||
|
"csrf": generate_csrf_token(),
|
||||||
|
}
|
||||||
|
|
||||||
|
async def snippets_data(self, session, **kw):
|
||||||
|
from quart import g, url_for as qurl
|
||||||
|
from sqlalchemy import select, or_
|
||||||
|
from models import Snippet
|
||||||
|
from shared.browser.app.csrf import generate_csrf_token
|
||||||
|
|
||||||
|
uid = g.user.id
|
||||||
|
is_admin = g.rights.get("admin")
|
||||||
|
csrf = generate_csrf_token()
|
||||||
|
filters = [Snippet.user_id == uid, Snippet.visibility == "shared"]
|
||||||
|
if is_admin:
|
||||||
|
filters.append(Snippet.visibility == "admin")
|
||||||
|
rows = (await session.execute(
|
||||||
|
select(Snippet).where(or_(*filters)).order_by(Snippet.name)
|
||||||
|
)).scalars().all()
|
||||||
|
|
||||||
|
snippets = []
|
||||||
|
for s in rows:
|
||||||
|
s_id = s.id
|
||||||
|
s_vis = s.visibility or "private"
|
||||||
|
s_uid = s.user_id
|
||||||
|
owner = "You" if s_uid == uid else f"User #{s_uid}"
|
||||||
|
can_delete = s_uid == uid or is_admin
|
||||||
|
d = {
|
||||||
|
"id": s_id,
|
||||||
|
"name": s.name or "",
|
||||||
|
"visibility": s_vis,
|
||||||
|
"owner": owner,
|
||||||
|
"can_delete": can_delete,
|
||||||
|
}
|
||||||
|
if is_admin:
|
||||||
|
d["patch_url"] = qurl("snippets.patch_visibility", snippet_id=s_id)
|
||||||
|
if can_delete:
|
||||||
|
d["delete_url"] = qurl("snippets.delete_snippet", snippet_id=s_id)
|
||||||
|
snippets.append(d)
|
||||||
|
return {
|
||||||
|
"snippets": snippets,
|
||||||
|
"is_admin": bool(is_admin),
|
||||||
|
"csrf": csrf,
|
||||||
|
}
|
||||||
|
|
||||||
|
async def menu_items_data(self, session, **kw):
|
||||||
|
from quart import url_for as qurl
|
||||||
|
from bp.menu_items.services.menu_items import get_all_menu_items
|
||||||
|
from shared.browser.app.csrf import generate_csrf_token
|
||||||
|
|
||||||
|
menu_items = await get_all_menu_items(session)
|
||||||
|
csrf = generate_csrf_token()
|
||||||
|
items = []
|
||||||
|
for mi in menu_items:
|
||||||
|
i_id = mi.id
|
||||||
|
label = mi.label or ""
|
||||||
|
fi = getattr(mi, "feature_image", None)
|
||||||
|
sort = mi.position or 0
|
||||||
|
items.append({
|
||||||
|
"id": i_id,
|
||||||
|
"label": label,
|
||||||
|
"url": mi.url or "",
|
||||||
|
"sort_order": sort,
|
||||||
|
"feature_image": fi,
|
||||||
|
"edit_url": qurl("menu_items.edit_menu_item", item_id=i_id),
|
||||||
|
"delete_url": qurl("menu_items.delete_menu_item_route", item_id=i_id),
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
"menu_items": items,
|
||||||
|
"new_url": qurl("menu_items.new_menu_item"),
|
||||||
|
"csrf": csrf,
|
||||||
|
}
|
||||||
|
|
||||||
|
async def tag_groups_data(self, session, **kw):
|
||||||
|
from quart import url_for as qurl
|
||||||
|
from sqlalchemy import select
|
||||||
|
from models.tag_group import TagGroup
|
||||||
|
from bp.blog.admin.routes import _unassigned_tags
|
||||||
|
from shared.browser.app.csrf import generate_csrf_token
|
||||||
|
|
||||||
|
groups_rows = list(
|
||||||
|
(await session.execute(
|
||||||
|
select(TagGroup).order_by(TagGroup.sort_order, TagGroup.name)
|
||||||
|
)).scalars()
|
||||||
|
)
|
||||||
|
unassigned = await _unassigned_tags(session)
|
||||||
|
|
||||||
|
groups = []
|
||||||
|
for g in groups_rows:
|
||||||
|
groups.append({
|
||||||
|
"id": g.id,
|
||||||
|
"name": g.name or "",
|
||||||
|
"slug": getattr(g, "slug", "") or "",
|
||||||
|
"feature_image": getattr(g, "feature_image", None),
|
||||||
|
"colour": getattr(g, "colour", None),
|
||||||
|
"sort_order": getattr(g, "sort_order", 0) or 0,
|
||||||
|
"edit_href": qurl("blog.tag_groups_admin.defpage_tag_group_edit", id=g.id),
|
||||||
|
})
|
||||||
|
|
||||||
|
unassigned_tags = []
|
||||||
|
for t in unassigned:
|
||||||
|
unassigned_tags.append({
|
||||||
|
"name": getattr(t, "name", "") if hasattr(t, "name") else t.get("name", ""),
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"groups": groups,
|
||||||
|
"unassigned_tags": unassigned_tags,
|
||||||
|
"create_url": qurl("blog.tag_groups_admin.create"),
|
||||||
|
"csrf": generate_csrf_token(),
|
||||||
|
}
|
||||||
|
|
||||||
|
async def tag_group_edit_data(self, session, *, id=None, **kw):
|
||||||
|
from quart import abort, url_for as qurl
|
||||||
|
from sqlalchemy import select
|
||||||
|
from models.tag_group import TagGroup, TagGroupTag
|
||||||
|
from models.ghost_content import Tag
|
||||||
|
from shared.browser.app.csrf import generate_csrf_token
|
||||||
|
|
||||||
|
tg = await session.get(TagGroup, id)
|
||||||
|
if not tg:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
assigned_rows = list(
|
||||||
|
(await session.execute(
|
||||||
|
select(TagGroupTag.tag_id).where(TagGroupTag.tag_group_id == id)
|
||||||
|
)).scalars()
|
||||||
|
)
|
||||||
|
assigned_set = set(assigned_rows)
|
||||||
|
|
||||||
|
all_tags_rows = list(
|
||||||
|
(await session.execute(
|
||||||
|
select(Tag).where(
|
||||||
|
Tag.deleted_at.is_(None),
|
||||||
|
(Tag.visibility == "public") | (Tag.visibility.is_(None)),
|
||||||
|
).order_by(Tag.name)
|
||||||
|
)).scalars()
|
||||||
|
)
|
||||||
|
|
||||||
|
all_tags = []
|
||||||
|
for t in all_tags_rows:
|
||||||
|
all_tags.append({
|
||||||
|
"id": t.id,
|
||||||
|
"name": getattr(t, "name", "") or "",
|
||||||
|
"feature_image": getattr(t, "feature_image", None),
|
||||||
|
"checked": t.id in assigned_set,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"group": {
|
||||||
|
"id": tg.id,
|
||||||
|
"name": tg.name or "",
|
||||||
|
"colour": getattr(tg, "colour", "") or "",
|
||||||
|
"sort_order": getattr(tg, "sort_order", 0) or 0,
|
||||||
|
"feature_image": getattr(tg, "feature_image", "") or "",
|
||||||
|
},
|
||||||
|
"all_tags": all_tags,
|
||||||
|
"save_url": qurl("blog.tag_groups_admin.save", id=tg.id),
|
||||||
|
"delete_url": qurl("blog.tag_groups_admin.delete_group", id=tg.id),
|
||||||
|
"csrf": generate_csrf_token(),
|
||||||
|
}
|
||||||
|
|
||||||
|
async def post_admin_data(self, session, *, slug=None, **kw):
|
||||||
|
"""Post admin panel — just needs post loaded into context."""
|
||||||
|
from quart import g
|
||||||
|
from sqlalchemy import select
|
||||||
|
from shared.models.page_config import PageConfig
|
||||||
|
|
||||||
|
# _ensure_post_data is called by before_request in defpage context
|
||||||
|
post = (g.post_data or {}).get("post", {})
|
||||||
|
features = {}
|
||||||
|
sumup_configured = False
|
||||||
|
if post.get("is_page"):
|
||||||
|
pc = (await session.execute(
|
||||||
|
select(PageConfig).where(
|
||||||
|
PageConfig.container_type == "page",
|
||||||
|
PageConfig.container_id == post["id"],
|
||||||
|
)
|
||||||
|
)).scalar_one_or_none()
|
||||||
|
if pc:
|
||||||
|
features = pc.features or {}
|
||||||
|
sumup_configured = bool(pc.sumup_api_key)
|
||||||
|
return {
|
||||||
|
"features": features,
|
||||||
|
"sumup_configured": sumup_configured,
|
||||||
|
}
|
||||||
|
|
||||||
|
async def preview_data(self, session, *, slug=None, **kw):
|
||||||
|
"""Build preview data with prettified/rendered content."""
|
||||||
|
from quart import g
|
||||||
|
from models.ghost_content import Post
|
||||||
|
from sqlalchemy import select as sa_select
|
||||||
|
|
||||||
|
post_id = g.post_data["post"]["id"]
|
||||||
|
post = (await session.execute(
|
||||||
|
sa_select(Post).where(Post.id == post_id)
|
||||||
|
)).scalar_one_or_none()
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
sx_content = getattr(post, "sx_content", None) or ""
|
||||||
|
if sx_content:
|
||||||
|
from shared.sx.prettify import sx_to_pretty_sx
|
||||||
|
result["sx_pretty"] = sx_to_pretty_sx(sx_content)
|
||||||
|
lexical_raw = getattr(post, "lexical", None) or ""
|
||||||
|
if lexical_raw:
|
||||||
|
from shared.sx.prettify import json_to_pretty_sx
|
||||||
|
result["json_pretty"] = json_to_pretty_sx(lexical_raw)
|
||||||
|
if sx_content:
|
||||||
|
from shared.sx.parser import parse as sx_parse
|
||||||
|
from shared.sx.html import render as sx_html_render
|
||||||
|
from shared.sx.jinja_bridge import _COMPONENT_ENV
|
||||||
|
try:
|
||||||
|
parsed = sx_parse(sx_content)
|
||||||
|
result["sx_rendered"] = sx_html_render(parsed, dict(_COMPONENT_ENV))
|
||||||
|
except Exception:
|
||||||
|
result["sx_rendered"] = "<em>Error rendering sx</em>"
|
||||||
|
if lexical_raw:
|
||||||
|
from bp.blog.ghost.lexical_renderer import render_lexical
|
||||||
|
try:
|
||||||
|
result["lex_rendered"] = render_lexical(lexical_raw)
|
||||||
|
except Exception:
|
||||||
|
result["lex_rendered"] = "<em>Error rendering lexical</em>"
|
||||||
|
return result
|
||||||
114
blog/sx/admin.sx
114
blog/sx/admin.sx
@@ -169,3 +169,117 @@
|
|||||||
(details :class "border rounded bg-white"
|
(details :class "border rounded bg-white"
|
||||||
(summary :class "cursor-pointer px-4 py-3 font-medium text-sm bg-stone-100 hover:bg-stone-200 select-none" title)
|
(summary :class "cursor-pointer px-4 py-3 font-medium text-sm bg-stone-100 hover:bg-stone-200 select-none" title)
|
||||||
(div :class "p-4 overflow-x-auto text-xs" content)))
|
(div :class "p-4 overflow-x-auto text-xs" content)))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
;; Data-driven content defcomps (called from defpages with service data)
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
;; Snippets — receives serialized snippet dicts from service
|
||||||
|
(defcomp ~blog-snippets-content (&key snippets is-admin csrf)
|
||||||
|
(~blog-snippets-panel
|
||||||
|
:list (if (empty? (or snippets (list)))
|
||||||
|
(~empty-state :icon "fa fa-puzzle-piece"
|
||||||
|
:message "No snippets yet. Create one from the blog editor.")
|
||||||
|
(~blog-snippets-list
|
||||||
|
:rows (map (lambda (s)
|
||||||
|
(let* ((badge-colours (dict
|
||||||
|
"private" "bg-stone-200 text-stone-700"
|
||||||
|
"shared" "bg-blue-100 text-blue-700"
|
||||||
|
"admin" "bg-amber-100 text-amber-700"))
|
||||||
|
(vis (or (get s "visibility") "private"))
|
||||||
|
(badge-cls (or (get badge-colours vis) "bg-stone-200 text-stone-700"))
|
||||||
|
(name (get s "name"))
|
||||||
|
(owner (get s "owner"))
|
||||||
|
(can-delete (get s "can_delete")))
|
||||||
|
(~blog-snippet-row
|
||||||
|
:name name :owner owner :badge-cls badge-cls :visibility vis
|
||||||
|
:extra (<>
|
||||||
|
(when is-admin
|
||||||
|
(~blog-snippet-visibility-select
|
||||||
|
:patch-url (get s "patch_url")
|
||||||
|
:hx-headers (str "{\"X-CSRFToken\": \"" csrf "\"}")
|
||||||
|
:options (<>
|
||||||
|
(~blog-snippet-option :value "private" :selected (= vis "private") :label "private")
|
||||||
|
(~blog-snippet-option :value "shared" :selected (= vis "shared") :label "shared")
|
||||||
|
(~blog-snippet-option :value "admin" :selected (= vis "admin") :label "admin"))))
|
||||||
|
(when can-delete
|
||||||
|
(~delete-btn
|
||||||
|
:url (get s "delete_url")
|
||||||
|
:trigger-target "#snippets-list"
|
||||||
|
:title "Delete snippet?"
|
||||||
|
:text (str "Delete \u201c" name "\u201d?")
|
||||||
|
:sx-headers (str "{\"X-CSRFToken\": \"" csrf "\"}")
|
||||||
|
:cls "px-3 py-1 text-sm bg-red-200 hover:bg-red-300 rounded text-red-800 flex-shrink-0"))))))
|
||||||
|
(or snippets (list)))))))
|
||||||
|
|
||||||
|
;; Menu Items — receives serialized menu item dicts from service
|
||||||
|
(defcomp ~blog-menu-items-content (&key menu-items new-url csrf)
|
||||||
|
(~blog-menu-items-panel
|
||||||
|
:new-url new-url
|
||||||
|
:list (if (empty? (or menu-items (list)))
|
||||||
|
(~empty-state :icon "fa fa-inbox"
|
||||||
|
:message "No menu items yet. Add one to get started!")
|
||||||
|
(~blog-menu-items-list
|
||||||
|
:rows (map (lambda (mi)
|
||||||
|
(~blog-menu-item-row
|
||||||
|
:img (~img-or-placeholder
|
||||||
|
:src (get mi "feature_image") :alt (get mi "label")
|
||||||
|
:size-cls "w-12 h-12 rounded-full object-cover flex-shrink-0")
|
||||||
|
:label (get mi "label")
|
||||||
|
:slug (get mi "url")
|
||||||
|
:sort-order (str (or (get mi "sort_order") 0))
|
||||||
|
:edit-url (get mi "edit_url")
|
||||||
|
:delete-url (get mi "delete_url")
|
||||||
|
:confirm-text (str "Remove " (get mi "label") " from the menu?")
|
||||||
|
:hx-headers (str "{\"X-CSRFToken\": \"" csrf "\"}")))
|
||||||
|
(or menu-items (list)))))))
|
||||||
|
|
||||||
|
;; Tag Groups — receives serialized tag group data from service
|
||||||
|
(defcomp ~blog-tag-groups-content (&key groups unassigned-tags create-url csrf)
|
||||||
|
(~blog-tag-groups-main
|
||||||
|
:form (~blog-tag-groups-create-form :create-url create-url :csrf csrf)
|
||||||
|
:groups (if (empty? (or groups (list)))
|
||||||
|
(~empty-state :icon "fa fa-tags" :message "No tag groups yet.")
|
||||||
|
(~blog-tag-groups-list
|
||||||
|
:items (map (lambda (g)
|
||||||
|
(let* ((fi (get g "feature_image"))
|
||||||
|
(colour (get g "colour"))
|
||||||
|
(name (get g "name"))
|
||||||
|
(initial (slice (or name "?") 0 1))
|
||||||
|
(icon (if fi
|
||||||
|
(~blog-tag-group-icon-image :src fi :name name)
|
||||||
|
(~blog-tag-group-icon-color
|
||||||
|
:style (if colour (str "background:" colour) "background:#e7e5e4")
|
||||||
|
:initial initial))))
|
||||||
|
(~blog-tag-group-li
|
||||||
|
:icon icon
|
||||||
|
:edit-href (get g "edit_href")
|
||||||
|
:name name
|
||||||
|
:slug (or (get g "slug") "")
|
||||||
|
:sort-order (or (get g "sort_order") 0))))
|
||||||
|
(or groups (list)))))
|
||||||
|
:unassigned (when (not (empty? (or unassigned-tags (list))))
|
||||||
|
(~blog-unassigned-tags
|
||||||
|
:heading (str (len (or unassigned-tags (list))) " Unassigned Tags")
|
||||||
|
:spans (map (lambda (t)
|
||||||
|
(~blog-unassigned-tag :name (get t "name")))
|
||||||
|
(or unassigned-tags (list)))))))
|
||||||
|
|
||||||
|
;; Tag Group Edit — receives serialized tag group + tags from service
|
||||||
|
(defcomp ~blog-tag-group-edit-content (&key group all-tags save-url delete-url csrf)
|
||||||
|
(~blog-tag-group-edit-main
|
||||||
|
:edit-form (~blog-tag-group-edit-form
|
||||||
|
:save-url save-url :csrf csrf
|
||||||
|
:name (get group "name")
|
||||||
|
:colour (get group "colour")
|
||||||
|
:sort-order (get group "sort_order")
|
||||||
|
:feature-image (get group "feature_image")
|
||||||
|
:tags (map (lambda (t)
|
||||||
|
(~blog-tag-checkbox
|
||||||
|
:tag-id (get t "id")
|
||||||
|
:checked (get t "checked")
|
||||||
|
:img (when (get t "feature_image")
|
||||||
|
(~blog-tag-checkbox-image :src (get t "feature_image")))
|
||||||
|
:name (get t "name")))
|
||||||
|
(or all-tags (list))))
|
||||||
|
:delete-form (~blog-tag-group-delete-form :delete-url delete-url :csrf csrf)))
|
||||||
|
|||||||
@@ -289,12 +289,6 @@ def _register_blog_helpers() -> None:
|
|||||||
"post-entries-content": _h_post_entries_content,
|
"post-entries-content": _h_post_entries_content,
|
||||||
"post-settings-content": _h_post_settings_content,
|
"post-settings-content": _h_post_settings_content,
|
||||||
"post-edit-content": _h_post_edit_content,
|
"post-edit-content": _h_post_edit_content,
|
||||||
"settings-content": _h_settings_content,
|
|
||||||
"cache-content": _h_cache_content,
|
|
||||||
"snippets-content": _h_snippets_content,
|
|
||||||
"menu-items-content": _h_menu_items_content,
|
|
||||||
"tag-groups-content": _h_tag_groups_content,
|
|
||||||
"tag-group-edit-content": _h_tag_group_edit_content,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -471,104 +465,3 @@ async def _h_post_edit_content(slug=None, **kw):
|
|||||||
return await _post_edit_content_sx(tctx)
|
return await _post_edit_content_sx(tctx)
|
||||||
|
|
||||||
|
|
||||||
# --- Settings helpers ---
|
|
||||||
|
|
||||||
async def _h_settings_content(**kw):
|
|
||||||
from shared.sx.page import get_template_context
|
|
||||||
from sx.sx_components import _settings_main_panel_sx
|
|
||||||
tctx = await get_template_context()
|
|
||||||
return _settings_main_panel_sx(tctx)
|
|
||||||
|
|
||||||
|
|
||||||
async def _h_cache_content(**kw):
|
|
||||||
from shared.sx.page import get_template_context
|
|
||||||
from sx.sx_components import _cache_main_panel_sx
|
|
||||||
tctx = await get_template_context()
|
|
||||||
return await _cache_main_panel_sx(tctx)
|
|
||||||
|
|
||||||
|
|
||||||
# --- Snippets helper ---
|
|
||||||
|
|
||||||
async def _h_snippets_content(**kw):
|
|
||||||
from quart import g
|
|
||||||
from sqlalchemy import select, or_
|
|
||||||
from models import Snippet
|
|
||||||
uid = g.user.id
|
|
||||||
is_admin = g.rights.get("admin")
|
|
||||||
filters = [Snippet.user_id == uid, Snippet.visibility == "shared"]
|
|
||||||
if is_admin:
|
|
||||||
filters.append(Snippet.visibility == "admin")
|
|
||||||
rows = (await g.s.execute(
|
|
||||||
select(Snippet).where(or_(*filters)).order_by(Snippet.name)
|
|
||||||
)).scalars().all()
|
|
||||||
from shared.sx.page import get_template_context
|
|
||||||
from sx.sx_components import _snippets_main_panel_sx
|
|
||||||
tctx = await get_template_context()
|
|
||||||
tctx["snippets"] = rows
|
|
||||||
tctx["is_admin"] = is_admin
|
|
||||||
return await _snippets_main_panel_sx(tctx)
|
|
||||||
|
|
||||||
|
|
||||||
# --- Menu Items helper ---
|
|
||||||
|
|
||||||
async def _h_menu_items_content(**kw):
|
|
||||||
from quart import g
|
|
||||||
from bp.menu_items.services.menu_items import get_all_menu_items
|
|
||||||
menu_items = await get_all_menu_items(g.s)
|
|
||||||
from shared.sx.page import get_template_context
|
|
||||||
from sx.sx_components import _menu_items_main_panel_sx
|
|
||||||
tctx = await get_template_context()
|
|
||||||
tctx["menu_items"] = menu_items
|
|
||||||
return await _menu_items_main_panel_sx(tctx)
|
|
||||||
|
|
||||||
|
|
||||||
# --- Tag Groups helpers ---
|
|
||||||
|
|
||||||
async def _h_tag_groups_content(**kw):
|
|
||||||
from quart import g
|
|
||||||
from sqlalchemy import select
|
|
||||||
from models.tag_group import TagGroup
|
|
||||||
from bp.blog.admin.routes import _unassigned_tags
|
|
||||||
groups = list(
|
|
||||||
(await g.s.execute(
|
|
||||||
select(TagGroup).order_by(TagGroup.sort_order, TagGroup.name)
|
|
||||||
)).scalars()
|
|
||||||
)
|
|
||||||
unassigned = await _unassigned_tags(g.s)
|
|
||||||
from shared.sx.page import get_template_context
|
|
||||||
from sx.sx_components import _tag_groups_main_panel_sx
|
|
||||||
tctx = await get_template_context()
|
|
||||||
tctx.update({"groups": groups, "unassigned_tags": unassigned})
|
|
||||||
return await _tag_groups_main_panel_sx(tctx)
|
|
||||||
|
|
||||||
|
|
||||||
async def _h_tag_group_edit_content(id=None, **kw):
|
|
||||||
from quart import g, abort
|
|
||||||
from sqlalchemy import select
|
|
||||||
from models.tag_group import TagGroup, TagGroupTag
|
|
||||||
from models.ghost_content import Tag
|
|
||||||
tg = await g.s.get(TagGroup, id)
|
|
||||||
if not tg:
|
|
||||||
abort(404)
|
|
||||||
assigned_rows = list(
|
|
||||||
(await g.s.execute(
|
|
||||||
select(TagGroupTag.tag_id).where(TagGroupTag.tag_group_id == id)
|
|
||||||
)).scalars()
|
|
||||||
)
|
|
||||||
all_tags = list(
|
|
||||||
(await g.s.execute(
|
|
||||||
select(Tag).where(
|
|
||||||
Tag.deleted_at.is_(None),
|
|
||||||
(Tag.visibility == "public") | (Tag.visibility.is_(None)),
|
|
||||||
).order_by(Tag.name)
|
|
||||||
)).scalars()
|
|
||||||
)
|
|
||||||
from shared.sx.page import get_template_context
|
|
||||||
from sx.sx_components import _tag_groups_edit_main_panel_sx
|
|
||||||
tctx = await get_template_context()
|
|
||||||
tctx.update({
|
|
||||||
"group": tg,
|
|
||||||
"all_tags": all_tags,
|
|
||||||
"assigned_tag_ids": set(assigned_rows),
|
|
||||||
})
|
|
||||||
return await _tag_groups_edit_main_panel_sx(tctx)
|
|
||||||
|
|||||||
@@ -59,13 +59,14 @@
|
|||||||
:path "/settings/"
|
:path "/settings/"
|
||||||
:auth :admin
|
:auth :admin
|
||||||
:layout :blog-settings
|
:layout :blog-settings
|
||||||
:content (settings-content))
|
:content (div :class "max-w-2xl mx-auto px-4 py-6"))
|
||||||
|
|
||||||
(defpage cache-page
|
(defpage cache-page
|
||||||
:path "/settings/cache/"
|
:path "/settings/cache/"
|
||||||
:auth :admin
|
:auth :admin
|
||||||
:layout :blog-cache
|
:layout :blog-cache
|
||||||
:content (cache-content))
|
:data (service "blog-page" "cache-data")
|
||||||
|
:content (~blog-cache-panel :clear-url clear-url :csrf csrf))
|
||||||
|
|
||||||
; --- Snippets ---
|
; --- Snippets ---
|
||||||
|
|
||||||
@@ -73,7 +74,9 @@
|
|||||||
:path "/settings/snippets/"
|
:path "/settings/snippets/"
|
||||||
:auth :login
|
:auth :login
|
||||||
:layout :blog-snippets
|
:layout :blog-snippets
|
||||||
:content (snippets-content))
|
:data (service "blog-page" "snippets-data")
|
||||||
|
:content (~blog-snippets-content
|
||||||
|
:snippets snippets :is-admin is-admin :csrf csrf))
|
||||||
|
|
||||||
; --- Menu Items ---
|
; --- Menu Items ---
|
||||||
|
|
||||||
@@ -81,7 +84,9 @@
|
|||||||
:path "/settings/menu_items/"
|
:path "/settings/menu_items/"
|
||||||
:auth :admin
|
:auth :admin
|
||||||
:layout :blog-menu-items
|
:layout :blog-menu-items
|
||||||
:content (menu-items-content))
|
:data (service "blog-page" "menu-items-data")
|
||||||
|
:content (~blog-menu-items-content
|
||||||
|
:menu-items menu-items :new-url new-url :csrf csrf))
|
||||||
|
|
||||||
; --- Tag Groups ---
|
; --- Tag Groups ---
|
||||||
|
|
||||||
@@ -89,10 +94,16 @@
|
|||||||
:path "/settings/tag-groups/"
|
:path "/settings/tag-groups/"
|
||||||
:auth :admin
|
:auth :admin
|
||||||
:layout :blog-tag-groups
|
:layout :blog-tag-groups
|
||||||
:content (tag-groups-content))
|
:data (service "blog-page" "tag-groups-data")
|
||||||
|
:content (~blog-tag-groups-content
|
||||||
|
:groups groups :unassigned-tags unassigned-tags
|
||||||
|
:create-url create-url :csrf csrf))
|
||||||
|
|
||||||
(defpage tag-group-edit
|
(defpage tag-group-edit
|
||||||
:path "/settings/tag-groups/<int:id>/"
|
:path "/settings/tag-groups/<int:id>/"
|
||||||
:auth :admin
|
:auth :admin
|
||||||
:layout :blog-tag-group-edit
|
:layout :blog-tag-group-edit
|
||||||
:content (tag-group-edit-content id))
|
:data (service "blog-page" "tag-group-edit-data" :id id)
|
||||||
|
:content (~blog-tag-group-edit-content
|
||||||
|
:group group :all-tags all-tags
|
||||||
|
:save-url save-url :delete-url delete-url :csrf csrf))
|
||||||
|
|||||||
Reference in New Issue
Block a user