diigo-week: a dashboard card drafts last week in one click

The card shows last week's issue with a link to it, or a button to
draft it. Drafting a week that already has an issue (draft or
published) is refused, with a link to that issue, rather than making
a duplicate.

Generated-by: Claude Opus 5.5
This commit is contained in:
dckc committed 2026-10-09 02:12:41 -05:00
1 parent 9c09e8c5a4
commit 68132ff4b3
2 files changed
+77 -31

No files matched your search

+1 -1
View File
@@ -12,7 +12,7 @@ pnpm install
(cd ../potluck && pnpm dev) # → http://localhost:4321/_emdash/admin
```
In potluck's admin: Plugins → **Draft from diigo** → **Draft issue**. The draft shows up under Issues.
On potluck's admin dashboard, the **This week's issue** card has a **Draft 2026-Wnn from diigo** button for last week; it links to the draft once made. For any other week: Plugins → **Draft from diigo**. A week that already has an issue isn't drafted again.
## How it reads the week
+76 -30
View File
@@ -61,6 +61,56 @@ async function tagIssue(ctx: RouteContext, id: string, tags: string[]): Promise<
return failed;
}
/** the issue already drafted or published for `week`, if any (issues
* are few: a scan of the newest is enough) */
async function issueFor(ctx: RouteContext, week: string): Promise<{ id: string; status: string; title: string } | undefined> {
const found = (await ctx.content!.list("issues", { limit: 100 })).items.find((it) => it.data.week === week);
return found && { id: found.id, status: found.status, title: String(found.data.title ?? week) };
}
/** a button-row link to an issue in the editor */
const openIssue = (id: string, label: string): Block => ({
type: "actions",
elements: [{ type: "link", label, target: { kind: "content", collection: "issues", id }, appearance: "primary" }],
});
/** draft `week` from `user`'s bookmarks, unless the week has an issue
* already: blocks that say what happened (with a link), and a toast */
async function draft(ctx: RouteContext, user: string, week: string): Promise<{ note: Block[]; toast: BlockResponse["toast"] }> {
try {
if (!/^[A-Za-z0-9_-]{1,64}$/.test(user)) throw new Error("diigo username: letters, digits, _ and - only");
const existing = await issueFor(ctx, week);
if (existing) {
return {
note: [
{ type: "banner", variant: "default", title: `${week} already has an issue`, description: `"${existing.title}" (${existing.status}). Delete it to draft the week again.` },
openIssue(existing.id, "Open it"),
],
toast: { message: `${week} already has an issue`, type: "info" },
};
}
const bookmarks = await readWeek(await getter(await diigoCache()), user, week);
const issue = draftIssue(week, bookmarks);
const content = ctx.content;
if (typeof content?.create !== "function") throw new Error("this plugin lacks content:write");
const created = await content.create("issues", issue);
const untagged = await tagIssue(ctx, created.id, issueTags(bookmarks));
if (untagged.length) ctx.log.warn("tags not assigned", { id: created.id, untagged });
ctx.log.info("drafted issue", { week, bookmarks: bookmarks.length, id: created.id });
return {
note: [
{ type: "banner", variant: "default", title: `Drafted "${issue.title}"`, description: `${bookmarks.length} bookmarks, as a draft at /issues/${created.slug ?? "(no slug)"}. Cut, comment, and publish.${untagged.length ? ` Tags not assigned: ${untagged.join(", ")}.` : ""}` },
openIssue(created.id, "Open the draft"),
],
toast: { message: `Drafted ${issue.title}`, type: "success" },
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
ctx.log.error("draft failed", { week, message });
return { note: [{ type: "banner", variant: "alert", title: "Couldn't draft the issue", description: message }], toast: { message, type: "error" } };
}
}
/** the admin page: a form for the diigo username and the week */
async function page(ctx: RouteContext, note: Block[] = [], toast?: BlockResponse["toast"]): Promise<BlockResponse> {
const user = (await ctx.kv.get<string>("user")) ?? DEFAULT_USER;
@@ -84,6 +134,20 @@ async function page(ctx: RouteContext, note: Block[] = [], toast?: BlockResponse
return { blocks, toast };
}
/** the dashboard widget: last week's issue if there is one, else a button to draft it */
async function widget(ctx: RouteContext, note: Block[] = [], toast?: BlockResponse["toast"]): Promise<BlockResponse> {
const week = lastWeek(new Date());
if (note.length) return { blocks: note, toast };
const existing = await issueFor(ctx, week);
const blocks: Block[] = existing
? [{ type: "section", text: `${week}: "${existing.title}" (${existing.status})` }, openIssue(existing.id, "Open it")]
: [
{ type: "section", text: `${week} has no issue yet.` },
{ type: "actions", elements: [{ type: "button", action_id: "draft-last-week", label: `Draft ${week} from diigo`, style: "primary" }] },
];
return { blocks, toast };
}
export function createPlugin(): PluginDefinition {
return definePlugin({
id: "diigo-week",
@@ -91,42 +155,24 @@ export function createPlugin(): PluginDefinition {
capabilities: ["content:write", "taxonomies:write"],
admin: {
pages: [{ path: "/draft", label: "Draft from diigo", icon: "bookmark" }],
widgets: [{ id: "last-week", title: "This week's issue", size: "half" }],
},
routes: {
admin: {
permission: "plugins:manage",
handler: async (ctx) => {
const input = ctx.input as { type?: string; action_id?: string; values?: Record<string, unknown> } | undefined;
if (input?.type !== "form_submit" || input.action_id !== "draft") return page(ctx);
const user = String(input.values?.user ?? "").trim();
const week = String(input.values?.week ?? "").trim();
try {
if (!/^[A-Za-z0-9_-]{1,64}$/.test(user)) throw new Error("diigo username: letters, digits, _ and - only");
await ctx.kv.set("user", user);
const bookmarks = await readWeek(await getter(await diigoCache()), user, week);
const issue = draftIssue(week, bookmarks);
const content = ctx.content;
if (typeof content?.create !== "function") throw new Error("this plugin lacks content:write");
const created = await content.create("issues", issue);
const untagged = await tagIssue(ctx, created.id, issueTags(bookmarks));
if (untagged.length) ctx.log.warn("tags not assigned", { id: created.id, untagged });
ctx.log.info("drafted issue", { week, bookmarks: bookmarks.length, id: created.id });
return page(
ctx,
[
{ type: "banner", variant: "default", title: `Drafted "${issue.title}"`, description: `${bookmarks.length} bookmarks, as a draft at /issues/${created.slug ?? "(no slug)"}. Cut, comment, and publish.${untagged.length ? ` Tags not assigned: ${untagged.join(", ")}.` : ""}` },
{
type: "actions",
elements: [{ type: "link", label: "Open the draft", target: { kind: "content", collection: "issues", id: created.id }, appearance: "primary" }],
},
],
{ message: `Drafted ${issue.title}`, type: "success" },
);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
ctx.log.error("draft failed", { week, message });
return page(ctx, [{ type: "banner", variant: "alert", title: "Couldn't draft the issue", description: message }], { message, type: "error" });
const input = ctx.input as { type?: string; page?: string; action_id?: string; values?: Record<string, unknown> } | undefined;
const user = (await ctx.kv.get<string>("user")) ?? DEFAULT_USER;
if (input?.type === "block_action" && input.action_id === "draft-last-week") {
const { note, toast } = await draft(ctx, user, lastWeek(new Date()));
return widget(ctx, note, toast);
}
if (input?.page?.startsWith("widget:")) return widget(ctx);
if (input?.type !== "form_submit" || input.action_id !== "draft") return page(ctx);
const formUser = String(input.values?.user ?? "").trim();
if (/^[A-Za-z0-9_-]{1,64}$/.test(formUser)) await ctx.kv.set("user", formUser);
const { note, toast } = await draft(ctx, formUser, String(input.values?.week ?? "").trim());
return page(ctx, note, toast);
},
},
},