Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Add multi language spell check #5452

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2f988bc
Added UI
SimonBrandner Nov 26, 2020
051368e
Fix i18n
SimonBrandner Nov 26, 2020
aba5ef1
Merge branch 'develop' into feature-multi-language-spell-check
SimonBrandner Nov 28, 2020
557e650
Added spell-check-languages setting
SimonBrandner Nov 28, 2020
43daec0
Added setSpellCheckLanguages() method
SimonBrandner Nov 28, 2020
5e4f990
Added persistance
SimonBrandner Nov 28, 2020
f0bbed0
Allow default value
SimonBrandner Nov 29, 2020
8f40cd3
Added styling
SimonBrandner Nov 29, 2020
7609f20
Added newline to end
SimonBrandner Nov 29, 2020
ead00dc
Set spell-check languages without reloading
SimonBrandner Nov 29, 2020
38080c5
Added getAvailableSpellCheckLanguages() methods
SimonBrandner Nov 29, 2020
5d9f5ba
Fix indentation
SimonBrandner Nov 30, 2020
b207a87
Change label
SimonBrandner Dec 1, 2020
cf61d50
Added SpellCheckLanguagesDropdown
SimonBrandner Dec 1, 2020
a6d6af1
Added defaults
SimonBrandner Dec 1, 2020
e9203d7
Removed unnecessary imports
SimonBrandner Dec 1, 2020
8287f19
Fix i18n
SimonBrandner Dec 1, 2020
44a363f
Fix default value
SimonBrandner Dec 1, 2020
3c2bb6e
Cleanup
SimonBrandner Dec 1, 2020
db5bc0c
Fix formatting
SimonBrandner Dec 1, 2020
bab541a
Hide spell-check settings if not using Electron
SimonBrandner Dec 2, 2020
fa19adc
Simplifie
SimonBrandner Dec 3, 2020
b8008b5
Added in if statement
SimonBrandner Dec 3, 2020
19d9ea3
Merge branch 'develop' into feature-multi-language-spell-check
SimonBrandner Dec 3, 2020
bd0e544
Merge branch 'develop' into feature-multi-language-spell-check
SimonBrandner Feb 18, 2021
5de99c7
Fix licenses
SimonBrandner Feb 18, 2021
ed02503
Fix one more license
SimonBrandner Feb 18, 2021
2ebc125
Removed unnecessary functions
SimonBrandner Feb 18, 2021
305d64c
Removed log
SimonBrandner Feb 18, 2021
1ba512a
Use getSpellCheckLanguages() instead of a setting
SimonBrandner Feb 18, 2021
5a6e393
Removed spell-check-languages
SimonBrandner Feb 18, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions res/css/_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@
@import "./views/settings/_DevicesPanel.scss";
@import "./views/settings/_E2eAdvancedPanel.scss";
@import "./views/settings/_EmailAddresses.scss";
@import "./views/settings/_SpellCheckLanguages.scss";
@import "./views/settings/_IntegrationManager.scss";
@import "./views/settings/_Notifications.scss";
@import "./views/settings/_PhoneNumbers.scss";
Expand Down
35 changes: 35 additions & 0 deletions res/css/views/settings/_SpellCheckLanguages.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
Copyright 2021 Šimon Brandner <[email protected]>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

.mx_ExistingSpellCheckLanguage {
display: flex;
align-items: center;
margin-bottom: 5px;
}

.mx_ExistingSpellCheckLanguage_language {
flex: 1;
margin-right: 10px;
}

.mx_GeneralUserSettingsTab_spellCheckLanguageInput {
margin-top: 1em;
margin-bottom: 1em;
}

.mx_SpellCheckLanguages {
@mixin mx_Settings_fullWidthField;
}
18 changes: 18 additions & 0 deletions src/BasePlatform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,14 @@ export default abstract class BasePlatform {
hideUpdateToast();
}

/**
* Return true if platform supports multi-language
* spell-checking, otherwise false.
*/
supportsMultiLanguageSpellCheck(): boolean {
return false;
}

/**
* Returns true if the platform supports displaying
* notifications, otherwise false.
Expand Down Expand Up @@ -240,6 +248,16 @@ export default abstract class BasePlatform {

setLanguage(preferredLangs: string[]) {}

setSpellCheckLanguages(preferredLangs: string[]) {}

getSpellCheckLanguages(): Promise<string[]> | null {
return null;
}

getAvailableSpellCheckLanguages(): Promise<string[]> | null {
return null;
}

protected getSSOCallbackUrl(fragmentAfterLogin: string): URL {
const url = new URL(window.location.href);
url.hash = fragmentAfterLogin || "";
Expand Down
6 changes: 3 additions & 3 deletions src/components/views/elements/LanguageDropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,10 @@ export default class LanguageDropdown extends React.Component {
let language = SettingsStore.getValue("language", null, /*excludeDefault:*/true);
let value = null;
if (language) {
value = this.props.value || language;
value = this.props.value || language;
} else {
language = navigator.language || navigator.userLanguage;
value = this.props.value || language;
language = navigator.language || navigator.userLanguage;
value = this.props.value || language;
}

return <Dropdown
Expand Down
126 changes: 126 additions & 0 deletions src/components/views/elements/SpellCheckLanguagesDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
Copyright 2021 Šimon Brandner <[email protected]>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';

