This is an automated email from the ASF dual-hosted git repository.

janhoy pushed a commit to branch common-posts-page
in repository https://gitbox.apache.org/repos/asf/solr-site.git

commit 0d1bf4be97b5bc7c5f8af2e132faacbca622425a
Author: Jan Høydahl <[email protected]>
AuthorDate: Tue Nov 18 08:54:27 2025 +0100

    Common Posts page
---
 content/pages/posts.md              |   4 ++
 pelicanconf.py                      |   6 +-
 plugins/combined_posts/__init__.py  | 110 ++++++++++++++++++++++++++++++++++++
 themes/solr/static/css/base.css     |  73 +++++++++++++++++++++++-
 themes/solr/templates/_header.html  |   5 +-
 themes/solr/templates/bloghome.html |   9 +++
 themes/solr/templates/news.html     |   9 +++
 themes/solr/templates/posts.html    |  65 +++++++++++++++++++++
 8 files changed, 275 insertions(+), 6 deletions(-)

diff --git a/content/pages/posts.md b/content/pages/posts.md
new file mode 100644
index 000000000..8c120fdd5
--- /dev/null
+++ b/content/pages/posts.md
@@ -0,0 +1,4 @@
+Title: Posts
+URL: posts.html
+save_as: posts.html
+template: posts
diff --git a/pelicanconf.py b/pelicanconf.py
index 5c74eed45..37e3df7ee 100755
--- a/pelicanconf.py
+++ b/pelicanconf.py
@@ -97,10 +97,14 @@ PLUGINS = [
     'jinja2content',
     'regex_replace',
     'age_days_lt',
-    'vex'
+    'vex',
+    'combined_posts',
 #    'md_inline_extension',
 ]
 
