mirror of
https://github.com/onlyworlds/obsidian-plugin.git
synced 2026-07-22 11:00:31 +00:00
Wake the dormant vault/element-file.ts write path and make YAML frontmatter the note format for new writes, with read-tolerance for legacy span notes. Pure logic (Obsidian-free, unit-tested under `npm test`): - vault/element-transform.ts: frontmatter<->payload, span parse, span->frontmatter, link normalization (single string / multi list, never comma-joined), extension-key passthrough (atlas_/shadow_/x_), body<->description|story mapping, read-before-PATCH diff. - test/element-transform.test.ts + tsconfig.test.json: 16 node:test cases incl. a real span-format Character fixture. Wired to `npm test` (pretest compiles to test-dist/). Wiring: - element-file.ts: readElement preserves extension fields + maps body to story for Narrative (R3/R5); frontmatter-first with legacy span fallback; writeElement round-trips extension keys and respects count-suffixed folders. - DownloadWorldCommand + CreateElementCommand: emit frontmatter via writeElement (no more Handlebars element templates). - NoteLinker: reworked onto metadataCache + SDK FIELD_SCHEMA; writes link IDs into frontmatter (new FieldSelectionModal picks the field). NameChanger syncs frontmatter name. - SaveElementCommand: read-before-PATCH (GET, diff, PATCH only changed fields) (R7). - ExportWorldCommand + CopyWorldCommand: read frontmatter via readElement, span as fallback (mixed worlds upload/copy correctly). - MigrateWorldCommand: "Migrate world notes to frontmatter" — backup-first (abort on failure), idempotent, end report modal (converted/skipped/failed). Deferred (see docs report): PasteWorld still emits span; ValidateWorld is still a span linter (diagnostic only, no writes). docs/SMOKE-CHECKLIST.md is the manual release gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
71 lines
3.3 KiB
TypeScript
71 lines
3.3 KiB
TypeScript
import { App, normalizePath, Notice, TFile, TFolder } from 'obsidian';
|
|
import { WorldService } from 'Scripts/WorldService';
|
|
import { v7 as uuidv7 } from 'uuid';
|
|
import { writeElement } from '../vault/element-file';
|
|
|
|
export class CreateElementCommand {
|
|
app: App;
|
|
manifest: any;
|
|
worldService: WorldService;
|
|
|
|
constructor(app: App, manifest: any, worldService: WorldService) {
|
|
this.app = app;
|
|
this.manifest = manifest;
|
|
this.worldService = worldService;
|
|
}
|
|
|
|
async execute(category: string, name: string, worldName?: string, openFile: boolean = true): Promise<void> {
|
|
const uuid = uuidv7();
|
|
// Phase B: new elements are created directly as frontmatter notes via
|
|
// writeElement — no Handlebars template fetch. A fresh element carries
|
|
// only id + name; the user fills in fields (as Properties) and body.
|
|
const topWorld = worldName || await this.worldService.getWorldName();
|
|
try {
|
|
// Resolve the (possibly count-suffixed) category folder + a collision-free
|
|
// filename, so we write into the same folder the vault already uses.
|
|
const existingFolder = await this.worldService.findCategoryFolderByBaseName(topWorld, category);
|
|
const folderPath = existingFolder
|
|
? existingFolder.path
|
|
: normalizePath(`OnlyWorlds/Worlds/${topWorld}/Elements/${category}`);
|
|
const fileName = await this.worldService.generateUniqueFileName(folderPath, name, uuid);
|
|
const file = await writeElement(this.app, topWorld, category, uuid, { name }, { folderPath, fileName });
|
|
new Notice(`New ${category.toLowerCase()} created: ${name}`);
|
|
await this.worldService.updateCategoryFolderName(topWorld, category);
|
|
if (openFile) {
|
|
await this.openNoteInNewPane(file);
|
|
}
|
|
} catch (error) {
|
|
console.error(`Failed to create ${category} note`, error);
|
|
new Notice(`Failed to create ${category}: ${error instanceof Error ? error.message : 'unknown error'}`);
|
|
}
|
|
}
|
|
|
|
|
|
async openNoteInNewPane(file: TFile) {
|
|
const leaf = this.app.workspace.getLeaf(true);
|
|
await leaf.openFile(file);
|
|
}
|
|
|
|
async determineTopWorldFolder(): Promise<string> {
|
|
const worldsPath = normalizePath('OnlyWorlds/Worlds');
|
|
const worldsFolder = this.app.vault.getAbstractFileByPath(worldsPath);
|
|
if (worldsFolder instanceof TFolder) {
|
|
const subFolders = worldsFolder.children.filter(child => child instanceof TFolder);
|
|
return subFolders.length > 0 ? subFolders[0].name : 'DefaultWorld';
|
|
}
|
|
return 'DefaultWorld';
|
|
}
|
|
|
|
async createFolderIfNeeded(folderPath: string): Promise<void> {
|
|
const normalizedPath = normalizePath(folderPath);
|
|
let existingFolder = this.app.vault.getAbstractFileByPath(normalizedPath);
|
|
if (!existingFolder) {
|
|
try {
|
|
await this.app.vault.createFolder(normalizedPath);
|
|
// new Notice(`Created folder: ${normalizedPath}`);
|
|
} catch (error) {
|
|
console.error(`Error creating folder: ${normalizedPath}`, error);
|
|
}
|
|
}
|
|
}
|
|
}
|