v0.1.1 - Bootstrap frontend rendering via Twig templates
All checks were successful
Create Release Package / PHP Lint (push) Successful in 49s
Create Release Package / Build Release (push) Successful in 1m18s

Replace FSE block markup on the frontend with proper Bootstrap 5 HTML
rendered through Twig templates. The Site Editor remains functional for
admin editing while the public site outputs Bootstrap navbar, cards,
pagination, grid layout, and responsive components.

New PHP classes: TemplateController, ContextBuilder, NavWalker
New Twig templates: 20 files (base, pages, partials, components)
Enhanced TwigService with WordPress functions and globals

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-02-08 15:11:00 +01:00
parent d069a203b4
commit cb288d6e74
32 changed files with 1439 additions and 29 deletions

View File

@@ -0,0 +1,39 @@
<div class="comment d-flex gap-3 mb-4{% if depth > 0 %} ms-5{% endif %}" id="comment-{{ comment.id }}">
<div class="flex-shrink-0">
<img src="{{ comment.avatar_url }}" alt="{{ comment.author }}"
class="rounded-circle" width="40" height="40">
</div>
<div class="flex-grow-1">
<div class="d-flex align-items-center gap-2 mb-1">
<strong class="small">
{% if comment.author_url %}
<a href="{{ comment.author_url }}" class="text-decoration-none text-body" rel="nofollow">
{{ comment.author }}
</a>
{% else %}
{{ comment.author }}
{% endif %}
</strong>
<time class="text-body-secondary small" datetime="{{ comment.date_iso }}">
{{ comment.date }}
</time>
{% if comment.edit_url %}
<a href="{{ comment.edit_url }}" class="text-body-secondary small">{{ __('Edit') }}</a>
{% endif %}
</div>
<div class="comment-content small">
{{ comment.content|raw }}
</div>
{% if comment.reply_url %}
<div class="mt-1">
{{ comment.reply_url|raw }}
</div>
{% endif %}
{% if comment.children|length > 0 %}
{% for child in comment.children %}
{% include 'partials/comment-item.html.twig' with {'comment': child, 'depth': depth + 1} only %}
{% endfor %}
{% endif %}
</div>
</div>

View File

@@ -0,0 +1,19 @@
{% if comments is defined and (comments.is_open or comments.count > 0) %}
<section class="comments-section border-top pt-5 mt-5" id="comments">
<h2 class="h4 mb-4">{{ comments.title }}</h2>
{% if comments.list|length > 0 %}
<div class="comment-list mb-4">
{% for comment in comments.list %}
{% include 'partials/comment-item.html.twig' with {'comment': comment, 'depth': 0} only %}
{% endfor %}
</div>
{% endif %}
{% if comments.is_open %}
<div class="comment-form mt-4">
{{ comments.form|raw }}
</div>
{% endif %}
</section>
{% endif %}

View File

@@ -0,0 +1,21 @@
<button type="button" class="wp-bootstrap-dark-mode-toggle ms-2"
data-bs-theme-toggle
aria-label="{{ __('Switch to dark mode') }}"
data-label-dark="{{ __('Switch to dark mode') }}"
data-label-light="{{ __('Switch to light mode') }}"
aria-pressed="false">
<svg class="wp-bootstrap-sun-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none;" aria-hidden="true">
<circle cx="12" cy="12" r="5"/>
<line x1="12" y1="1" x2="12" y2="3"/>
<line x1="12" y1="21" x2="12" y2="23"/>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
<line x1="1" y1="12" x2="3" y2="12"/>
<line x1="21" y1="12" x2="23" y2="12"/>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
</svg>
<svg class="wp-bootstrap-moon-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
</svg>
</button>

View File

@@ -0,0 +1,34 @@
<footer class="bg-body-tertiary mt-auto">
<div class="container py-5">
<div class="row">
<div class="col-md-6">
<h5 class="fw-bold">{{ site.name }}</h5>
<p class="text-body-secondary">{{ site.description }}</p>
</div>
<div class="col-md-6 text-md-end">
{% if footer_menu|length > 0 %}
<ul class="list-unstyled">
{% for item in footer_menu %}
<li>
<a href="{{ item.url }}" class="text-body-secondary text-decoration-none">
{{ item.title }}
</a>
</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
<hr>
<div class="row align-items-center">
<div class="col-md-6">
<p class="text-body-secondary small mb-0">&copy; {{ current_year }} {{ site.name }}</p>
</div>
<div class="col-md-6 text-md-end">
<p class="text-body-secondary small mb-0">
{{ __('Powered by %s')|format('<a href="https://wordpress.org" rel="nofollow" class="text-body-secondary">WordPress</a>')|raw }}
</p>
</div>
</div>
</div>
</footer>

View File

