Skip to content

Commit 40e9572

Browse files
committed
added discussion link
1 parent c40a917 commit 40e9572

File tree

3 files changed

+158
-0
lines changed

3 files changed

+158
-0
lines changed

main.cjs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const { detect } = require('detect-port');
77
const defaultPort = 3024;
88
const viteDevPort = defaultPort; // Using same port for consistency
99
const { shell } = require('electron');
10+
const { exec } = require('child_process');
1011
const config = 'easyedit.json';
1112
const configPath = path.join(app.getPath('userData'), config);
1213

@@ -210,6 +211,28 @@ async function handleFileOpen(filePath) {
210211
resolve();
211212
});
212213
} else {
214+
// Detect WSL (Windows Subsystem for Linux). In WSL, Linux openers like xdg-open
215+
// are often unavailable; prefer calling the Windows host to open the URL.
216+
let isWSL = false;
217+
try {
218+
if (fs.existsSync('/proc/version')) {
219+
const ver = fs.readFileSync('/proc/version', 'utf8').toLowerCase();
220+
if (ver.indexOf('microsoft') !== -1 || ver.indexOf('wsl') !== -1) isWSL = true;
221+
}
222+
} catch (wslErr) {
223+
// ignore
224+
}
225+
226+
if (isWSL) {
227+
try {
228+
// Prefer cmd.exe start which opens the default Windows browser from WSL.
229+
exec(`cmd.exe /C start "" "${url.replace(/"/g, '\\"')}"`, (e) => { if (e) console.error('Fallback cmd.exe start failed:', e); });
230+
return { success: true, fallback: true, wsl: true };
231+
} catch (we) {
232+
console.error('WSL fallback failed:', we);
233+
// continue to general Linux attempts below
234+
}
235+
}
213236
mainWindow.webContents.send('file-opened', content);
214237
resolve();
215238
}
@@ -250,6 +273,106 @@ function setupIPCHandlers() {
250273
});
251274
}
252275

276+
// Support opening external links from renderer safely when running inside Electron
277+
ipcMain.handle('open-external', async (_event, url) => {
278+
// Detect WSL up-front so we can prefer Windows host openers instead of shell.openExternal
279+
let isWSL = false;
280+
try {
281+
if (fs.existsSync('/proc/version')) {
282+
const ver = fs.readFileSync('/proc/version', 'utf8').toLowerCase();
283+
if (ver.indexOf('microsoft') !== -1 || ver.indexOf('wsl') !== -1) isWSL = true;
284+
}
285+
} catch (wslErr) {
286+
// ignore
287+
}
288+
289+
// If running inside WSL, prefer invoking the Windows cmd.exe to open the URL
290+
if (isWSL) {
291+
try {
292+
// Try plain cmd.exe first (should be available in WSL distros), else try absolute path
293+
const tryCmd = (cmd) => new Promise((resolve) => {
294+
exec(`${cmd} /C start "" "${url.replace(/"/g, '\\"')}"`, (e) => {
295+
if (e) {
296+
console.error('WSL cmd fallback failed for', cmd, e);
297+
resolve({ success: false, error: String(e) });
298+
} else {
299+
resolve({ success: true, used: cmd });
300+
}
301+
});
302+
});
303+
304+
let res = await tryCmd('cmd.exe');
305+
if (!res.success) {
306+
// common absolute path in WSL mounts
307+
const alt = '/mnt/c/Windows/System32/cmd.exe';
308+
if (fs.existsSync(alt)) {
309+
res = await tryCmd(alt);
310+
}
311+
}
312+
return res;
313+
} catch (we) {
314+
console.error('WSL open-external fallback failed:', we);
315+
return { success: false, error: String(we) };
316+
}
317+
}
318+
319+
// Try shell.openExternal first, then fall back to platform commands if necessary.
320+
try {
321+
await shell.openExternal(url);
322+
return { success: true };
323+
} catch (err) {
324+
console.warn('shell.openExternal failed, attempting platform fallback:', err);
325+
// Platform-specific fallbacks
326+
try {
327+
if (process.platform === 'darwin') {
328+
exec(`open "${url}"`, (e) => { if (e) console.error('Fallback open failed:', e); });
329+
return { success: true, fallback: true };
330+
} else if (process.platform === 'win32') {
331+
// start is a shell builtin on Windows; use cmd to run it
332+
exec(`cmd /c start "" "${url}"`, (e) => { if (e) console.error('Fallback start failed:', e); });
333+
return { success: true, fallback: true };
334+
} else {
335+
// Linux and other Unix-like: try common openers by absolute path first
336+
const candidates = [
337+
'/usr/bin/xdg-open',
338+
'/bin/xdg-open',
339+
'/usr/local/bin/xdg-open',
340+
'/usr/bin/gio',
341+
'/usr/bin/gnome-open',
342+
'/usr/bin/kde-open5'
343+
];
344+
let used = false;
345+
for (const c of candidates) {
346+
try {
347+
if (fs.existsSync(c)) {
348+
// gio needs different args: 'gio open <url>' works
349+
if (c.endsWith('/gio')) {
350+
exec(`${c} open "${url}"`, (e) => { if (e) console.error(`Fallback ${c} failed:`, e); });
351+
} else {
352+
exec(`${c} "${url}"`, (e) => { if (e) console.error(`Fallback ${c} failed:`, e); });
353+
}
354+
used = true;
355+
break;
356+
}
357+
} catch (fsErr) {
358+
// ignore and try next
359+
}
360+
}
361+
362+
if (!used) {
363+
// Last resort: try xdg-open on PATH (may still fail if missing)
364+
exec(`xdg-open "${url}"`, (e) => { if (e) console.error('Fallback xdg-open failed:', e); });
365+
}
366+
367+
return { success: true, fallback: true, usedPath: used };
368+
}
369+
} catch (err2) {
370+
console.error('All methods to open external URL failed:', err2);
371+
return { success: false, error: String(err2) };
372+
}
373+
}
374+
});
375+
253376
// function createMenuTemplate() {
254377
// const menuTemplate = [
255378
// {

