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
|
||||
*/
|
||||
|
||||
const { contextBridge, ipcRenderer } = require('electron');
|
||||
const { contextBridge, ipcRenderer, webUtils } = require('electron');
|
||||
|
||||
// Define allowed IPC channels for security
|
||||
const ALLOWED_SEND_CHANNELS = [
|
||||
@@ -431,6 +431,21 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
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: {
|
||||
processOperation: (data) => ipcRenderer.send('process-pdf-operation', data),
|
||||
|
||||
+25
-13
@@ -3,7 +3,7 @@
|
||||
* @version 4.5.0
|
||||
*/
|
||||
|
||||
const { ipcRenderer } = require('electron');
|
||||
const { ipcRenderer, webUtils } = require('electron');
|
||||
const marked = require('marked');
|
||||
const { markedHighlight } = require('marked-highlight');
|
||||
const createDOMPurify = require('dompurify');
|
||||
@@ -16,6 +16,7 @@ const { showPdfBatchDialog } = require('./renderer/pdf-batch-dialog');
|
||||
const { showDocumentCompareDialog } = require('./renderer/document-compare-dialog');
|
||||
const { initExportPresets, refreshExportPresets } = require('./renderer/export-presets');
|
||||
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.
|
||||
@@ -69,6 +70,13 @@ if (typeof window !== 'undefined' && !window.electronAPI) {
|
||||
ipcRenderer.removeAllListeners(channel);
|
||||
},
|
||||
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: {
|
||||
read: (filePath) => ipcRenderer.invoke('read-file', filePath),
|
||||
write: (filePath, content) =>
|
||||
@@ -2647,7 +2655,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
document.getElementById('template-file-input').addEventListener('change', (e) => {
|
||||
const file = e.target.files[0];
|
||||
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) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
document.getElementById('bibliography-file').value = file.path;
|
||||
document.getElementById('bibliography-file').value = getFilePath(file);
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
@@ -2739,7 +2747,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
input.onchange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
document.getElementById('csl-file').value = file.path;
|
||||
document.getElementById('csl-file').value = getFilePath(file);
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
@@ -3590,8 +3598,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
input.onchange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
converterFilePath = file.path;
|
||||
document.getElementById('converter-file-path').value = file.path;
|
||||
const filePath = getFilePath(file);
|
||||
converterFilePath = filePath;
|
||||
document.getElementById('converter-file-path').value = filePath;
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
@@ -3942,8 +3951,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
input.onchange = (e) => {
|
||||
const files = Array.from(e.target.files);
|
||||
files.forEach((file) => {
|
||||
if (!mergeFilePaths.includes(file.path)) {
|
||||
mergeFilePaths.push(file.path);
|
||||
const filePath = getFilePath(file);
|
||||
if (!mergeFilePaths.includes(filePath)) {
|
||||
mergeFilePaths.push(filePath);
|
||||
}
|
||||
});
|
||||
updateMergeFilesList();
|
||||
@@ -4127,8 +4137,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
input.onchange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
document.getElementById(button.inputId).value = file.path;
|
||||
onPDFFileSelected(button.inputId, file.path);
|
||||
const filePath = getFilePath(file);
|
||||
document.getElementById(button.inputId).value = filePath;
|
||||
onPDFFileSelected(button.inputId, filePath);
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
@@ -4137,10 +4148,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
input.onchange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
document.getElementById(button.inputId).value = file.path;
|
||||
onPDFFileSelected(button.inputId, file.path);
|
||||
const filePath = getFilePath(file);
|
||||
document.getElementById(button.inputId).value = filePath;
|
||||
onPDFFileSelected(button.inputId, filePath);
|
||||
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
|
||||
* status line reusing the `info-message` / `warning-message` classes. 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
|
||||
* existing `read-file` invoke channel (the same one the renderer's electronAPI
|
||||
* adapters use), not through a new privileged renderer-side file path.
|
||||
@@ -28,6 +29,7 @@
|
||||
*/
|
||||
|
||||
const { ipcRenderer } = require('electron');
|
||||
const { getFilePath } = require('../utils/file-path');
|
||||
const { computeLineDiff } = require('../utils/line-diff');
|
||||
|
||||
const COMPARE_ACCEPT = '.md,.markdown,.txt,.text,.log,.json,.xml,.yml,.yaml,.csv';
|
||||
@@ -140,7 +142,7 @@ function chooseFile(input) {
|
||||
fileInput.onchange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
input.value = file.path;
|
||||
input.value = getFilePath(file);
|
||||
clearResult();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -12,8 +12,9 @@
|
||||
* three media kinds together cover 13 distinct operations.
|
||||
*
|
||||
* 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
|
||||
* same approach already used throughout the PDF Editor and Universal Converter
|
||||
* whose chosen File is resolved to a path via `window.electronAPI.getFilePath`
|
||||
* (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
|
||||
* *folder* selection (used by the video "Extract Frames" operation, and by batch
|
||||
* mode below) reuses the existing generic `select-folder` / `folder-selected` IPC
|
||||
@@ -33,6 +34,7 @@
|
||||
*/
|
||||
|
||||
const { ipcRenderer } = require('electron');
|
||||
const { getFilePath } = require('../utils/file-path');
|
||||
|
||||
const IMAGE_ACCEPT = '.jpg,.jpeg,.png,.webp,.avif,.tiff,.tif,.gif';
|
||||
const AUDIO_ACCEPT = '.mp3,.wav,.ogg,.flac,.aac,.m4a,.wma';
|
||||
@@ -447,7 +449,7 @@ function renderFileField(field) {
|
||||
fileInput.accept = field.accept || '*';
|
||||
fileInput.onchange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) input.value = file.path;
|
||||
if (file) input.value = getFilePath(file);
|
||||
};
|
||||
fileInput.click();
|
||||
},
|
||||
@@ -462,7 +464,7 @@ function renderSaveField(field) {
|
||||
fileInput.nwsaveas = true;
|
||||
fileInput.onchange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) input.value = file.path;
|
||||
if (file) input.value = getFilePath(file);
|
||||
};
|
||||
fileInput.click();
|
||||
},
|
||||
@@ -588,8 +590,9 @@ function renderFilesField(field) {
|
||||
fileInput.multiple = true;
|
||||
fileInput.onchange = (e) => {
|
||||
Array.from(e.target.files).forEach((file) => {
|
||||
if (!mergeFilePaths.includes(file.path)) {
|
||||
mergeFilePaths.push(file.path);
|
||||
const filePath = getFilePath(file);
|
||||
if (!mergeFilePaths.includes(filePath)) {
|
||||
mergeFilePaths.push(filePath);
|
||||
}
|
||||
});
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
// 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(),
|
||||
invoke: jest.fn(() => Promise.resolve(null)),
|
||||
removeAllListeners: jest.fn(),
|
||||
// webUtils.getPathForFile bridge (File.path was removed in Electron 32)
|
||||
getFilePath: jest.fn((file) => file && file.path),
|
||||
file: {
|
||||
save: jest.fn(),
|
||||
saveCurrent: jest.fn(),
|
||||
|
||||
Reference in New Issue
Block a user