@@ -0,0 +1,56 @@
<header>
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container">
<a class="navbar-brand fw-bold" href="{{ site.url }}">
{{ site.name }}
</a>
<button class="navbar-toggler" type="button"
data-bs-toggle="collapse" data-bs-target="#navbarMain"
aria-controls="navbarMain" aria-expanded="false"
aria-label="{{ __('Toggle navigation') }}">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarMain">
<ul class="navbar-nav ms-auto mb-2 mb-lg-0">
{% for item in menu %}
{% if item.children|length > 0 %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle{{ item.active ? ' active' : '' }}"
href="{{ item.url }}" role="button"
data-bs-toggle="dropdown" aria-expanded="false">
{{ item.title }}
</a>
<ul class="dropdown-menu">
{% for child in item.children %}
<li>
<a class="dropdown-item{{ child.active ? ' active' : '' }}"
href="{{ child.url }}"
{% if child.target %}target="{{ child.target }}"{% endif %}>
{{ child.title }}
</a>
</li>
{% endfor %}
</ul>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link{{ item.active ? ' active' : '' }}"
href="{{ item.url }}"
{% if item.active %}aria-current="page"{% endif %}
{% if item.target %}target="{{ item.target }}"{% endif %}>
{{ item.title }}
</a>
</li>
{% endif %}
{% endfor %}
</ul>
{% if dark_mode %}
{% include 'partials/dark-mode-toggle.html.twig' %}
{% endif %}
</div>
</div>
</nav>
</header>

View File

@@ -0,0 +1,11 @@
<div class="text-body-secondary small mb-3">
<time datetime="{{ post.date_iso }}">{{ post.date }}</time>
<span class="mx-1">&middot;</span>
<a href="{{ post.author.url }}" class="text-body-secondary text-decoration-none">{{ post.author.name }}</a>
{% if post.categories is defined and post.categories|length > 0 %}
<span class="mx-1">&middot;</span>
{% for cat in post.categories %}
<a href="{{ cat.url }}" class="text-body-secondary text-decoration-none">{{ cat.name }}</a>{% if not loop.last %}, {% endif %}
{% endfor %}
{% endif %}
</div>

View File

@@ -0,0 +1,26 @@
{% if pagination and pagination.pages is defined and pagination.pages|length > 1 %}
<nav aria-label="{{ __('Page navigation') }}" class="my-5">
<ul class="pagination justify-content-center">
<li class="page-item{{ pagination.prev_url is null ? ' disabled' : '' }}">
<a class="page-link" href="{{ pagination.prev_url ?? '#' }}"
{% if pagination.prev_url is null %}tabindex="-1" aria-disabled="true"{% endif %}>
{{ pagination.prev_text }}
</a>
</li>
{% for page in pagination.pages %}
<li class="page-item{{ page.is_current ? ' active' : '' }}">
<a class="page-link" href="{{ page.url }}"
{% if page.is_current %}aria-current="page"{% endif %}>
{{ page.number }}
</a>
</li>
{% endfor %}
<li class="page-item{{ pagination.next_url is null ? ' disabled' : '' }}">
<a class="page-link" href="{{ pagination.next_url ?? '#' }}"
{% if pagination.next_url is null %}tabindex="-1" aria-disabled="true"{% endif %}>
{{ pagination.next_text }}
</a>
</li>
</ul>
</nav>
{% endif %}

View File

@@ -0,0 +1,22 @@
{% if post_navigation is defined and post_navigation|length > 0 %}
<nav class="border-top border-bottom py-4 my-4" aria-label="{{ __('Post navigation') }}">
<div class="row">
<div class="col-6">
{% if post_navigation.previous is defined %}
<small class="text-body-secondary d-block mb-1">{{ __('Previous') }}</small>
<a href="{{ post_navigation.previous.url }}" class="text-decoration-none">
&larr; {{ post_navigation.previous.title }}
</a>
{% endif %}
</div>
<div class="col-6 text-end">
{% if post_navigation.next is defined %}
<small class="text-body-secondary d-block mb-1">{{ __('Next') }}</small>
<a href="{{ post_navigation.next.url }}" class="text-decoration-none">
{{ post_navigation.next.title }} &rarr;
</a>
{% endif %}
</div>
</div>
</nav>
{% endif %}

View File

@@ -0,0 +1,11 @@
<form role="search" method="get" action="{{ site.url }}" class="mb-4">
<div class="input-group">
<input type="search" class="form-control" name="s"
placeholder="{{ __('Search...') }}"
value="{{ search_query is defined ? search_query : '' }}"
aria-label="{{ __('Search') }}">
<button class="btn btn-primary" type="submit">
{{ __('Search') }}
</button>
</div>
</form>

View File

@@ -0,0 +1,44 @@
<aside>
{% if sidebar.recent_posts is defined and sidebar.recent_posts|length > 0 %}
<div class="mb-4">
<h3 class="h6 text-uppercase fw-semibold" style="letter-spacing: 1.6px">
{{ __('Recent Posts') }}
</h3>
<ul class="list-unstyled">
{% for post in sidebar.recent_posts %}
<li class="mb-2">
<a href="{{ post.url }}" class="text-decoration-none">{{ post.title }}</a>
<br>
<small class="text-body-secondary">{{ post.date }}</small>
</li>
{% endfor %}
</ul>
</div>
<hr>
{% endif %}
<div class="mb-4">
<h3 class="h6 text-uppercase fw-semibold" style="letter-spacing: 1.6px">
{{ __('Search') }}
</h3>
{% include 'partials/search-form.html.twig' %}
</div>
<hr>
{% if sidebar.tags is defined and sidebar.tags|length > 0 %}
<div class="mb-4">
<h3 class="h6 text-uppercase fw-semibold" style="letter-spacing: 1.6px">
{{ __('Tags') }}
</h3>
<div>
{% for tag in sidebar.tags %}
<a href="{{ tag.url }}" class="badge bg-secondary text-decoration-none me-1 mb-1">
{{ tag.name }}
</a>
{% endfor %}
</div>
</div>
{% endif %}
</aside>