You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-11-06 09:19:22 +02:00
Desktop: Added toolbar button to switch spell checker language
This commit is contained in:
@@ -427,6 +427,9 @@ packages/app-desktop/gui/MainScreen/commands/showNoteProperties.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/showShareNoteDialog.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/showShareNoteDialog.js
|
||||
packages/app-desktop/gui/MainScreen/commands/showShareNoteDialog.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/showSpellCheckerMenu.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/showSpellCheckerMenu.js
|
||||
packages/app-desktop/gui/MainScreen/commands/showSpellCheckerMenu.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleEditors.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleEditors.js
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleEditors.js.map
|
||||
@@ -886,6 +889,9 @@ packages/lib/services/CommandService.js.map
|
||||
packages/lib/services/KeymapService.d.ts
|
||||
packages/lib/services/KeymapService.js
|
||||
packages/lib/services/KeymapService.js.map
|
||||
packages/lib/services/KvStore.d.ts
|
||||
packages/lib/services/KvStore.js
|
||||
packages/lib/services/KvStore.js.map
|
||||
packages/lib/services/ResourceEditWatcher/index.d.ts
|
||||
packages/lib/services/ResourceEditWatcher/index.js
|
||||
packages/lib/services/ResourceEditWatcher/index.js.map
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -419,6 +419,9 @@ packages/app-desktop/gui/MainScreen/commands/showNoteProperties.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/showShareNoteDialog.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/showShareNoteDialog.js
|
||||
packages/app-desktop/gui/MainScreen/commands/showShareNoteDialog.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/showSpellCheckerMenu.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/showSpellCheckerMenu.js
|
||||
packages/app-desktop/gui/MainScreen/commands/showSpellCheckerMenu.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleEditors.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleEditors.js
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleEditors.js.map
|
||||
@@ -878,6 +881,9 @@ packages/lib/services/CommandService.js.map
|
||||
packages/lib/services/KeymapService.d.ts
|
||||
packages/lib/services/KeymapService.js
|
||||
packages/lib/services/KeymapService.js.map
|
||||
packages/lib/services/KvStore.d.ts
|
||||
packages/lib/services/KvStore.js
|
||||
packages/lib/services/KvStore.js.map
|
||||
packages/lib/services/ResourceEditWatcher/index.d.ts
|
||||
packages/lib/services/ResourceEditWatcher/index.js
|
||||
packages/lib/services/ResourceEditWatcher/index.js.map
|
||||
|
||||
6
.ignore
6
.ignore
@@ -374,6 +374,9 @@ packages/app-desktop/gui/MainScreen/commands/showNoteProperties.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/showShareNoteDialog.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/showShareNoteDialog.js
|
||||
packages/app-desktop/gui/MainScreen/commands/showShareNoteDialog.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/showSpellCheckerMenu.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/showSpellCheckerMenu.js
|
||||
packages/app-desktop/gui/MainScreen/commands/showSpellCheckerMenu.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleEditors.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleEditors.js
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleEditors.js.map
|
||||
@@ -833,6 +836,9 @@ packages/lib/services/CommandService.js.map
|
||||
packages/lib/services/KeymapService.d.ts
|
||||
packages/lib/services/KeymapService.js
|
||||
packages/lib/services/KeymapService.js.map
|
||||
packages/lib/services/KvStore.d.ts
|
||||
packages/lib/services/KvStore.js
|
||||
packages/lib/services/KvStore.js.map
|
||||
packages/lib/services/ResourceEditWatcher/index.d.ts
|
||||
packages/lib/services/ResourceEditWatcher/index.js
|
||||
packages/lib/services/ResourceEditWatcher/index.js.map
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
|
||||
const { asyncTest, fileContentEqual, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('./test-utils.js');
|
||||
const KvStore = require('@joplin/lib/services/KvStore.js');
|
||||
const KvStore = require('@joplin/lib/services/KvStore').default;
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
|
||||
@@ -41,7 +41,7 @@ const DecryptionWorker = require('@joplin/lib/services/DecryptionWorker.js');
|
||||
const ResourceService = require('@joplin/lib/services/ResourceService.js');
|
||||
const RevisionService = require('@joplin/lib/services/RevisionService.js');
|
||||
const ResourceFetcher = require('@joplin/lib/services/ResourceFetcher.js');
|
||||
const KvStore = require('@joplin/lib/services/KvStore.js');
|
||||
const KvStore = require('@joplin/lib/services/KvStore').default;
|
||||
const WebDavApi = require('@joplin/lib/WebDavApi');
|
||||
const DropboxApi = require('@joplin/lib/DropboxApi');
|
||||
const { OneDriveApi } = require('@joplin/lib/onedrive-api');
|
||||
|
||||
@@ -59,6 +59,7 @@ const commands = [
|
||||
require('./gui/MainScreen/commands/showNoteContentProperties'),
|
||||
require('./gui/MainScreen/commands/showNoteProperties'),
|
||||
require('./gui/MainScreen/commands/showShareNoteDialog'),
|
||||
require('./gui/MainScreen/commands/showSpellCheckerMenu'),
|
||||
require('./gui/MainScreen/commands/toggleNoteList'),
|
||||
require('./gui/MainScreen/commands/toggleSideBar'),
|
||||
require('./gui/MainScreen/commands/toggleVisiblePanes'),
|
||||
@@ -453,6 +454,8 @@ class Application extends BaseApplication {
|
||||
}
|
||||
|
||||
setupContextMenu() {
|
||||
const MenuItem = bridge().MenuItem;
|
||||
|
||||
// The context menu must be setup in renderer process because that's where
|
||||
// the spell checker service lives.
|
||||
require('electron-context-menu')({
|
||||
@@ -463,7 +466,7 @@ class Application extends BaseApplication {
|
||||
},
|
||||
|
||||
menu: (actions:any, props:any) => {
|
||||
const spellCheckerMenuItems = SpellCheckerService.instance().contextMenuItems(props.misspelledWord, props.dictionarySuggestions);
|
||||
const spellCheckerMenuItems = SpellCheckerService.instance().contextMenuItems(props.misspelledWord, props.dictionarySuggestions).map((item:any) => new MenuItem(item));
|
||||
|
||||
const output = [
|
||||
actions.cut(),
|
||||
|
||||
@@ -60,6 +60,7 @@ const commands = [
|
||||
require('./commands/showNoteContentProperties'),
|
||||
require('./commands/showNoteProperties'),
|
||||
require('./commands/showShareNoteDialog'),
|
||||
require('./commands/showSpellCheckerMenu'),
|
||||
require('./commands/toggleEditors'),
|
||||
require('./commands/toggleNoteList'),
|
||||
require('./commands/toggleSideBar'),
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { CommandContext, CommandDeclaration, CommandRuntime } from '@joplin/lib/services/CommandService';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import bridge from '../../../services/bridge';
|
||||
import SpellCheckerService from '@joplin/lib/services/spellChecker/SpellCheckerService';
|
||||
import { AppState } from '../../../app';
|
||||
|
||||
const Menu = bridge().Menu;
|
||||
|
||||
export const declaration:CommandDeclaration = {
|
||||
name: 'showSpellCheckerMenu',
|
||||
label: () => _('Spell checker'),
|
||||
iconName: 'fas fa-globe',
|
||||
};
|
||||
|
||||
export const runtime = ():CommandRuntime => {
|
||||
return {
|
||||
execute: async (context:CommandContext, selectedLanguage:string = null, useSpellChecker:boolean = null) => {
|
||||
selectedLanguage = selectedLanguage === null ? context.state.settings['spellChecker.language'] : selectedLanguage;
|
||||
useSpellChecker = useSpellChecker === null ? context.state.settings['spellChecker.enabled'] : useSpellChecker;
|
||||
|
||||
const menuItems = SpellCheckerService.instance().spellCheckerConfigMenuItems(selectedLanguage, useSpellChecker);
|
||||
const menu = Menu.buildFromTemplate(menuItems);
|
||||
menu.popup(bridge().window());
|
||||
},
|
||||
|
||||
mapStateToTitle(state:AppState):string {
|
||||
if (!state.settings['spellChecker.enabled']) return null;
|
||||
const language = state.settings['spellChecker.language'];
|
||||
if (!language) return null;
|
||||
const s = language.split('-');
|
||||
return s[0];
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -35,9 +35,6 @@ export default function(editor:any) {
|
||||
const element = contextMenuElement(editor, params.x, params.y);
|
||||
if (!element) return;
|
||||
|
||||
const Menu = bridge().Menu;
|
||||
const MenuItem = bridge().MenuItem;
|
||||
|
||||
let itemType:ContextMenuItemType = ContextMenuItemType.None;
|
||||
let resourceId = '';
|
||||
let linkToCopy = null;
|
||||
@@ -65,27 +62,28 @@ export default function(editor:any) {
|
||||
isReadOnly: false,
|
||||
};
|
||||
|
||||
const menu = new Menu();
|
||||
const template = [];
|
||||
|
||||
for (const itemName in contextMenuItems) {
|
||||
const item = contextMenuItems[itemName];
|
||||
|
||||
if (!item.isActive(itemType, contextMenuActionOptions.current)) continue;
|
||||
|
||||
menu.append(new MenuItem({
|
||||
template.push({
|
||||
label: item.label,
|
||||
click: () => {
|
||||
item.onAction(contextMenuActionOptions.current);
|
||||
},
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
const spellCheckerMenuItems = SpellCheckerService.instance().contextMenuItems(params.misspelledWord, params.dictionarySuggestions);
|
||||
|
||||
for (const item of spellCheckerMenuItems) {
|
||||
menu.append(item);
|
||||
template.push(item);
|
||||
}
|
||||
|
||||
menu.popup();
|
||||
const menu = bridge().Menu.buildFromTemplate(template);
|
||||
menu.popup(bridge().window());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ const mapStateToProps = (state:any) => {
|
||||
|
||||
return {
|
||||
toolbarButtonInfos: toolbarButtonUtils.commandsToToolbarButtons([
|
||||
'showSpellCheckerMenu',
|
||||
'editAlarm',
|
||||
'toggleVisiblePanes',
|
||||
'showNoteProperties',
|
||||
|
||||
@@ -21,6 +21,8 @@ export const StyledRoot = styled.a<RootProps>`
|
||||
box-sizing: border-box;
|
||||
color: ${(props:RootProps) => props.theme.color3};
|
||||
font-size: ${(props:RootProps) => props.theme.toolbarIconSize * 0.8}px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props:RootProps) => props.disabled ? 'none' : props.theme.backgroundColorHover3};
|
||||
|
||||
@@ -25,11 +25,6 @@ export default class SpellCheckerServiceDriverNative extends SpellCheckerService
|
||||
return languages.length ? languages[0] : '';
|
||||
}
|
||||
|
||||
public makeMenuItem(item:any):any {
|
||||
const MenuItem = bridge().MenuItem;
|
||||
return new MenuItem(item);
|
||||
}
|
||||
|
||||
public addWordToSpellCheckerDictionary(_language:string, word:string) {
|
||||
// Actually on Electron all languages share the same dictionary, or
|
||||
// perhaps it's added to the currently active language.
|
||||
|
||||
@@ -2,7 +2,7 @@ import shim from '@joplin/lib/shim';
|
||||
const { dirname } = require('@joplin/lib/path-utils');
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const pluginAssets = require('./pluginAssets/index');
|
||||
const KvStore = require('@joplin/lib/services/KvStore.js');
|
||||
const KvStore = require('@joplin/lib/services/KvStore').default;
|
||||
|
||||
export default class PluginAssetsLoader {
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const BaseService = require('@joplin/lib/services/BaseService').default;
|
||||
const ResourceService = require('@joplin/lib/services/ResourceService');
|
||||
const RevisionService = require('@joplin/lib/services/RevisionService');
|
||||
const KvStore = require('@joplin/lib/services/KvStore');
|
||||
const KvStore = require('@joplin/lib/services/KvStore').default;
|
||||
const { JoplinDatabase } = require('@joplin/lib/joplin-database.js');
|
||||
const { Database } = require('@joplin/lib/database.js');
|
||||
const { NotesScreen } = require('./components/screens/notes.js');
|
||||
|
||||
@@ -5,6 +5,7 @@ import BaseService from './services/BaseService';
|
||||
import reducer from './reducer';
|
||||
import KeychainServiceDriver from './services/keychain/KeychainServiceDriver.node';
|
||||
import { _, setLocale } from './locale';
|
||||
import KvStore from './services/KvStore';
|
||||
|
||||
const { createStore, applyMiddleware } = require('redux');
|
||||
const { defaultState, stateUtils } = require('./reducer');
|
||||
@@ -41,7 +42,6 @@ const RevisionService = require('./services/RevisionService');
|
||||
const ResourceService = require('./services/RevisionService');
|
||||
const DecryptionWorker = require('./services/DecryptionWorker');
|
||||
const { loadKeychainServiceAndSettings } = require('./services/SettingUtils');
|
||||
const KvStore = require('./services/KvStore');
|
||||
const MigrationService = require('./services/MigrationService');
|
||||
const { toSystemSlashes } = require('./path-utils');
|
||||
const { setAutoFreeze } = require('immer');
|
||||
@@ -89,7 +89,7 @@ export default class BaseApplication {
|
||||
await reg.cancelTimers();
|
||||
|
||||
this.eventEmitter_.removeAllListeners();
|
||||
KvStore.instance_ = null;
|
||||
KvStore.destroyInstance();
|
||||
BaseModel.setDb(null);
|
||||
reg.setDb(null);
|
||||
|
||||
|
||||
@@ -1,34 +1,49 @@
|
||||
const BaseService = require('./BaseService').default;
|
||||
import BaseService from './BaseService';
|
||||
const Mutex = require('async-mutex').Mutex;
|
||||
|
||||
class KvStore extends BaseService {
|
||||
static instance() {
|
||||
enum ValueType {
|
||||
Int = 1,
|
||||
Text = 2,
|
||||
}
|
||||
|
||||
export default class KvStore extends BaseService {
|
||||
|
||||
private incMutex_:any = null
|
||||
private db_:any = null;
|
||||
|
||||
private static instance_:KvStore = null;
|
||||
|
||||
public static instance() {
|
||||
if (this.instance_) return this.instance_;
|
||||
this.instance_ = new KvStore();
|
||||
return this.instance_;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
public static destroyInstance() {
|
||||
this.instance_ = null;
|
||||
}
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
this.incMutex_ = new Mutex();
|
||||
}
|
||||
|
||||
setDb(v) {
|
||||
public setDb(v:any) {
|
||||
this.db_ = v;
|
||||
}
|
||||
|
||||
db() {
|
||||
private db() {
|
||||
if (!this.db_) throw new Error('Accessing DB before it has been set!');
|
||||
return this.db_;
|
||||
}
|
||||
|
||||
typeFromValue_(value) {
|
||||
if (typeof value === 'string') return KvStore.TYPE_TEXT;
|
||||
if (typeof value === 'number') return KvStore.TYPE_INT;
|
||||
private typeFromValue_(value:any) {
|
||||
if (typeof value === 'string') return ValueType.Text;
|
||||
if (typeof value === 'number') return ValueType.Int;
|
||||
throw new Error(`Unsupported value type: ${typeof value}`);
|
||||
}
|
||||
|
||||
formatValues_(kvs) {
|
||||
private formatValues_(kvs:any[]) {
|
||||
const output = [];
|
||||
for (const kv of kvs) {
|
||||
kv.value = this.formatValue_(kv.value, kv.type);
|
||||
@@ -37,47 +52,48 @@ class KvStore extends BaseService {
|
||||
return output;
|
||||
}
|
||||
|
||||
formatValue_(value, type) {
|
||||
if (type === KvStore.TYPE_INT) return Number(value);
|
||||
if (type === KvStore.TYPE_TEXT) return `${value}`;
|
||||
private formatValue_(value:any, type:ValueType):string | number {
|
||||
if (type === ValueType.Int) return Number(value);
|
||||
if (type === ValueType.Text) return `${value}`;
|
||||
throw new Error(`Unknown type: ${type}`);
|
||||
}
|
||||
|
||||
async value(key) {
|
||||
public async value<T>(key:string):Promise<T> {
|
||||
const r = await this.db().selectOne('SELECT `value`, `type` FROM key_values WHERE `key` = ?', [key]);
|
||||
if (!r) return null;
|
||||
return this.formatValue_(r.value, r.type);
|
||||
return this.formatValue_(r.value, r.type) as any;
|
||||
}
|
||||
|
||||
async setValue(key, value) {
|
||||
public async setValue(key:string, value:any) {
|
||||
const t = Date.now();
|
||||
await this.db().exec('INSERT OR REPLACE INTO key_values (`key`, `value`, `type`, `updated_time`) VALUES (?, ?, ?, ?)', [key, value, this.typeFromValue_(value), t]);
|
||||
}
|
||||
|
||||
async deleteValue(key) {
|
||||
public async deleteValue(key:string) {
|
||||
await this.db().exec('DELETE FROM key_values WHERE `key` = ?', [key]);
|
||||
}
|
||||
|
||||
async deleteByPrefix(prefix) {
|
||||
public async deleteByPrefix(prefix:string) {
|
||||
await this.db().exec('DELETE FROM key_values WHERE `key` LIKE ?', [`${prefix}%`]);
|
||||
}
|
||||
|
||||
async clear() {
|
||||
public async clear() {
|
||||
await this.db().exec('DELETE FROM key_values');
|
||||
}
|
||||
|
||||
async all() {
|
||||
public async all() {
|
||||
return this.formatValues_(await this.db().selectAll('SELECT * FROM key_values'));
|
||||
}
|
||||
|
||||
// Note: atomicity is done at application level so two difference instances
|
||||
// accessing the db at the same time could mess up the increment.
|
||||
async incValue(key, inc = 1) {
|
||||
public async incValue(key:string, inc:number = 1) {
|
||||
const release = await this.incMutex_.acquire();
|
||||
|
||||
try {
|
||||
const result = await this.db().selectOne('SELECT `value`, `type` FROM key_values WHERE `key` = ?', [key]);
|
||||
const newValue = result ? this.formatValue_(result.value, result.type) + inc : inc;
|
||||
const value = this.formatValue_(result.value, result.type) as ValueType.Int;
|
||||
const newValue = result ? value + inc : inc;
|
||||
await this.setValue(key, newValue);
|
||||
release();
|
||||
return newValue;
|
||||
@@ -87,18 +103,13 @@ class KvStore extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
async searchByPrefix(prefix) {
|
||||
public async searchByPrefix(prefix:string) {
|
||||
const results = await this.db().selectAll('SELECT `key`, `value`, `type` FROM key_values WHERE `key` LIKE ?', [`${prefix}%`]);
|
||||
return this.formatValues_(results);
|
||||
}
|
||||
|
||||
async countKeys() {
|
||||
public async countKeys() {
|
||||
const r = await this.db().selectOne('SELECT count(*) as total FROM key_values');
|
||||
return r.total ? r.total : 0;
|
||||
}
|
||||
}
|
||||
|
||||
KvStore.TYPE_INT = 1;
|
||||
KvStore.TYPE_TEXT = 2;
|
||||
|
||||
module.exports = KvStore;
|
||||
@@ -2,10 +2,12 @@ import Setting from '../../models/Setting';
|
||||
import CommandService from '../CommandService';
|
||||
import SpellCheckerServiceDriverBase from './SpellCheckerServiceDriverBase';
|
||||
import { _, countryDisplayName } from '../../locale';
|
||||
import KvStore from '../KvStore';
|
||||
|
||||
export default class SpellCheckerService {
|
||||
|
||||
private driver_:SpellCheckerServiceDriverBase;
|
||||
private latestSelectedLanguages_:string[] = [];
|
||||
|
||||
private static instance_:SpellCheckerService;
|
||||
|
||||
@@ -17,6 +19,7 @@ export default class SpellCheckerService {
|
||||
|
||||
public async initialize(driver:SpellCheckerServiceDriverBase) {
|
||||
this.driver_ = driver;
|
||||
this.latestSelectedLanguages_ = await this.loadLatestSelectedLanguages();
|
||||
this.setupDefaultLanguage();
|
||||
this.applyStateToDriver();
|
||||
}
|
||||
@@ -25,6 +28,26 @@ export default class SpellCheckerService {
|
||||
return 'en-US';
|
||||
}
|
||||
|
||||
private async loadLatestSelectedLanguages():Promise<string[]> {
|
||||
const result = await KvStore.instance().value<string>('spellCheckerService.latestSelectedLanguages');
|
||||
if (!result) return [];
|
||||
return JSON.parse(result);
|
||||
}
|
||||
|
||||
private async addLatestSelectedLanguage(language:string) {
|
||||
const languages = this.latestSelectedLanguages_.slice();
|
||||
if (languages.length > 5) languages.splice(0, 1);
|
||||
|
||||
if (languages.includes(language)) {
|
||||
languages.splice(languages.indexOf(language), 1);
|
||||
}
|
||||
|
||||
languages.splice(0, 0, language);
|
||||
|
||||
this.latestSelectedLanguages_ = languages;
|
||||
await KvStore.instance().setValue('spellCheckerService.latestSelectedLanguages', JSON.stringify(this.latestSelectedLanguages_));
|
||||
}
|
||||
|
||||
public setupDefaultLanguage() {
|
||||
if (!Setting.value('spellChecker.language')) {
|
||||
const l = this.driver_.language;
|
||||
@@ -43,6 +66,7 @@ export default class SpellCheckerService {
|
||||
public setLanguage(language:string) {
|
||||
Setting.setValue('spellChecker.language', language);
|
||||
this.applyStateToDriver();
|
||||
this.addLatestSelectedLanguage(language);
|
||||
}
|
||||
|
||||
public get language():string {
|
||||
@@ -58,100 +82,121 @@ export default class SpellCheckerService {
|
||||
this.applyStateToDriver();
|
||||
}
|
||||
|
||||
private makeMenuItem(item:any):any {
|
||||
return this.driver_.makeMenuItem(item);
|
||||
}
|
||||
|
||||
private async addToDictionary(language:string, word:string) {
|
||||
this.driver_.addWordToSpellCheckerDictionary(language, word);
|
||||
}
|
||||
|
||||
public contextMenuItems<T>(misspelledWord:string, dictionarySuggestions:string[]):T[] {
|
||||
public contextMenuItems(misspelledWord:string, dictionarySuggestions:string[]):any[] {
|
||||
if (!misspelledWord) return [];
|
||||
|
||||
const output = [];
|
||||
|
||||
output.push(this.makeMenuItem({ type: 'separator' }));
|
||||
output.push({ type: 'separator' });
|
||||
|
||||
if (dictionarySuggestions.length) {
|
||||
for (const suggestion of dictionarySuggestions) {
|
||||
output.push(this.makeMenuItem({
|
||||
output.push({
|
||||
label: suggestion,
|
||||
click: () => {
|
||||
CommandService.instance().execute('replaceSelection', suggestion);
|
||||
},
|
||||
}));
|
||||
});
|
||||
}
|
||||
} else {
|
||||
output.push(this.makeMenuItem({
|
||||
output.push({
|
||||
label: `(${_('No suggestions')})`,
|
||||
enabled: false,
|
||||
click: () => {},
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
output.push(this.makeMenuItem({ type: 'separator' }));
|
||||
output.push({ type: 'separator' });
|
||||
|
||||
output.push(this.makeMenuItem({
|
||||
output.push({
|
||||
label: _('Add to dictionary'),
|
||||
click: () => {
|
||||
this.addToDictionary(this.language, misspelledWord);
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private changeLanguageMenuItem(language:string, enabled:boolean, checked:boolean) {
|
||||
return {
|
||||
label: countryDisplayName(language),
|
||||
type: 'radio',
|
||||
checked: checked,
|
||||
enabled: enabled,
|
||||
click: () => {
|
||||
this.setLanguage(language);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private changeLanguageMenuItems(selectedLanguage:string, enabled:boolean) {
|
||||
const languageMenuItems = [];
|
||||
|
||||
for (const locale of this.driver_.availableLanguages) {
|
||||
languageMenuItems.push({
|
||||
label: countryDisplayName(locale),
|
||||
type: 'radio',
|
||||
checked: locale === selectedLanguage,
|
||||
enabled: enabled,
|
||||
click: () => {
|
||||
this.setLanguage(locale);
|
||||
},
|
||||
});
|
||||
languageMenuItems.push(this.changeLanguageMenuItem(locale, enabled, locale === selectedLanguage));
|
||||
}
|
||||
|
||||
languageMenuItems.sort((a:any, b:any) => {
|
||||
return a.label < b.label ? -1 : +1;
|
||||
});
|
||||
|
||||
return languageMenuItems.map((item:any) => this.makeMenuItem(item));
|
||||
return languageMenuItems;
|
||||
}
|
||||
|
||||
public spellCheckerConfigMenuItems(selectedLanguage:string, useSpellChecker:boolean) {
|
||||
const latestLanguageItems = this.latestSelectedLanguages_.map((language:string) => {
|
||||
return this.changeLanguageMenuItem(language, true, language === selectedLanguage);
|
||||
});
|
||||
|
||||
if (latestLanguageItems.length) latestLanguageItems.splice(0, 0, { type: 'separator' } as any);
|
||||
|
||||
latestLanguageItems.sort((a:any, b:any) => {
|
||||
return a.label < b.label ? -1 : +1;
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
label: _('Use spell checker'),
|
||||
type: 'checkbox',
|
||||
checked: useSpellChecker,
|
||||
click: () => {
|
||||
this.toggleEnabled();
|
||||
},
|
||||
},
|
||||
|
||||
...latestLanguageItems,
|
||||
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
|
||||
// Can be removed once it does work
|
||||
{
|
||||
label: '⚠ Spell checker doesn\'t work in Markdown editor ⚠',
|
||||
enabled: false,
|
||||
},
|
||||
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
|
||||
{
|
||||
label: _('Change language'),
|
||||
submenu: this.changeLanguageMenuItems(selectedLanguage, useSpellChecker),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
public spellCheckerConfigMenuItem(selectedLanguage:string, useSpellChecker:boolean) {
|
||||
return this.makeMenuItem({
|
||||
return {
|
||||
label: _('Spell checker'),
|
||||
submenu: [
|
||||
this.makeMenuItem({
|
||||
label: _('Use spell checker'),
|
||||
type: 'checkbox',
|
||||
checked: useSpellChecker,
|
||||
click: () => {
|
||||
this.toggleEnabled();
|
||||
},
|
||||
}),
|
||||
this.makeMenuItem({
|
||||
type: 'separator',
|
||||
}),
|
||||
|
||||
// Can be removed once it does work
|
||||
this.makeMenuItem({
|
||||
label: '⚠ Spell checker doesn\'t work in Markdown editor ⚠',
|
||||
enabled: false,
|
||||
}),
|
||||
|
||||
this.makeMenuItem({
|
||||
type: 'separator',
|
||||
}),
|
||||
...this.changeLanguageMenuItems(selectedLanguage, useSpellChecker),
|
||||
],
|
||||
});
|
||||
submenu: this.spellCheckerConfigMenuItems(selectedLanguage, useSpellChecker),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,10 +12,6 @@ export default class SpellCheckerServiceDriverBase {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
public makeMenuItem(_item:any):any {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
public addWordToSpellCheckerDictionary(_language:string, _word:string) {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user