feat: add APIs to enable/disable spell checker (#26276)
* feat: add APIs to enable/disable bulitin spell checker * feat: add togglespellchecker menu item role
This commit is contained in:
parent
f77b56e926
commit
bb3fb548d8
9 changed files with 97 additions and 5 deletions
|
@ -88,6 +88,7 @@ The `role` property can have following values:
|
||||||
* `resetZoom` - Reset the focused page's zoom level to the original size.
|
* `resetZoom` - Reset the focused page's zoom level to the original size.
|
||||||
* `zoomIn` - Zoom in the focused page by 10%.
|
* `zoomIn` - Zoom in the focused page by 10%.
|
||||||
* `zoomOut` - Zoom out the focused page by 10%.
|
* `zoomOut` - Zoom out the focused page by 10%.
|
||||||
|
* `toggleSpellChecker` - Enable/disable builtin spell checker.
|
||||||
* `fileMenu` - Whole default "File" menu (Close / Quit)
|
* `fileMenu` - Whole default "File" menu (Close / Quit)
|
||||||
* `editMenu` - Whole default "Edit" menu (Undo, Copy, etc.).
|
* `editMenu` - Whole default "Edit" menu (Undo, Copy, etc.).
|
||||||
* `viewMenu` - Whole default "View" menu (Reload, Toggle Developer Tools, etc.)
|
* `viewMenu` - Whole default "View" menu (Reload, Toggle Developer Tools, etc.)
|
||||||
|
|
|
@ -673,6 +673,16 @@ this session just before normal `preload` scripts run.
|
||||||
Returns `String[]` an array of paths to preload scripts that have been
|
Returns `String[]` an array of paths to preload scripts that have been
|
||||||
registered.
|
registered.
|
||||||
|
|
||||||
|
#### `ses.setSpellCheckerEnabled(enable)`
|
||||||
|
|
||||||
|
* `enable` Boolean
|
||||||
|
|
||||||
|
Sets whether to enable the builtin spell checker.
|
||||||
|
|
||||||
|
#### `ses.isSpellCheckerEnabled()`
|
||||||
|
|
||||||
|
Returns `Boolean` - Whether the builtin spell checker is enabled.
|
||||||
|
|
||||||
#### `ses.setSpellCheckerLanguages(languages)`
|
#### `ses.setSpellCheckerLanguages(languages)`
|
||||||
|
|
||||||
* `languages` String[] - An array of language codes to enable the spellchecker for.
|
* `languages` String[] - An array of language codes to enable the spellchecker for.
|
||||||
|
@ -803,6 +813,10 @@ The following properties are available on instances of `Session`:
|
||||||
A `String[]` array which consists of all the known available spell checker languages. Providing a language
|
A `String[]` array which consists of all the known available spell checker languages. Providing a language
|
||||||
code to the `setSpellCheckerLanguages` API that isn't in this array will result in an error.
|
code to the `setSpellCheckerLanguages` API that isn't in this array will result in an error.
|
||||||
|
|
||||||
|
#### `ses.spellCheckerEnabled`
|
||||||
|
|
||||||
|
A `Boolean` indicating whether builtin spell checker is enabled.
|
||||||
|
|
||||||
#### `ses.cookies` _Readonly_
|
#### `ses.cookies` _Readonly_
|
||||||
|
|
||||||
A [`Cookies`](cookies.md) object for this session.
|
A [`Cookies`](cookies.md) object for this session.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { app, BrowserWindow, WebContents, MenuItemConstructorOptions } from 'electron/main';
|
import { app, BrowserWindow, session, webContents, WebContents, MenuItemConstructorOptions } from 'electron/main';
|
||||||
|
|
||||||
const isMac = process.platform === 'darwin';
|
const isMac = process.platform === 'darwin';
|
||||||
const isWindows = process.platform === 'win32';
|
const isWindows = process.platform === 'win32';
|
||||||
|
@ -6,10 +6,12 @@ const isLinux = process.platform === 'linux';
|
||||||
|
|
||||||
type RoleId = 'about' | 'close' | 'copy' | 'cut' | 'delete' | 'forcereload' | 'front' | 'help' | 'hide' | 'hideothers' | 'minimize' |
|
type RoleId = 'about' | 'close' | 'copy' | 'cut' | 'delete' | 'forcereload' | 'front' | 'help' | 'hide' | 'hideothers' | 'minimize' |
|
||||||
'paste' | 'pasteandmatchstyle' | 'quit' | 'redo' | 'reload' | 'resetzoom' | 'selectall' | 'services' | 'recentdocuments' | 'clearrecentdocuments' | 'startspeaking' | 'stopspeaking' |
|
'paste' | 'pasteandmatchstyle' | 'quit' | 'redo' | 'reload' | 'resetzoom' | 'selectall' | 'services' | 'recentdocuments' | 'clearrecentdocuments' | 'startspeaking' | 'stopspeaking' |
|
||||||
'toggledevtools' | 'togglefullscreen' | 'undo' | 'unhide' | 'window' | 'zoom' | 'zoomin' | 'zoomout' | 'appmenu' | 'filemenu' | 'editmenu' | 'viewmenu' | 'windowmenu' | 'sharemenu'
|
'toggledevtools' | 'togglefullscreen' | 'undo' | 'unhide' | 'window' | 'zoom' | 'zoomin' | 'zoomout' | 'togglespellchecker' |
|
||||||
|
'appmenu' | 'filemenu' | 'editmenu' | 'viewmenu' | 'windowmenu' | 'sharemenu'
|
||||||
interface Role {
|
interface Role {
|
||||||
label: string;
|
label: string;
|
||||||
accelerator?: string;
|
accelerator?: string;
|
||||||
|
checked?: boolean;
|
||||||
windowMethod?: ((window: BrowserWindow) => void);
|
windowMethod?: ((window: BrowserWindow) => void);
|
||||||
webContentsMethod?: ((webContents: WebContents) => void);
|
webContentsMethod?: ((webContents: WebContents) => void);
|
||||||
appMethod?: () => void;
|
appMethod?: () => void;
|
||||||
|
@ -180,6 +182,19 @@ export const roleList: Record<RoleId, Role> = {
|
||||||
webContents.zoomLevel -= 0.5;
|
webContents.zoomLevel -= 0.5;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
togglespellchecker: {
|
||||||
|
label: 'Check Spelling While Typing',
|
||||||
|
get checked () {
|
||||||
|
const wc = webContents.getFocusedWebContents();
|
||||||
|
const ses = wc ? wc.session : session.defaultSession;
|
||||||
|
return ses.spellCheckerEnabled;
|
||||||
|
},
|
||||||
|
nonNativeMacOSRole: true,
|
||||||
|
webContentsMethod: (wc: WebContents) => {
|
||||||
|
const ses = wc ? wc.session : session.defaultSession;
|
||||||
|
ses.spellCheckerEnabled = !ses.spellCheckerEnabled;
|
||||||
|
}
|
||||||
|
},
|
||||||
// App submenu should be used for Mac only
|
// App submenu should be used for Mac only
|
||||||
appmenu: {
|
appmenu: {
|
||||||
get label () {
|
get label () {
|
||||||
|
@ -281,10 +296,23 @@ const canExecuteRole = (role: keyof typeof roleList) => {
|
||||||
return roleList[role].nonNativeMacOSRole;
|
return roleList[role].nonNativeMacOSRole;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function getDefaultType (role: RoleId) {
|
||||||
|
if (shouldOverrideCheckStatus(role)) return 'checkbox';
|
||||||
|
return 'normal';
|
||||||
|
}
|
||||||
|
|
||||||
export function getDefaultLabel (role: RoleId) {
|
export function getDefaultLabel (role: RoleId) {
|
||||||
return hasRole(role) ? roleList[role].label : '';
|
return hasRole(role) ? roleList[role].label : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCheckStatus (role: RoleId) {
|
||||||
|
if (hasRole(role)) return roleList[role].checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldOverrideCheckStatus (role: RoleId) {
|
||||||
|
return hasRole(role) && Object.prototype.hasOwnProperty.call(roleList[role], 'checked');
|
||||||
|
}
|
||||||
|
|
||||||
export function getDefaultAccelerator (role: RoleId) {
|
export function getDefaultAccelerator (role: RoleId) {
|
||||||
if (hasRole(role)) return roleList[role].accelerator;
|
if (hasRole(role)) return roleList[role].accelerator;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ const MenuItem = function (this: any, options: any) {
|
||||||
throw new Error('Invalid submenu');
|
throw new Error('Invalid submenu');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.overrideReadOnlyProperty('type', 'normal');
|
this.overrideReadOnlyProperty('type', roles.getDefaultType(this.role));
|
||||||
this.overrideReadOnlyProperty('role');
|
this.overrideReadOnlyProperty('role');
|
||||||
this.overrideReadOnlyProperty('accelerator');
|
this.overrideReadOnlyProperty('accelerator');
|
||||||
this.overrideReadOnlyProperty('icon');
|
this.overrideReadOnlyProperty('icon');
|
||||||
|
@ -46,7 +46,8 @@ const MenuItem = function (this: any, options: any) {
|
||||||
const click = options.click;
|
const click = options.click;
|
||||||
this.click = (event: Event, focusedWindow: BrowserWindow, focusedWebContents: WebContents) => {
|
this.click = (event: Event, focusedWindow: BrowserWindow, focusedWebContents: WebContents) => {
|
||||||
// Manually flip the checked flags when clicked.
|
// Manually flip the checked flags when clicked.
|
||||||
if (this.type === 'checkbox' || this.type === 'radio') {
|
if (!roles.shouldOverrideCheckStatus(this.role) &&
|
||||||
|
(this.type === 'checkbox' || this.type === 'radio')) {
|
||||||
this.checked = !this.checked;
|
this.checked = !this.checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +67,11 @@ MenuItem.prototype.getDefaultRoleAccelerator = function () {
|
||||||
return roles.getDefaultAccelerator(this.role);
|
return roles.getDefaultAccelerator(this.role);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MenuItem.prototype.getCheckStatus = function () {
|
||||||
|
if (!roles.shouldOverrideCheckStatus(this.role)) return this.checked;
|
||||||
|
return roles.getCheckStatus(this.role);
|
||||||
|
};
|
||||||
|
|
||||||
MenuItem.prototype.overrideProperty = function (name: string, defaultValue: any = null) {
|
MenuItem.prototype.overrideProperty = function (name: string, defaultValue: any = null) {
|
||||||
if (this[name] == null) {
|
if (this[name] == null) {
|
||||||
this[name] = defaultValue;
|
this[name] = defaultValue;
|
||||||
|
|
|
@ -17,7 +17,9 @@ Menu.prototype._init = function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
Menu.prototype._isCommandIdChecked = function (id) {
|
Menu.prototype._isCommandIdChecked = function (id) {
|
||||||
return this.commandsMap[id] ? this.commandsMap[id].checked : false;
|
const item = this.commandsMap[id];
|
||||||
|
if (!item) return false;
|
||||||
|
return item.getCheckStatus();
|
||||||
};
|
};
|
||||||
|
|
||||||
Menu.prototype._isCommandIdEnabled = function (id) {
|
Menu.prototype._isCommandIdEnabled = function (id) {
|
||||||
|
|
|
@ -1077,6 +1077,17 @@ bool Session::RemoveWordFromSpellCheckerDictionary(const std::string& word) {
|
||||||
#endif
|
#endif
|
||||||
return service->GetCustomDictionary()->RemoveWord(word);
|
return service->GetCustomDictionary()->RemoveWord(word);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Session::SetSpellCheckerEnabled(bool b) {
|
||||||
|
browser_context_->prefs()->SetBoolean(spellcheck::prefs::kSpellCheckEnable,
|
||||||
|
b);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Session::IsSpellCheckerEnabled() const {
|
||||||
|
return browser_context_->prefs()->GetBoolean(
|
||||||
|
spellcheck::prefs::kSpellCheckEnable);
|
||||||
|
}
|
||||||
|
|
||||||
#endif // BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
|
#endif // BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
|
||||||
|
|
||||||
// static
|
// static
|
||||||
|
@ -1179,6 +1190,10 @@ gin::ObjectTemplateBuilder Session::GetObjectTemplateBuilder(
|
||||||
&Session::AddWordToSpellCheckerDictionary)
|
&Session::AddWordToSpellCheckerDictionary)
|
||||||
.SetMethod("removeWordFromSpellCheckerDictionary",
|
.SetMethod("removeWordFromSpellCheckerDictionary",
|
||||||
&Session::RemoveWordFromSpellCheckerDictionary)
|
&Session::RemoveWordFromSpellCheckerDictionary)
|
||||||
|
.SetMethod("setSpellCheckerEnabled", &Session::SetSpellCheckerEnabled)
|
||||||
|
.SetMethod("isSpellCheckerEnabled", &Session::IsSpellCheckerEnabled)
|
||||||
|
.SetProperty("spellCheckerEnabled", &Session::IsSpellCheckerEnabled,
|
||||||
|
&Session::SetSpellCheckerEnabled)
|
||||||
#endif
|
#endif
|
||||||
.SetMethod("preconnect", &Session::Preconnect)
|
.SetMethod("preconnect", &Session::Preconnect)
|
||||||
.SetMethod("closeAllConnections", &Session::CloseAllConnections)
|
.SetMethod("closeAllConnections", &Session::CloseAllConnections)
|
||||||
|
|
|
@ -131,6 +131,8 @@ class Session : public gin::Wrappable<Session>,
|
||||||
v8::Local<v8::Promise> ListWordsInSpellCheckerDictionary();
|
v8::Local<v8::Promise> ListWordsInSpellCheckerDictionary();
|
||||||
bool AddWordToSpellCheckerDictionary(const std::string& word);
|
bool AddWordToSpellCheckerDictionary(const std::string& word);
|
||||||
bool RemoveWordFromSpellCheckerDictionary(const std::string& word);
|
bool RemoveWordFromSpellCheckerDictionary(const std::string& word);
|
||||||
|
void SetSpellCheckerEnabled(bool b);
|
||||||
|
bool IsSpellCheckerEnabled() const;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||||
|
|
|
@ -101,6 +101,29 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', () => {
|
||||||
expect(await callWebFrameFn('getWordSuggestions("testt")')).to.not.be.empty();
|
expect(await callWebFrameFn('getWordSuggestions("testt")')).to.not.be.empty();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('spellCheckerEnabled', () => {
|
||||||
|
it('is enabled by default', async () => {
|
||||||
|
expect(w.webContents.session.spellCheckerEnabled).to.be.true();
|
||||||
|
});
|
||||||
|
|
||||||
|
ifit(shouldRun)('can be dynamically changed', async () => {
|
||||||
|
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "Beautifulllll asd asd"');
|
||||||
|
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
|
||||||
|
// Wait for spellchecker to load
|
||||||
|
await delay(500);
|
||||||
|
|
||||||
|
const callWebFrameFn = (expr: string) => w.webContents.executeJavaScript('require("electron").webFrame.' + expr);
|
||||||
|
|
||||||
|
w.webContents.session.spellCheckerEnabled = false;
|
||||||
|
expect(w.webContents.session.spellCheckerEnabled).to.be.false();
|
||||||
|
expect(await callWebFrameFn('isWordMisspelled("testt")')).to.equal(false);
|
||||||
|
|
||||||
|
w.webContents.session.spellCheckerEnabled = true;
|
||||||
|
expect(w.webContents.session.spellCheckerEnabled).to.be.true();
|
||||||
|
expect(await callWebFrameFn('isWordMisspelled("testt")')).to.equal(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('custom dictionary word list API', () => {
|
describe('custom dictionary word list API', () => {
|
||||||
let ses: Session;
|
let ses: Session;
|
||||||
|
|
||||||
|
|
1
typings/internal-electron.d.ts
vendored
1
typings/internal-electron.d.ts
vendored
|
@ -139,6 +139,7 @@ declare namespace Electron {
|
||||||
overrideReadOnlyProperty(property: string, value: any): void;
|
overrideReadOnlyProperty(property: string, value: any): void;
|
||||||
groupId: number;
|
groupId: number;
|
||||||
getDefaultRoleAccelerator(): Accelerator | undefined;
|
getDefaultRoleAccelerator(): Accelerator | undefined;
|
||||||
|
getCheckStatus(): boolean;
|
||||||
acceleratorWorksWhenHidden?: boolean;
|
acceleratorWorksWhenHidden?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue