[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: = <=>
This commit is contained in:
Guillermo Vayá 2021-07-26 15:28:49 +02:00 committed by GitHub
parent f5ca0f9ef5
commit d77c823bd5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 129 additions and 10 deletions

View file

@ -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;
}

View file

@ -88,6 +88,7 @@ const configDataSchemaV2 = Joi.object<ConfigV2>({
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<ConfigV3>({
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);
}

View file

@ -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);

View file

@ -34,6 +34,7 @@ type State = DeepPartial<CombinedConfig> & {
firstRun?: boolean;
savingState: SavingStateItems;
userOpenedDownloadDialog: boolean;
allowSaveSpellCheckerURL: boolean;
}
type SavingStateItems = {
@ -62,6 +63,7 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
bounceIconRef: React.RefObject<HTMLInputElement>;
showUnreadBadgeRef: React.RefObject<HTMLInputElement>;
useSpellCheckerRef: React.RefObject<HTMLInputElement>;
spellCheckerURLRef: React.RefObject<HTMLInputElement>;
enableHardwareAccelerationRef: React.RefObject<HTMLInputElement>;
saveQueue: SaveQueueItem[];
@ -77,6 +79,7 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
servers: SavingState.SAVING_STATE_DONE,
},
userOpenedDownloadDialog: false,
allowSaveSpellCheckerURL: false,
};
this.getConfig();
@ -90,6 +93,7 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
this.showUnreadBadgeRef = React.createRef();
this.useSpellCheckerRef = React.createRef();
this.enableHardwareAccelerationRef = React.createRef();
this.spellCheckerURLRef = React.createRef();
this.saveQueue = [];
}
@ -327,6 +331,30 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
this.setState({userOpenedDownloadDialog: false});
}
saveSpellCheckerURL = (): void => {
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<HTMLInputElement>): 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<Record<string, nev
{'Setting takes effect after restarting the app.'}
</FormText>
</FormCheck>);
if (process.platform !== 'darwin') {
if (this.state.spellCheckerURL === null || typeof this.state.spellCheckerURL === 'undefined') {
options.push(
<Button
id='editSpellcheckerURL'
key='editSpellcheckerURL'
onClick={() => this.setState({spellCheckerURL: '', allowSaveSpellCheckerURL: false})}
variant='link'
>{'Use an alternative dictionary URL'}</Button>,
);
} else {
options.push(
<div
style={settingsPage.container}
key='containerInputSpellchekerURL'
>
<input
disabled={!this.state.useSpellChecker}
style={settingsPage.downloadLocationInput}
key='inputSpellCheckerURL'
id='inputSpellCheckerURL'
ref={this.spellCheckerURLRef}
onChange={this.handleChangeSpellCheckerURL}
value={this.state.spellCheckerURL}
/>
<Button
disabled={!this.state.allowSaveSpellCheckerURL}
key='saveSpellCheckerURL'
style={settingsPage.downloadLocationButton}
id='saveSpellCheckerURL'
onClick={this.saveSpellCheckerURL}
>
<span>{'Save'}</span>
</Button>
<FormText>
{'Specify the url where dictionary definitions can be retrieved'}
</FormText>
<Button
id='revertSpellcheckerURL'
key='revertSpellcheckerURL'
onClick={this.resetSpellCheckerURL}
variant='link'
>{'Revert to default'}</Button>
</div>);
}
}
if (window.process.platform === 'darwin' || window.process.platform === 'win32') {
const TASKBAR = window.process.platform === 'win32' ? 'taskbar' : 'Dock';
options.push(
<FormCheck>
<FormCheck
key='showunreadbadge'
>
<FormCheck.Input
type='checkbox'
key='inputShowUnreadBadge'
@ -585,10 +660,11 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
if (window.process.platform === 'darwin' || window.process.platform === 'linux') {
options.push(
<FormCheck>
<FormCheck
key='inputShowTrayIcon'
>
<FormCheck.Input
type='checkbox'
key='inputShowTrayIcon'
id='inputShowTrayIcon'
ref={this.showTrayIconRef}
checked={this.state.showTrayIcon}
@ -652,10 +728,11 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
}
options.push(
<FormCheck>
<FormCheck
key='inputEnableHardwareAcceleration'
>
<FormCheck.Input
type='checkbox'
key='inputEnableHardwareAcceleration'
id='inputEnableHardwareAcceleration'
ref={this.enableHardwareAccelerationRef}
checked={this.state.enableHardwareAcceleration}
@ -670,7 +747,10 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
);
options.push(
<div style={settingsPage.container}>
<div
style={settingsPage.container}
key='containerDownloadLocation'
>
<hr/>
<div>{'Download Location'}</div>
<input

View file

@ -34,6 +34,7 @@ export type ConfigV3 = {
spellCheckerLocale: string;
darkMode: boolean;
downloadLocation: string;
spellCheckerURL?: string;
}
export type ConfigV2 = {
@ -56,6 +57,7 @@ export type ConfigV2 = {
enableHardwareAcceleration: boolean;
autostart: boolean;
spellCheckerLocale: string;
spellCheckerURL?: string;
darkMode: boolean;
downloadLocation: string;
}
@ -76,6 +78,7 @@ export type ConfigV1 = {
};
showUnreadBadge: boolean;
useSpellChecker: boolean;
spellCheckerURL?: string;
enableHardwareAcceleration: boolean;
autostart: boolean;
spellCheckerLocale: string;