feat: implement Pages as Spaces Phase 1
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 45s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 45s
- Add PageConfig model with feature flags (calendar, market) - Auto-create PageConfig on Ghost page sync - Add create_page() for Ghost /pages/ API endpoint - Add /new-page/ route for creating pages - Add ?type=pages blog filter with Posts|Pages tab toggle - Add list_pages() to DBClient with PageConfig eager loading - Add PUT /<slug>/admin/features/ route for feature toggles - Add feature badges (calendar, market) on page cards - Add features panel to page admin dashboard - Update shared_lib submodule with PageConfig model Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
{# New Post + Drafts toggle — shown in aside (desktop + mobile) #}
|
||||
{# New Post/Page + Drafts toggle — shown in aside (desktop + mobile) #}
|
||||
<div class="flex flex-wrap gap-2 px-4 py-3">
|
||||
{% if has_access('blog.new_post') %}
|
||||
{% set new_href = url_for('blog.new_post')|host %}
|
||||
@@ -14,6 +14,19 @@
|
||||
>
|
||||
<i class="fa fa-plus mr-1"></i> New Post
|
||||
</a>
|
||||
{% set new_page_href = url_for('blog.new_page')|host %}
|
||||
<a
|
||||
href="{{ new_page_href }}"
|
||||
hx-get="{{ new_page_href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{hx_select_search}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
class="px-3 py-1 rounded bg-blue-600 text-white text-sm hover:bg-blue-700 transition-colors"
|
||||
title="New Page"
|
||||
>
|
||||
<i class="fa fa-plus mr-1"></i> New Page
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if g.user and (draft_count or drafts) %}
|
||||
{% if drafts %}
|
||||
|
||||
@@ -1,4 +1,39 @@
|
||||
|
||||
{# Content type tabs: Posts | Pages #}
|
||||
<div class="flex justify-center gap-1 px-3 pt-3">
|
||||
{% set posts_href = (url_for('blog.home'))|host %}
|
||||
{% set pages_href = (url_for('blog.home') ~ '?type=pages')|host %}
|
||||
<a
|
||||
href="{{ posts_href }}"
|
||||
hx-get="{{ posts_href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{ hx_select_search }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
class="px-4 py-1.5 rounded-t text-sm font-medium transition-colors
|
||||
{{ 'bg-stone-700 text-white' if content_type != 'pages' else 'bg-stone-100 text-stone-600 hover:bg-stone-200' }}"
|
||||
>Posts</a>
|
||||
<a
|
||||
href="{{ pages_href }}"
|
||||
hx-get="{{ pages_href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{ hx_select_search }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
class="px-4 py-1.5 rounded-t text-sm font-medium transition-colors
|
||||
{{ 'bg-stone-700 text-white' if content_type == 'pages' else 'bg-stone-100 text-stone-600 hover:bg-stone-200' }}"
|
||||
>Pages</a>
|
||||
</div>
|
||||
|
||||
{% if content_type == 'pages' %}
|
||||
{# Pages listing #}
|
||||
<div class="max-w-full px-3 py-3 space-y-3">
|
||||
{% set page_num = page %}
|
||||
{% include "_types/blog/_page_cards.html" %}
|
||||
</div>
|
||||
<div class="pb-8"></div>
|
||||
{% else %}
|
||||
|
||||
{# View toggle bar - desktop only #}
|
||||
<div class="hidden md:flex justify-end px-3 pt-3 gap-1">
|
||||
{% set list_href = (current_local_href ~ {'view': None}|qs)|host %}
|
||||
@@ -46,3 +81,4 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="pb-8"></div>
|
||||
{% endif %}{# end content_type check #}
|
||||
|
||||
56
templates/_types/blog/_page_card.html
Normal file
56
templates/_types/blog/_page_card.html
Normal file
@@ -0,0 +1,56 @@
|
||||
{# Single page card for pages listing #}
|
||||
<article class="border-b pb-6 last:border-b-0 relative">
|
||||
{% set _href = url_for('blog.post.post_detail', slug=page.slug)|host %}
|
||||
<a
|
||||
href="{{ _href }}"
|
||||
hx-get="{{ _href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{ hx_select_search }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
class="block rounded-xl bg-white shadow hover:shadow-md transition overflow-hidden"
|
||||
>
|
||||
<header class="mb-2 text-center">
|
||||
<h2 class="text-4xl font-bold text-stone-900">
|
||||
{{ page.title }}
|
||||
</h2>
|
||||
|
||||
{# Feature badges #}
|
||||
{% if page.features %}
|
||||
<div class="flex justify-center gap-2 mt-2">
|
||||
{% if page.features.get('calendar') %}
|
||||
<span class="inline-block px-2 py-0.5 rounded-full text-xs font-semibold bg-blue-100 text-blue-800">
|
||||
<i class="fa fa-calendar mr-1"></i>Calendar
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if page.features.get('market') %}
|
||||
<span class="inline-block px-2 py-0.5 rounded-full text-xs font-semibold bg-green-100 text-green-800">
|
||||
<i class="fa fa-shopping-bag mr-1"></i>Market
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if page.published_at %}
|
||||
<p class="text-sm text-stone-500">
|
||||
Published: {{ page.published_at.strftime("%-d %b %Y at %H:%M") }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</header>
|
||||
|
||||
{% if page.feature_image %}
|
||||
<div class="mb-4">
|
||||
<img
|
||||
src="{{ page.feature_image }}"
|
||||
alt=""
|
||||
class="rounded-lg w-full object-cover"
|
||||
>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if page.custom_excerpt or page.excerpt %}
|
||||
<p class="text-stone-700 text-lg leading-relaxed text-center">
|
||||
{{ page.custom_excerpt or page.excerpt }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</a>
|
||||
</article>
|
||||
19
templates/_types/blog/_page_cards.html
Normal file
19
templates/_types/blog/_page_cards.html
Normal file
@@ -0,0 +1,19 @@
|
||||
{# Page cards loop with pagination sentinel #}
|
||||
{% for page in pages %}
|
||||
{% include "_types/blog/_page_card.html" %}
|
||||
{% endfor %}
|
||||
{% if page_num < total_pages|int %}
|
||||
<div
|
||||
id="sentinel-{{ page_num }}-d"
|
||||
class="h-4 opacity-0 pointer-events-none"
|
||||
hx-get="{{ (current_local_href ~ {'page': page_num + 1}|qs)|host }}"
|
||||
hx-trigger="intersect once delay:250ms"
|
||||
hx-swap="outerHTML"
|
||||
></div>
|
||||
{% else %}
|
||||
{% if pages %}
|
||||
<div class="col-span-full mt-4 text-center text-xs text-stone-400">End of results</div>
|
||||
{% else %}
|
||||
<div class="col-span-full mt-8 text-center text-stone-500">No pages found.</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
49
templates/_types/post/admin/_features_panel.html
Normal file
49
templates/_types/post/admin/_features_panel.html
Normal file
@@ -0,0 +1,49 @@
|
||||
{# Feature toggles for PageConfig #}
|
||||
<div id="features-panel" class="space-y-4 p-4 bg-white rounded-lg border border-stone-200">
|
||||
<h3 class="text-lg font-semibold text-stone-800">Page Features</h3>
|
||||
|
||||
<form
|
||||
hx-put="{{ url_for('blog.post.admin.update_features', slug=post.slug)|host }}"
|
||||
hx-target="#features-panel"
|
||||
hx-swap="outerHTML"
|
||||
hx-headers='{"Content-Type": "application/json"}'
|
||||
hx-ext="json-enc"
|
||||
class="space-y-3"
|
||||
>
|
||||
<label class="flex items-center gap-3 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="calendar"
|
||||
value="true"
|
||||
{{ 'checked' if features.get('calendar') }}
|
||||
class="h-5 w-5 rounded border-stone-300 text-blue-600 focus:ring-blue-500"
|
||||
_="on change trigger submit on closest <form/>"
|
||||
>
|
||||
<span class="text-sm text-stone-700">
|
||||
<i class="fa fa-calendar text-blue-600 mr-1"></i>
|
||||
Calendar — enable event booking on this page
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label class="flex items-center gap-3 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="market"
|
||||
value="true"
|
||||
{{ 'checked' if features.get('market') }}
|
||||
class="h-5 w-5 rounded border-stone-300 text-green-600 focus:ring-green-500"
|
||||
_="on change trigger submit on closest <form/>"
|
||||
>
|
||||
<span class="text-sm text-stone-700">
|
||||
<i class="fa fa-shopping-bag text-green-600 mr-1"></i>
|
||||
Market — enable product catalog on this page
|
||||
</span>
|
||||
</label>
|
||||
</form>
|
||||
|
||||
{# Phase 3: SumUp connection placeholder #}
|
||||
<div class="mt-4 pt-4 border-t border-stone-100">
|
||||
<h4 class="text-sm font-medium text-stone-500">SumUp Payment</h4>
|
||||
<p class="text-xs text-stone-400 mt-1">Payment connection coming soon.</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -3,5 +3,10 @@
|
||||
id="main-panel"
|
||||
class="flex-1 md:h-full md:min-h-0 overflow-y-auto overscroll-contain js-grid-viewport"
|
||||
>
|
||||
{% if post and post.is_page %}
|
||||
<div class="max-w-lg mx-auto mt-6 px-4">
|
||||
{% include "_types/post/admin/_features_panel.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="pb-8"></div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user