diigo-cache: the public page is an h-feed of h-entry bookmarks

Microformats are the public page's API for other sites (potluck
first): each bookmark is an h-entry with u-bookmark-of, p-name,
p-summary, a p-category per tag (linked to a tag search),
dt-published (bookmarked) and dt-updated (last edited). Prev/next
links carry rel=prev/next, so a reader can follow a filter's pages.
Tests parse the page with microformats-parser to pin the contract.

Generated-by: Claude Opus 5.5
This commit is contained in:
dckc committed 2026-10-09 01:49:40 -05:00
1 parent c66aaea366
commit be796d9d0d
4 files changed
+87 -13

No files matched your search

+32 -12
View File
@@ -515,8 +515,8 @@ async function publicPage(request: Request, env: Env, username: string): Promise
notice = `<p><em>no page ${esc(String(raw))}: pages run 1–${pages}</em></p>`;
}
const nav = (n: number, label: string) =>
n >= 1 && n <= pages ? `<a href="${here}?page=${n}">${label}</a>` : `<span>${label}</span>`;
const list = notice ? "" : itemList(items, "updated");
n >= 1 && n <= pages ? `<a rel="${n < pageN ? "prev" : "next"}" href="${here}?page=${n}">${label}</a>` : `<span>${label}</span>`;
const list = notice ? "" : itemList(items, "updated", here);
const body = publicFrame(
username,
`<p>${pages} page${pages === 1 ? "" : "s"} (diigo's, ${PUBLIC_PER_PAGE} bookmarks each; private ones are left out, so a page may show fewer).</p>
@@ -555,7 +555,7 @@ function filteredPage(
const kept = [...params].filter(([k, v]) => k !== "page" && v !== "");
const href = (n: number) => `${here}?${new URLSearchParams([...kept, ["page", String(n)]])}`;
const nav = (n: number, label: string) =>
n >= 1 && n <= pages ? `<a href="${esc(href(n))}">${label}</a>` : `<span>${label}</span>`;
n >= 1 && n <= pages ? `<a rel="${n < pageN ? "prev" : "next"}" href="${esc(href(n))}">${label}</a>` : `<span>${label}</span>`;
const status = found.matched
? `${found.matched} match${found.matched === 1 ? "" : "es"}, page ${pageN} of ${pages}, newest ${query.by} first`
: "no matches";
@@ -566,7 +566,7 @@ function filteredPage(
${fill ? fillLine(fill) : ""}
${filterForm(here, params)}
<p>${nav(pageN - 1, "← prev")} ${nav(pageN + 1, "next →")} <a href="${here}">all, by diigo page</a></p>
${itemList(items, query.by)}`,
${itemList(items, query.by, here)}`,
);
return new Response(body, { headers: { "content-type": "text/html;charset=utf-8" } });
}
@@ -602,19 +602,39 @@ words, "phrases", #tags, -negation, and after:, before:, newer_than:7d, during:2
</form>`;
}
/** bookmarks as a list, each led by its `by` date */
function itemList(items: Item[], by: Query["by"]): string {
return `<ol>
/**
* bookmarks as a list, each led by its `by` date, marked up as
* microformats: an `h-feed` of `h-entry` bookmarks (`u-bookmark-of`,
* `p-name`, `p-summary`, `p-category` per tag, `dt-published` when
* bookmarked, `dt-updated` when last edited). This markup is the public
* page's API for other sites (tests/caches.test.ts pins it); tag links
* search this page (`here`) by tag.
*
* @see https://microformats.org/wiki/h-entry
* @see https://indieweb.org/bookmark
*/
function itemList(items: Item[], by: Query["by"], here: string): string {
const time = (it: Item, which: Query["by"], hidden: boolean) => {
const secs = dateOf(it, which);
if (secs === undefined) return hidden ? "" : "(no date)";
const when = new Date(secs * 1000).toISOString();
const cls = which === "created" ? "dt-published" : "dt-updated";
return hidden ? `<time class="${cls}" datetime="${when}" hidden></time>` : `<time class="${cls}" datetime="${when}">${when}</time>`;
};
return `<ol class="h-feed">
${items
.map((it) => {
const url = typeof it.url === "string" ? it.url : "";
const title = typeof it.title === "string" && it.title ? it.title : url;
const desc = typeof it.description === "string" && it.description ? `<br><small>${esc(it.description)}</small>` : "";
const desc = typeof it.description === "string" && it.description ? `<br><small class="p-summary">${esc(it.description)}</small>` : "";
const href = /^https?:\/\//i.test(url) ? url : "#";
const secs = dateOf(it, by);
const when = secs === undefined ? "" : new Date(secs * 1000).toISOString();
const date = when ? `<time datetime="${when}">${when}</time>` : "(no date)";
return `<li>${date} <a href="${esc(href)}">${esc(title)}</a>${desc}</li>`;
const tags = (typeof it.tags === "string" ? it.tags.split(",") : [])
.map((t) => t.trim())
.filter(Boolean)
.map((t) => ` <a class="p-category" href="${esc(`${here}?q=${encodeURIComponent(`#${t}`)}`)}">${esc(t)}</a>`)
.join("");
const other = by === "created" ? "updated" : "created";
return `<li class="h-entry">${time(it, by, false)} <a class="u-bookmark-of p-name" href="${esc(href)}">${esc(title)}</a>${tags}${desc}${time(it, other, true)}</li>`;
})
.join("\n")}
</ol>`;