onlyworlds_obsidian-plugin/Commands/RenameWorldCommand.ts
Titus 734b32d77b fix wrong-world writes + false-success rename toast (2.2.1)
The wrong-world write class: per-world commands fell back to the settings
key whenever a world's own World.md key was missing, silently writing to
whatever world that key named. The settings key survives a vault-content
clear (.obsidian/data.json), so a read-key download + stale settings key
routed rename/save to the settings world. Shared resolveWorldKey() now
uses the world's OWN key; settings is fallback ONLY when the world has
none, and the fallback warns.

Rename false-success: the toast fired on the local rename before the API
PATCH ran, so a read-key (or any failed) push still said 'success'. Now
the push runs first and the notice states plainly whether onlyworlds.com
was updated or only the vault.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 14:52:40 +02:00

130 lines
5.9 KiB
TypeScript

import { App, Notice, requestUrl, normalizePath, TFile, TFolder } from 'obsidian';
import { WorldRenameModal } from 'Modals/WorldRenameModal';
import { resolveWorldKey } from '../vault/world-key';
import type OnlyWorldsPlugin from '../main';
export class RenameWorldCommand {
app: App;
manifest: any;
plugin: OnlyWorldsPlugin | null;
constructor(app: App, manifest: any, plugin?: OnlyWorldsPlugin) {
this.app = app;
this.manifest = manifest;
this.plugin = plugin ?? null;
}
execute() {
const modal = new WorldRenameModal(this.app, async (oldWorldName: string, newWorldName: string) => {
try {
// Ensures each step completes before starting the next one
await this.renameWorldFile(oldWorldName, newWorldName);
await this.renameWorldFolder(oldWorldName, newWorldName);
await this.checkSettingsFile(oldWorldName, newWorldName);
// Push to the API BEFORE claiming success — the notice must
// reflect what actually happened on onlyworlds.com, not just the
// local rename (the false-success class, 2026-07-12).
await this.pushRenameToApi(newWorldName);
} catch (error) {
console.error('Error in renaming process:', error);
new Notice('Failed to complete the renaming process.');
}
});
modal.open();
}
// PATCH the world name via the SDK (WorldResource.update → PATCH /world/),
// using THIS world's own key. A local-only rename is stated plainly, never
// dressed up as a synced success.
private async pushRenameToApi(newWorldName: string): Promise<void> {
if (!this.plugin) {
new Notice('World renamed in your vault (not synced: no plugin context).');
return;
}
// World.md key wins; settings is fallback ONLY when the world has no key
// of its own. Never silently write to whatever world the settings key
// names (the wrong-world class).
const resolved = await resolveWorldKey(this.app, newWorldName, this.plugin.settings.apiKey);
if (!resolved.apiKey) {
new Notice('World renamed in your vault. Not synced: no API key for this world.');
return;
}
const client = await this.plugin.buildClient(resolved.apiKey);
if (!client) {
new Notice('World renamed in your vault. Not synced: PIN not provided.');
return;
}
try {
await client.worlds.update({ name: newWorldName });
new Notice(`World renamed on onlyworlds.com${resolved.ownWorld ? '' : ' (using your default key)'}.`);
} catch (error) {
console.error('Failed to push world rename to API:', error);
const msg = error instanceof Error ? error.message : 'Unknown error';
// Read-only key or any write failure: say so, do NOT claim success.
new Notice(`World renamed in your vault, but onlyworlds.com was not updated: ${msg}`, 12000);
}
}
async renameWorldFile(oldWorldName: string, newWorldName: string) {
const worldFilePath = normalizePath(`OnlyWorlds/Worlds/${oldWorldName}/World.md`);
const worldFile = this.app.vault.getAbstractFileByPath(worldFilePath);
if (worldFile instanceof TFile) {
const worldFileContent = await this.app.vault.read(worldFile);
const updatedContent = this.updateWorldNameInContent(worldFileContent, newWorldName);
await this.app.vault.modify(worldFile, updatedContent);
new Notice('World file name updated successfully.');
} else {
new Notice('World file does not exist.');
}
}
private updateWorldNameInContent(content: string, newName: string): string {
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
if (lines[i].startsWith('- **Name:**')) {
lines[i] = `- **Name:** ${newName}`;
break;
}
}
return lines.join('\n');
}
async renameWorldFolder(oldWorldName: string, newWorldName: string) {
const oldPath = normalizePath(`OnlyWorlds/Worlds/${oldWorldName}`);
const newPath = normalizePath(`OnlyWorlds/Worlds/${newWorldName}`);
const oldFolder = this.app.vault.getAbstractFileByPath(oldPath) as TFolder | null;
if (oldFolder instanceof TFolder) {
// Correctly using renameFolder through the FileManager
await this.app.fileManager.renameFile(oldFolder, newPath);
new Notice(`World folder renamed successfully from '${oldWorldName}' to '${newWorldName}'.`);
} else {
new Notice('World folder does not exist.');
}
}
async checkSettingsFile(oldWorldName: string, newWorldName: string) {
const settingsFilePath = normalizePath('OnlyWorlds/Settings.md');
const settingsFile = this.app.vault.getAbstractFileByPath(settingsFilePath);
if (settingsFile instanceof TFile) {
const content = await this.app.vault.read(settingsFile);
const updatedContent = this.updateWorldNameInSettings(content, oldWorldName, newWorldName);
await this.app.vault.modify(settingsFile, updatedContent);
new Notice('Settings file updated successfully.');
} else {
new Notice('Settings file does not exist.');
}
}
updateWorldNameInSettings(content: string, oldWorldName: string, newWorldName: string): string {
const regex = new RegExp(`^- \\*\\*Primary World Name:\\*\\*\\s+${oldWorldName.trim()}$`, 'm');
return content.replace(regex, `- **Primary World Name:** ${newWorldName}`);
}
}