add visual bell when no replacements are available

also add try/catch in _tabCompleteName so errors don't get swallowed
This commit is contained in:
Bruno Windels 2019-08-28 15:53:16 +02:00
parent c44fbb73d0
commit 85efb71a23
3 changed files with 53 additions and 18 deletions

View file

@ -27,6 +27,15 @@ limitations under the License.
white-space: nowrap;
}
@keyframes visualbell {
from { background-color: #faa; }
to { background-color: $primary-bg-color; }
}
&.mx_BasicMessageComposer_input_error {
animation: 0.2s visualbell;
}
.mx_BasicMessageComposer_input {
white-space: pre-wrap;
word-wrap: break-word;

View file

@ -14,6 +14,8 @@ 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 classNames from 'classnames';
import React from 'react';
import PropTypes from 'prop-types';
import EditorModel from '../../../editor/model';
@ -271,7 +273,7 @@ export default class BasicMessageEditor extends React.Component {
return; // don't preventDefault on anything else
}
} else if (event.key === "Tab") {
this._tabCompleteName(event);
this._tabCompleteName();
handled = true;
}
}
@ -281,20 +283,30 @@ export default class BasicMessageEditor extends React.Component {
}
}
async _tabCompleteName(event) {
const {model} = this.props;
const caret = this.getCaret();
const position = model.positionForOffset(caret.offset, caret.atNodeEnd);
const range = model.startRange(position);
range.expandBackwardsWhile((index, offset, part) => {
return part.text[offset] !== " " && (part.type === "plain" || part.type === "pill-candidate");
});
const {partCreator} = model;
await model.transform(() => {
const addedLen = range.replace([partCreator.pillCandidate(range.text)]);
return model.positionForOffset(caret.offset + addedLen, true);
});
await model.autoComplete.onTab();
async _tabCompleteName() {
try {
await new Promise(resolve => this.setState({showVisualBell: false}, resolve));
const {model} = this.props;
const caret = this.getCaret();
const position = model.positionForOffset(caret.offset, caret.atNodeEnd);
const range = model.startRange(position);
range.expandBackwardsWhile((index, offset, part) => {
return part.text[offset] !== " " && (part.type === "plain" || part.type === "pill-candidate");
});
const {partCreator} = model;
// await for auto-complete to be open
await model.transform(() => {
const addedLen = range.replace([partCreator.pillCandidate(range.text)]);
return model.positionForOffset(caret.offset + addedLen, true);
});
await model.autoComplete.onTab();
if (!model.autoComplete.hasSelection()) {
this.setState({showVisualBell: true});
model.autoComplete.close();
}
} catch (err) {
console.error(err);
}
}
isModified() {
@ -324,7 +336,14 @@ export default class BasicMessageEditor extends React.Component {
// not really, but we could not serialize the parts, and just change the autoCompleter
partCreator.setAutoCompleteCreator(autoCompleteCreator(
() => this._autocompleteRef,
query => new Promise(resolve => this.setState({query}, resolve)),
query => {
return new Promise(resolve => this.setState({query}, resolve));
// if setState
// if (this.state.query === query) {
// return Promise.resolve();
// } else {
// }
},
));
this.historyManager = new HistoryManager(partCreator);
// initial render of model
@ -365,7 +384,10 @@ export default class BasicMessageEditor extends React.Component {
/>
</div>);
}
return (<div className="mx_BasicMessageComposer">
const classes = classNames("mx_BasicMessageComposer", {
"mx_BasicMessageComposer_input_error": this.state.showVisualBell,
});
return (<div className={classes}>
{ autoComplete }
<div
className="mx_BasicMessageComposer_input"

View file

@ -33,6 +33,10 @@ export default class AutocompleteWrapperModel {
});
}
close() {
this._updateCallback({close: true});
}
hasSelection() {
return this._getAutocompleterComponent().hasSelection();
}
@ -67,7 +71,7 @@ export default class AutocompleteWrapperModel {
// so we can restore it in onComponentSelectionChange when the value is undefined (meaning it should be the typed text)
this._queryPart = part;
this._queryOffset = offset;
this._updateQuery(part.text);
return this._updateQuery(part.text);
}
onComponentSelectionChange(completion) {