@@ -166,3 +166,171 @@ And include it in `main.html`:
166166``` jinja title="docs/theme/overrides/main.html"
167167{% include "partials/json_feed.html.jinja2" %}
168168```
169+
170+ ----
171+
172+ ## Display a recent updates page from the JSON feed
173+
174+ The updated JSON feed can also be used inside your documentation site to render a
175+ small "recently updated pages" index. This is useful for documentation portals or
176+ digital gardens where readers may want to see what changed recently without using
177+ a feed reader.
178+
179+ First, keep the updated JSON feed enabled and expose enough entries for the page:
180+
181+ ``` yaml title="mkdocs.yml"
182+ plugins :
183+ - rss :
184+ length : 100
185+ match_path : " ^(?!updates\\ .md$)(?!.*(?:^|/)(README|index)\\ .md$).+\\ .md$"
186+ feeds_filenames :
187+ json_updated : feed_json_updated.json
188+
189+ extra_javascript :
190+ - javascripts/recent-updates.js
191+
192+ nav :
193+ - Recently updated : updates.md
194+ ` ` `
195+
196+ The page can only show entries that are present in the generated JSON feed. Set
197+ ` length` to at least the maximum number of updates you want readers to browse.
198+
199+ Then create a page that contains the target container and optional direct feed
200+ links :
201+
202+ ` ` ` markdown title="docs/updates.md"
203+ # Recently updated
204+
205+ <p class="updates-note">
206+ Sorted by Git last-modified time from the updated feed. Index pages and this
207+ page are excluded.
208+ </p>
209+
210+ <p class="updates-feeds">
211+ <a href="../feed_json_updated.json">JSON feed</a>
212+ </p>
213+
214+ <div data-recent-updates data-page-size="20">
215+ Loading updates...
216+ </div>
217+ ` ` `
218+
219+ Finally, add a small script that fetches the JSON feed and progressively renders
220+ entries :
221+
222+ ` ` ` javascript title="docs/javascripts/recent-updates.js"
223+ (function () {
224+ var root = document.querySelector("[data-recent-updates]");
225+
226+ if (!root) {
227+ return;
228+ }
229+
230+ function feedUrl() {
231+ return new URL("../feed_json_updated.json", window.location.href).toString();
232+ }
233+
234+ function itemDate(item) {
235+ return item.date_modified || item.date_published || item.date_created || "";
236+ }
237+
238+ function formatDate(value) {
239+ var date = new Date(value);
240+
241+ if (Number.isNaN(date.getTime())) {
242+ return value;
243+ }
244+
245+ return date.toLocaleString(undefined, {
246+ year: "numeric",
247+ month: "short",
248+ day: "numeric",
249+ hour: "2-digit",
250+ minute: "2-digit",
251+ });
252+ }
253+
254+ function itemSummary(item) {
255+ var container = document.createElement("div");
256+ var text = "";
257+
258+ container.innerHTML = item.content_html || item.summary || "";
259+ text = (container.textContent || "").replace(/\s +/g, " ").trim();
260+
261+ return text.length > 160 ? text.slice(0, 160) + "..." : text;
262+ }
263+
264+ function render(items) {
265+ var pageSize = parseInt(root.getAttribute("data-page-size") || "20", 10);
266+ var visibleCount = Math.min(pageSize, items.length);
267+ var list = document.createElement("ol");
268+ var button = document.createElement("button");
269+
270+ function renderVisibleItems() {
271+ list.innerHTML = "";
272+
273+ items.slice(0, visibleCount).forEach(function (item) {
274+ var entry = document.createElement("li");
275+ var link = document.createElement("a");
276+ var summary = document.createElement("p");
277+ var time = document.createElement("time");
278+ var date = itemDate(item);
279+
280+ link.href = item.url || item.id || "#";
281+ link.textContent = item.title || link.href;
282+ summary.textContent = itemSummary(item);
283+ time.dateTime = date;
284+ time.textContent = formatDate(date);
285+
286+ entry.appendChild(link);
287+ entry.appendChild(summary);
288+ entry.appendChild(time);
289+ list.appendChild(entry);
290+ });
291+
292+ button.hidden = visibleCount >= items.length;
293+ }
294+
295+ button.type = "button";
296+ button.textContent = "Show more updates";
297+ button.addEventListener("click", function () {
298+ visibleCount = Math.min(visibleCount + pageSize, items.length);
299+ renderVisibleItems();
300+ });
301+
302+ root.replaceChildren(list, button);
303+ renderVisibleItems();
304+ }
305+
306+ fetch(feedUrl(), { cache: "no-store" })
307+ .then(function (response) {
308+ if (!response.ok) {
309+ throw new Error("Unable to load updates feed");
310+ }
311+
312+ return response.json();
313+ })
314+ .then(function (feed) {
315+ var items = Array.isArray(feed.items) ? feed.items : [];
316+
317+ if (!items.length) {
318+ root.textContent = "No recent updates found.";
319+ return;
320+ }
321+
322+ render(items);
323+ })
324+ .catch(function () {
325+ root.textContent = "Unable to load recent updates.";
326+ });
327+ })();
328+ ` ` `
329+
330+ The example intentionally uses the updated feed (`feed_json_updated.json`) rather
331+ than the created feed, because the page is meant to answer "what changed
332+ recently?". You can adapt `match_path` to include only blog posts, release notes,
333+ or any other section of your documentation.
334+
335+ This is a client-side enhancement. Readers without JavaScript can still use the
336+ direct feed link, but the list itself is rendered in the browser.
0 commit comments