[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:
parent
f5ca0f9ef5
commit
d77c823bd5
|
@ -229,6 +229,11 @@ export default class Config extends EventEmitter {
|
||||||
get useSpellChecker() {
|
get useSpellChecker() {
|
||||||
return this.combinedData?.useSpellChecker ?? defaultPreferences.useSpellChecker;
|
return this.combinedData?.useSpellChecker ?? defaultPreferences.useSpellChecker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get spellCheckerURL(): (string|undefined) {
|
||||||
|
return this.combinedData?.spellCheckerURL;
|
||||||
|
}
|
||||||
|
|
||||||
get spellCheckerLocale() {
|
get spellCheckerLocale() {
|
||||||
return this.combinedData?.spellCheckerLocale ?? defaultPreferences.spellCheckerLocale;
|
return this.combinedData?.spellCheckerLocale ?? defaultPreferences.spellCheckerLocale;
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,6 +88,7 @@ const configDataSchemaV2 = Joi.object<ConfigV2>({
|
||||||
enableHardwareAcceleration: Joi.boolean().default(true),
|
enableHardwareAcceleration: Joi.boolean().default(true),
|
||||||
autostart: Joi.boolean().default(true),
|
autostart: Joi.boolean().default(true),
|
||||||
spellCheckerLocale: Joi.string().regex(/^[a-z]{2}-[A-Z]{2}$/).default('en-US'),
|
spellCheckerLocale: Joi.string().regex(/^[a-z]{2}-[A-Z]{2}$/).default('en-US'),
|
||||||
|
spellCheckerURL: Joi.string().allow(null),
|
||||||
darkMode: Joi.boolean().default(false),
|
darkMode: Joi.boolean().default(false),
|
||||||
downloadLocation: Joi.string(),
|
downloadLocation: Joi.string(),
|
||||||
});
|
});
|
||||||
|
@ -117,6 +118,7 @@ const configDataSchemaV3 = Joi.object<ConfigV3>({
|
||||||
enableHardwareAcceleration: Joi.boolean().default(true),
|
enableHardwareAcceleration: Joi.boolean().default(true),
|
||||||
autostart: Joi.boolean().default(true),
|
autostart: Joi.boolean().default(true),
|
||||||
spellCheckerLocale: Joi.string().regex(/^[a-z]{2}-[A-Z]{2}$/).default('en-US'),
|
spellCheckerLocale: Joi.string().regex(/^[a-z]{2}-[A-Z]{2}$/).default('en-US'),
|
||||||
|
spellCheckerURL: Joi.string().allow(null),
|
||||||
darkMode: Joi.boolean().default(false),
|
darkMode: Joi.boolean().default(false),
|
||||||
downloadLocation: Joi.string(),
|
downloadLocation: Joi.string(),
|
||||||
});
|
});
|
||||||
|
@ -208,6 +210,10 @@ export function validateV2ConfigData(data: ConfigV2) {
|
||||||
// replace original teams
|
// replace original teams
|
||||||
data.teams = 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);
|
return validateAgainstSchema(data, configDataSchemaV2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,6 +233,10 @@ export function validateV3ConfigData(data: ConfigV3) {
|
||||||
// replace original teams
|
// replace original teams
|
||||||
data.teams = 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);
|
return validateAgainstSchema(data, configDataSchemaV3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -506,6 +506,27 @@ function handleNewServerModal() {
|
||||||
|
|
||||||
function initializeAfterAppReady() {
|
function initializeAfterAppReady() {
|
||||||
app.setAppUserModelId('Mattermost.Desktop'); // Use explicit AppUserModelID
|
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');
|
const appVersionJson = path.join(app.getPath('userData'), 'app-state.json');
|
||||||
appVersion = new AppVersionManager(appVersionJson);
|
appVersion = new AppVersionManager(appVersionJson);
|
||||||
|
@ -548,7 +569,7 @@ function initializeAfterAppReady() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initCookieManager(session.defaultSession);
|
initCookieManager(defaultSession);
|
||||||
|
|
||||||
WindowManager.showMainWindow(deeplinkingURL);
|
WindowManager.showMainWindow(deeplinkingURL);
|
||||||
|
|
||||||
|
@ -571,7 +592,7 @@ function initializeAfterAppReady() {
|
||||||
}
|
}
|
||||||
setupBadge();
|
setupBadge();
|
||||||
|
|
||||||
session.defaultSession.on('will-download', (event, item, webContents) => {
|
defaultSession.on('will-download', (event, item, webContents) => {
|
||||||
const filename = item.getFilename();
|
const filename = item.getFilename();
|
||||||
const fileElements = filename.split('.');
|
const fileElements = filename.split('.');
|
||||||
const filters = [];
|
const filters = [];
|
||||||
|
@ -609,7 +630,7 @@ function initializeAfterAppReady() {
|
||||||
|
|
||||||
// handle permission requests
|
// handle permission requests
|
||||||
// - approve if a supported permission type and the request comes from the renderer or one of the defined servers
|
// - 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?
|
// is the requested permission type supported?
|
||||||
if (!supportedPermissionTypes.includes(permission)) {
|
if (!supportedPermissionTypes.includes(permission)) {
|
||||||
callback(false);
|
callback(false);
|
||||||
|
|
|
@ -34,6 +34,7 @@ type State = DeepPartial<CombinedConfig> & {
|
||||||
firstRun?: boolean;
|
firstRun?: boolean;
|
||||||
savingState: SavingStateItems;
|
savingState: SavingStateItems;
|
||||||
userOpenedDownloadDialog: boolean;
|
userOpenedDownloadDialog: boolean;
|
||||||
|
allowSaveSpellCheckerURL: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type SavingStateItems = {
|
type SavingStateItems = {
|
||||||
|
@ -62,6 +63,7 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
|
||||||
bounceIconRef: React.RefObject<HTMLInputElement>;
|
bounceIconRef: React.RefObject<HTMLInputElement>;
|
||||||
showUnreadBadgeRef: React.RefObject<HTMLInputElement>;
|
showUnreadBadgeRef: React.RefObject<HTMLInputElement>;
|
||||||
useSpellCheckerRef: React.RefObject<HTMLInputElement>;
|
useSpellCheckerRef: React.RefObject<HTMLInputElement>;
|
||||||
|
spellCheckerURLRef: React.RefObject<HTMLInputElement>;
|
||||||
enableHardwareAccelerationRef: React.RefObject<HTMLInputElement>;
|
enableHardwareAccelerationRef: React.RefObject<HTMLInputElement>;
|
||||||
|
|
||||||
saveQueue: SaveQueueItem[];
|
saveQueue: SaveQueueItem[];
|
||||||
|
@ -77,6 +79,7 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
|
||||||
servers: SavingState.SAVING_STATE_DONE,
|
servers: SavingState.SAVING_STATE_DONE,
|
||||||
},
|
},
|
||||||
userOpenedDownloadDialog: false,
|
userOpenedDownloadDialog: false,
|
||||||
|
allowSaveSpellCheckerURL: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getConfig();
|
this.getConfig();
|
||||||
|
@ -90,6 +93,7 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
|
||||||
this.showUnreadBadgeRef = React.createRef();
|
this.showUnreadBadgeRef = React.createRef();
|
||||||
this.useSpellCheckerRef = React.createRef();
|
this.useSpellCheckerRef = React.createRef();
|
||||||
this.enableHardwareAccelerationRef = React.createRef();
|
this.enableHardwareAccelerationRef = React.createRef();
|
||||||
|
this.spellCheckerURLRef = React.createRef();
|
||||||
|
|
||||||
this.saveQueue = [];
|
this.saveQueue = [];
|
||||||
}
|
}
|
||||||
|
@ -327,6 +331,30 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
|
||||||
this.setState({userOpenedDownloadDialog: false});
|
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) => {
|
updateTeam = (index: number, newData: Team) => {
|
||||||
const teams = this.state.teams || [];
|
const teams = this.state.teams || [];
|
||||||
teams[index] = newData;
|
teams[index] = newData;
|
||||||
|
@ -495,11 +523,58 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
|
||||||
{'Setting takes effect after restarting the app.'}
|
{'Setting takes effect after restarting the app.'}
|
||||||
</FormText>
|
</FormText>
|
||||||
</FormCheck>);
|
</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') {
|
if (window.process.platform === 'darwin' || window.process.platform === 'win32') {
|
||||||
const TASKBAR = window.process.platform === 'win32' ? 'taskbar' : 'Dock';
|
const TASKBAR = window.process.platform === 'win32' ? 'taskbar' : 'Dock';
|
||||||
options.push(
|
options.push(
|
||||||
<FormCheck>
|
<FormCheck
|
||||||
|
key='showunreadbadge'
|
||||||
|
>
|
||||||
<FormCheck.Input
|
<FormCheck.Input
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
key='inputShowUnreadBadge'
|
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') {
|
if (window.process.platform === 'darwin' || window.process.platform === 'linux') {
|
||||||
options.push(
|
options.push(
|
||||||
<FormCheck>
|
<FormCheck
|
||||||
|
key='inputShowTrayIcon'
|
||||||
|
>
|
||||||
<FormCheck.Input
|
<FormCheck.Input
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
key='inputShowTrayIcon'
|
|
||||||
id='inputShowTrayIcon'
|
id='inputShowTrayIcon'
|
||||||
ref={this.showTrayIconRef}
|
ref={this.showTrayIconRef}
|
||||||
checked={this.state.showTrayIcon}
|
checked={this.state.showTrayIcon}
|
||||||
|
@ -652,10 +728,11 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
|
||||||
}
|
}
|
||||||
|
|
||||||
options.push(
|
options.push(
|
||||||
<FormCheck>
|
<FormCheck
|
||||||
|
key='inputEnableHardwareAcceleration'
|
||||||
|
>
|
||||||
<FormCheck.Input
|
<FormCheck.Input
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
key='inputEnableHardwareAcceleration'
|
|
||||||
id='inputEnableHardwareAcceleration'
|
id='inputEnableHardwareAcceleration'
|
||||||
ref={this.enableHardwareAccelerationRef}
|
ref={this.enableHardwareAccelerationRef}
|
||||||
checked={this.state.enableHardwareAcceleration}
|
checked={this.state.enableHardwareAcceleration}
|
||||||
|
@ -670,7 +747,10 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
|
||||||
);
|
);
|
||||||
|
|
||||||
options.push(
|
options.push(
|
||||||
<div style={settingsPage.container}>
|
<div
|
||||||
|
style={settingsPage.container}
|
||||||
|
key='containerDownloadLocation'
|
||||||
|
>
|
||||||
<hr/>
|
<hr/>
|
||||||
<div>{'Download Location'}</div>
|
<div>{'Download Location'}</div>
|
||||||
<input
|
<input
|
||||||
|
|
|
@ -34,6 +34,7 @@ export type ConfigV3 = {
|
||||||
spellCheckerLocale: string;
|
spellCheckerLocale: string;
|
||||||
darkMode: boolean;
|
darkMode: boolean;
|
||||||
downloadLocation: string;
|
downloadLocation: string;
|
||||||
|
spellCheckerURL?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ConfigV2 = {
|
export type ConfigV2 = {
|
||||||
|
@ -56,6 +57,7 @@ export type ConfigV2 = {
|
||||||
enableHardwareAcceleration: boolean;
|
enableHardwareAcceleration: boolean;
|
||||||
autostart: boolean;
|
autostart: boolean;
|
||||||
spellCheckerLocale: string;
|
spellCheckerLocale: string;
|
||||||
|
spellCheckerURL?: string;
|
||||||
darkMode: boolean;
|
darkMode: boolean;
|
||||||
downloadLocation: string;
|
downloadLocation: string;
|
||||||
}
|
}
|
||||||
|
@ -76,6 +78,7 @@ export type ConfigV1 = {
|
||||||
};
|
};
|
||||||
showUnreadBadge: boolean;
|
showUnreadBadge: boolean;
|
||||||
useSpellChecker: boolean;
|
useSpellChecker: boolean;
|
||||||
|
spellCheckerURL?: string;
|
||||||
enableHardwareAcceleration: boolean;
|
enableHardwareAcceleration: boolean;
|
||||||
autostart: boolean;
|
autostart: boolean;
|
||||||
spellCheckerLocale: string;
|
spellCheckerLocale: string;
|
||||||
|
|
Loading…
Reference in a new issue