preload.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
1111
ipcRenderer.removeAllListeners('update-preview-spacing');
1212
}
1313
},
14+
openExternal: (url) => ipcRenderer.invoke('open-external', url),
1415
getLineHeight: () => ipcRenderer.send('get-line-height'),
1516
setLineHeight: (callback) => {
1617
if (callback) {

src/App.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ const App = () => {
198198
handlePreviewSpacing?: (callback: any) => void;
199199
getLineHeight?: () => void;
200200
setLineHeight?: (callback: any) => void;
201+
openExternal?: (url: string) => Promise<any>;
201202
}
202203

203204
// Update the electronAPI declaration
@@ -848,6 +849,39 @@ const App = () => {
848849
<div className="hdr-title">Features</div>
849850
<div className="hdr-desc">View app features and highlights</div>
850851
</button>
852+
<div className="hdr-sep" />
853+
<button className="dropdown-item" onClick={async () => {
854+
const url = 'https://github.com/gcclinux/EasyEdit/discussions';
855+
let opened = false;
856+
try {
857+
if (electronAPI && electronAPI.openExternal) {
858+
const res = await electronAPI.openExternal(url);
859+
if (res && res.success) opened = true;
860+
else console.warn('openExternal returned failure:', res);
861+
} else {
862+
const w = window.open(url, '_blank', 'noopener');
863+
if (w) opened = true;
864+
}
865+
} catch (e) {
866+
console.warn('openExternal/window.open threw:', e);
867+
}
868+
869+
if (!opened) {
870+
// Try to copy to clipboard as a last-resort fallback and inform the user
871+
try {
872+
await navigator.clipboard.writeText(url);
873+
alert('Unable to open link automatically. The URL has been copied to your clipboard:\n' + url);
874+
} catch (e) {
875+
// If clipboard isn't available, just show the URL to the user
876+
alert('Unable to open or copy link automatically. Please open this URL manually:\n' + url);
877+
}
878+
}
879+
880+
setShowHelpDropdown(false);
881+
}}>
882+
<div className="hdr-title">Support</div>
883+
<div className="hdr-desc">Support & Discussion</div>
884+
</button>
851885
</div>, document.body
852886
)}
853887
</div>

0 commit comments

Comments
 (0)