mirror of
https://github.com/onlyworlds/obsidian-plugin.git
synced 2026-07-22 11:00:31 +00:00
Major rebuild. Plugin now uses @onlyworlds/sdk for API calls via Obsidian's requestUrl, native PluginSettingTab for configuration, and a debounced auto-sync engine that pushes element edits to onlyworlds.com after idle. Changes - Drop handrolled API code; route all writes through @onlyworlds/sdk subclass - Native settings tab: API key, PIN, default world, sync mode, debounce - PIN handling: persist in settings or cache per session; no per-save prompts - Auto-sync toggle with configurable debounce (default 3s) - Status bar + ribbon icon reflect sync state (idle/dirty/syncing/synced/error) - Ribbon click opens menu (Sync / Settings / About) instead of firing sync - Settings API key wins over per-world World.md (legacy fallback preserved) - Add Map, Pin, Marker to element category enum (now 22) - ESLint config (eslint-plugin-obsidianmd) scoped to new code - Modernize build: TS 5.8, esbuild 0.25, stricter tsconfig - Drop dead code: GraphViewExtensions, dead command imports, handleApiError Compatibility - Existing v1 vaults work unchanged. Span-tag format preserved. - OnlyWorlds/Settings.md still readable; new settings tab takes precedence. - isDesktopOnly: true. Mobile pending verification.
86 lines
2.4 KiB
TypeScript
86 lines
2.4 KiB
TypeScript
import { OnlyWorldsClient, OnlyWorldsConfig } from "@onlyworlds/sdk";
|
|
import { requestUrl, RequestUrlParam } from "obsidian";
|
|
|
|
/**
|
|
* OnlyWorlds SDK client subclass that routes HTTP through Obsidian's requestUrl().
|
|
*
|
|
* The base SDK uses native fetch() which Obsidian plugin reviewers flag (CORS,
|
|
* mobile compatibility). This subclass overrides request() to delegate to requestUrl
|
|
* while preserving the SDK's resource accessors, _ids/_id rewriting, and error handling.
|
|
*/
|
|
export class ObsidianOnlyWorldsClient extends OnlyWorldsClient {
|
|
private obsidianBaseUrl: string;
|
|
private obsidianHeaders: Record<string, string>;
|
|
|
|
constructor(config: OnlyWorldsConfig) {
|
|
super(config);
|
|
this.obsidianBaseUrl = config.baseUrl || "https://www.onlyworlds.com/api/worldapi";
|
|
this.obsidianHeaders = {
|
|
"Content-Type": "application/json",
|
|
"API-Key": config.apiKey,
|
|
"API-Pin": config.apiPin,
|
|
};
|
|
}
|
|
|
|
async request<T>(
|
|
method: string,
|
|
path: string,
|
|
options?: {
|
|
params?: Record<string, unknown>;
|
|
body?: unknown;
|
|
}
|
|
): Promise<T> {
|
|
const url = new URL(`${this.obsidianBaseUrl}${path}`);
|
|
|
|
if (options?.params) {
|
|
for (const [key, value] of Object.entries(options.params)) {
|
|
if (value !== undefined && value !== null) {
|
|
url.searchParams.append(key, String(value));
|
|
}
|
|
}
|
|
}
|
|
|
|
const req: RequestUrlParam = {
|
|
url: url.toString(),
|
|
method,
|
|
headers: this.obsidianHeaders,
|
|
throw: false,
|
|
};
|
|
|
|
if (options?.body && ["POST", "PATCH", "PUT"].includes(method)) {
|
|
req.body = JSON.stringify(options.body);
|
|
}
|
|
|
|
const response = await requestUrl(req);
|
|
|
|
if (response.status >= 400) {
|
|
let errorMessage = `API Error ${response.status}`;
|
|
const text = response.text;
|
|
if (text) {
|
|
try {
|
|
const errJson = JSON.parse(text);
|
|
if (Array.isArray(errJson.detail)) {
|
|
const validationErrors = errJson.detail
|
|
.map((err: { loc?: unknown[]; msg?: string }) => {
|
|
const location = Array.isArray(err.loc) ? err.loc.join(".") : "unknown";
|
|
return `${location}: ${err.msg ?? ""}`;
|
|
})
|
|
.join("; ");
|
|
errorMessage += `: ${validationErrors}`;
|
|
} else {
|
|
errorMessage += `: ${errJson.detail ?? errJson.error ?? text}`;
|
|
}
|
|
} catch {
|
|
errorMessage += `: ${text}`;
|
|
}
|
|
}
|
|
throw new Error(errorMessage);
|
|
}
|
|
|
|
if (response.status === 204) {
|
|
return undefined as unknown as T;
|
|
}
|
|
|
|
return response.json as T;
|
|
}
|
|
}
|