From 047d565e98de962142aaec640934cf310006e2c6 Mon Sep 17 00:00:00 2001
From: catwithtudou <949812478@qq.com>
Date: Sun, 7 Jun 2026 17:01:38 +0800
Subject: [PATCH] docs: add recent updates JSON feed recipe
---
docs/integrations.md | 168 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 168 insertions(+)
diff --git a/docs/integrations.md b/docs/integrations.md
index b4f4a09..b8f30eb 100644
--- a/docs/integrations.md
+++ b/docs/integrations.md
@@ -166,3 +166,171 @@ And include it in `main.html`:
```jinja title="docs/theme/overrides/main.html"
{% include "partials/json_feed.html.jinja2" %}
```
+
+----
+
+## Display a recent updates page from the JSON feed
+
+The updated JSON feed can also be used inside your documentation site to render a
+small "recently updated pages" index. This is useful for documentation portals or
+digital gardens where readers may want to see what changed recently without using
+a feed reader.
+
+First, keep the updated JSON feed enabled and expose enough entries for the page:
+
+```yaml title="mkdocs.yml"
+plugins:
+ - rss:
+ length: 100
+ match_path: "^(?!updates\\.md$)(?!.*(?:^|/)(README|index)\\.md$).+\\.md$"
+ feeds_filenames:
+ json_updated: feed_json_updated.json
+
+extra_javascript:
+ - javascripts/recent-updates.js
+
+nav:
+ - Recently updated: updates.md
+```
+
+The page can only show entries that are present in the generated JSON feed. Set
+`length` to at least the maximum number of updates you want readers to browse.
+
+Then create a page that contains the target container and optional direct feed
+links:
+
+```markdown title="docs/updates.md"
+# Recently updated
+
+
+ Sorted by Git last-modified time from the updated feed. Index pages and this
+ page are excluded.
+
+
+
+ JSON feed
+
+
+
+ Loading updates...
+
+```
+
+Finally, add a small script that fetches the JSON feed and progressively renders
+entries:
+
+```javascript title="docs/javascripts/recent-updates.js"
+(function () {
+ var root = document.querySelector("[data-recent-updates]");
+
+ if (!root) {
+ return;
+ }
+
+ function feedUrl() {
+ return new URL("../feed_json_updated.json", window.location.href).toString();
+ }
+
+ function itemDate(item) {
+ return item.date_modified || item.date_published || item.date_created || "";
+ }
+
+ function formatDate(value) {
+ var date = new Date(value);
+
+ if (Number.isNaN(date.getTime())) {
+ return value;
+ }
+
+ return date.toLocaleString(undefined, {
+ year: "numeric",
+ month: "short",
+ day: "numeric",
+ hour: "2-digit",
+ minute: "2-digit",
+ });
+ }
+
+ function itemSummary(item) {
+ var container = document.createElement("div");
+ var text = "";
+
+ container.innerHTML = item.content_html || item.summary || "";
+ text = (container.textContent || "").replace(/\s+/g, " ").trim();
+
+ return text.length > 160 ? text.slice(0, 160) + "..." : text;
+ }
+
+ function render(items) {
+ var pageSize = parseInt(root.getAttribute("data-page-size") || "20", 10);
+ var visibleCount = Math.min(pageSize, items.length);
+ var list = document.createElement("ol");
+ var button = document.createElement("button");
+
+ function renderVisibleItems() {
+ list.innerHTML = "";
+
+ items.slice(0, visibleCount).forEach(function (item) {
+ var entry = document.createElement("li");
+ var link = document.createElement("a");
+ var summary = document.createElement("p");
+ var time = document.createElement("time");
+ var date = itemDate(item);
+
+ link.href = item.url || item.id || "#";
+ link.textContent = item.title || link.href;
+ summary.textContent = itemSummary(item);
+ time.dateTime = date;
+ time.textContent = formatDate(date);
+
+ entry.appendChild(link);
+ entry.appendChild(summary);
+ entry.appendChild(time);
+ list.appendChild(entry);
+ });
+
+ button.hidden = visibleCount >= items.length;
+ }
+
+ button.type = "button";
+ button.textContent = "Show more updates";
+ button.addEventListener("click", function () {
+ visibleCount = Math.min(visibleCount + pageSize, items.length);
+ renderVisibleItems();
+ });
+
+ root.replaceChildren(list, button);
+ renderVisibleItems();
+ }
+
+ fetch(feedUrl(), { cache: "no-store" })
+ .then(function (response) {
+ if (!response.ok) {
+ throw new Error("Unable to load updates feed");
+ }
+
+ return response.json();
+ })
+ .then(function (feed) {
+ var items = Array.isArray(feed.items) ? feed.items : [];
+
+ if (!items.length) {
+ root.textContent = "No recent updates found.";
+ return;
+ }
+
+ render(items);
+ })
+ .catch(function () {
+ root.textContent = "Unable to load recent updates.";
+ });
+})();
+```
+
+The example intentionally uses the updated feed (`feed_json_updated.json`) rather
+than the created feed, because the page is meant to answer "what changed
+recently?". You can adapt `match_path` to include only blog posts, release notes,
+or any other section of your documentation.
+
+This is a client-side enhancement. Readers without JavaScript can still use the
+direct feed link, but the list itself is rendered in the browser.