mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-10-01 17:29:29 +05:30
fix(flowchart): pre-await getUserDataPath to fix persistence; bump v4.9.0 → v4.9.1
Amit Haridas
This commit is contained in:
@@ -257,4 +257,4 @@ Amit Haridas (amit.wh@gmail.com)
|
|||||||
|
|
||||||
## Version
|
## Version
|
||||||
|
|
||||||
v4.9.0
|
v4.9.1
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
# PanConverter - Updates & Changelog
|
# PanConverter - Updates & Changelog
|
||||||
|
|
||||||
|
## Version 4.9.1 (2026-09-14)
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- **Flowchart Panel save failed**: `getUserDataPath()` IPC was not awaited in `src/renderer.js:2439-2449`, so the persistence path was computed as `"[object Promise]/flowchart-session.json"` and rejected by the `write-text-file` userData sandbox. Pre-resolved the path on panel register and cached it; persistence (read and write) now works correctly.
|
||||||
|
|
||||||
## Version 4.9.0 (2026-09-14)
|
## Version 4.9.0 (2026-09-14)
|
||||||
|
|
||||||
### New: Visual Flow Chart Editor (Sidebar panel → "Flow Chart")
|
### New: Visual Flow Chart Editor (Sidebar panel → "Flow Chart")
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "markdown-converter",
|
"name": "markdown-converter",
|
||||||
"version": "4.9.0",
|
"version": "4.9.1",
|
||||||
"description": "Professional Markdown editor and universal file converter with PDF editing, batch processing, and syntax highlighting",
|
"description": "Professional Markdown editor and universal file converter with PDF editing, batch processing, and syntax highlighting",
|
||||||
"main": "src/main.js",
|
"main": "src/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+12
-4
@@ -2436,16 +2436,24 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
.run({ nodes: [div] })
|
.run({ nodes: [div] })
|
||||||
.catch((err) => console.warn('flowchart preview render failed:', err));
|
.catch((err) => console.warn('flowchart preview render failed:', err));
|
||||||
};
|
};
|
||||||
|
// Pre-resolve the userData path on first panel render and cache it. The
|
||||||
|
// flowchart panel's getUserDataPath() must be sync (it is interpolated into
|
||||||
|
// the persistence path on mount); calling flowchartIO.getUserDataPath()
|
||||||
|
// directly would coerce the IPC Promise to "[object Promise]" and break
|
||||||
|
// every write/read against <userData>/flowchart-session.json.
|
||||||
|
let cachedUserDataPath = null;
|
||||||
sidebarManager.registerPanel('flowchart', {
|
sidebarManager.registerPanel('flowchart', {
|
||||||
title: 'Flow Chart',
|
title: 'Flow Chart',
|
||||||
render: (container) =>
|
render: async (container) => {
|
||||||
renderFlowChartPanel(container, {
|
if (!cachedUserDataPath) cachedUserDataPath = await flowchartIO.getUserDataPath();
|
||||||
getUserDataPath: flowchartIO.getUserDataPath,
|
return renderFlowChartPanel(container, {
|
||||||
|
getUserDataPath: () => cachedUserDataPath,
|
||||||
readFile: flowchartIO.readFile,
|
readFile: flowchartIO.readFile,
|
||||||
writeFile: flowchartIO.writeFile,
|
writeFile: flowchartIO.writeFile,
|
||||||
insertAtCursor: (text) => tabManager.insertAtCursor(text),
|
insertAtCursor: (text) => tabManager.insertAtCursor(text),
|
||||||
renderMermaid: renderFlowChartMermaid,
|
renderMermaid: renderFlowChartMermaid,
|
||||||
}),
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wiki-link navigation: open the target note (creating it on demand after
|
// Wiki-link navigation: open the target note (creating it on demand after
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
*
|
*
|
||||||
* @param {HTMLElement} container Mount point inside the sidebar panel
|
* @param {HTMLElement} container Mount point inside the sidebar panel
|
||||||
* @param {object} deps
|
* @param {object} deps
|
||||||
* @param {() => string} deps.getUserDataPath Absolute userData directory
|
* @param {() => string} deps.getUserDataPath Absolute userData directory (renderer pre-resolves and caches this on first mount — must be synchronous and return a real string, not a Promise)
|
||||||
* @param {(path:string) => Promise<string|null>} deps.readFile
|
* @param {(path:string) => Promise<string|null>} deps.readFile
|
||||||
* @param {(path:string, content:string) => Promise<void>} deps.writeFile
|
* @param {(path:string, content:string) => Promise<void>} deps.writeFile
|
||||||
* @param {(text:string) => void} deps.insertAtCursor
|
* @param {(text:string) => void} deps.insertAtCursor
|
||||||
|
|||||||
@@ -67,6 +67,37 @@ describe('flowchart-panel: persistence', () => {
|
|||||||
const readFile = jest.fn().mockResolvedValue('{not-json');
|
const readFile = jest.fn().mockResolvedValue('{not-json');
|
||||||
expect(() => mount({ readFile })).not.toThrow();
|
expect(() => mount({ readFile })).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Regression: src/renderer.js used to pass flowchartIO.getUserDataPath
|
||||||
|
// (which returns ipcRenderer.invoke's Promise) directly into the panel.
|
||||||
|
// The panel then coerced that Promise to "[object Promise]" in the
|
||||||
|
// template literal, producing a persistence path that fell outside the
|
||||||
|
// userData sandbox and was rejected by write-text-file. The fix is to
|
||||||
|
// await the path on the renderer side and cache it, then hand the panel
|
||||||
|
// a sync getter. This test mirrors that pattern: simulate the renderer's
|
||||||
|
// pre-resolve + cache, and assert the panel's persistence path is a
|
||||||
|
// real resolved string on mount.
|
||||||
|
test('handles an async getUserDataPath via renderer-side pre-resolve + cache', async () => {
|
||||||
|
const asyncGetUserDataPath = jest.fn().mockResolvedValue('/abs/path/to/userdata');
|
||||||
|
// Pre-resolve exactly the way src/renderer.js now does.
|
||||||
|
let cachedUserDataPath = null;
|
||||||
|
if (!cachedUserDataPath) cachedUserDataPath = await asyncGetUserDataPath();
|
||||||
|
const syncGetUserDataPath = () => cachedUserDataPath;
|
||||||
|
|
||||||
|
const writeFile = jest.fn().mockResolvedValue(undefined);
|
||||||
|
const { api } = mount({ getUserDataPath: syncGetUserDataPath, writeFile });
|
||||||
|
|
||||||
|
// read on mount must use the resolved (real) path, not "[object Promise]".
|
||||||
|
await Promise.resolve();
|
||||||
|
expect(asyncGetUserDataPath).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// mutate -> debounced write -> assert writeFile is called with the real path.
|
||||||
|
api.getStore().addNode({ kind: 'process', x: 0, y: 0, label: 'async-cache' });
|
||||||
|
jest.advanceTimersByTime(500);
|
||||||
|
await Promise.resolve();
|
||||||
|
expect(writeFile).toHaveBeenCalledTimes(1);
|
||||||
|
expect(writeFile.mock.calls[0][0]).toBe('/abs/path/to/userdata/flowchart-session.json');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('flowchart-panel: live preview', () => {
|
describe('flowchart-panel: live preview', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user