From d77c823bd5a161a09df8579f3d1d8d827b311d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillermo=20Vay=C3=A1?= Date: Mon, 26 Jul 2021 15:28:49 +0200 Subject: [PATCH] [MM-36747] Spellchecker custom urls (cherrypick to TS) (#1669) * [MM-36747] Allow users to specify spellchecker url for downloading dictionaries * fix settings keys Co-authored-by: = <=> --- src/common/config/index.ts | 5 ++ src/main/Validator.ts | 10 +++ src/main/main.ts | 27 ++++++- src/renderer/components/SettingsPage.tsx | 94 ++++++++++++++++++++++-- src/types/config.ts | 3 + 5 files changed, 129 insertions(+), 10 deletions(-) diff --git a/src/common/config/index.ts b/src/common/config/index.ts index 6635d7f9..437968db 100644 --- a/src/common/config/index.ts +++ b/src/common/config/index.ts @@ -229,6 +229,11 @@ export default class Config extends EventEmitter { get useSpellChecker() { return this.combinedData?.useSpellChecker ?? defaultPreferences.useSpellChecker; } + + get spellCheckerURL(): (string|undefined) { + return this.combinedData?.spellCheckerURL; + } + get spellCheckerLocale() { return this.combinedData?.spellCheckerLocale ?? defaultPreferences.spellCheckerLocale; } diff --git a/src/main/Validator.ts b/src/main/Validator.ts index 9d5c76cd..55305335 100644 --- a/src/main/Validator.ts +++ b/src/main/Validator.ts @@ -88,6 +88,7 @@ const configDataSchemaV2 = Joi.object({ enableHardwareAcceleration: Joi.boolean().default(true), autostart: Joi.boolean().default(true), spellCheckerLocale: Joi.string().regex(/^[a-z]{2}-[A-Z]{2}$/).default('en-US'), + spellCheckerURL: Joi.string().allow(null), darkMode: Joi.boolean().default(false), downloadLocation: Joi.string(), }); @@ -117,6 +118,7 @@ const configDataSchemaV3 = Joi.object({ enableHardwareAcceleration: Joi.boolean().default(true), autostart: Joi.boolean().default(true), spellCheckerLocale: Joi.string().regex(/^[a-z]{2}-[A-Z]{2}$/).default('en-US'), + spellCheckerURL: Joi.string().allow(null), darkMode: Joi.boolean().default(false), downloadLocation: Joi.string(), }); @@ -208,6 +210,10 @@ export function validateV2ConfigData(data: ConfigV2) { // replace original teams data.teams = teams; } + if (data.spellCheckerURL && !urlUtils.isValidURL(data.spellCheckerURL)) { + log.error('Invalid download location for spellchecker dictionary, removing from config'); + delete data.spellCheckerURL; + } return validateAgainstSchema(data, configDataSchemaV2); } @@ -227,6 +233,10 @@ export function validateV3ConfigData(data: ConfigV3) { // replace original teams data.teams = teams; } + if (data.spellCheckerURL && !urlUtils.isValidURL(data.spellCheckerURL)) { + log.error('Invalid download location for spellchecker dictionary, removing from config'); + delete data.spellCheckerURL; + } return validateAgainstSchema(data, configDataSchemaV3); } diff --git a/src/main/main.ts b/src/main/main.ts index 04aa5ee6..25dadcd0 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -506,6 +506,27 @@ function handleNewServerModal() { function initializeAfterAppReady() { app.setAppUserModelId('Mattermost.Desktop'); // Use explicit AppUserModelID + const defaultSession = session.defaultSession; + + if (process.platform !== 'darwin') { + defaultSession.on('spellcheck-dictionary-download-failure', (event, lang) => { + if (config.spellCheckerURL) { + log.error(`There was an error while trying to load the dictionary definitions for ${lang} fromfully the specified url. Please review you have access to the needed files. Url used was ${config.spellCheckerURL}`); + } else { + log.warn(`There was an error while trying to download the dictionary definitions for ${lang}, spellchecking might not work properly.`); + } + }); + + if (config.spellCheckerURL) { + const spellCheckerURL = config.spellCheckerURL.endsWith('/') ? config.spellCheckerURL : `${config.spellCheckerURL}/`; + log.info(`Configuring spellchecker using download URL: ${spellCheckerURL}`); + defaultSession.setSpellCheckerDictionaryDownloadURL(spellCheckerURL); + + defaultSession.on('spellcheck-dictionary-download-success', (event, lang) => { + log.info(`Dictionary definitions downloaded successfully for ${lang}`); + }); + } + } const appVersionJson = path.join(app.getPath('userData'), 'app-state.json'); appVersion = new AppVersionManager(appVersionJson); @@ -548,7 +569,7 @@ function initializeAfterAppReady() { } } - initCookieManager(session.defaultSession); + initCookieManager(defaultSession); WindowManager.showMainWindow(deeplinkingURL); @@ -571,7 +592,7 @@ function initializeAfterAppReady() { } setupBadge(); - session.defaultSession.on('will-download', (event, item, webContents) => { + defaultSession.on('will-download', (event, item, webContents) => { const filename = item.getFilename(); const fileElements = filename.split('.'); const filters = []; @@ -609,7 +630,7 @@ function initializeAfterAppReady() { // handle permission requests // - approve if a supported permission type and the request comes from the renderer or one of the defined servers - session.defaultSession.setPermissionRequestHandler((webContents, permission, callback) => { + defaultSession.setPermissionRequestHandler((webContents, permission, callback) => { // is the requested permission type supported? if (!supportedPermissionTypes.includes(permission)) { callback(false); diff --git a/src/renderer/components/SettingsPage.tsx b/src/renderer/components/SettingsPage.tsx index 7be4122f..fea5d4df 100644 --- a/src/renderer/components/SettingsPage.tsx +++ b/src/renderer/components/SettingsPage.tsx @@ -34,6 +34,7 @@ type State = DeepPartial & { firstRun?: boolean; savingState: SavingStateItems; userOpenedDownloadDialog: boolean; + allowSaveSpellCheckerURL: boolean; } type SavingStateItems = { @@ -62,6 +63,7 @@ export default class SettingsPage extends React.PureComponent; showUnreadBadgeRef: React.RefObject; useSpellCheckerRef: React.RefObject; + spellCheckerURLRef: React.RefObject; enableHardwareAccelerationRef: React.RefObject; saveQueue: SaveQueueItem[]; @@ -77,6 +79,7 @@ export default class SettingsPage extends React.PureComponent { + window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'spellCheckerURL', data: this.state.spellCheckerURL}); + } + + resetSpellCheckerURL = (): void => { + this.setState({spellCheckerURL: undefined, allowSaveSpellCheckerURL: false}); + window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'spellCheckerURL', data: null}); + } + + handleChangeSpellCheckerURL= (e: React.ChangeEvent): void => { + const dictionaryURL = e.target.value; + let allowSaveSpellCheckerURL; + try { + // eslint-disable-next-line no-new + new URL(dictionaryURL); + allowSaveSpellCheckerURL = true; + } catch { + allowSaveSpellCheckerURL = false; + } + this.setState({ + spellCheckerURL: dictionaryURL, + allowSaveSpellCheckerURL, + }); + } updateTeam = (index: number, newData: Team) => { const teams = this.state.teams || []; teams[index] = newData; @@ -495,11 +523,58 @@ export default class SettingsPage extends React.PureComponent ); - + if (process.platform !== 'darwin') { + if (this.state.spellCheckerURL === null || typeof this.state.spellCheckerURL === 'undefined') { + options.push( + , + ); + } else { + options.push( +
+ + + + {'Specify the url where dictionary definitions can be retrieved'} + + +
); + } + } if (window.process.platform === 'darwin' || window.process.platform === 'win32') { const TASKBAR = window.process.platform === 'win32' ? 'taskbar' : 'Dock'; options.push( - + + + +

{'Download Location'}