From 14e31018f7b50519da55ca986961db3426131ce8 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Sat, 28 Jun 2025 08:36:32 +0800 Subject: [PATCH] fix: support spell check for mini app (#7602) * feat(IpcChannel): add Webview_SetSpellCheckEnabled channel and implement spell check handling for webviews - Introduced a new IPC channel for enabling/disabling spell check in webviews. - Updated the registerIpc function to handle spell check settings for all webviews. - Enhanced WebviewContainer to set spell check state on DOM ready event. - Refactored context menu setup to accommodate webview context menus. * refactor(ContextMenu): update methods to use Electron.WebContents instead of BrowserWindow - Changed method signatures to accept Electron.WebContents for better context handling. - Updated internal calls to utilize the new WebContents reference for toggling dev tools and managing spell check functionality. * refactor(WebviewContainer): clean up import order and remove unused code - Adjusted the import order in WebviewContainer.tsx for better readability. - Removed redundant import of useSettings to streamline the component. --- packages/shared/IpcChannel.ts | 1 + src/main/ipc.ts | 15 +++++-- src/main/services/ContextMenu.ts | 16 +++---- src/main/services/WindowService.ts | 7 ++-- src/preload/index.ts | 4 +- .../components/MinApp/WebviewContainer.tsx | 11 +++++ .../src/pages/settings/GeneralSettings.tsx | 42 +++++++++---------- 7 files changed, 59 insertions(+), 37 deletions(-) diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts index 8da9a67429..8782d02f24 100644 --- a/packages/shared/IpcChannel.ts +++ b/packages/shared/IpcChannel.ts @@ -38,6 +38,7 @@ export enum IpcChannel { Notification_OnClick = 'notification:on-click', Webview_SetOpenLinkExternal = 'webview:set-open-link-external', + Webview_SetSpellCheckEnabled = 'webview:set-spell-check-enabled', // Open Open_Path = 'open:path', diff --git a/src/main/ipc.ts b/src/main/ipc.ts index b2003f8db8..32baecac35 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -8,7 +8,7 @@ import { handleZoomFactor } from '@main/utils/zoom' import { UpgradeChannel } from '@shared/config/constant' import { IpcChannel } from '@shared/IpcChannel' import { Shortcut, ThemeMode } from '@types' -import { BrowserWindow, dialog, ipcMain, session, shell } from 'electron' +import { BrowserWindow, dialog, ipcMain, session, shell, webContents } from 'electron' import log from 'electron-log' import { Notification } from 'src/renderer/src/types/notification' @@ -93,9 +93,10 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { // spell check ipcMain.handle(IpcChannel.App_SetEnableSpellCheck, (_, isEnable: boolean) => { - const windows = BrowserWindow.getAllWindows() - windows.forEach((window) => { - window.webContents.session.setSpellCheckerEnabled(isEnable) + // disable spell check for all webviews + const webviews = webContents.getAllWebContents() + webviews.forEach((webview) => { + webview.session.setSpellCheckerEnabled(isEnable) }) }) @@ -494,6 +495,12 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { setOpenLinkExternal(webviewId, isExternal) ) + ipcMain.handle(IpcChannel.Webview_SetSpellCheckEnabled, (_, webviewId: number, isEnable: boolean) => { + const webview = webContents.fromId(webviewId) + if (!webview) return + webview.session.setSpellCheckerEnabled(isEnable) + }) + // store sync storeSyncService.registerIpcHandler() diff --git a/src/main/services/ContextMenu.ts b/src/main/services/ContextMenu.ts index 34ec4b911a..411d6e075d 100644 --- a/src/main/services/ContextMenu.ts +++ b/src/main/services/ContextMenu.ts @@ -4,8 +4,8 @@ import { locales } from '../utils/locales' import { configManager } from './ConfigManager' class ContextMenu { - public contextMenu(w: Electron.BrowserWindow) { - w.webContents.on('context-menu', (_event, properties) => { + public contextMenu(w: Electron.WebContents) { + w.on('context-menu', (_event, properties) => { const template: MenuItemConstructorOptions[] = this.createEditMenuItems(properties) const filtered = template.filter((item) => item.visible !== false) if (filtered.length > 0) { @@ -26,7 +26,7 @@ class ContextMenu { }) } - private createInspectMenuItems(w: Electron.BrowserWindow): MenuItemConstructorOptions[] { + private createInspectMenuItems(w: Electron.WebContents): MenuItemConstructorOptions[] { const locale = locales[configManager.getLanguage()] const { common } = locale.translation const template: MenuItemConstructorOptions[] = [ @@ -34,7 +34,7 @@ class ContextMenu { id: 'inspect', label: common.inspect, click: () => { - w.webContents.toggleDevTools() + w.toggleDevTools() }, enabled: true } @@ -86,7 +86,7 @@ class ContextMenu { private createSpellCheckMenuItem( properties: Electron.ContextMenuParams, - mainWindow: Electron.BrowserWindow + w: Electron.WebContents ): MenuItemConstructorOptions { const hasText = properties.selectionText.length > 0 @@ -95,14 +95,14 @@ class ContextMenu { label: '&Learn Spelling', visible: Boolean(properties.isEditable && hasText && properties.misspelledWord), click: () => { - mainWindow.webContents.session.addWordToSpellCheckerDictionary(properties.misspelledWord) + w.session.addWordToSpellCheckerDictionary(properties.misspelledWord) } } } private createDictionarySuggestions( properties: Electron.ContextMenuParams, - mainWindow: Electron.BrowserWindow + w: Electron.WebContents ): MenuItemConstructorOptions[] { const hasText = properties.selectionText.length > 0 @@ -126,7 +126,7 @@ class ContextMenu { label: suggestion, visible: Boolean(properties.isEditable && hasText && properties.misspelledWord), click: (menuItem: Electron.MenuItem) => { - mainWindow.webContents.replaceMisspelling(menuItem.label) + w.replaceMisspelling(menuItem.label) } })) } diff --git a/src/main/services/WindowService.ts b/src/main/services/WindowService.ts index 78784120b0..ada014f0db 100644 --- a/src/main/services/WindowService.ts +++ b/src/main/services/WindowService.ts @@ -143,9 +143,10 @@ export class WindowService { } private setupContextMenu(mainWindow: BrowserWindow) { - contextMenu.contextMenu(mainWindow) - app.on('browser-window-created', (_, win) => { - contextMenu.contextMenu(win) + contextMenu.contextMenu(mainWindow.webContents) + // setup context menu for all webviews like miniapp + app.on('web-contents-created', (_, webContents) => { + contextMenu.contextMenu(webContents) }) // Dangerous API diff --git a/src/preload/index.ts b/src/preload/index.ts index ed2a2042e0..7867c66917 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -229,7 +229,9 @@ const api = { }, webview: { setOpenLinkExternal: (webviewId: number, isExternal: boolean) => - ipcRenderer.invoke(IpcChannel.Webview_SetOpenLinkExternal, webviewId, isExternal) + ipcRenderer.invoke(IpcChannel.Webview_SetOpenLinkExternal, webviewId, isExternal), + setSpellCheckEnabled: (webviewId: number, isEnable: boolean) => + ipcRenderer.invoke(IpcChannel.Webview_SetSpellCheckEnabled, webviewId, isEnable) }, storeSync: { subscribe: () => ipcRenderer.invoke(IpcChannel.StoreSync_Subscribe), diff --git a/src/renderer/src/components/MinApp/WebviewContainer.tsx b/src/renderer/src/components/MinApp/WebviewContainer.tsx index e5f08c350b..507de765af 100644 --- a/src/renderer/src/components/MinApp/WebviewContainer.tsx +++ b/src/renderer/src/components/MinApp/WebviewContainer.tsx @@ -1,3 +1,4 @@ +import { useSettings } from '@renderer/hooks/useSettings' import { WebviewTag } from 'electron' import { memo, useEffect, useRef } from 'react' @@ -21,6 +22,7 @@ const WebviewContainer = memo( onNavigateCallback: (appid: string, url: string) => void }) => { const webviewRef = useRef(null) + const { enableSpellCheck } = useSettings() const setRef = (appid: string) => { onSetRefCallback(appid, null) @@ -46,6 +48,14 @@ const WebviewContainer = memo( onNavigateCallback(appid, event.url) } + const handleDomReady = () => { + const webviewId = webviewRef.current?.getWebContentsId() + if (webviewId) { + window.api?.webview?.setSpellCheckEnabled?.(webviewId, enableSpellCheck) + } + } + + webviewRef.current.addEventListener('dom-ready', handleDomReady) webviewRef.current.addEventListener('did-finish-load', handleLoaded) webviewRef.current.addEventListener('did-navigate-in-page', handleNavigate) @@ -55,6 +65,7 @@ const WebviewContainer = memo( return () => { webviewRef.current?.removeEventListener('did-finish-load', handleLoaded) webviewRef.current?.removeEventListener('did-navigate-in-page', handleNavigate) + webviewRef.current?.removeEventListener('dom-ready', handleDomReady) } // because the appid and url are enough, no need to add onLoadedCallback // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/renderer/src/pages/settings/GeneralSettings.tsx b/src/renderer/src/pages/settings/GeneralSettings.tsx index 3c9ebd84d4..3f166a1cd4 100644 --- a/src/renderer/src/pages/settings/GeneralSettings.tsx +++ b/src/renderer/src/pages/settings/GeneralSettings.tsx @@ -172,27 +172,6 @@ const GeneralSettings: FC = () => { /> - - {t('settings.proxy.mode.title')} - - - {storeProxyMode === 'custom' && ( - <> - - - {t('settings.proxy.title')} - setProxyUrl(e.target.value)} - style={{ width: 180 }} - onBlur={() => onSetProxyUrl()} - type="url" - /> - - - )} - {t('settings.general.spell_check')} @@ -223,6 +202,27 @@ const GeneralSettings: FC = () => { )} + + + {t('settings.proxy.mode.title')} + + + {storeProxyMode === 'custom' && ( + <> + + + {t('settings.proxy.title')} + setProxyUrl(e.target.value)} + style={{ width: 180 }} + onBlur={() => onSetProxyUrl()} + type="url" + /> + + + )} {t('settings.notification.title')}