mirror of
https://github.com/element-hq/element-web.git
synced 2024-12-03 03:50:42 +03:00
Merge remote-tracking branch 'origin/develop' into dbkr/groups_better_groupview
This commit is contained in:
commit
696c72be2b
12 changed files with 95 additions and 123 deletions
|
@ -481,7 +481,7 @@ const onMessage = function(event) {
|
||||||
// All strings start with the empty string, so for sanity return if the length
|
// All strings start with the empty string, so for sanity return if the length
|
||||||
// of the event origin is 0.
|
// of the event origin is 0.
|
||||||
let url = SdkConfig.get().integrations_ui_url;
|
let url = SdkConfig.get().integrations_ui_url;
|
||||||
if (event.origin.length === 0 || !url.startsWith(event.origin)) {
|
if (event.origin.length === 0 || !url.startsWith(event.origin) || !event.data.action) {
|
||||||
return; // don't log this - debugging APIs like to spam postMessage which floods the log otherwise
|
return; // don't log this - debugging APIs like to spam postMessage which floods the log otherwise
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,11 +25,6 @@ import { _t } from './languageHandler';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
LABS_FEATURES: [
|
LABS_FEATURES: [
|
||||||
{
|
|
||||||
name: "-",
|
|
||||||
id: 'rich_text_editor',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "-",
|
name: "-",
|
||||||
id: 'matrix_apps',
|
id: 'matrix_apps',
|
||||||
|
@ -39,8 +34,7 @@ export default {
|
||||||
|
|
||||||
// horrible but it works. The locality makes this somewhat more palatable.
|
// horrible but it works. The locality makes this somewhat more palatable.
|
||||||
doTranslations: function() {
|
doTranslations: function() {
|
||||||
this.LABS_FEATURES[0].name = _t("New Composer & Autocomplete");
|
this.LABS_FEATURES[0].name = _t("Matrix Apps");
|
||||||
this.LABS_FEATURES[1].name = _t("Matrix Apps");
|
|
||||||
},
|
},
|
||||||
|
|
||||||
loadProfileInfo: function() {
|
loadProfileInfo: function() {
|
||||||
|
|
|
@ -18,7 +18,7 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { _t } from '../languageHandler';
|
import { _t } from '../languageHandler';
|
||||||
import AutocompleteProvider from './AutocompleteProvider';
|
import AutocompleteProvider from './AutocompleteProvider';
|
||||||
import {emojioneList, shortnameToImage, shortnameToUnicode, asciiRegexp} from 'emojione';
|
import {emojioneList, shortnameToImage, shortnameToUnicode, asciiRegexp, unicodeRegexp} from 'emojione';
|
||||||
import FuzzyMatcher from './FuzzyMatcher';
|
import FuzzyMatcher from './FuzzyMatcher';
|
||||||
import sdk from '../index';
|
import sdk from '../index';
|
||||||
import {PillCompletion} from './Components';
|
import {PillCompletion} from './Components';
|
||||||
|
@ -41,7 +41,15 @@ const CATEGORY_ORDER = [
|
||||||
];
|
];
|
||||||
|
|
||||||
// Match for ":wink:" or ascii-style ";-)" provided by emojione
|
// Match for ":wink:" or ascii-style ";-)" provided by emojione
|
||||||
const EMOJI_REGEX = new RegExp('(' + asciiRegexp + '|:\\w*:?)$', 'g');
|
// (^|\s|(emojiUnicode)) to make sure we're either at the start of the string or there's a
|
||||||
|
// whitespace character or an emoji before the emoji. The reason for unicodeRegexp is
|
||||||
|
// that we need to support inputting multiple emoji with no space between them.
|
||||||
|
const EMOJI_REGEX = new RegExp('(?:^|\\s|' + unicodeRegexp + ')(' + asciiRegexp + '|:\\w*:?)$', 'g');
|
||||||
|
|
||||||
|
// We also need to match the non-zero-length prefixes to remove them from the final match,
|
||||||
|
// and update the range so that we don't replace the whitespace or the previous emoji.
|
||||||
|
const MATCH_PREFIX_REGEX = new RegExp('(\\s|' + unicodeRegexp + ')');
|
||||||
|
|
||||||
const EMOJI_SHORTNAMES = Object.keys(EmojiData).map((key) => EmojiData[key]).sort(
|
const EMOJI_SHORTNAMES = Object.keys(EmojiData).map((key) => EmojiData[key]).sort(
|
||||||
(a, b) => {
|
(a, b) => {
|
||||||
if (a.category === b.category) {
|
if (a.category === b.category) {
|
||||||
|
@ -73,9 +81,18 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
const EmojiText = sdk.getComponent('views.elements.EmojiText');
|
const EmojiText = sdk.getComponent('views.elements.EmojiText');
|
||||||
|
|
||||||
let completions = [];
|
let completions = [];
|
||||||
let {command, range} = this.getCurrentCommand(query, selection);
|
const {command, range} = this.getCurrentCommand(query, selection);
|
||||||
if (command) {
|
if (command) {
|
||||||
completions = this.matcher.match(command[0]).map(result => {
|
let matchedString = command[0];
|
||||||
|
|
||||||
|
// Remove prefix of any length (single whitespace or unicode emoji)
|
||||||
|
const prefixMatch = MATCH_PREFIX_REGEX.exec(matchedString);
|
||||||
|
if (prefixMatch) {
|
||||||
|
matchedString = matchedString.slice(prefixMatch[0].length);
|
||||||
|
range.start += prefixMatch[0].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
completions = this.matcher.match(matchedString).map((result) => {
|
||||||
const {shortname} = result;
|
const {shortname} = result;
|
||||||
const unicode = shortnameToUnicode(shortname);
|
const unicode = shortnameToUnicode(shortname);
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -91,8 +91,8 @@ export default class UserProvider extends AutocompleteProvider {
|
||||||
if (member.userId !== currentUserId) return true;
|
if (member.userId !== currentUserId) return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.users = _sortBy(this.users, (completion) =>
|
this.users = _sortBy(this.users, (member) =>
|
||||||
1E20 - lastSpoken[completion.user.userId] || 1E20,
|
1E20 - lastSpoken[member.userId] || 1E20,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.matcher.setObjects(this.users);
|
this.matcher.setObjects(this.users);
|
||||||
|
|
|
@ -190,7 +190,7 @@ export default React.createClass({
|
||||||
</div>;
|
</div>;
|
||||||
|
|
||||||
let nameNode;
|
let nameNode;
|
||||||
if (summary.profile.name) {
|
if (summary.profile && summary.profile.name) {
|
||||||
nameNode = <div className="mx_RoomHeader_name">
|
nameNode = <div className="mx_RoomHeader_name">
|
||||||
<span>{summary.profile.name}</span>
|
<span>{summary.profile.name}</span>
|
||||||
<span className="mx_GroupView_header_groupid">
|
<span className="mx_GroupView_header_groupid">
|
||||||
|
@ -203,6 +203,8 @@ export default React.createClass({
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const groupAvatarUrl = summary.profile ? summary.profile.avatar_url : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_GroupView">
|
<div className="mx_GroupView">
|
||||||
<div className="mx_RoomHeader">
|
<div className="mx_RoomHeader">
|
||||||
|
@ -210,7 +212,7 @@ export default React.createClass({
|
||||||
<div className="mx_RoomHeader_avatar">
|
<div className="mx_RoomHeader_avatar">
|
||||||
<GroupAvatar
|
<GroupAvatar
|
||||||
groupId={this.props.groupId}
|
groupId={this.props.groupId}
|
||||||
groupAvatarUrl={summary.profile.avatar_url}
|
groupAvatarUrl={groupAvatarUrl}
|
||||||
width={48} height={48}
|
width={48} height={48}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -61,9 +61,6 @@ export default withMatrixClient(React.createClass({
|
||||||
this._fetch();
|
this._fetch();
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
|
||||||
},
|
|
||||||
|
|
||||||
_onCreateGroupClick: function() {
|
_onCreateGroupClick: function() {
|
||||||
const CreateGroupDialog = sdk.getComponent("dialogs.CreateGroupDialog");
|
const CreateGroupDialog = sdk.getComponent("dialogs.CreateGroupDialog");
|
||||||
Modal.createDialog(CreateGroupDialog);
|
Modal.createDialog(CreateGroupDialog);
|
||||||
|
@ -73,7 +70,7 @@ export default withMatrixClient(React.createClass({
|
||||||
this.props.matrixClient.getJoinedGroups().done((result) => {
|
this.props.matrixClient.getJoinedGroups().done((result) => {
|
||||||
this.setState({groups: result.groups, error: null});
|
this.setState({groups: result.groups, error: null});
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
this.setState({result: null, error: err});
|
this.setState({groups: null, error: err});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -93,12 +90,12 @@ export default withMatrixClient(React.createClass({
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
content = <div>
|
content = <div>
|
||||||
<div>{_t('You are a member of these groups')}:</div>
|
<div>{_t('You are a member of these groups:')}</div>
|
||||||
{groupNodes}
|
{groupNodes}
|
||||||
</div>;
|
</div>;
|
||||||
} else if (this.state.error) {
|
} else if (this.state.error) {
|
||||||
content = <div className="mx_MyGroups_error">
|
content = <div className="mx_MyGroups_error">
|
||||||
Error whilst fetching joined groups
|
{_t('Error whilst fetching joined groups')}
|
||||||
</div>;
|
</div>;
|
||||||
} else {
|
} else {
|
||||||
content = <Loader />;
|
content = <Loader />;
|
||||||
|
|
|
@ -130,10 +130,10 @@ export default React.createClass({
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
const Loader = sdk.getComponent("elements.Spinner");
|
const Spinner = sdk.getComponent('elements.Spinner');
|
||||||
|
|
||||||
if (this.state.creating) {
|
if (this.state.creating) {
|
||||||
return <Loader />;
|
return <Spinner />;
|
||||||
}
|
}
|
||||||
|
|
||||||
let createErrorNode;
|
let createErrorNode;
|
||||||
|
@ -154,29 +154,32 @@ export default React.createClass({
|
||||||
>
|
>
|
||||||
<form onSubmit={this._onFormSubmit}>
|
<form onSubmit={this._onFormSubmit}>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
<div className="mx_CreateGroupDialog_label">
|
<div className="mx_CreateGroupDialog_inputRow">
|
||||||
<label htmlFor="groupname">{_t('Group Name')}</label>
|
<div className="mx_CreateGroupDialog_label">
|
||||||
|
<label htmlFor="groupname">{_t('Group Name')}</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input id="groupname" className="mx_CreateGroupDialog_input"
|
||||||
|
autoFocus={true} size="64"
|
||||||
|
placeholder={_t('Example')}
|
||||||
|
onChange={this._onGroupNameChange}
|
||||||
|
value={this.state.groupName}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="mx_CreateGroupDialog_inputRow">
|
||||||
<input id="groupname" className="mx_CreateGroupDialog_input"
|
<div className="mx_CreateGroupDialog_label">
|
||||||
autoFocus={true} size="64"
|
<label htmlFor="groupid">{_t('Group ID')}</label>
|
||||||
placeholder={_t('Example')}
|
</div>
|
||||||
onChange={this._onGroupNameChange}
|
<div>
|
||||||
value={this.state.groupName}
|
<input id="groupid" className="mx_CreateGroupDialog_input"
|
||||||
/>
|
size="64"
|
||||||
</div>
|
placeholder={_t('+example:%(domain)s', {domain: MatrixClientPeg.get().getDomain()})}
|
||||||
<br />
|
onChange={this._onGroupIdChange}
|
||||||
<div className="mx_CreateGroupDialog_label">
|
onBlur={this._onGroupIdBlur}
|
||||||
<label htmlFor="groupid">{_t('Group ID')}</label>
|
value={this.state.groupId}
|
||||||
</div>
|
/>
|
||||||
<div>
|
</div>
|
||||||
<input id="groupid" className="mx_CreateGroupDialog_input"
|
|
||||||
size="64"
|
|
||||||
placeholder={_t('+example:%(domain)s', {domain: MatrixClientPeg.get().getDomain()})}
|
|
||||||
onChange={this._onGroupIdChange}
|
|
||||||
onBlur={this._onGroupIdBlur}
|
|
||||||
value={this.state.groupId}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="error">
|
<div className="error">
|
||||||
{this.state.groupIdError}
|
{this.state.groupIdError}
|
||||||
|
|
|
@ -50,6 +50,7 @@ export default class Autocomplete extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
complete(query, selection) {
|
complete(query, selection) {
|
||||||
|
this.queryRequested = query;
|
||||||
if (this.debounceCompletionsRequest) {
|
if (this.debounceCompletionsRequest) {
|
||||||
clearTimeout(this.debounceCompletionsRequest);
|
clearTimeout(this.debounceCompletionsRequest);
|
||||||
}
|
}
|
||||||
|
@ -74,16 +75,25 @@ export default class Autocomplete extends React.Component {
|
||||||
|
|
||||||
const deferred = Q.defer();
|
const deferred = Q.defer();
|
||||||
this.debounceCompletionsRequest = setTimeout(() => {
|
this.debounceCompletionsRequest = setTimeout(() => {
|
||||||
getCompletions(
|
this.processQuery(query, selection).then(() => {
|
||||||
query, selection, this.state.forceComplete,
|
|
||||||
).then((completions) => {
|
|
||||||
this.processCompletions(completions);
|
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
});
|
});
|
||||||
}, autocompleteDelay);
|
}, autocompleteDelay);
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
processQuery(query, selection) {
|
||||||
|
return getCompletions(
|
||||||
|
query, selection, this.state.forceComplete,
|
||||||
|
).then((completions) => {
|
||||||
|
// Only ever process the completions for the most recent query being processed
|
||||||
|
if (query !== this.queryRequested) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.processCompletions(completions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
processCompletions(completions) {
|
processCompletions(completions) {
|
||||||
const completionList = flatMap(completions, (provider) => provider.completions);
|
const completionList = flatMap(completions, (provider) => provider.completions);
|
||||||
|
|
||||||
|
@ -105,14 +115,9 @@ export default class Autocomplete extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
let hide = this.state.hide;
|
let hide = this.state.hide;
|
||||||
// These are lists of booleans that indicate whether whether the corresponding provider had a matching pattern
|
// If `completion.command.command` is truthy, then a provider has matched with the query
|
||||||
const oldMatches = this.state.completions.map((completion) => !!completion.command.command),
|
const anyMatches = completions.some((completion) => !!completion.command.command);
|
||||||
newMatches = completions.map((completion) => !!completion.command.command);
|
hide = !anyMatches;
|
||||||
|
|
||||||
// So, essentially, we re-show autocomplete if any provider finds a new pattern or stops finding an old one
|
|
||||||
if (!isEqual(oldMatches, newMatches)) {
|
|
||||||
hide = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
completions,
|
completions,
|
||||||
|
|
|
@ -226,21 +226,6 @@ export default class MessageComposer extends React.Component {
|
||||||
this.setState({inputState});
|
this.setState({inputState});
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpArrow() {
|
|
||||||
return this.refs.autocomplete.onUpArrow();
|
|
||||||
}
|
|
||||||
|
|
||||||
onDownArrow() {
|
|
||||||
return this.refs.autocomplete.onDownArrow();
|
|
||||||
}
|
|
||||||
|
|
||||||
_tryComplete(): boolean {
|
|
||||||
if (this.refs.autocomplete) {
|
|
||||||
return this.refs.autocomplete.onCompletionClicked();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onAutocompleteConfirm(range, completion) {
|
_onAutocompleteConfirm(range, completion) {
|
||||||
if (this.messageComposerInput) {
|
if (this.messageComposerInput) {
|
||||||
this.messageComposerInput.setDisplayedCompletion(range, completion);
|
this.messageComposerInput.setDisplayedCompletion(range, completion);
|
||||||
|
@ -267,8 +252,7 @@ export default class MessageComposer extends React.Component {
|
||||||
const uploadInputStyle = {display: 'none'};
|
const uploadInputStyle = {display: 'none'};
|
||||||
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
|
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
|
||||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||||
const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput" +
|
const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput");
|
||||||
(UserSettingsStore.isFeatureEnabled('rich_text_editor') ? "" : "Old"));
|
|
||||||
|
|
||||||
const controls = [];
|
const controls = [];
|
||||||
|
|
||||||
|
@ -351,8 +335,7 @@ export default class MessageComposer extends React.Component {
|
||||||
title={_t("Show Text Formatting Toolbar")}
|
title={_t("Show Text Formatting Toolbar")}
|
||||||
src="img/button-text-formatting.svg"
|
src="img/button-text-formatting.svg"
|
||||||
onClick={this.onToggleFormattingClicked}
|
onClick={this.onToggleFormattingClicked}
|
||||||
style={{visibility: this.state.showFormatting ||
|
style={{visibility: this.state.showFormatting ? 'hidden' : 'visible'}}
|
||||||
!UserSettingsStore.isFeatureEnabled('rich_text_editor') ? 'hidden' : 'visible'}}
|
|
||||||
key="controls_formatting" />
|
key="controls_formatting" />
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -367,10 +350,7 @@ export default class MessageComposer extends React.Component {
|
||||||
room={this.props.room}
|
room={this.props.room}
|
||||||
placeholder={placeholderText}
|
placeholder={placeholderText}
|
||||||
tryComplete={this._tryComplete}
|
tryComplete={this._tryComplete}
|
||||||
onUpArrow={this.onUpArrow}
|
|
||||||
onDownArrow={this.onDownArrow}
|
|
||||||
onFilesPasted={this.uploadFiles}
|
onFilesPasted={this.uploadFiles}
|
||||||
tabComplete={this.props.tabComplete} // used for old messagecomposerinput/tabcomplete
|
|
||||||
onContentChanged={this.onInputContentChanged}
|
onContentChanged={this.onInputContentChanged}
|
||||||
onInputStateChanged={this.onInputStateChanged} />,
|
onInputStateChanged={this.onInputStateChanged} />,
|
||||||
formattingButton,
|
formattingButton,
|
||||||
|
@ -389,18 +369,6 @@ export default class MessageComposer extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let autoComplete;
|
|
||||||
if (UserSettingsStore.isFeatureEnabled('rich_text_editor')) {
|
|
||||||
autoComplete = <div className="mx_MessageComposer_autocomplete_wrapper">
|
|
||||||
<Autocomplete
|
|
||||||
ref="autocomplete"
|
|
||||||
onConfirm={this._onAutocompleteConfirm}
|
|
||||||
query={this.state.autocompleteQuery}
|
|
||||||
selection={this.state.selection} />
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const {style, blockType} = this.state.inputState;
|
const {style, blockType} = this.state.inputState;
|
||||||
const formatButtons = ["bold", "italic", "strike", "underline", "code", "quote", "bullet", "numbullet"].map(
|
const formatButtons = ["bold", "italic", "strike", "underline", "code", "quote", "bullet", "numbullet"].map(
|
||||||
(name) => {
|
(name) => {
|
||||||
|
@ -424,22 +392,20 @@ export default class MessageComposer extends React.Component {
|
||||||
{controls}
|
{controls}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{UserSettingsStore.isFeatureEnabled('rich_text_editor') ?
|
<div className="mx_MessageComposer_formatbar_wrapper">
|
||||||
<div className="mx_MessageComposer_formatbar_wrapper">
|
<div className="mx_MessageComposer_formatbar" style={this.state.showFormatting ? {} : {display: 'none'}}>
|
||||||
<div className="mx_MessageComposer_formatbar" style={this.state.showFormatting ? {} : {display: 'none'}}>
|
{formatButtons}
|
||||||
{formatButtons}
|
<div style={{flex: 1}}></div>
|
||||||
<div style={{flex: 1}}></div>
|
<img title={ this.state.inputState.isRichtextEnabled ? _t("Turn Markdown on") : _t("Turn Markdown off") }
|
||||||
<img title={ this.state.inputState.isRichtextEnabled ? _t("Turn Markdown on") : _t("Turn Markdown off") }
|
onMouseDown={this.onToggleMarkdownClicked}
|
||||||
onMouseDown={this.onToggleMarkdownClicked}
|
className="mx_MessageComposer_formatbar_markdown mx_filterFlipColor"
|
||||||
className="mx_MessageComposer_formatbar_markdown mx_filterFlipColor"
|
src={`img/button-md-${!this.state.inputState.isRichtextEnabled}.png`} />
|
||||||
src={`img/button-md-${!this.state.inputState.isRichtextEnabled}.png`} />
|
<img title={ _t("Hide Text Formatting Toolbar") }
|
||||||
<img title={ _t("Hide Text Formatting Toolbar") }
|
onClick={this.onToggleFormattingClicked}
|
||||||
onClick={this.onToggleFormattingClicked}
|
className="mx_MessageComposer_formatbar_cancel mx_filterFlipColor"
|
||||||
className="mx_MessageComposer_formatbar_cancel mx_filterFlipColor"
|
src="img/icon-text-cancel.svg" />
|
||||||
src="img/icon-text-cancel.svg" />
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>: null
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -514,6 +514,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
const currentBlockType = RichUtils.getCurrentBlockType(this.state.editorState);
|
const currentBlockType = RichUtils.getCurrentBlockType(this.state.editorState);
|
||||||
// If we're in any of these three types of blocks, shift enter should insert soft newlines
|
// If we're in any of these three types of blocks, shift enter should insert soft newlines
|
||||||
// And just enter should end the block
|
// And just enter should end the block
|
||||||
|
// XXX: Empirically enter does not end these blocks
|
||||||
if(['blockquote', 'unordered-list-item', 'ordered-list-item'].includes(currentBlockType)) {
|
if(['blockquote', 'unordered-list-item', 'ordered-list-item'].includes(currentBlockType)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -629,8 +630,6 @@ export default class MessageComposerInput extends React.Component {
|
||||||
editorState: this.createEditorState(),
|
editorState: this.createEditorState(),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.autocomplete.hide();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -909,14 +908,7 @@ MessageComposerInput.propTypes = {
|
||||||
// called with current plaintext content (as a string) whenever it changes
|
// called with current plaintext content (as a string) whenever it changes
|
||||||
onContentChanged: React.PropTypes.func,
|
onContentChanged: React.PropTypes.func,
|
||||||
|
|
||||||
onUpArrow: React.PropTypes.func,
|
|
||||||
|
|
||||||
onDownArrow: React.PropTypes.func,
|
|
||||||
|
|
||||||
onFilesPasted: React.PropTypes.func,
|
onFilesPasted: React.PropTypes.func,
|
||||||
|
|
||||||
// attempts to confirm currently selected completion, returns whether actually confirmed
|
|
||||||
tryComplete: React.PropTypes.func,
|
|
||||||
|
|
||||||
onInputStateChanged: React.PropTypes.func,
|
onInputStateChanged: React.PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
|
@ -948,10 +948,10 @@
|
||||||
"Group IDs must be of the form +localpart:%(domain)s": "Group IDs must be of the form +localpart:%(domain)s",
|
"Group IDs must be of the form +localpart:%(domain)s": "Group IDs must be of the form +localpart:%(domain)s",
|
||||||
"It is currently only possible to create groups on your own home server: use a group ID ending with %(domain)s": "It is currently only possible to create groups on your own home server: use a group ID ending with %(domain)s",
|
"It is currently only possible to create groups on your own home server: use a group ID ending with %(domain)s": "It is currently only possible to create groups on your own home server: use a group ID ending with %(domain)s",
|
||||||
"Room creation failed": "Room creation failed",
|
"Room creation failed": "Room creation failed",
|
||||||
"You are a member of these groups": "You are a member of these groups",
|
"You are a member of these groups:": "You are a member of these groups:",
|
||||||
"Create a group to represent your community! Define a set of rooms and your own custom homepage to mark out your space in the Matrix universe.": "Create a group to represent your community! Define a set of rooms and your own custom homepage to mark out your space in the Matrix universe.",
|
"Create a group to represent your community! Define a set of rooms and your own custom homepage to mark out your space in the Matrix universe.": "Create a group to represent your community! Define a set of rooms and your own custom homepage to mark out your space in the Matrix universe.",
|
||||||
"Join an existing group": "Join an existing group",
|
"Join an existing group": "Join an existing group",
|
||||||
"To join an exisitng group you'll have to know its group identifier; this will look something like <i>+example:matrix.org</i>.": "To join an exisitng group you'll have to know its group identifier; this will look something like <i>+example:matrix.org</i>.",
|
"To join an exisitng group you'll have to know its group identifier; this will look something like <i>+example:matrix.org</i>.": "To join an exisitng group you'll have to know its group identifier; this will look something like <i>+example:matrix.org</i>.",
|
||||||
"Featured Rooms:": "Featured Rooms:",
|
"Featured Rooms:": "Featured Rooms:",
|
||||||
"Autocomplete Delay (ms):": "Autocomplete Delay (ms):"
|
"Error whilst fetching joined groups": "Error whilst fetching joined groups"
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,14 +27,10 @@ describe('MessageComposerInput', () => {
|
||||||
mci = null,
|
mci = null,
|
||||||
room = testUtils.mkStubRoom('!DdJkzRliezrwpNebLk:matrix.org');
|
room = testUtils.mkStubRoom('!DdJkzRliezrwpNebLk:matrix.org');
|
||||||
|
|
||||||
// TODO Remove when RTE is out of labs.
|
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
testUtils.beforeEach(this);
|
testUtils.beforeEach(this);
|
||||||
sandbox = testUtils.stubClient(sandbox);
|
sandbox = testUtils.stubClient(sandbox);
|
||||||
client = MatrixClientPeg.get();
|
client = MatrixClientPeg.get();
|
||||||
UserSettingsStore.isFeatureEnabled = sinon.stub()
|
|
||||||
.withArgs('rich_text_editor').returns(true);
|
|
||||||
|
|
||||||
parentDiv = document.createElement('div');
|
parentDiv = document.createElement('div');
|
||||||
document.body.appendChild(parentDiv);
|
document.body.appendChild(parentDiv);
|
||||||
|
|
Loading…
Reference in a new issue