+# Configuration for combined posts pagination
+COMBINED_POSTS_PER_PAGE = 20
+
 MARKDOWN = {
     'extension_configs': {
         'toc': {},
diff --git a/plugins/combined_posts/__init__.py 
b/plugins/combined_posts/__init__.py
new file mode 100644
index 000000000..23268794a
--- /dev/null
+++ b/plugins/combined_posts/__init__.py
@@ -0,0 +1,110 @@
+"""
+Pelican plugin to generate a combined posts page with pagination.
+
+Combines articles (solr/news, solr/security) with pages (solr/blogposts),
+sorts them chronologically, and generates paginated output.
+"""
+
+from pelican import signals
+from pelican.generators import Generator
+from pelican.paginator import Paginator
+from pelican.writers import Writer
+import os
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+class CombinedPostsGenerator(Generator):
+    """
+    Generator for creating a combined posts page with pagination.
+
+    Combines articles from 'solr/news' and 'solr/security' categories
+    with pages from 'solr/blogposts' category, sorts chronologically,
+    and generates paginated HTML pages.
+    """
+
+    def generate_output(self, writer):
+        """Generate paginated combined posts pages."""
+
+        # Get all articles and pages
+        articles = self.context.get('articles', [])
+        pages = self.context.get('pages', [])
+
+        # Filter and combine
+        # Include articles from news and security categories
+        combined_articles = [
+            a for a in articles
+            if hasattr(a, 'category') and a.category and
+               a.category.name in ['solr/news', 'solr/security']
+        ]
+
+        # Include pages from blogposts category
+        combined_pages = [
+            p for p in pages
+            if hasattr(p, 'category') and p.category and
+               p.category.name == 'solr/blogposts'
+        ]
+
+        # Combine all
+        combined_posts = combined_articles + combined_pages
+
+        # Sort by date, newest first
+        combined_posts.sort(key=lambda x: x.date, reverse=True)
+
+        # Get pagination settings
+        per_page = self.settings.get('COMBINED_POSTS_PER_PAGE', 20)
+
+        # Create paginator
+        paginator = Paginator('posts', 'posts{number}.html',
+                              combined_posts, self.settings, per_page)
+
+        # Get the template
+        template = self.get_template('posts')
+
+        # Generate each page
+        for page_num in range(1, paginator.num_pages + 1):
+            posts_page = paginator.page(page_num)
+
+            # Build context
+            context = self.context.copy()
+            context.update({
+                'posts': posts_page.object_list,
+                'posts_page': posts_page,
+                'posts_paginator': paginator,
+                'page': posts_page,  # For breadcrumb/metadata
+                'title': 'Posts',
+                'slug': 'posts',
+            })
+
+            # Determine output path
+            if page_num == 1:
+                output_path = os.path.join(self.output_path, 'posts.html')
+                url = 'posts.html'
+            else:
+                output_path = os.path.join(
+                    self.output_path,
+                    f'posts{page_num}.html'
+                )
+                url = f'posts{page_num}.html'
+
+            # Ensure output directory exists
+            os.makedirs(os.path.dirname(output_path), exist_ok=True)
+
+            # Render and write
+            output = template.render(context)
+            with open(output_path, 'w', encoding='utf-8') as f:
+                f.write(output)
+
+            # Log
+            logger.info(f'Writing {url} ({page_num}/{paginator.num_pages})')
+
+
+def get_generators(pelican):
+    """Register the CombinedPostsGenerator."""
+    return CombinedPostsGenerator
+
+
+def register():
+    """Plugin registration hook."""
+    signals.get_generators.connect(get_generators)
diff --git a/themes/solr/static/css/base.css b/themes/solr/static/css/base.css
index e33785f13..9e995007f 100644
--- a/themes/solr/static/css/base.css
+++ b/themes/solr/static/css/base.css
@@ -1032,6 +1032,77 @@ ul li div.box div.img.logo-container.orange-background {
   background-color:#D9411E;
 }
 .full-width .gray .box.logo-box {
-  position: relative; 
+  position: relative;
   border: 1px solid #CCC;
 }
+
+/*
+ * Posts Page Styles
+ */
+.posts-filter {
+  margin-bottom: 30px;
+  padding-bottom: 20px;
+  border-bottom: 1px solid #e4e2dd;
+}
+
+.posts-filter a {
+  margin-right: 20px;
+  text-transform: uppercase;
+  font-size: 0.9em;
+  font-weight: 500;
+}
+
+.posts-filter a.active {
+  color: #2d7a3e;
+  font-weight: bold;
+}
+
+.post-item-title {
+  color: #D9411E;
+}
+
+.post-item-title a {
+  color: #D9411E;
+  text-decoration: none;
+}
+
+.post-item-title a:hover {
+  text-decoration: underline;
+}
+
+.read-more {
+  margin-top: 10px;
+  font-size: 0.9em;
+}
+
+.read-more a {
+  color: #D9411E;
+  font-weight: 600;
+}
+
+/*
+ * Posts Page Pagination
+ */
+.pagination {
+  margin-top: 40px;
+  padding-top: 20px;
+  border-top: 1px solid #e4e2dd;
+  text-align: center;
+}
+
+.pagination a {
+  margin: 0 10px;
+  color: #D9411E;
+  font-weight: 600;
+  text-decoration: none;
+}
+
+.pagination a:hover {
+  text-decoration: underline;
+}
+
+.pagination-info {
+  margin: 10px 0;
+  font-size: 0.95em;
+  color: #555;
+}
diff --git a/themes/solr/templates/_header.html 
b/themes/solr/templates/_header.html
index 190dd5ffb..f04060144 100644
--- a/themes/solr/templates/_header.html
+++ b/themes/solr/templates/_header.html
@@ -11,10 +11,7 @@
       <div class="top-bar-section">
         <ul class="navigation right">
           <li>
-            <a href="{{ SITEURL }}/blog.html">Blog</a>
-          </li>
-          <li>
-            <a href="{{ SITEURL }}/news.html">News</a>
+            <a href="{{ SITEURL }}/posts.html">Posts</a>
           </li>
           <li>
             <a href="{{ SITEURL }}/security.html">Security</a>
diff --git a/themes/solr/templates/bloghome.html 
b/themes/solr/templates/bloghome.html
index 61782a6cb..4b4b96732 100644
--- a/themes/solr/templates/bloghome.html
+++ b/themes/solr/templates/bloghome.html
@@ -17,7 +17,16 @@
       padding-top: 70px;
       margin-top: -70px;
     }
+    .breadcrumb {
+      margin-bottom: 20px;
+      font-size: 0.9em;
+    }
   </style>
+
+  <div class="breadcrumb">
+    <a href="{{ SITEURL }}/posts.html">← Back to All Posts</a>
+  </div>
+
   <h1 id="solr-blogs">Solr<sup>™</sup> Blog Posts<a class="headerlink" 
href="#solr-blog-posts" title="Permanent link">¶</a></h1>
   {{page.content}}
 
diff --git a/themes/solr/templates/news.html b/themes/solr/templates/news.html
index 827aac9b8..edc20c4ae 100644
--- a/themes/solr/templates/news.html
+++ b/themes/solr/templates/news.html
@@ -18,7 +18,16 @@
       padding-top: 70px;
       margin-top: -70px;
     }
+    .breadcrumb {
+      margin-bottom: 20px;
+      font-size: 0.9em;
+    }
   </style>
+
+  <div class="breadcrumb">
+    <a href="{{ SITEURL }}/posts.html">← Back to All Posts</a>
+  </div>
+
   <h1 id="solr-news">Solr<sup>™</sup> News<a class="headerlink" 
href="#solr-news" title="Permanent link">¶</a></h1>
   {{page.content}}
 
diff --git a/themes/solr/templates/posts.html b/themes/solr/templates/posts.html
new file mode 100644
index 000000000..2e5e44dd0
--- /dev/null
+++ b/themes/solr/templates/posts.html
@@ -0,0 +1,65 @@
+{% extends "page.html" %}
+
+{% block ng_directives %}x-ng-app-root="/solr"{% endblock %}
+{% block rss %}<link rel="alternate" type="application/atom+xml" title="Solr 
news except security news" href="/feeds/solr/news.atom.xml" />{% endblock %}
+
+{% block content_inner %}
+<div class="small-12 columns">
+
+
+  <h1 id="solr-posts">Solr<sup>™</sup> Posts<a class="headerlink" 
href="#solr-posts" title="Permanent link">¶</a></h1>
+  {{page.content}}
+
+  <div class="posts-filter">
+    <strong>View:</strong>
+    <a href="{{ SITEURL }}/posts.html" class="active">All Posts</a>
+    <a href="{{ SITEURL }}/blog.html">Blog</a>
+    <a href="{{ SITEURL }}/news.html">News</a>
+  </div>
+
+  {% for article in posts %}
+    <h2 id="{{ article.slug }}" class="post-item-title">
+      {% if article.save_as %}
+        {# Blog post - has its own page #}
+        <a href="{{ article.url }}">{{ article.title }}</a>
+      {% else %}
+        {# News/Security article - link to news.html with anchor #}
+        <a href="{{ SITEURL }}/news.html#{{ article.slug }}">{{ article.title 
}}</a>
+      {% endif %}
+      <a class="headerlink" href="#{{ article.slug }}" title="Permanent 
link">¶</a>
+    </h2>
+    <h5>
+      {% if article.category and article.category.name in ['solr/news', 
'solr/security'] %}
+        <strong>{{ article.category.name | replace('solr/', '') | title 
}}:</strong>
+      {% endif %}
+      {{ article.locale_date }}
+    </h5>
+    {% if article.summary %}
+      <p>{{ article.summary | striptags | truncate(512) }}</p>
+    {% else %}
+      <p>{{ article.content | striptags | truncate(512) }}</p>
+    {% endif %}
+    {% if article.category and article.category.name in ['solr/news', 
'solr/security'] %}
+      <div class="read-more">
+        <a href="{{ SITEURL }}/news.html#{{ article.slug }}">Read full article 
on news page →</a>
+      </div>
+    {% endif %}
+    <hr/>
+  {% endfor %}
+
+  {% if posts_paginator and posts_paginator.num_pages > 1 %}
+  <div class="pagination">
+    {% if posts_page.has_previous() %}
+      <a href="{{ SITEURL }}/{% if posts_page.previous_page_number() == 1 
%}posts.html{% else %}posts{{ posts_page.previous_page_number() }}.html{% endif 
%}">← Previous</a>
+    {% endif %}
+    <span class="pagination-info">
+      Page {{ posts_page.number }} of {{ posts_paginator.num_pages }}
+    </span>
+    {% if posts_page.has_next() %}
+      <a href="{{ SITEURL }}/posts{{ posts_page.next_page_number() 
}}.html">Next →</a>
+    {% endif %}
+  </div>
+  {% endif %}
+
+</div>
+{% endblock content_inner %}

Reply via email to