fix(batch): resolve pandoc path handling and include-subfolders option

- Normalize pandoc command parsing with path.basename() to support bundled binary paths
- Use bundled pandoc binary in convertWithPandoc instead of relying on PATH
- Forward includeSubfolders checkbox state from renderer to main process
- Add pandoc availability check before batch conversion
- Re-enable Start button when batch conversion completes
- Clean up obsolete dist build artifact causing test snapshot warning
- Bump version to 4.4.4
This commit is contained in:
2026-06-30 12:56:33 +05:30
parent 02e307f758
commit d705cfc30b
86 changed files with 16876 additions and 13476 deletions
+69 -39
View File
@@ -9,7 +9,7 @@ class WritingStudioPlugin extends PluginAPI {
this.context = context;
this.sprintEngine = new SprintEngine({
onEvent: (name, data) => context.events.emit(name, data)
onEvent: (name, data) => context.events.emit(name, data),
});
this.goalTracker = new GoalTracker(context.settings);
this.snapshotManager = new SnapshotManager(context.settings);
@@ -17,14 +17,14 @@ class WritingStudioPlugin extends PluginAPI {
readFile: (p) => context.ipc.invoke('read-file', p),
writeFile: (p, c) => context.ipc.invoke('write-file', p, c),
fileExists: (p) => context.ipc.invoke('path-exists', p),
listDir: (p) => context.ipc.invoke('list-directory', p)
listDir: (p) => context.ipc.invoke('list-directory', p),
});
this._engines = {
sprint: this.sprintEngine,
goals: this.goalTracker,
snapshots: this.snapshotManager,
projects: this.projectManager
projects: this.projectManager,
};
this._registerCommands(context);
@@ -34,59 +34,89 @@ class WritingStudioPlugin extends PluginAPI {
_registerCommands(context) {
const { sprintEngine, snapshotManager, goalTracker } = this;
context.commands.register('start-sprint', 'Studio: Start Sprint', () => {
const duration = context.settings.get('sprintDuration') || 25;
const content = context.editor.getContent() || '';
const words = content.split(/\s+/).filter(Boolean).length;
sprintEngine.start(duration, words);
}, 'Ctrl+Alt+S');
context.commands.register(
'start-sprint',
'Studio: Start Sprint',
() => {
const duration = context.settings.get('sprintDuration') || 25;
const content = context.editor.getContent() || '';
const words = content.split(/\s+/).filter(Boolean).length;
sprintEngine.start(duration, words);
},
'Ctrl+Alt+S'
);
context.commands.register('stop-sprint', 'Studio: Stop Sprint', () => {
if (!sprintEngine.isActive()) return;
const content = context.editor.getContent() || '';
const words = content.split(/\s+/).filter(Boolean).length;
const result = sprintEngine.stop(words);
goalTracker.addWords(result.wordDelta);
context.events.emit('sprint:stopped', result);
}, 'Ctrl+Alt+Shift+S');
context.commands.register(
'stop-sprint',
'Studio: Stop Sprint',
() => {
if (!sprintEngine.isActive()) return;
const content = context.editor.getContent() || '';
const words = content.split(/\s+/).filter(Boolean).length;
const result = sprintEngine.stop(words);
goalTracker.addWords(result.wordDelta);
context.events.emit('sprint:stopped', result);
},
'Ctrl+Alt+Shift+S'
);
context.commands.register('take-snapshot', 'Studio: Take Snapshot', () => {
const content = context.editor.getContent() || '';
snapshotManager.create(content, 'manual');
context.events.emit('snapshot:created', {});
}, 'Ctrl+Alt+N');
context.commands.register(
'take-snapshot',
'Studio: Take Snapshot',
() => {
const content = context.editor.getContent() || '';
snapshotManager.create(content, 'manual');
context.events.emit('snapshot:created', {});
},
'Ctrl+Alt+N'
);
context.commands.register('restore-last-snapshot', 'Studio: Restore Last Snapshot', () => {
const snaps = snapshotManager.list();
if (snaps.length === 0) return;
const content = snapshotManager.restore(snaps[0].id);
context.editor.insertAtCursor(content);
}, 'Ctrl+Alt+Z');
context.commands.register(
'restore-last-snapshot',
'Studio: Restore Last Snapshot',
() => {
const snaps = snapshotManager.list();
if (snaps.length === 0) return;
const content = snapshotManager.restore(snaps[0].id);
context.editor.insertAtCursor(content);
},
'Ctrl+Alt+Z'
);
context.commands.register('new-project', 'Studio: New Project', () => {
context.events.emit('studio:new-project', {});
});
context.commands.register('compile-manuscript', 'Studio: Compile Manuscript', () => {
context.events.emit('studio:compile', {});
}, 'Ctrl+Alt+E');
context.commands.register(
'compile-manuscript',
'Studio: Compile Manuscript',
() => {
context.events.emit('studio:compile', {});
},
'Ctrl+Alt+E'
);
context.commands.register('proofread-document', 'Studio: Proofread Document', () => {
if (context.events.hasHandler('ai:analyze')) {
const content = context.editor.getContent() || '';
context.events.emit('ai:analyze', { text: content, type: 'grammar' });
}
}, 'Ctrl+Alt+G');
context.commands.register(
'proofread-document',
'Studio: Proofread Document',
() => {
if (context.events.hasHandler('ai:analyze')) {
const content = context.editor.getContent() || '';
context.events.emit('ai:analyze', { text: content, type: 'grammar' });
}
},
'Ctrl+Alt+G'
);
}
_registerStatusBar(context) {
context.statusBar.registerIndicator('word-goal', {
text: '0/1000',
tooltip: 'Daily word goal progress'
tooltip: 'Daily word goal progress',
});
context.statusBar.registerIndicator('sprint-timer', {
text: '',
tooltip: 'Writing sprint timer'
tooltip: 'Writing sprint timer',
});
}
@@ -15,17 +15,34 @@
{ "id": "start-sprint", "label": "Studio: Start Sprint", "shortcut": "Ctrl+Alt+S" },
{ "id": "stop-sprint", "label": "Studio: Stop Sprint", "shortcut": "Ctrl+Alt+Shift+S" },
{ "id": "take-snapshot", "label": "Studio: Take Snapshot", "shortcut": "Ctrl+Alt+N" },
{ "id": "restore-last-snapshot", "label": "Studio: Restore Last Snapshot", "shortcut": "Ctrl+Alt+Z" },
{
"id": "restore-last-snapshot",
"label": "Studio: Restore Last Snapshot",
"shortcut": "Ctrl+Alt+Z"
},
{ "id": "new-project", "label": "Studio: New Project", "shortcut": "" },
{ "id": "compile-manuscript", "label": "Studio: Compile Manuscript", "shortcut": "Ctrl+Alt+E" },
{ "id": "proofread-document", "label": "Studio: Proofread Document", "shortcut": "Ctrl+Alt+G" }
{
"id": "compile-manuscript",
"label": "Studio: Compile Manuscript",
"shortcut": "Ctrl+Alt+E"
},
{
"id": "proofread-document",
"label": "Studio: Proofread Document",
"shortcut": "Ctrl+Alt+G"
}
],
"statusBar": { "indicators": ["sprint-timer", "word-goal"] }
},
"settings": [
{ "key": "dailyGoal", "type": "number", "default": 1000, "label": "Daily word goal" },
{ "key": "sprintDuration", "type": "number", "default": 25, "label": "Sprint duration (min)" },
{ "key": "autoSnapshotInterval", "type": "number", "default": 0, "label": "Auto-snapshot interval (min, 0=off)" },
{
"key": "autoSnapshotInterval",
"type": "number",
"default": 0,
"label": "Auto-snapshot interval (min, 0=off)"
},
{ "key": "maxSnapshots", "type": "number", "default": 50, "label": "Max snapshots to keep" }
]
}
@@ -29,7 +29,8 @@ function renderGoalsPanel(container, { engines, settings }) {
const row = document.createElement('div');
row.className = 'ws-stat-row';
const label = document.createElement('span');
label.textContent = progress.written.toLocaleString() + ' / ' + dailyGoal.toLocaleString() + ' words';
label.textContent =
progress.written.toLocaleString() + ' / ' + dailyGoal.toLocaleString() + ' words';
const pct = document.createElement('span');
pct.className = 'ws-pct';
pct.textContent = progress.pct + '%';
@@ -81,7 +82,7 @@ function renderGoalsPanel(container, { engines, settings }) {
const chart = document.createElement('div');
chart.className = 'ws-chart';
const maxWords = Math.max(...last30.map(d => d.words), 1);
const maxWords = Math.max(...last30.map((d) => d.words), 1);
for (const day of last30) {
const barEl = document.createElement('div');
const height = Math.max(2, (day.words / maxWords) * 60);
@@ -69,7 +69,8 @@ function renderManuscriptPanel(container, { engines, editor, settings }) {
const row = document.createElement('div');
row.className = 'ws-stat-row';
const label = document.createElement('span');
label.textContent = stats.totalWords.toLocaleString() + ' / ' + stats.targetWords.toLocaleString() + ' words';
label.textContent =
stats.totalWords.toLocaleString() + ' / ' + stats.targetWords.toLocaleString() + ' words';
const pct = document.createElement('span');
pct.className = 'ws-pct';
pct.textContent = stats.pctComplete + '%';
@@ -42,7 +42,7 @@ function renderProofreadPanel(container, { events, editor }) {
if (result && result.issues) {
renderIssues(issuesList, result.issues);
}
}
},
});
});
}
@@ -80,7 +80,10 @@ function renderIssues(container, issues) {
const actions = document.createElement('div');
actions.className = 'ws-issue-actions';
for (const [action, label] of [['accept', 'Accept'], ['dismiss', 'Dismiss']]) {
for (const [, label] of [
['accept', 'Accept'],
['dismiss', 'Dismiss'],
]) {
const actionBtn = document.createElement('button');
actionBtn.className = 'ws-btn ws-btn-sm';
actionBtn.textContent = label;
@@ -45,7 +45,11 @@ function renderSnapshotsPanel(container, { engines, editor }) {
const actions = document.createElement('div');
actions.className = 'ws-snapshot-actions';
for (const [action, text, cls] of [['restore', 'Restore', ''], ['diff', 'Diff', ''], ['delete', 'Delete', 'ws-btn-danger']]) {
for (const [action, text, cls] of [
['restore', 'Restore', ''],
['diff', 'Diff', ''],
['delete', 'Delete', 'ws-btn-danger'],
]) {
const actionBtn = document.createElement('button');
actionBtn.className = 'ws-btn ws-btn-sm' + (cls ? ' ' + cls : '');
actionBtn.textContent = text;
@@ -12,7 +12,7 @@ class ProjectManager {
type: opts.type || 'manuscript',
target: { words: opts.targetWords || 0, deadline: opts.deadline || null },
chapters: [],
metadata: opts.metadata || {}
metadata: opts.metadata || {},
};
this.fs.writeFile(dir + '/.project.json', JSON.stringify(project, null, 2));
return project;
@@ -66,7 +66,7 @@ class ProjectManager {
totalWords,
chapterCount: project.chapters.length,
targetWords: target,
pctComplete: target > 0 ? Math.min(100, Math.round((totalWords / target) * 100)) : 0
pctComplete: target > 0 ? Math.min(100, Math.round((totalWords / target) * 100)) : 0,
};
}
}
@@ -24,7 +24,7 @@ class SnapshotManager {
timestamp: new Date().toISOString(),
content,
wordCount: content.split(/\s+/).filter(Boolean).length,
label
label,
};
snaps.unshift(snap);
this._saveAll(snaps);
@@ -36,7 +36,7 @@ class SnapshotManager {
}
getById(id) {
return this._getAll().find(s => s.id === id) || null;
return this._getAll().find((s) => s.id === id) || null;
}
restore(id) {
@@ -46,7 +46,7 @@ class SnapshotManager {
}
delete(id) {
const snaps = this._getAll().filter(s => s.id !== id);
const snaps = this._getAll().filter((s) => s.id !== id);
this._saveAll(snaps);
}
@@ -59,8 +59,12 @@ class SnapshotManager {
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++; }
for (const line of newLines) {
if (!oldSet.has(line)) added++;
}
for (const line of oldLines) {
if (!newSet.has(line)) removed++;
}
return { added, removed };
}
@@ -40,7 +40,9 @@ class SprintEngine {
}
}
isActive() { return this._active; }
isActive() {
return this._active;
}
getRemaining() {
if (!this._active) return 0;