2025-04-21 19:47:59 +00:00
|
|
|
import { App, normalizePath, Notice, TFile, TFolder } from 'obsidian';
|
2024-08-18 13:36:28 +00:00
|
|
|
import { WorldService } from 'Scripts/WorldService';
|
|
|
|
|
import { v7 as uuidv7 } from 'uuid';
|
Phase B: frontmatter flip (span -> YAML frontmatter)
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>
2026-07-16 11:43:56 +00:00
|
|
|
import { writeElement } from '../vault/element-file';
|
2024-08-18 13:36:28 +00:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-09 07:47:30 +00:00
|
|
|
async execute(category: string, name: string, worldName?: string, openFile: boolean = true): Promise<void> {
|
2024-08-18 13:36:28 +00:00
|
|
|
const uuid = uuidv7();
|
Phase B: frontmatter flip (span -> YAML frontmatter)
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>
2026-07-16 11:43:56 +00:00
|
|
|
// 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.
|
2025-07-08 18:13:22 +00:00
|
|
|
const topWorld = worldName || await this.worldService.getWorldName();
|
2024-08-18 13:36:28 +00:00
|
|
|
try {
|
Phase B: frontmatter flip (span -> YAML frontmatter)
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>
2026-07-16 11:43:56 +00:00
|
|
|
// 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 });
|
2024-10-08 18:45:57 +00:00
|
|
|
new Notice(`New ${category.toLowerCase()} created: ${name}`);
|
Phase B: frontmatter flip (span -> YAML frontmatter)
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>
2026-07-16 11:43:56 +00:00
|
|
|
await this.worldService.updateCategoryFolderName(topWorld, category);
|
2025-07-09 07:47:30 +00:00
|
|
|
if (openFile) {
|
Phase B: frontmatter flip (span -> YAML frontmatter)
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>
2026-07-16 11:43:56 +00:00
|
|
|
await this.openNoteInNewPane(file);
|
2025-07-09 07:47:30 +00:00
|
|
|
}
|
2024-08-18 13:36:28 +00:00
|
|
|
} catch (error) {
|
Phase B: frontmatter flip (span -> YAML frontmatter)
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>
2026-07-16 11:43:56 +00:00
|
|
|
console.error(`Failed to create ${category} note`, error);
|
|
|
|
|
new Notice(`Failed to create ${category}: ${error instanceof Error ? error.message : 'unknown error'}`);
|
2024-08-18 13:36:28 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Phase B: frontmatter flip (span -> YAML frontmatter)
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>
2026-07-16 11:43:56 +00:00
|
|
|
|
2024-08-18 13:36:28 +00:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|