diigo-week: draft a potluck issue from a week of diigo bookmarks
An EmDash plugin (native, content:write only) with one admin page: a diigo username and an ISO week. It reads diigo-cache's public page (?in=<week>&by=created) as microformats, follows rel=next, and creates a draft in issues shaped like seed/issues.json. Transport is a DIIGO_CACHE service binding rather than ctx.http.fetch: EmDash's SSRF guard refuses localhost, and the binding names one worker. Logic lives in src/week.ts with the fetcher passed in; tests read fixtures in diigo-cache's markup. The checklist's goal 2 now names potluck; DiigoBridge waits. Generated-by: Claude Opus 5.5
This commit is contained in:
1 parent
be796d9d0d
commit
94a059af25
17 files changed
+6705
-22
No files matched your search
@@ -0,0 +1,99 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { draftIssue, issueTags, lastWeek, readWeek, weekStart } from "../src/week.js";
|
||||
|
||||
/** an h-entry as diigo-cache's public page marks it up (by=created) */
|
||||
const entry = (n: number, created: string, extra = "") =>
|
||||
`<li class="h-entry"><time class="dt-published" datetime="${created}">${created}</time> <a class="u-bookmark-of p-name" href="https://ex.example/${n}">bookmark ${n}</a>${extra}<time class="dt-updated" datetime="${created}" hidden></time></li>`;
|
||||
|
||||
const page = (entries: string[], next?: string) => `<!doctype html><html><body>
|
||||
<p>${next ? `<a rel="next" href="${next}">next →</a>` : "<span>next →</span>"}</p>
|
||||
<ol class="h-feed">
|
||||
${entries.join("\n")}
|
||||
</ol></body></html>`;
|
||||
|
||||
describe("readWeek", () => {
|
||||
it("reads h-entry bookmarks across rel=next pages, oldest first", async () => {
|
||||
const asked: string[] = [];
|
||||
const pages: Record<string, string> = {
|
||||
"/public/dckc-madmode?in=2026-W40&by=created": page(
|
||||
[
|
||||
entry(
|
||||
3,
|
||||
"2026-10-03T12:00:00.000Z",
|
||||
` <a class="p-category" href="/public/dckc-madmode?q=%23ocap">ocap</a> <a class="p-category" href="/public/dckc-madmode?q=%23weekly">weekly</a><br><small class="p-summary">a week of fragments</small>`,
|
||||
),
|
||||
entry(2, "2026-10-01T12:00:00.000Z"),
|
||||
],
|
||||
"/public/dckc-madmode?in=2026-W40&by=created&page=2",
|
||||
),
|
||||
"/public/dckc-madmode?in=2026-W40&by=created&page=2": page([entry(1, "2026-09-28T12:00:00.000Z")]),
|
||||
};
|
||||
const get = async (path: string) => {
|
||||
asked.push(path);
|
||||
const html = pages[path];
|
||||
if (!html) throw new Error(`no page ${path}`);
|
||||
return html;
|
||||
};
|
||||
const bookmarks = await readWeek(get, "dckc-madmode", "2026-W40");
|
||||
expect(asked).toHaveLength(2);
|
||||
expect(bookmarks.map((b) => b.title)).toEqual(["bookmark 1", "bookmark 2", "bookmark 3"]);
|
||||
expect(bookmarks[2]).toEqual({
|
||||
url: "https://ex.example/3",
|
||||
title: "bookmark 3",
|
||||
description: "a week of fragments",
|
||||
tags: ["ocap", "weekly"],
|
||||
published: "2026-10-03T12:00:00.000Z",
|
||||
});
|
||||
});
|
||||
|
||||
it("doesn't follow a rel=next to another site", async () => {
|
||||
const asked: string[] = [];
|
||||
const get = async (path: string) => {
|
||||
asked.push(path);
|
||||
return page([entry(1, "2026-10-01T12:00:00.000Z")], "https://elsewhere.example/public/x");
|
||||
};
|
||||
await readWeek(get, "dckc-madmode", "2026-W40");
|
||||
expect(asked).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("weeks", () => {
|
||||
it("lastWeek is the full ISO week before now", () => {
|
||||
expect(lastWeek(new Date("2026-10-09T01:00:00Z"))).toBe("2026-W40");
|
||||
// Monday 2026-01-05 starts W02; W01 began Monday 2025-12-29
|
||||
expect(lastWeek(new Date("2026-01-05T00:00:00Z"))).toBe("2026-W01");
|
||||
});
|
||||
|
||||
it("weekStart is Monday 00:00Z, and refuses a week that doesn't exist", () => {
|
||||
expect(weekStart("2026-W40")?.toISOString()).toBe("2026-09-28T00:00:00.000Z");
|
||||
expect(weekStart("2025-W01")?.toISOString()).toBe("2024-12-30T00:00:00.000Z");
|
||||
expect(weekStart("2025-W53")).toBeUndefined();
|
||||
expect(weekStart("2026-40")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("draftIssue", () => {
|
||||
it("drafts title, week, dek, and a body: an intro, then a paragraph per bookmark with tag links", () => {
|
||||
const issue = draftIssue("2026-W40", [
|
||||
{ url: "https://ex.example/1", title: "one", description: "nice", tags: ["ocap"], published: "2026-09-28T12:00:00.000Z" },
|
||||
]);
|
||||
expect(issue.title).toBe("Week 2026-W40");
|
||||
expect(issue.week).toBe("2026-W40");
|
||||
expect(issue.dek).toBe("Sep 28–Oct 4: 1 bookmark. (draft)");
|
||||
const [intro, b] = issue.body as { markDefs: { href: string }[]; children: { text: string }[] }[];
|
||||
expect(intro.children[0].text).toMatch(/draft/);
|
||||
expect(b.markDefs.map((m) => m.href)).toEqual(["https://ex.example/1", "/tag/ocap"]);
|
||||
expect(b.children.map((c) => c.text).join("")).toBe("one: nice ocap");
|
||||
});
|
||||
|
||||
it("refuses a malformed week", () => {
|
||||
expect(() => draftIssue("last week", [])).toThrow(/ISO week/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("issueTags", () => {
|
||||
it("is every bookmark's tags, once each, in order of first use", () => {
|
||||
const b = (tags: string[]) => ({ url: "https://ex.example/", title: "", description: "", tags, published: "" });
|
||||
expect(issueTags([b(["governance", "economics"]), b([]), b(["governance", "scaling"])])).toEqual(["governance", "economics", "scaling"]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user