mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-10-01 09:19:34 +05:30
Anthropic-compatible provider: - New 'anthropic-compatible' option for any base URL speaking the Anthropic messages schema (LiteLLM proxies, Bedrock gateways, local servers) - Sends x-api-key AND Bearer auth when a key is set (gateway-friendly, harmless for the official API); keyless proxies supported - Tolerates base URLs with or without a trailing /v1 segment - Settings modal, manifest, and provider docs updated Runtime bug fixes found by booting the app (run-to-verify pass): - PDF editor: File > Open PDF sends operation=null which matched no switch case and crashed on getElementById(undefined); now defaults to the merge section - backlinks-panel: wrong require depth (../../utils -> ../utils) threw at panel registration time - writing-studio stack was written against a synchronous settings backend but the real one is IPC-backed: GoalTracker/SnapshotManager/ProjectManager and all four panels now await; JSON.parse(Promise) crashes eliminated - manuscript panel used window.prompt (unavailable in Electron); replaced with an inline dialog - collaboration comment-store/save-load made async to match its IPC IO 617/617 tests green; 4 consecutive clean app boots (no uncaught errors)
84 lines
2.2 KiB
JavaScript
84 lines
2.2 KiB
JavaScript
/**
|
|
* Content snapshots with a rolling window, stored via the plugin settings
|
|
* backend. All methods are async because the real backend is the IPC-backed
|
|
* SettingsStore (get/set return Promises); `await` also works against the
|
|
* synchronous fakes used in unit tests.
|
|
*/
|
|
class SnapshotManager {
|
|
/**
|
|
* @param {object} store - { get(key), set(key, value) }
|
|
* @param {string} storeKey - settings key for snapshots
|
|
*/
|
|
constructor(store, storeKey = 'plugins.writing-studio.snapshots') {
|
|
this.store = store;
|
|
this.storeKey = storeKey;
|
|
}
|
|
|
|
async _getAll() {
|
|
const raw = await this.store.get(this.storeKey);
|
|
return raw ? JSON.parse(raw) : [];
|
|
}
|
|
|
|
async _saveAll(snaps) {
|
|
await this.store.set(this.storeKey, JSON.stringify(snaps));
|
|
}
|
|
|
|
async create(content, label = 'manual') {
|
|
const snaps = await this._getAll();
|
|
const snap = {
|
|
id: 'snap-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8),
|
|
timestamp: new Date().toISOString(),
|
|
content,
|
|
wordCount: content.split(/\s+/).filter(Boolean).length,
|
|
label,
|
|
};
|
|
snaps.unshift(snap);
|
|
await this._saveAll(snaps);
|
|
return snap;
|
|
}
|
|
|
|
async list() {
|
|
return this._getAll();
|
|
}
|
|
|
|
async getById(id) {
|
|
return (await this._getAll()).find((s) => s.id === id) || null;
|
|
}
|
|
|
|
async restore(id) {
|
|
const snap = await this.getById(id);
|
|
if (!snap) throw new Error('Snapshot not found');
|
|
return snap.content;
|
|
}
|
|
|
|
async delete(id) {
|
|
const snaps = (await this._getAll()).filter((s) => s.id !== id);
|
|
await this._saveAll(snaps);
|
|
}
|
|
|
|
async diff(id, currentContent) {
|
|
const snap = await this.getById(id);
|
|
if (!snap) throw new Error('Snapshot not found');
|
|
const oldLines = snap.content.split('\n');
|
|
const newLines = currentContent.split('\n');
|
|
const oldSet = new Set(oldLines);
|
|
const newSet = new Set(newLines);
|
|
let added = 0;
|
|
let removed = 0;
|
|
for (const line of newLines) {
|
|
if (!oldSet.has(line)) added++;
|
|
}
|
|
for (const line of oldLines) {
|
|
if (!newSet.has(line)) removed++;
|
|
}
|
|
return { added, removed };
|
|
}
|
|
|
|
async prune(keepCount) {
|
|
const snaps = await this._getAll();
|
|
await this._saveAll(snaps.slice(0, keepCount));
|
|
}
|
|
}
|
|
|
|
module.exports = { SnapshotManager };
|