From 966b25b54c537c8b1c470b6321e1606851d1af00 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Mon, 26 Jan 2026 20:17:33 +0100 Subject: [PATCH] add a menu item --- src/main.ts | 105 +++++++++++++++++++++++++++++++++++++ src/ui/newDocumentModal.ts | 83 +++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 src/ui/newDocumentModal.ts diff --git a/src/main.ts b/src/main.ts index 32ae64d..c5e0bfc 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,6 +3,7 @@ import { ScriptoriumSettings, EventKind, EventMetadata, DEFAULT_SETTINGS } from import { ScriptoriumSettingTab } from "./ui/settingsTab"; import { MetadataModal } from "./ui/metadataModal"; import { StructurePreviewModal } from "./ui/structurePreviewModal"; +import { NewDocumentModal } from "./ui/newDocumentModal"; import { readMetadata, writeMetadata, createDefaultMetadata, validateMetadata, mergeWithHeaderTitle } from "./metadataManager"; import { buildEvents } from "./eventManager"; import { saveEvents, loadEvents, eventsFileExists } from "./eventStorage"; @@ -47,6 +48,17 @@ export default class ScriptoriumPlugin extends Plugin { callback: () => this.handleEditMetadata(), }); + this.addCommand({ + id: "new-nostr-document", + name: "New Nostr Document", + callback: () => this.handleNewDocument(), + }); + + // Add ribbon icon for creating new documents + this.addRibbonIcon("file-plus", "New Nostr Document", () => { + this.handleNewDocument(); + }); + // Status bar this.addStatusBarItem().setText("Scriptorium"); } @@ -316,4 +328,97 @@ export default class ScriptoriumPlugin extends Plugin { safeConsoleError("Error editing metadata:", error); } } + + private async handleNewDocument() { + new NewDocumentModal(this.app, async (kind: EventKind, title: string) => { + try { + // Sanitize filename from title + const sanitizedTitle = this.sanitizeFilename(title); + + // Determine file extension based on kind + let extension = "md"; + if (kind === 30040 || kind === 30041 || kind === 30818) { + extension = "adoc"; + } + + // Get current folder or root + const activeFile = this.app.workspace.getActiveFile(); + let folderPath = ""; + if (activeFile) { + const folder = this.app.vault.getAbstractFileByPath(activeFile.path); + if (folder && folder.parent) { + folderPath = folder.parent.path; + } + } + + // Create file path + const filename = `${sanitizedTitle}.${extension}`; + const filePath = folderPath ? `${folderPath}/${filename}` : filename; + + // Check if file already exists + const existingFile = this.app.vault.getAbstractFileByPath(filePath); + if (existingFile) { + new Notice(`File ${filename} already exists`); + return; + } + + // Create default content based on kind + let content = ""; + if (kind === 30040) { + // AsciiDoc document header for 30040 + content = `= ${title}\n\n`; + } else if (kind === 30023 || kind === 30817 || kind === 30818) { + // Add title as heading for other kinds that require title + if (kind === 30817 || kind === 30818) { + content = `# ${title}\n\n`; + } else { + content = `# ${title}\n\n`; + } + } + + // Create the file + const file = await this.app.vault.create(filePath, content); + + // Create metadata + const metadata = createDefaultMetadata(kind); + if (metadata.title === "" && title) { + (metadata as any).title = title; + } + await writeMetadata(file, metadata, this.app); + + // Open the new file + await this.app.workspace.openLinkText(filePath, "", true); + + new Notice(`Created ${filename} with metadata`); + } catch (error: any) { + const safeMessage = error?.message ? String(error.message).replace(/nsec1[a-z0-9]{58,}/gi, "[REDACTED]").replace(/[0-9a-f]{64}/gi, "[REDACTED]") : "Unknown error"; + new Notice(`Error creating document: ${safeMessage}`); + safeConsoleError("Error creating document:", error); + } + }).open(); + } + + /** + * Sanitize a string to be used as a filename + */ + private sanitizeFilename(title: string): string { + // Remove or replace invalid filename characters + let sanitized = title + .replace(/[<>:"/\\|?*]/g, "-") // Replace invalid chars with dash + .replace(/\s+/g, "-") // Replace spaces with dash + .replace(/-+/g, "-") // Replace multiple dashes with single + .replace(/^-+|-+$/g, ""); // Remove leading/trailing dashes + + // Limit length + if (sanitized.length > 100) { + sanitized = sanitized.substring(0, 100); + } + + // Ensure it's not empty + if (!sanitized) { + sanitized = "untitled"; + } + + return sanitized; + } } diff --git a/src/ui/newDocumentModal.ts b/src/ui/newDocumentModal.ts new file mode 100644 index 0000000..7cf31a4 --- /dev/null +++ b/src/ui/newDocumentModal.ts @@ -0,0 +1,83 @@ +import { App, Modal, Setting } from "obsidian"; +import { EventKind } from "../types"; +import { createDefaultMetadata, writeMetadata } from "../metadataManager"; + +/** + * Modal for creating a new Nostr document + */ +export class NewDocumentModal extends Modal { + private selectedKind: EventKind; + private title: string; + private onSubmit: (kind: EventKind, title: string) => void; + + constructor(app: App, onSubmit: (kind: EventKind, title: string) => void) { + super(app); + this.onSubmit = onSubmit; + this.selectedKind = 1; + this.title = ""; + } + + onOpen() { + const { contentEl } = this; + + contentEl.empty(); + contentEl.createEl("h2", { text: "Create New Nostr Document" }); + + // Event kind selection + new Setting(contentEl) + .setName("Event Kind") + .setDesc("Select the type of Nostr event to create") + .addDropdown((dropdown) => { + dropdown + .addOption("1", "1 - Normal Note") + .addOption("11", "11 - Discussion Thread OP") + .addOption("30023", "30023 - Long-form Article") + .addOption("30040", "30040 - Publication Index (AsciiDoc)") + .addOption("30041", "30041 - Publication Content (AsciiDoc)") + .addOption("30817", "30817 - Wiki Page (Markdown)") + .addOption("30818", "30818 - Wiki Page (AsciiDoc)") + .setValue(String(this.selectedKind)) + .onChange((value) => { + this.selectedKind = parseInt(value) as EventKind; + }); + }); + + // Title input + new Setting(contentEl) + .setName("Title / Filename") + .setDesc("Enter a title for your document (will be used as filename)") + .addText((text) => { + text.setPlaceholder("My Document Title") + .setValue(this.title) + .onChange((value) => { + this.title = value.trim(); + }); + text.inputEl.focus(); + }); + + // Buttons + const buttonContainer = contentEl.createDiv({ cls: "scriptorium-modal-buttons" }); + const createButton = buttonContainer.createEl("button", { + text: "Create", + cls: "mod-cta", + }); + createButton.addEventListener("click", () => { + if (!this.title) { + // Use default title if empty + this.title = "Untitled"; + } + this.onSubmit(this.selectedKind, this.title); + this.close(); + }); + + const cancelButton = buttonContainer.createEl("button", { text: "Cancel" }); + cancelButton.addEventListener("click", () => { + this.close(); + }); + } + + onClose() { + const { contentEl } = this; + contentEl.empty(); + } +}