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:
1 parent
9c09e8c5a4
commit
68132ff4b3
2 files changed
+77
-31
No files matched your search
@@ -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
@@ -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);
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user