Skip to content

Commit 047d565

Browse files
committed
docs: add recent updates JSON feed recipe
1 parent 49b30e7 commit 047d565

1 file changed

Lines changed: 168 additions & 0 deletions

File tree

docs/integrations.md

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)