mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-23 23:10:17 +05:30
fix(renderer): migrate File.path reads to webUtils.getPathForFile for Electron 41
This commit is contained in:
+16
-1
@@ -12,7 +12,7 @@
|
|||||||
* @version 4.4.1
|
* @version 4.4.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { contextBridge, ipcRenderer } = require('electron');
|
const { contextBridge, ipcRenderer, webUtils } = require('electron');
|
||||||
|
|
||||||
// Define allowed IPC channels for security
|
// Define allowed IPC channels for security
|
||||||
const ALLOWED_SEND_CHANNELS = [
|
const ALLOWED_SEND_CHANNELS = [
|
||||||
@@ -431,6 +431,21 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
updateSettings: (settings) => ipcRenderer.send('update-page-settings', settings),
|
updateSettings: (settings) => ipcRenderer.send('update-page-settings', settings),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a File object chosen via `<input type="file">` to its absolute
|
||||||
|
* path. `File.path` was removed in Electron 32; `webUtils.getPathForFile`
|
||||||
|
* is its replacement. Falls back to `file.path` on older Electron where
|
||||||
|
* webUtils is unavailable.
|
||||||
|
* @param {File} file - File object from a file input's files list
|
||||||
|
* @returns {string | undefined} Absolute filesystem path when resolvable
|
||||||
|
*/
|
||||||
|
getFilePath: (file) => {
|
||||||
|
if (webUtils && typeof webUtils.getPathForFile === 'function') {
|
||||||
|
return webUtils.getPathForFile(file);
|
||||||
|
}
|
||||||
|
return file && file.path;
|
||||||
|
},
|
||||||
|
|
||||||
// PDF Operations
|
// PDF Operations
|
||||||
pdf: {
|
pdf: {
|
||||||
processOperation: (data) => ipcRenderer.send('process-pdf-operation', data),
|
processOperation: (data) => ipcRenderer.send('process-pdf-operation', data),
|
||||||
|
|||||||
+25
-13
@@ -3,7 +3,7 @@
|
|||||||
* @version 4.5.0
|
* @version 4.5.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { ipcRenderer } = require('electron');
|
const { ipcRenderer, webUtils } = require('electron');
|
||||||
const marked = require('marked');
|
const marked = require('marked');
|
||||||
const { markedHighlight } = require('marked-highlight');
|
const { markedHighlight } = require('marked-highlight');
|
||||||
const createDOMPurify = require('dompurify');
|
const createDOMPurify = require('dompurify');
|
||||||
@@ -16,6 +16,7 @@ const { showPdfBatchDialog } = require('./renderer/pdf-batch-dialog');
|
|||||||
const { showDocumentCompareDialog } = require('./renderer/document-compare-dialog');
|
const { showDocumentCompareDialog } = require('./renderer/document-compare-dialog');
|
||||||
const { initExportPresets, refreshExportPresets } = require('./renderer/export-presets');
|
const { initExportPresets, refreshExportPresets } = require('./renderer/export-presets');
|
||||||
const { csvToMarkdownTable } = require('./utils/csv-to-markdown-table');
|
const { csvToMarkdownTable } = require('./utils/csv-to-markdown-table');
|
||||||
|
const { getFilePath } = require('./utils/file-path');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle body classes that drive the monospace font + ligatures CSS tokens.
|
* Toggle body classes that drive the monospace font + ligatures CSS tokens.
|
||||||
@@ -69,6 +70,13 @@ if (typeof window !== 'undefined' && !window.electronAPI) {
|
|||||||
ipcRenderer.removeAllListeners(channel);
|
ipcRenderer.removeAllListeners(channel);
|
||||||
},
|
},
|
||||||
getAppVersion: () => ipcRenderer.invoke('get-app-version'),
|
getAppVersion: () => ipcRenderer.invoke('get-app-version'),
|
||||||
|
// File.path was removed in Electron 32 — resolve picker paths via webUtils.
|
||||||
|
getFilePath: (file) => {
|
||||||
|
if (webUtils && typeof webUtils.getPathForFile === 'function') {
|
||||||
|
return webUtils.getPathForFile(file);
|
||||||
|
}
|
||||||
|
return file && file.path;
|
||||||
|
},
|
||||||
file: {
|
file: {
|
||||||
read: (filePath) => ipcRenderer.invoke('read-file', filePath),
|
read: (filePath) => ipcRenderer.invoke('read-file', filePath),
|
||||||
write: (filePath, content) =>
|
write: (filePath, content) =>
|
||||||
@@ -2647,7 +2655,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
document.getElementById('template-file-input').addEventListener('change', (e) => {
|
document.getElementById('template-file-input').addEventListener('change', (e) => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
document.getElementById('custom-template-path').value = file.path;
|
document.getElementById('custom-template-path').value = getFilePath(file);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2725,7 +2733,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
input.onchange = (e) => {
|
input.onchange = (e) => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
document.getElementById('bibliography-file').value = file.path;
|
document.getElementById('bibliography-file').value = getFilePath(file);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
input.click();
|
input.click();
|
||||||
@@ -2739,7 +2747,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
input.onchange = (e) => {
|
input.onchange = (e) => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
document.getElementById('csl-file').value = file.path;
|
document.getElementById('csl-file').value = getFilePath(file);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
input.click();
|
input.click();
|
||||||
@@ -3590,8 +3598,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
input.onchange = (e) => {
|
input.onchange = (e) => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
converterFilePath = file.path;
|
const filePath = getFilePath(file);
|
||||||
document.getElementById('converter-file-path').value = file.path;
|
converterFilePath = filePath;
|
||||||
|
document.getElementById('converter-file-path').value = filePath;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
input.click();
|
input.click();
|
||||||
@@ -3942,8 +3951,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
input.onchange = (e) => {
|
input.onchange = (e) => {
|
||||||
const files = Array.from(e.target.files);
|
const files = Array.from(e.target.files);
|
||||||
files.forEach((file) => {
|
files.forEach((file) => {
|
||||||
if (!mergeFilePaths.includes(file.path)) {
|
const filePath = getFilePath(file);
|
||||||
mergeFilePaths.push(file.path);
|
if (!mergeFilePaths.includes(filePath)) {
|
||||||
|
mergeFilePaths.push(filePath);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
updateMergeFilesList();
|
updateMergeFilesList();
|
||||||
@@ -4127,8 +4137,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
input.onchange = (e) => {
|
input.onchange = (e) => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
document.getElementById(button.inputId).value = file.path;
|
const filePath = getFilePath(file);
|
||||||
onPDFFileSelected(button.inputId, file.path);
|
document.getElementById(button.inputId).value = filePath;
|
||||||
|
onPDFFileSelected(button.inputId, filePath);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
input.click();
|
input.click();
|
||||||
@@ -4137,10 +4148,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
input.onchange = (e) => {
|
input.onchange = (e) => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
document.getElementById(button.inputId).value = file.path;
|
const filePath = getFilePath(file);
|
||||||
onPDFFileSelected(button.inputId, file.path);
|
document.getElementById(button.inputId).value = filePath;
|
||||||
|
onPDFFileSelected(button.inputId, filePath);
|
||||||
if (button.inputId === 'fill-form-input-path') {
|
if (button.inputId === 'fill-form-input-path') {
|
||||||
loadFillFormFields(file.path);
|
loadFillFormFields(filePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
* markup appended to document.body on first use, driven by ModalManager, with a
|
* markup appended to document.body on first use, driven by ModalManager, with a
|
||||||
* status line reusing the `info-message` / `warning-message` classes. File
|
* status line reusing the `info-message` / `warning-message` classes. File
|
||||||
* selection reuses the app's existing convention: a plain `<input type="file">`
|
* selection reuses the app's existing convention: a plain `<input type="file">`
|
||||||
* whose `.path` is read directly (nodeIntegration is enabled for this renderer) —
|
* whose chosen File is resolved to a path via `window.electronAPI.getFilePath`
|
||||||
|
* (webUtils.getPathForFile — `File.path` was removed in Electron 32) —
|
||||||
* no new IPC channel for picking files. File *contents* are read through the
|
* no new IPC channel for picking files. File *contents* are read through the
|
||||||
* existing `read-file` invoke channel (the same one the renderer's electronAPI
|
* existing `read-file` invoke channel (the same one the renderer's electronAPI
|
||||||
* adapters use), not through a new privileged renderer-side file path.
|
* adapters use), not through a new privileged renderer-side file path.
|
||||||
@@ -28,6 +29,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const { ipcRenderer } = require('electron');
|
const { ipcRenderer } = require('electron');
|
||||||
|
const { getFilePath } = require('../utils/file-path');
|
||||||
const { computeLineDiff } = require('../utils/line-diff');
|
const { computeLineDiff } = require('../utils/line-diff');
|
||||||
|
|
||||||
const COMPARE_ACCEPT = '.md,.markdown,.txt,.text,.log,.json,.xml,.yml,.yaml,.csv';
|
const COMPARE_ACCEPT = '.md,.markdown,.txt,.text,.log,.json,.xml,.yml,.yaml,.csv';
|
||||||
@@ -140,7 +142,7 @@ function chooseFile(input) {
|
|||||||
fileInput.onchange = (e) => {
|
fileInput.onchange = (e) => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
input.value = file.path;
|
input.value = getFilePath(file);
|
||||||
clearResult();
|
clearResult();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,8 +12,9 @@
|
|||||||
* three media kinds together cover 13 distinct operations.
|
* three media kinds together cover 13 distinct operations.
|
||||||
*
|
*
|
||||||
* File selection reuses the app's existing convention: a plain `<input type="file">`
|
* File selection reuses the app's existing convention: a plain `<input type="file">`
|
||||||
* whose `.path` is read directly (nodeIntegration is enabled for this renderer), the
|
* whose chosen File is resolved to a path via `window.electronAPI.getFilePath`
|
||||||
* same approach already used throughout the PDF Editor and Universal Converter
|
* (webUtils.getPathForFile — `File.path` was removed in Electron 32), the same
|
||||||
|
* approach already used throughout the PDF Editor and Universal Converter
|
||||||
* dialogs. No new IPC channel is needed for single-file or save-file pickers. Output
|
* dialogs. No new IPC channel is needed for single-file or save-file pickers. Output
|
||||||
* *folder* selection (used by the video "Extract Frames" operation, and by batch
|
* *folder* selection (used by the video "Extract Frames" operation, and by batch
|
||||||
* mode below) reuses the existing generic `select-folder` / `folder-selected` IPC
|
* mode below) reuses the existing generic `select-folder` / `folder-selected` IPC
|
||||||
@@ -33,6 +34,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const { ipcRenderer } = require('electron');
|
const { ipcRenderer } = require('electron');
|
||||||
|
const { getFilePath } = require('../utils/file-path');
|
||||||
|
|
||||||
const IMAGE_ACCEPT = '.jpg,.jpeg,.png,.webp,.avif,.tiff,.tif,.gif';
|
const IMAGE_ACCEPT = '.jpg,.jpeg,.png,.webp,.avif,.tiff,.tif,.gif';
|
||||||
const AUDIO_ACCEPT = '.mp3,.wav,.ogg,.flac,.aac,.m4a,.wma';
|
const AUDIO_ACCEPT = '.mp3,.wav,.ogg,.flac,.aac,.m4a,.wma';
|
||||||
@@ -447,7 +449,7 @@ function renderFileField(field) {
|
|||||||
fileInput.accept = field.accept || '*';
|
fileInput.accept = field.accept || '*';
|
||||||
fileInput.onchange = (e) => {
|
fileInput.onchange = (e) => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (file) input.value = file.path;
|
if (file) input.value = getFilePath(file);
|
||||||
};
|
};
|
||||||
fileInput.click();
|
fileInput.click();
|
||||||
},
|
},
|
||||||
@@ -462,7 +464,7 @@ function renderSaveField(field) {
|
|||||||
fileInput.nwsaveas = true;
|
fileInput.nwsaveas = true;
|
||||||
fileInput.onchange = (e) => {
|
fileInput.onchange = (e) => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (file) input.value = file.path;
|
if (file) input.value = getFilePath(file);
|
||||||
};
|
};
|
||||||
fileInput.click();
|
fileInput.click();
|
||||||
},
|
},
|
||||||
@@ -588,8 +590,9 @@ function renderFilesField(field) {
|
|||||||
fileInput.multiple = true;
|
fileInput.multiple = true;
|
||||||
fileInput.onchange = (e) => {
|
fileInput.onchange = (e) => {
|
||||||
Array.from(e.target.files).forEach((file) => {
|
Array.from(e.target.files).forEach((file) => {
|
||||||
if (!mergeFilePaths.includes(file.path)) {
|
const filePath = getFilePath(file);
|
||||||
mergeFilePaths.push(file.path);
|
if (!mergeFilePaths.includes(filePath)) {
|
||||||
|
mergeFilePaths.push(filePath);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
updateMergeFilesList(listContainer);
|
updateMergeFilesList(listContainer);
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* File-input path resolution for Electron 41.
|
||||||
|
*
|
||||||
|
* `File.path` was removed in Electron 32, so a plain `<input type="file">`
|
||||||
|
* picker can no longer read the chosen file's absolute path directly. The
|
||||||
|
* replacement is `webUtils.getPathForFile(file)`, exposed to renderers as
|
||||||
|
* `window.electronAPI.getFilePath` (by src/preload.js in preload-loaded
|
||||||
|
* windows, and by the fallback shim in src/renderer.js for the main window,
|
||||||
|
* which does not load the preload script).
|
||||||
|
*
|
||||||
|
* @module utils/file-path
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a File object chosen via `<input type="file">` to its absolute path.
|
||||||
|
* Falls back to `file.path` when the electronAPI helper is absent
|
||||||
|
* (Electron < 32, or jsdom tests without the bridge).
|
||||||
|
*
|
||||||
|
* @param {File} file - File object from a file input's files list
|
||||||
|
* @returns {string | undefined} Absolute filesystem path when resolvable
|
||||||
|
*/
|
||||||
|
function getFilePath(file) {
|
||||||
|
const api = typeof window !== 'undefined' ? window.electronAPI : undefined;
|
||||||
|
if (api && typeof api.getFilePath === 'function') {
|
||||||
|
return api.getFilePath(file);
|
||||||
|
}
|
||||||
|
return file && file.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { getFilePath };
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Tests for the File-input path resolution helper (Electron 41 migration).
|
||||||
|
* File.path was removed in Electron 32; getFilePath() routes through
|
||||||
|
* window.electronAPI.getFilePath (webUtils.getPathForFile) when the bridge is
|
||||||
|
* available and falls back to file.path otherwise (older Electron, jsdom).
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { getFilePath } = require('../src/utils/file-path');
|
||||||
|
|
||||||
|
describe('getFilePath helper', () => {
|
||||||
|
const originalAPI = window.electronAPI;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
window.electronAPI = originalAPI;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('delegates to window.electronAPI.getFilePath when exposed', () => {
|
||||||
|
const file = { path: '/stale/file.path' };
|
||||||
|
window.electronAPI = { getFilePath: jest.fn(() => '/resolved/report.md') };
|
||||||
|
|
||||||
|
expect(getFilePath(file)).toBe('/resolved/report.md');
|
||||||
|
expect(window.electronAPI.getFilePath).toHaveBeenCalledWith(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to file.path when the helper is absent', () => {
|
||||||
|
const file = { path: '/legacy/electron/file.md' };
|
||||||
|
window.electronAPI = { send: jest.fn() }; // no getFilePath on the surface
|
||||||
|
|
||||||
|
expect(getFilePath(file)).toBe('/legacy/electron/file.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to file.path when electronAPI is undefined', () => {
|
||||||
|
const file = { path: '/no-bridge/file.md' };
|
||||||
|
window.electronAPI = undefined;
|
||||||
|
|
||||||
|
expect(getFilePath(file)).toBe('/no-bridge/file.md');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -182,4 +182,65 @@ describe('Preload Security', () => {
|
|||||||
expect(window.electronAPI.pdf.getPageCount).toBeDefined();
|
expect(window.electronAPI.pdf.getPageCount).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Loads the real src/preload.js against a mocked electron module, following
|
||||||
|
// the jest.mock('electron') pattern used by the renderer dialog tests.
|
||||||
|
describe('getFilePath (webUtils.getPathForFile bridge)', () => {
|
||||||
|
const setupApi = window.electronAPI;
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
window.electronAPI = setupApi;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('exposes getFilePath and delegates to webUtils.getPathForFile', () => {
|
||||||
|
jest.mock('electron', () => ({
|
||||||
|
contextBridge: {
|
||||||
|
exposeInMainWorld: (key, api) => {
|
||||||
|
window[key] = api;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ipcRenderer: {
|
||||||
|
send: jest.fn(),
|
||||||
|
invoke: jest.fn(),
|
||||||
|
on: jest.fn(),
|
||||||
|
once: jest.fn(),
|
||||||
|
removeListener: jest.fn(),
|
||||||
|
removeAllListeners: jest.fn(),
|
||||||
|
},
|
||||||
|
webUtils: {
|
||||||
|
getPathForFile: jest.fn((file) => file && `/resolved${file.name}`),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
const { webUtils } = require('electron');
|
||||||
|
require('../src/preload.js');
|
||||||
|
|
||||||
|
expect(typeof window.electronAPI.getFilePath).toBe('function');
|
||||||
|
const file = { name: '/report.md' };
|
||||||
|
expect(window.electronAPI.getFilePath(file)).toBe('/resolved/report.md');
|
||||||
|
expect(webUtils.getPathForFile).toHaveBeenCalledWith(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('falls back to file.path when webUtils is unavailable', () => {
|
||||||
|
jest.mock('electron', () => ({
|
||||||
|
contextBridge: {
|
||||||
|
exposeInMainWorld: (key, api) => {
|
||||||
|
window[key] = api;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ipcRenderer: {
|
||||||
|
send: jest.fn(),
|
||||||
|
invoke: jest.fn(),
|
||||||
|
on: jest.fn(),
|
||||||
|
once: jest.fn(),
|
||||||
|
removeListener: jest.fn(),
|
||||||
|
removeAllListeners: jest.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
require('../src/preload.js');
|
||||||
|
|
||||||
|
expect(typeof window.electronAPI.getFilePath).toBe('function');
|
||||||
|
const file = { path: '/legacy/file.md' };
|
||||||
|
expect(window.electronAPI.getFilePath(file)).toBe('/legacy/file.md');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ global.window.electronAPI = {
|
|||||||
once: jest.fn(),
|
once: jest.fn(),
|
||||||
invoke: jest.fn(() => Promise.resolve(null)),
|
invoke: jest.fn(() => Promise.resolve(null)),
|
||||||
removeAllListeners: jest.fn(),
|
removeAllListeners: jest.fn(),
|
||||||
|
// webUtils.getPathForFile bridge (File.path was removed in Electron 32)
|
||||||
|
getFilePath: jest.fn((file) => file && file.path),
|
||||||
file: {
|
file: {
|
||||||
save: jest.fn(),
|
save: jest.fn(),
|
||||||
saveCurrent: jest.fn(),
|
saveCurrent: jest.fn(),
|
||||||
|
|||||||
Reference in New Issue
Block a user