From abf0bc7adab2faa316b86a3500ec6763a134547a Mon Sep 17 00:00:00 2001 From: Shelley Vohr Date: Fri, 5 Jan 2024 17:17:39 +0100 Subject: [PATCH] feat: enable opening external URLs in editor --- src/ambient.d.ts | 1 + src/ipc-events.ts | 2 ++ src/main/windows.ts | 4 +++ src/preload/preload.ts | 3 ++ src/renderer/components/editor.tsx | 50 ++++++++++++++++++++++++------ src/renderer/components/output.tsx | 50 ++++++++++++++++++++++++------ 6 files changed, 90 insertions(+), 20 deletions(-) diff --git a/src/ambient.d.ts b/src/ambient.d.ts index 6de037e74f..87e396e077 100644 --- a/src/ambient.d.ts +++ b/src/ambient.d.ts @@ -156,6 +156,7 @@ declare global { pushOutputEntry(entry: OutputEntry): void; readThemeFile(name?: string): Promise; reloadWindows(): void; + openExternal(url: string): Promise; removeAllListeners(type: FiddleEvent): void; removeVersion(version: string): Promise; saveFilesToTemp(files: Files): Promise; diff --git a/src/ipc-events.ts b/src/ipc-events.ts index 1b90a15cfe..0b9cd79c12 100644 --- a/src/ipc-events.ts +++ b/src/ipc-events.ts @@ -31,6 +31,7 @@ export enum IpcEvents { TASK_DONE = 'TASK_DONE', OUTPUT_ENTRY = 'OUTPUT_ENTRY', RELOAD_WINDOW = 'RELOAD_WINDOW', + OPEN_EXTERNAL = 'OPEN_EXTERNAL', SET_NATIVE_THEME = 'SET_NATIVE_THEME', SHOW_WINDOW = 'SHOW_WINDOW', GET_TEMPLATE_VALUES = 'GET_TEMPLATE_VALUES', @@ -86,6 +87,7 @@ export const ipcMainEvents = [ IpcEvents.OUTPUT_ENTRY, IpcEvents.TASK_DONE, IpcEvents.RELOAD_WINDOW, + IpcEvents.OPEN_EXTERNAL, IpcEvents.SET_NATIVE_THEME, IpcEvents.SHOW_WINDOW, IpcEvents.GET_TEMPLATE_VALUES, diff --git a/src/main/windows.ts b/src/main/windows.ts index c530ae6f9a..3aec104d64 100644 --- a/src/main/windows.ts +++ b/src/main/windows.ts @@ -105,6 +105,10 @@ export function createMainWindow(): Electron.BrowserWindow { browserWindow?.reload(); }); + ipcMainManager.on(IpcEvents.OPEN_EXTERNAL, (_, url: string) => { + shell.openExternal(url); + }); + browserWindows.push(browserWindow); return browserWindow; diff --git a/src/preload/preload.ts b/src/preload/preload.ts index 3684239b7f..1a9814cf8d 100644 --- a/src/preload/preload.ts +++ b/src/preload/preload.ts @@ -197,6 +197,9 @@ export async function setupFiddleGlobal() { reloadWindows() { ipcRenderer.send(IpcEvents.RELOAD_WINDOW); }, + openExternal(url: string) { + ipcRenderer.send(IpcEvents.OPEN_EXTERNAL, url); + }, readThemeFile(name?: string) { return ipcRenderer.invoke(IpcEvents.READ_THEME_FILE, name); }, diff --git a/src/renderer/components/editor.tsx b/src/renderer/components/editor.tsx index 219fae8f91..c945a95670 100644 --- a/src/renderer/components/editor.tsx +++ b/src/renderer/components/editor.tsx @@ -82,16 +82,22 @@ export class Editor extends React.Component { const { fontFamily, fontSize } = appState; if (ref) { - this.editor = monaco.editor.create(ref, { - automaticLayout: true, - language: this.language, - theme: 'main', - fontFamily, - fontSize, - contextmenu: false, - model: null, - ...monacoOptions, - }); + this.editor = monaco.editor.create( + ref, + { + automaticLayout: true, + language: this.language, + theme: 'main', + fontFamily, + fontSize, + contextmenu: false, + model: null, + ...monacoOptions, + }, + { + openerService: this.openerService(), + }, + ); // mark this editor as focused whenever it is this.editor.onDidFocusEditorText(() => { @@ -113,6 +119,30 @@ export class Editor extends React.Component { } } + /** + * Implements external url handling for Monaco. + * + * @returns {MonacoType.editor.IOpenerService} + */ + private openerService() { + const { appState } = this.props; + + return { + open: (url: string) => { + appState + .showConfirmDialog({ + label: `Open ${url} in external browser?`, + ok: 'Open', + }) + .then((open) => { + if (open) { + window.ElectronFiddle.openExternal(url); + } + }); + }, + }; + } + public render() { return
; } diff --git a/src/renderer/components/output.tsx b/src/renderer/components/output.tsx index 497a40ee0c..a8787ddd7c 100644 --- a/src/renderer/components/output.tsx +++ b/src/renderer/components/output.tsx @@ -78,16 +78,22 @@ export const Output = observer( const ref = this.outputRef.current; if (ref) { this.setupCustomOutputEditorLanguage(monaco); - this.editor = monaco.editor.create(ref, { - language: this.language, - theme: 'main', - readOnly: true, - contextmenu: false, - automaticLayout: true, - model: this.model, - ...monacoOptions, - wordWrap: 'on', - }); + this.editor = monaco.editor.create( + ref, + { + language: this.language, + theme: 'main', + readOnly: true, + contextmenu: false, + automaticLayout: true, + model: this.model, + ...monacoOptions, + wordWrap: 'on', + }, + { + openerService: this.openerService(), + }, + ); } } @@ -193,6 +199,30 @@ export const Output = observer( return lines; } + /** + * Implements external url handling for Monaco. + * + * @returns {MonacoType.editor.IOpenerService} + */ + private openerService() { + const { appState } = this.props; + + return { + open: (url: string) => { + appState + .showConfirmDialog({ + label: `Open ${url} in external browser?`, + ok: 'Open', + }) + .then((open) => { + if (open) { + window.ElectronFiddle.openExternal(url); + } + }); + }, + }; + } + public render(): JSX.Element | null { const { isConsoleShowing } = this.props.appState;