import Dropdown from "../../views/elements/Dropdown"
import * as sdk from '../../../index';
import PlatformPeg from "../../../PlatformPeg";
import SettingsStore from "../../../settings/SettingsStore";
import { _t } from "../../../languageHandler";

function languageMatchesSearchQuery(query, language) {
if (language.label.toUpperCase().includes(query.toUpperCase())) return true;
if (language.value.toUpperCase() === query.toUpperCase()) return true;
return false;
}

interface SpellCheckLanguagesDropdownIProps {
className: string,
value: string,
onOptionChange(language: string),
}

interface SpellCheckLanguagesDropdownIState {
searchQuery: string,
languages: any,
}

export default class SpellCheckLanguagesDropdown extends React.Component<SpellCheckLanguagesDropdownIProps,
SpellCheckLanguagesDropdownIState> {
constructor(props) {
super(props);
this._onSearchChange = this._onSearchChange.bind(this);

this.state = {
searchQuery: '',
languages: null,
};
}

componentDidMount() {
const plaf = PlatformPeg.get();
if (plaf) {
plaf.getAvailableSpellCheckLanguages().then((languages) => {
languages.sort(function(a, b) {
if (a < b) return -1;
if (a > b) return 1;
return 0;
});
const langs = [];
languages.forEach((language) => {
langs.push({
label: language,
value: language,
})
})
this.setState({languages: langs});
}).catch((e) => {
this.setState({languages: ['en']});
});
}
}

_onSearchChange(search) {
this.setState({
searchQuery: search,
});
}

render() {
if (this.state.languages === null) {
const Spinner = sdk.getComponent('elements.Spinner');
return <Spinner />;
}

let displayedLanguages;
if (this.state.searchQuery) {
displayedLanguages = this.state.languages.filter((lang) => {
return languageMatchesSearchQuery(this.state.searchQuery, lang);
});
} else {
displayedLanguages = this.state.languages;
}

const options = displayedLanguages.map((language) => {
return <div key={language.value}>
{ language.label }
</div>;
});

// default value here too, otherwise we need to handle null / undefined;
// values between mounting and the initial value propgating
let language = SettingsStore.getValue("language", null, /*excludeDefault:*/true);
let value = null;
if (language) {
value = this.props.value || language;
} else {
language = navigator.language || navigator.userLanguage;
value = this.props.value || language;
}

return <Dropdown
id="mx_LanguageDropdown"
className={this.props.className}
onOptionChange={this.props.onOptionChange}
onSearchChange={this._onSearchChange}
searchEnabled={true}
value={value}
label={_t("Language Dropdown")}>
{ options }
</Dropdown>;
}
}
111 changes: 111 additions & 0 deletions src/components/views/settings/SpellCheckSettings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
Copyright 2021 Šimon Brandner <[email protected]>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import SpellCheckLanguagesDropdown from "../../../components/views/elements/SpellCheckLanguagesDropdown";
import AccessibleButton from "../../../components/views/elements/AccessibleButton";
import {_t} from "../../../languageHandler";

interface ExistingSpellCheckLanguageIProps {
language: string,
onRemoved(language: string),
}

interface SpellCheckLanguagesIProps {
languages: Array<string>,
onLanguagesChange(languages: Array<string>),
}

interface SpellCheckLanguagesIState {
newLanguage: string,
}

export class ExistingSpellCheckLanguage extends React.Component<ExistingSpellCheckLanguageIProps> {
_onRemove = (e) => {
e.stopPropagation();
e.preventDefault();

return this.props.onRemoved(this.props.language);
};

render() {
return (
<div className="mx_ExistingSpellCheckLanguage">
<span className="mx_ExistingSpellCheckLanguage_language">{this.props.language}</span>
<AccessibleButton onClick={this._onRemove} kind="danger_sm">
{_t("Remove")}
</AccessibleButton>
</div>
);
}
}

export default class SpellCheckLanguages extends React.Component<SpellCheckLanguagesIProps, SpellCheckLanguagesIState> {
constructor(props) {
super(props);
this.state = {
newLanguage: "",
}
}

_onRemoved = (language) => {
const languages = this.props.languages.filter((e) => e !== language);
this.props.onLanguagesChange(languages);
};

_onAddClick = (e) => {
e.stopPropagation();
e.preventDefault();

const language = this.state.newLanguage;

if (!language) return;
if (this.props.languages.includes(language)) return;

this.props.languages.push(language)
this.props.onLanguagesChange(this.props.languages);
};

_onNewLanguageChange = (language: string) => {
if (this.state.newLanguage === language) return;
this.setState({newLanguage: language});
};

render() {
const existingSpellCheckLanguages = this.props.languages.map((e) => {
return <ExistingSpellCheckLanguage language={e} onRemoved={this._onRemoved} key={e} />;
});

const addButton = (
<AccessibleButton onClick={this._onAddClick} kind="primary">
{_t("Add")}
</AccessibleButton>
);

return (
<div className="mx_SpellCheckLanguages">
{existingSpellCheckLanguages}
<form onSubmit={this._onAddClick} noValidate={true}>
<SpellCheckLanguagesDropdown
className="mx_GeneralUserSettingsTab_spellCheckLanguageInput"
value={this.state.newLanguage}
onOptionChange={this._onNewLanguageChange} />
{addButton}
</form>
</div>
);
}
}
Loading