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:
1 parent
c66aaea366
commit
be796d9d0d
4 files changed
+87
-13
No files matched your search
+32
-12
@@ -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>`;
|
||||
|
||||
Reference in new issue
Block a user