You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
291 lines
9.0 KiB
291 lines
9.0 KiB
import { Plugin, TFile, Notice } from "obsidian"; |
|
import { ScriptoriumSettings, EventKind, DEFAULT_SETTINGS } from "./types"; |
|
import { ScriptoriumSettingTab } from "./ui/settingsTab"; |
|
import { NewDocumentModal } from "./ui/newDocumentModal"; |
|
import { writeMetadata, createDefaultMetadata } from "./metadataManager"; |
|
import { safeConsoleError } from "./utils/security"; |
|
import { showErrorNotice } from "./utils/errorHandling"; |
|
import { log, logError } from "./utils/console"; |
|
import { getFolderNameForKind } from "./utils/eventKind"; |
|
import { |
|
getCurrentFile, |
|
ensureNostrNotesFolder, |
|
handleCreateEvents, |
|
handlePreviewStructure, |
|
handlePublishEvents, |
|
handleEditMetadata, |
|
} from "./commands/commandHandlers"; |
|
|
|
export default class ScriptoriumPlugin extends Plugin { |
|
settings!: ScriptoriumSettings; |
|
|
|
async onload() { |
|
log("Plugin loading..."); |
|
|
|
try { |
|
await this.loadSettings(); |
|
await this.loadPrivateKey(); |
|
|
|
// Note: We don't register file extensions for .adoc or .asciidoc files |
|
// Users should install the obsidian-asciidoc plugin for .adoc file support |
|
log("Plugin loaded - file extensions not registered"); |
|
log("Install obsidian-asciidoc plugin for .adoc file editing support"); |
|
|
|
// Add settings tab |
|
this.addSettingTab(new ScriptoriumSettingTab(this.app, this)); |
|
|
|
// Register commands |
|
this.addCommand({ |
|
id: "create-nostr-events", |
|
name: "Create Nostr Events", |
|
callback: () => this.handleCreateEvents(), |
|
}); |
|
|
|
this.addCommand({ |
|
id: "preview-structure", |
|
name: "Preview Document Structure", |
|
callback: () => this.handlePreviewStructure(), |
|
}); |
|
|
|
this.addCommand({ |
|
id: "publish-events", |
|
name: "Publish Events to Relays", |
|
callback: () => this.handlePublishEvents(), |
|
}); |
|
|
|
this.addCommand({ |
|
id: "edit-metadata", |
|
name: "Edit Metadata", |
|
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"); |
|
|
|
log("Plugin loaded successfully"); |
|
} catch (error: any) { |
|
logError("Error loading plugin", error); |
|
safeConsoleError("Error loading plugin:", error); |
|
} |
|
} |
|
|
|
onunload() {} |
|
|
|
async loadSettings() { |
|
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); |
|
} |
|
|
|
async saveSettings() { |
|
await this.saveData(this.settings); |
|
} |
|
|
|
async loadPrivateKey(): Promise<boolean> { |
|
// Load private key from environment variable only |
|
try { |
|
// @ts-ignore - process.env may not be typed in Obsidian context |
|
if (typeof process !== "undefined" && process.env?.SCRIPTORIUM_OBSIDIAN_KEY) { |
|
const envKey = process.env.SCRIPTORIUM_OBSIDIAN_KEY.trim(); |
|
if (envKey) { |
|
this.settings.privateKey = envKey; |
|
await this.saveSettings(); |
|
return true; |
|
} |
|
} |
|
} catch (error) { |
|
// Environment variable access not available |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
private async handleCreateEvents() { |
|
const file = await getCurrentFile(this.app); |
|
if (!file) return; |
|
await handleCreateEvents(this.app, file, this.settings); |
|
} |
|
|
|
private async handlePreviewStructure() { |
|
const file = await getCurrentFile(this.app); |
|
if (!file) return; |
|
await handlePreviewStructure(this.app, file); |
|
} |
|
|
|
private async handlePublishEvents() { |
|
const file = await getCurrentFile(this.app); |
|
if (!file) return; |
|
await handlePublishEvents(this.app, file, this.settings); |
|
} |
|
|
|
private async handleEditMetadata() { |
|
const file = await getCurrentFile(this.app); |
|
if (!file) return; |
|
await handleEditMetadata(this.app, file, this.settings.defaultEventKind); |
|
} |
|
|
|
private async handleNewDocument() { |
|
new NewDocumentModal(this.app, async (kind: EventKind, title: string) => { |
|
try { |
|
log(`Creating new document: kind=${kind}, title=${title}`); |
|
|
|
// Ensure folder structure exists |
|
const folderPath = await ensureNostrNotesFolder(this.app, kind); |
|
|
|
// 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"; |
|
} |
|
|
|
// Create file path in the appropriate folder |
|
const filename = `${sanitizedTitle}.${extension}`; |
|
const filePath = `${folderPath}/${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`; |
|
} |
|
} else if (kind === 1 || kind === 11) { |
|
// For kind 1 and 11, add a simple placeholder |
|
content = `\n`; |
|
} |
|
|
|
// Create the file - ensure we have at least a newline for empty files |
|
if (!content) { |
|
content = "\n"; |
|
} |
|
|
|
let file: TFile; |
|
try { |
|
log(`Creating file: ${filePath}`); |
|
file = await this.app.vault.create(filePath, content); |
|
log(`File created successfully: ${file.path}`); |
|
|
|
// Verify file was actually created |
|
const verifyFile = this.app.vault.getAbstractFileByPath(filePath); |
|
if (!verifyFile || !(verifyFile instanceof TFile)) { |
|
const msg = `Error: File ${filename} was not created properly`; |
|
log(msg); |
|
new Notice(msg); |
|
logError("File creation verification failed", { filePath }); |
|
return; |
|
} |
|
} catch (error: any) { |
|
logError("Error creating file", error); |
|
showErrorNotice("Error creating file", error); |
|
return; |
|
} |
|
|
|
// Create metadata with title preset from the filename |
|
const metadata = createDefaultMetadata(kind); |
|
// Always set title if provided (even for kind 1 where it's optional) |
|
if (title && title.trim()) { |
|
(metadata as any).title = title.trim(); |
|
} |
|
|
|
try { |
|
// Write metadata with all placeholders (title will be included if set) |
|
await writeMetadata(file, metadata, this.app); |
|
} catch (error: any) { |
|
showErrorNotice("Error creating metadata", error); |
|
safeConsoleError("Error creating metadata:", error); |
|
// Continue anyway - file was created |
|
} |
|
|
|
// For .adoc files, ensure file is visible in explorer but don't auto-open |
|
if (file.extension === "adoc" || file.extension === "asciidoc") { |
|
log(`AsciiDoc file created: ${file.path}`); |
|
|
|
// File should be visible in Obsidian's file explorer automatically |
|
// since we used vault.create(). The file explorer will refresh automatically. |
|
// We don't auto-open to prevent crashes (obsidian-asciidoc plugin handles opening) |
|
|
|
new Notice(`Created ${filename} in ${folderPath}. Install obsidian-asciidoc plugin to edit in Obsidian.`); |
|
} else { |
|
// Open the new file in Obsidian workspace (use active leaf or create new) |
|
// Use a small delay to ensure file is fully created before opening |
|
log("Waiting before opening file..."); |
|
await new Promise(resolve => setTimeout(resolve, 200)); |
|
|
|
try { |
|
log(`Attempting to open file: ${file.path} (extension: ${file.extension})`); |
|
|
|
const leaf = this.app.workspace.getMostRecentLeaf(); |
|
if (leaf && leaf.view) { |
|
await leaf.openFile(file, { active: true }); |
|
log("File opened successfully in existing leaf"); |
|
} else { |
|
// Fallback: open in new leaf |
|
const newLeaf = this.app.workspace.getLeaf("tab"); |
|
await newLeaf.openFile(file, { active: true }); |
|
log("File opened successfully in new leaf"); |
|
} |
|
} catch (error: any) { |
|
logError("Error opening file", error); |
|
safeConsoleError("Error opening file:", error); |
|
// File was created, just couldn't open it - show a notice |
|
new Notice(`File created but couldn't open: ${file.name}`); |
|
} |
|
} |
|
|
|
new Notice(`Created ${filename} in ${folderPath}`); |
|
} catch (error: any) { |
|
showErrorNotice("Error creating document", error); |
|
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; |
|
} |
|
}
|
|
|