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)
85 lines
2.6 KiB
JavaScript
85 lines
2.6 KiB
JavaScript
/**
|
|
* Manuscript project management (.project.json + chapter files).
|
|
*
|
|
* All methods are async: the fs backend injected by the plugin is the
|
|
* IPC-backed file API (readFile/writeFile/fileExists/listDir return
|
|
* Promises); `await` also works against the synchronous fakes used in unit
|
|
* tests.
|
|
*/
|
|
class ProjectManager {
|
|
/**
|
|
* @param {object} fs - { readFile(path), writeFile(path, content), fileExists(path), listDir(path) }
|
|
*/
|
|
constructor(fs) {
|
|
this.fs = fs;
|
|
}
|
|
|
|
async createProject(dir, opts) {
|
|
const project = {
|
|
title: opts.title,
|
|
type: opts.type || 'manuscript',
|
|
target: { words: opts.targetWords || 0, deadline: opts.deadline || null },
|
|
chapters: [],
|
|
metadata: opts.metadata || {},
|
|
};
|
|
await this.fs.writeFile(dir + '/.project.json', JSON.stringify(project, null, 2));
|
|
return project;
|
|
}
|
|
|
|
async loadProject(dir) {
|
|
const exists = await this.fs.fileExists(dir + '/.project.json');
|
|
if (!exists) return null;
|
|
const raw = await this.fs.readFile(dir + '/.project.json');
|
|
if (!raw) return null;
|
|
return JSON.parse(raw);
|
|
}
|
|
|
|
async _saveProject(dir, project) {
|
|
await this.fs.writeFile(dir + '/.project.json', JSON.stringify(project, null, 2));
|
|
}
|
|
|
|
async addChapter(dir, chapter) {
|
|
const project = await this.loadProject(dir);
|
|
if (!project) throw new Error('Project not found');
|
|
project.chapters.push(chapter);
|
|
await this._saveProject(dir, project);
|
|
}
|
|
|
|
async updateChapter(dir, index, updates) {
|
|
const project = await this.loadProject(dir);
|
|
if (!project) throw new Error('Project not found');
|
|
Object.assign(project.chapters[index], updates);
|
|
await this._saveProject(dir, project);
|
|
}
|
|
|
|
async compileManuscript(dir) {
|
|
const project = await this.loadProject(dir);
|
|
if (!project) throw new Error('Project not found');
|
|
const parts = [];
|
|
for (const ch of project.chapters) {
|
|
const content = await this.fs.readFile(dir + '/' + ch.file);
|
|
if (content) parts.push(content);
|
|
}
|
|
return parts.join('\n\n---\n\n');
|
|
}
|
|
|
|
async getStats(dir) {
|
|
const project = await this.loadProject(dir);
|
|
if (!project) throw new Error('Project not found');
|
|
let totalWords = 0;
|
|
for (const ch of project.chapters) {
|
|
const content = await this.fs.readFile(dir + '/' + ch.file);
|
|
if (content) totalWords += content.split(/\s+/).filter(Boolean).length;
|
|
}
|
|
const target = project.target.words || 0;
|
|
return {
|
|
totalWords,
|
|
chapterCount: project.chapters.length,
|
|
targetWords: target,
|
|
pctComplete: target > 0 ? Math.min(100, Math.round((totalWords / target) * 100)) : 0,
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports = { ProjectManager };
|