diff --git a/README.md b/README.md index 1fd5667..3d90614 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ +--- +slug: codium-nostr-extension +title: Codium nostr extension +summary: A Codium / VSCode extension to publish files to [Nostr]. +image: +tags: [nostr, writing, codium, vscode] +--- + # Nostr Publish A Codium / VSCode extension to publish files to [Nostr](https://github.com/nostr-protocol/nostr). diff --git a/package.json b/package.json index c035312..ab7bd3a 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "onCommand:nostr.fetchRelays", "onCommand:nostr.selectBackend", "onCommand:nostr.signInFromSidebar", + "onCommand:nostr.insertNoteHeader", "onView:nostr.sidebar" ], "contributes": { @@ -101,6 +102,12 @@ "command": "nostr.selectBackend", "title": "Nostr: Select Signer Backend", "category": "Nostr" + }, + { + "command": "nostr.insertNoteHeader", + "title": "Nostr: Insert Note Header", + "category": "Nostr", + "enablement": "editorLangId == markdown" } ], "menus": { @@ -124,6 +131,11 @@ "command": "nostr.publishLongForm", "when": "editorLangId == markdown", "group": "nostr@2" + }, + { + "command": "nostr.insertNoteHeader", + "when": "editorLangId == markdown", + "group": "nostr@0" } ] }, diff --git a/src/extension.ts b/src/extension.ts index 05eb0f8..fdabbc2 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -83,6 +83,11 @@ export async function activate(context: vscode.ExtensionContext): Promise cmdResetTimestamps(state), ), ); + context.subscriptions.push( + vscode.commands.registerCommand("nostr.insertNoteHeader", () => + cmdInsertNoteHeader(), + ), + ); context.subscriptions.push( vscode.commands.registerCommand("nostr.selectBackend", () => cmdSelectBackend(config, state, statusBar), @@ -914,6 +919,67 @@ async function cmdResetTimestamps(state: StateService): Promise { vscode.window.showInformationMessage("Published timestamps reset."); } +/** + * Command: insert a YAML front-matter note header template into the active + * markdown document. + * + * If the document already starts with a `---` front-matter block, the command + * does nothing and shows a warning. Otherwise, it inserts the template at the + * top of the document (or at the cursor if the document is empty). + * + * The slug defaults to the filename stem; the user can edit it after insertion. + */ +async function cmdInsertNoteHeader(): Promise { + const editor = vscode.window.activeTextEditor; + if (!editor) { + vscode.window.showErrorMessage("No active editor."); + return; + } + if (editor.document.languageId !== "markdown") { + vscode.window.showErrorMessage("Active file is not Markdown."); + return; + } + + const text = editor.document.getText(); + // Check if front-matter already exists. + if (/^---\r?\n/.test(text)) { + vscode.window.showWarningMessage("This document already has a front-matter block."); + return; + } + + // Derive a default slug from the filename stem. + const stem = activeFilenameStem() ?? "my-post"; + const slug = stem + .toLowerCase() + .replace(/\s+/g, "-") + .replace(/[^a-z0-9-]/g, "") + .replace(/-+/g, "-") + .replace(/^-|-$/g, "") || "my-post"; + + const header = `--- +slug: ${slug} +title: ${stem ?? "Untitled"} +summary: A one-sentence abstract for feeds. +image: https://example.com/cover.png +tags: [nostr, writing] +--- + +`; + + // Insert at position 0 (top of document). + const position = new vscode.Position(0, 0); + await editor.edit((editBuilder) => { + editBuilder.insert(position, header); + }); + + // Move cursor to the title line for easy editing. + const titleLine = 2; // 0-based: line 0 is `---`, line 1 is `slug:`, line 2 is `title:` + editor.selection = new vscode.Selection(titleLine, 7, titleLine, 7 + (stem?.length ?? 8)); + editor.revealRange(new vscode.Range(0, 0, titleLine, 0), vscode.TextEditorRevealType.AtTop); + + vscode.window.showInformationMessage("Inserted note header. Edit the metadata, then write your article below the --- line."); +} + /** * Command: sign in from the sidebar's method dropdown. * diff --git a/src/nsigner/client.ts b/src/nsigner/client.ts index 3bca135..d79e302 100644 --- a/src/nsigner/client.ts +++ b/src/nsigner/client.ts @@ -48,7 +48,7 @@ export class NsignerClient implements NsignerTransport { * Send a JSON-RPC request to n_signer and return the parsed response. * Throws on connection error, timeout, or frame parse failure. * - * @param method RPC verb (e.g. "get_public_key", "sign_event"). + * @param method RPC verb (e.g. "nostr_get_public_key", "nostr_sign_event"). * @param params Params array (e.g. `[{nostr_index: 0}]`). * @param timeoutMs Per-call timeout (default 30s — signing may need human * approval at the n_signer terminal, so this is deliberately long). diff --git a/src/signer/nsignerBackend.ts b/src/signer/nsignerBackend.ts index d2e7007..87c4e41 100644 --- a/src/signer/nsignerBackend.ts +++ b/src/signer/nsignerBackend.ts @@ -62,7 +62,7 @@ export class NsignerBackend implements NostrSigner { } async getPublicKey(): Promise { - const res = await this.transport.call("get_public_key", [{ nostr_index: this.nostrIndex }]); + const res = await this.transport.call("nostr_get_public_key", [{ nostr_index: this.nostrIndex }]); if (res.error) { throw new Error(`n_signer: ${res.error.message}`); } @@ -73,7 +73,7 @@ export class NsignerBackend implements NostrSigner { } async signEvent(event: EventTemplate): Promise { - const res = await this.transport.call("sign_event", [ + const res = await this.transport.call("nostr_sign_event", [ JSON.stringify(event), { nostr_index: this.nostrIndex }, ]); diff --git a/src/ui/sidebar.ts b/src/ui/sidebar.ts index 092df50..1e90a12 100644 --- a/src/ui/sidebar.ts +++ b/src/ui/sidebar.ts @@ -15,6 +15,7 @@ type WebviewMessage = | { type: "validateCurrentFile" } | { type: "publishLongForm" } | { type: "validateFrontMatter" } + | { type: "insertNoteHeader" } | { type: "fetchRelays" } | { type: "toggleRelay"; relay: string; enabled: boolean }; @@ -125,6 +126,9 @@ export class NostrSidebarProvider implements vscode.WebviewViewProvider { case "validateFrontMatter": await vscode.commands.executeCommand("nostr.validateFrontMatter"); break; + case "insertNoteHeader": + await vscode.commands.executeCommand("nostr.insertNoteHeader"); + break; case "fetchRelays": await vscode.commands.executeCommand("nostr.fetchRelays"); break; @@ -276,6 +280,7 @@ export class NostrSidebarProvider implements vscode.WebviewViewProvider {

Actions

+ @@ -362,6 +367,7 @@ export class NostrSidebarProvider implements vscode.WebviewViewProvider { $("validateBtn").addEventListener("click", () => vscode.postMessage({ type: "validateCurrentFile" })); $("publishLongFormBtn").addEventListener("click", () => vscode.postMessage({ type: "publishLongForm" })); $("validateFrontMatterBtn").addEventListener("click", () => vscode.postMessage({ type: "validateFrontMatter" })); + $("insertNoteHeaderBtn").addEventListener("click", () => vscode.postMessage({ type: "insertNoteHeader" })); $("fetchRelaysBtn").addEventListener("click", () => vscode.postMessage({ type: "fetchRelays" })); $("signOutBtn").addEventListener("click", () => vscode.postMessage({ type: "signOut" })); @@ -410,6 +416,7 @@ export class NostrSidebarProvider implements vscode.WebviewViewProvider { $("validateBtn").disabled = !s.canPublish; $("publishLongFormBtn").disabled = !s.canPublishLongForm; $("validateFrontMatterBtn").disabled = !s.canPublishLongForm; + $("insertNoteHeaderBtn").disabled = !s.canPublishLongForm; $("fetchRelaysBtn").disabled = !s.signedIn; // Identity