[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() {
|
||||
return this.combinedData?.useSpellChecker ?? defaultPreferences.useSpellChecker;
|
||||
}
|
||||
|
||||
get spellCheckerURL(): (string|undefined) {
|
||||
return this.combinedData?.spellCheckerURL;
|
||||
}
|
||||
|
||||
get spellCheckerLocale() {
|
||||
return this.combinedData?.spellCheckerLocale ?? defaultPreferences.spellCheckerLocale;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue