Merge remote-tracking branch 'origin/develop' into dbkr/groupview_edit

This commit is contained in:
David Baker 2017-07-17 17:23:02 +01:00
commit 7041106bf2
44 changed files with 363 additions and 203 deletions

View file

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import q from 'q';
import Promise from 'bluebird';
import React from 'react';
import Matrix from "matrix-js-sdk";
@ -224,7 +224,7 @@ module.exports = React.createClass({
// Used by _viewRoom before getting state from sync
this.firstSyncComplete = false;
this.firstSyncPromise = q.defer();
this.firstSyncPromise = Promise.defer();
if (this.props.config.sync_timeline_limit) {
MatrixClientPeg.opts.initialSyncLimit = this.props.config.sync_timeline_limit;
@ -323,9 +323,9 @@ module.exports = React.createClass({
return;
}
// the extra q() ensures that synchronous exceptions hit the same codepath as
// the extra Promise.resolve() ensures that synchronous exceptions hit the same codepath as
// asynchronous ones.
return q().then(() => {
return Promise.resolve().then(() => {
return Lifecycle.loadSession({
fragmentQueryParams: this.props.startingFragmentQueryParams,
enableGuest: this.props.enableGuest,
@ -694,7 +694,7 @@ module.exports = React.createClass({
// Wait for the first sync to complete so that if a room does have an alias,
// it would have been retrieved.
let waitFor = q(null);
let waitFor = Promise.resolve(null);
if (!this.firstSyncComplete) {
if (!this.firstSyncPromise) {
console.warn('Cannot view a room before first sync. room_id:', roomInfo.room_id);
@ -1039,7 +1039,7 @@ module.exports = React.createClass({
// since we're about to start the client and therefore about
// to do the first sync
this.firstSyncComplete = false;
this.firstSyncPromise = q.defer();
this.firstSyncPromise = Promise.defer();
const cli = MatrixClientPeg.get();
// Allow the JS SDK to reap timeline events. This reduces the amount of

View file

@ -22,7 +22,7 @@ limitations under the License.
var React = require("react");
var ReactDOM = require("react-dom");
var q = require("q");
import Promise from 'bluebird';
var classNames = require("classnames");
var Matrix = require("matrix-js-sdk");
import { _t } from '../../languageHandler';
@ -775,7 +775,7 @@ module.exports = React.createClass({
onSearchResultsFillRequest: function(backwards) {
if (!backwards) {
return q(false);
return Promise.resolve(false);
}
if (this.state.searchResults.next_batch) {
@ -785,7 +785,7 @@ module.exports = React.createClass({
return this._handleSearchResult(searchPromise);
} else {
debuglog("no more search results");
return q(false);
return Promise.resolve(false);
}
},
@ -846,7 +846,7 @@ module.exports = React.createClass({
return;
}
q().then(() => {
Promise.resolve().then(() => {
const signUrl = this.props.thirdPartyInvite ?
this.props.thirdPartyInvite.inviteSignUrl : undefined;
dis.dispatch({
@ -865,7 +865,7 @@ module.exports = React.createClass({
}
}
}
return q();
return Promise.resolve();
});
},

View file

@ -17,7 +17,7 @@ limitations under the License.
var React = require("react");
var ReactDOM = require("react-dom");
var GeminiScrollbar = require('react-gemini-scrollbar');
var q = require("q");
import Promise from 'bluebird';
var KeyCode = require('../../KeyCode');
var DEBUG_SCROLL = false;
@ -145,7 +145,7 @@ module.exports = React.createClass({
return {
stickyBottom: true,
startAtBottom: true,
onFillRequest: function(backwards) { return q(false); },
onFillRequest: function(backwards) { return Promise.resolve(false); },
onUnfillRequest: function(backwards, scrollToken) {},
onScroll: function() {},
};
@ -386,19 +386,12 @@ module.exports = React.createClass({
debuglog("ScrollPanel: starting "+dir+" fill");
// onFillRequest can end up calling us recursively (via onScroll
// events) so make sure we set this before firing off the call. That
// does present the risk that we might not ever actually fire off the
// fill request, so wrap it in a try/catch.
// events) so make sure we set this before firing off the call.
this._pendingFillRequests[dir] = true;
var fillPromise;
try {
fillPromise = this.props.onFillRequest(backwards);
} catch (e) {
this._pendingFillRequests[dir] = false;
throw e;
}
q.finally(fillPromise, () => {
Promise.try(() => {
return this.props.onFillRequest(backwards);
}).finally(() => {
this._pendingFillRequests[dir] = false;
}).then((hasMoreResults) => {
if (this.unmounted) {

View file

@ -17,7 +17,7 @@ limitations under the License.
var React = require('react');
var ReactDOM = require("react-dom");
var q = require("q");
import Promise from 'bluebird';
var Matrix = require("matrix-js-sdk");
var EventTimeline = Matrix.EventTimeline;
@ -311,13 +311,13 @@ var TimelinePanel = React.createClass({
if (!this.state[canPaginateKey]) {
debuglog("TimelinePanel: have given up", dir, "paginating this timeline");
return q(false);
return Promise.resolve(false);
}
if(!this._timelineWindow.canPaginate(dir)) {
debuglog("TimelinePanel: can't", dir, "paginate any further");
this.setState({[canPaginateKey]: false});
return q(false);
return Promise.resolve(false);
}
debuglog("TimelinePanel: Initiating paginate; backwards:"+backwards);

View file

@ -22,7 +22,7 @@ const PlatformPeg = require("../../PlatformPeg");
const Modal = require('../../Modal');
const dis = require("../../dispatcher");
import sessionStore from '../../stores/SessionStore';
const q = require('q');
import Promise from 'bluebird';
const packageJson = require('../../../package.json');
const UserSettingsStore = require('../../UserSettingsStore');
const CallMediaHandler = require('../../CallMediaHandler');
@ -93,6 +93,10 @@ const SETTINGS_LABELS = [
id: 'enableSyntaxHighlightLanguageDetection',
label: 'Enable automatic language detection for syntax highlighting',
},
{
id: 'MessageComposerInput.autoReplaceEmoji',
label: 'Automatically replace plain text Emoji',
},
/*
{
id: 'useFixedWidthFont',
@ -199,7 +203,7 @@ module.exports = React.createClass({
this._addThreepid = null;
if (PlatformPeg.get()) {
q().then(() => {
Promise.resolve().then(() => {
return PlatformPeg.get().getAppVersion();
}).done((appVersion) => {
if (this._unmounted) return;
@ -297,7 +301,7 @@ module.exports = React.createClass({
},
_refreshMediaDevices: function() {
q().then(() => {
Promise.resolve().then(() => {
return CallMediaHandler.getDevices();
}).then((mediaDevices) => {
// console.log("got mediaDevices", mediaDevices, this._unmounted);
@ -312,7 +316,7 @@ module.exports = React.createClass({
_refreshFromServer: function() {
const self = this;
q.all([
Promise.all([
UserSettingsStore.loadProfileInfo(), UserSettingsStore.loadThreePids(),
]).done(function(resps) {
self.setState({
@ -564,15 +568,16 @@ module.exports = React.createClass({
});
// reject the invites
const promises = rooms.map((room) => {
return MatrixClientPeg.get().leave(room.roomId);
return MatrixClientPeg.get().leave(room.roomId).catch((e) => {
// purposefully drop errors to the floor: we'll just have a non-zero number on the UI
// after trying to reject all the invites.
});
});
// purposefully drop errors to the floor: we'll just have a non-zero number on the UI
// after trying to reject all the invites.
q.allSettled(promises).then(() => {
Promise.all(promises).then(() => {
this.setState({
rejectingInvites: false,
});
}).done();
});
},
_onExportE2eKeysClicked: function() {

View file

@ -17,7 +17,7 @@ limitations under the License.
import Matrix from 'matrix-js-sdk';
import q from 'q';
import Promise from 'bluebird';
import React from 'react';
import sdk from '../../../index';
@ -180,7 +180,7 @@ module.exports = React.createClass({
// will just nop. The point of this being we might not have the email address
// that the user registered with at this stage (depending on whether this
// is the client they initiated registration).
let trackPromise = q(null);
let trackPromise = Promise.resolve(null);
if (this._rtsClient && extra.emailSid) {
// Track referral if this.props.referrer set, get team_token in order to
// retrieve team config and see welcome page etc.
@ -232,7 +232,7 @@ module.exports = React.createClass({
_setupPushers: function(matrixClient) {
if (!this.props.brand) {
return q();
return Promise.resolve();
}
return matrixClient.getPushers().then((resp)=>{
const pushers = resp.pushers;

View file

@ -23,7 +23,7 @@ import MatrixClientPeg from '../../../MatrixClientPeg';
import DMRoomMap from '../../../utils/DMRoomMap';
import Modal from '../../../Modal';
import AccessibleButton from '../elements/AccessibleButton';
import q from 'q';
import Promise from 'bluebird';
import dis from '../../../dispatcher';
const TRUNCATE_QUERY_LIST = 40;
@ -498,7 +498,7 @@ module.exports = React.createClass({
}
// wait a bit to let the user finish typing
return q.delay(500).then(() => {
return Promise.delay(500).then(() => {
if (cancelled) return null;
return MatrixClientPeg.get().lookupThreePid(medium, address);
}).then((res) => {

View file

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import q from 'q';
import Promise from 'bluebird';
import React from 'react';
import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg';

View file

@ -21,7 +21,9 @@ import React from 'react';
import MatrixClientPeg from '../../../MatrixClientPeg';
import ScalarAuthClient from '../../../ScalarAuthClient';
import SdkConfig from '../../../SdkConfig';
import Modal from '../../../Modal';
import { _t } from '../../../languageHandler';
import sdk from '../../../index';
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
@ -33,6 +35,7 @@ export default React.createClass({
url: React.PropTypes.string.isRequired,
name: React.PropTypes.string.isRequired,
room: React.PropTypes.object.isRequired,
type: React.PropTypes.string.isRequired,
},
getDefaultProps: function() {
@ -86,8 +89,13 @@ export default React.createClass({
});
},
_onEditClick: function() {
console.log("Edit widget %s", this.props.id);
_onEditClick: function(e) {
console.log("Edit widget ID ", this.props.id);
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
const src = this._scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId, 'type_' + this.props.type);
Modal.createDialog(IntegrationsManager, {
src: src,
}, "mx_IntegrationsManager");
},
_onDeleteClick: function() {
@ -120,10 +128,10 @@ export default React.createClass({
<div> Loading... </div>
);
} else {
// Note that there is advice saying allow-scripts shouldn;t be used with allow-same-origin
// Note that there is advice saying allow-scripts shouldn't be used with allow-same-origin
// because that would allow the iframe to prgramatically remove the sandbox attribute, but
// this would only be for content hosted on the same origin as the riot client: anything
// hosted on the same origin as the client will get the same access access as if you clicked
// hosted on the same origin as the client will get the same access as if you clicked
// a link to it.
const sandboxFlags = "allow-forms allow-popups allow-popups-to-escape-sandbox "+
"allow-same-origin allow-scripts";
@ -140,18 +148,22 @@ export default React.createClass({
</div>
);
}
// editing is done in scalar
const showEditButton = Boolean(this._scalarClient);
return (
<div className={this.props.fullWidth ? "mx_AppTileFullWidth" : "mx_AppTile"} id={this.props.id}>
<div className="mx_AppTileMenuBar">
{this.formatAppTileName()}
<span className="mx_AppTileMenuBarWidgets">
{/* Edit widget */}
{/* <img
{showEditButton && <img
src="img/edit.svg"
className="mx_filterFlipColor mx_AppTileMenuBarWidget mx_AppTileMenuBarWidgetPadding"
width="8" height="8" alt="Edit"
onClick={this._onEditClick}
/> */}
/>}
{/* Delete widget */}
<img src="img/cancel.svg"

View file

@ -16,7 +16,7 @@ limitations under the License.
import React from 'react';
import sdk from '../../../index';
import q from 'q';
import Promise from 'bluebird';
/**
* A component which wraps an EditableText, with a spinner while updates take
@ -148,5 +148,5 @@ EditableTextContainer.defaultProps = {
initialValue: "",
placeholder: "",
blurToSubmit: false,
onSubmit: function(v) {return q(); },
onSubmit: function(v) {return Promise.resolve(); },
};

View file

@ -24,7 +24,7 @@ import Modal from '../../../Modal';
import sdk from '../../../index';
import dis from '../../../dispatcher';
import { decryptFile, readBlobAsDataUri } from '../../../utils/DecryptFile';
import q from 'q';
import Promise from 'bluebird';
import UserSettingsStore from '../../../UserSettingsStore';
import { _t } from '../../../languageHandler';
@ -123,7 +123,7 @@ module.exports = React.createClass({
this.fixupHeight();
const content = this.props.mxEvent.getContent();
if (content.file !== undefined && this.state.decryptedUrl === null) {
let thumbnailPromise = q(null);
let thumbnailPromise = Promise.resolve(null);
if (content.info.thumbnail_file) {
thumbnailPromise = decryptFile(
content.info.thumbnail_file,

View file

@ -20,7 +20,7 @@ import React from 'react';
import MFileBody from './MFileBody';
import MatrixClientPeg from '../../../MatrixClientPeg';
import { decryptFile, readBlobAsDataUri } from '../../../utils/DecryptFile';
import q from 'q';
import Promise from 'bluebird';
import UserSettingsStore from '../../../UserSettingsStore';
import { _t } from '../../../languageHandler';
@ -89,7 +89,7 @@ module.exports = React.createClass({
componentDidMount: function() {
const content = this.props.mxEvent.getContent();
if (content.file !== undefined && this.state.decryptedUrl === null) {
var thumbnailPromise = q(null);
var thumbnailPromise = Promise.resolve(null);
if (content.info.thumbnail_file) {
thumbnailPromise = decryptFile(
content.info.thumbnail_file

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
var q = require("q");
import Promise from 'bluebird';
var React = require('react');
var ObjectUtils = require("../../../ObjectUtils");
var MatrixClientPeg = require('../../../MatrixClientPeg');
@ -104,7 +104,7 @@ module.exports = React.createClass({
}
if (oldCanonicalAlias !== this.state.canonicalAlias) {
console.log("AliasSettings: Updating canonical alias");
promises = [q.all(promises).then(
promises = [Promise.all(promises).then(
MatrixClientPeg.get().sendStateEvent(
this.props.roomId, "m.room.canonical_alias", {
alias: this.state.canonicalAlias

View file

@ -13,7 +13,7 @@ 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.
*/
var q = require("q");
import Promise from 'bluebird';
var React = require('react');
var sdk = require('../../../index');
@ -72,7 +72,7 @@ module.exports = React.createClass({
saveSettings: function() { // : Promise
if (!this.state.hasChanged) {
return q(); // They didn't explicitly give a color to save.
return Promise.resolve(); // They didn't explicitly give a color to save.
}
var originalState = this.getInitialState();
if (originalState.primary_color !== this.state.primary_color ||
@ -92,7 +92,7 @@ module.exports = React.createClass({
}
});
}
return q(); // no color diff
return Promise.resolve(); // no color diff
},
_getColorIndex: function(scheme) {

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
var q = require("q");
import Promise from 'bluebird';
var React = require('react');
var MatrixClientPeg = require('../../../MatrixClientPeg');
var sdk = require("../../../index");

View file

@ -169,6 +169,7 @@ module.exports = React.createClass({
id={app.id}
url={app.url}
name={app.name}
type={app.type}
fullWidth={arr.length<2 ? true : false}
room={this.props.room}
userId={this.props.userId}

View file

@ -5,7 +5,7 @@ import flatMap from 'lodash/flatMap';
import isEqual from 'lodash/isEqual';
import sdk from '../../../index';
import type {Completion} from '../../../autocomplete/Autocompleter';
import Q from 'q';
import Promise from 'bluebird';
import UserSettingsStore from '../../../UserSettingsStore';
import {getCompletions} from '../../../autocomplete/Autocompleter';
@ -64,7 +64,7 @@ export default class Autocomplete extends React.Component {
// Hide the autocomplete box
hide: true,
});
return Q(null);
return Promise.resolve(null);
}
let autocompleteDelay = UserSettingsStore.getLocalSetting('autocompleteDelay', 200);
@ -73,7 +73,7 @@ export default class Autocomplete extends React.Component {
autocompleteDelay = 0;
}
const deferred = Q.defer();
const deferred = Promise.defer();
this.debounceCompletionsRequest = setTimeout(() => {
this.processQuery(query, selection).then(() => {
deferred.resolve();
@ -176,7 +176,7 @@ export default class Autocomplete extends React.Component {
}
forceComplete() {
const done = Q.defer();
const done = Promise.defer();
this.setState({
forceComplete: true,
hide: false,

View file

@ -17,7 +17,7 @@ var React = require('react');
import { _t } from '../../../languageHandler';
var classNames = require('classnames');
var Matrix = require("matrix-js-sdk");
var q = require('q');
import Promise from 'bluebird';
var MatrixClientPeg = require("../../../MatrixClientPeg");
var Modal = require("../../../Modal");
var Entities = require("../../../Entities");

View file

@ -41,7 +41,6 @@ export default class MessageComposer extends React.Component {
this.onToggleMarkdownClicked = this.onToggleMarkdownClicked.bind(this);
this.onInputStateChanged = this.onInputStateChanged.bind(this);
this.onEvent = this.onEvent.bind(this);
this.onPageUnload = this.onPageUnload.bind(this);
this.state = {
autocompleteQuery: '',
@ -62,21 +61,12 @@ export default class MessageComposer extends React.Component {
// marked as encrypted.
// XXX: fragile as all hell - fixme somehow, perhaps with a dedicated Room.encryption event or something.
MatrixClientPeg.get().on("event", this.onEvent);
window.addEventListener('beforeunload', this.onPageUnload);
}
componentWillUnmount() {
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("event", this.onEvent);
}
window.removeEventListener('beforeunload', this.onPageUnload);
}
onPageUnload(event) {
if (this.messageComposerInput) {
this.messageComposerInput.sentHistory.saveLastTextEntry();
}
}
onEvent(event) {

View file

@ -16,16 +16,17 @@ limitations under the License.
import React from 'react';
import type SyntheticKeyboardEvent from 'react/lib/SyntheticKeyboardEvent';
import {Editor, EditorState, RichUtils, CompositeDecorator,
convertFromRaw, convertToRaw, Modifier, EditorChangeType,
getDefaultKeyBinding, KeyBindingUtil, ContentState, ContentBlock, SelectionState} from 'draft-js';
import {Editor, EditorState, RichUtils, CompositeDecorator, Modifier,
getDefaultKeyBinding, KeyBindingUtil, ContentState, ContentBlock, SelectionState,
Entity} from 'draft-js';
import classNames from 'classnames';
import escape from 'lodash/escape';
import Q from 'q';
import Promise from 'bluebird';
import MatrixClientPeg from '../../../MatrixClientPeg';
import type {MatrixClient} from 'matrix-js-sdk/lib/matrix';
import {RoomMember} from 'matrix-js-sdk';
import SlashCommands from '../../../SlashCommands';
import KeyCode from '../../../KeyCode';
import Modal from '../../../Modal';
@ -43,6 +44,14 @@ import Markdown from '../../../Markdown';
import ComposerHistoryManager from '../../../ComposerHistoryManager';
import MessageComposerStore from '../../../stores/MessageComposerStore';
import {MATRIXTO_URL_PATTERN} from '../../../linkify-matrix';
const REGEX_MATRIXTO = new RegExp(MATRIXTO_URL_PATTERN);
import {asciiRegexp, shortnameToUnicode, emojioneList, asciiList, mapUnicodeToShort} from 'emojione';
const EMOJI_SHORTNAMES = Object.keys(emojioneList);
const EMOJI_UNICODE_TO_SHORTNAME = mapUnicodeToShort();
const REGEX_EMOJI_WHITESPACE = new RegExp('(' + asciiRegexp + ')\\s$');
const TYPING_USER_TIMEOUT = 10000, TYPING_SERVER_TIMEOUT = 30000;
const ZWS_CODE = 8203;
@ -156,15 +165,72 @@ export default class MessageComposerInput extends React.Component {
this.client = MatrixClientPeg.get();
}
findLinkEntities(contentBlock, callback) {
contentBlock.findEntityRanges(
(character) => {
const entityKey = character.getEntity();
return (
entityKey !== null &&
Entity.get(entityKey).getType() === 'LINK'
);
}, callback,
);
}
/*
* "Does the right thing" to create an EditorState, based on:
* - whether we've got rich text mode enabled
* - contentState was passed in
*/
createEditorState(richText: boolean, contentState: ?ContentState): EditorState {
let decorators = richText ? RichText.getScopedRTDecorators(this.props) :
RichText.getScopedMDDecorators(this.props),
compositeDecorator = new CompositeDecorator(decorators);
const decorators = richText ? RichText.getScopedRTDecorators(this.props) :
RichText.getScopedMDDecorators(this.props);
decorators.push({
strategy: this.findLinkEntities.bind(this),
component: (props) => {
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
const RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
const {url} = Entity.get(props.entityKey).getData();
const matrixToMatch = REGEX_MATRIXTO.exec(url);
const isUserPill = matrixToMatch[2] === '@';
const isRoomPill = matrixToMatch[2] === '#' || matrixToMatch[2] === '!';
const classes = classNames({
"mx_UserPill": isUserPill,
"mx_RoomPill": isRoomPill,
});
let avatar = null;
if (isUserPill) {
// If this user is not a member of this room, default to the empty
// member. This could be improved by doing an async profile lookup.
const member = this.props.room.getMember(matrixToMatch[1]) ||
new RoomMember(null, matrixToMatch[1]);
avatar = member ? <MemberAvatar member={member} width={16} height={16}/> : null;
} else if (isRoomPill) {
const room = matrixToMatch[2] === '#' ?
MatrixClientPeg.get().getRooms().find((r) => {
return r.getCanonicalAlias() === matrixToMatch[1];
}) : MatrixClientPeg.get().getRoom(matrixToMatch[1]);
avatar = room ? <RoomAvatar room={room} width={16} height={16}/> : null;
}
if (isUserPill || isRoomPill) {
return (
<span className={classes}>
{avatar}
{props.children}
</span>
);
}
return (
<a href={url}>
{props.children}
</a>
);
},
});
const compositeDecorator = new CompositeDecorator(decorators);
let editorState = null;
if (contentState) {
@ -319,6 +385,60 @@ export default class MessageComposerInput extends React.Component {
onEditorContentChanged = (editorState: EditorState) => {
editorState = RichText.attachImmutableEntitiesToEmoji(editorState);
const currentBlock = editorState.getSelection().getStartKey();
const currentSelection = editorState.getSelection();
const currentStartOffset = editorState.getSelection().getStartOffset();
const block = editorState.getCurrentContent().getBlockForKey(currentBlock);
const text = block.getText();
const entityBeforeCurrentOffset = block.getEntityAt(currentStartOffset - 1);
const entityAtCurrentOffset = block.getEntityAt(currentStartOffset);
// If the cursor is on the boundary between an entity and a non-entity and the
// text before the cursor has whitespace at the end, set the entity state of the
// character before the cursor (the whitespace) to null. This allows the user to
// stop editing the link.
if (entityBeforeCurrentOffset && !entityAtCurrentOffset &&
/\s$/.test(text.slice(0, currentStartOffset))) {
editorState = RichUtils.toggleLink(
editorState,
currentSelection.merge({
anchorOffset: currentStartOffset - 1,
focusOffset: currentStartOffset,
}),
null,
);
// Reset selection
editorState = EditorState.forceSelection(editorState, currentSelection);
}
// Automatic replacement of plaintext emoji to Unicode emoji
if (UserSettingsStore.getSyncedSetting('MessageComposerInput.autoReplaceEmoji', false)) {
// The first matched group includes just the matched plaintext emoji
const emojiMatch = REGEX_EMOJI_WHITESPACE.exec(text.slice(0, currentStartOffset));
if(emojiMatch) {
// plaintext -> hex unicode
const emojiUc = asciiList[emojiMatch[1]];
// hex unicode -> shortname -> actual unicode
const unicodeEmoji = shortnameToUnicode(EMOJI_UNICODE_TO_SHORTNAME[emojiUc]);
const newContentState = Modifier.replaceText(
editorState.getCurrentContent(),
currentSelection.merge({
anchorOffset: currentStartOffset - emojiMatch[0].length,
focusOffset: currentStartOffset,
}),
unicodeEmoji,
);
editorState = EditorState.push(
editorState,
newContentState,
'insert-characters',
);
editorState = EditorState.forceSelection(editorState, newContentState.getSelectionAfter());
}
}
/* Since a modification was made, set originalEditorState to null, since newState is now our original */
this.setState({
editorState,
@ -517,10 +637,13 @@ export default class MessageComposerInput extends React.Component {
}
const currentBlockType = RichUtils.getCurrentBlockType(this.state.editorState);
// If we're in any of these three types of blocks, shift enter should insert soft newlines
// 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(
['code-block', 'blockquote', 'unordered-list-item', 'ordered-list-item']
.includes(currentBlockType)
) {
// By returning false, we allow the default draft-js key binding to occur,
// which in this case invokes "split-block". This creates a new block of the
// same type, allowing the user to delete it with backspace.
return false;
}
@ -581,6 +704,15 @@ export default class MessageComposerInput extends React.Component {
}
});
}
if (!shouldSendHTML) {
const hasLink = blocks.some((block) => {
return block.getCharacterList().filter((c) => {
const entityKey = c.getEntity();
return entityKey && Entity.get(entityKey).getType() === 'LINK';
}).size > 0;
});
shouldSendHTML = hasLink;
}
if (shouldSendHTML) {
contentHTML = HtmlUtils.processHtmlForSending(
RichText.contentStateToHTML(contentState),

View file

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import q from 'q';
import Promise from 'bluebird';
import React from 'react';
import { _t, _tJsx } from '../../../languageHandler';
import MatrixClientPeg from '../../../MatrixClientPeg';
@ -183,8 +183,14 @@ module.exports = React.createClass({
});
},
/**
* Returns a promise which resolves once all of the save operations have completed or failed.
*
* The result is a list of promise state snapshots, each with the form
* `{ state: "fulfilled", value: v }` or `{ state: "rejected", reason: r }`.
*/
save: function() {
var stateWasSetDefer = q.defer();
var stateWasSetDefer = Promise.defer();
// the caller may have JUST called setState on stuff, so we need to re-render before saving
// else we won't use the latest values of things.
// We can be a bit cheeky here and set a loading flag, and listen for the callback on that
@ -194,8 +200,18 @@ module.exports = React.createClass({
this.setState({ _loading: false});
});
function mapPromiseToSnapshot(p) {
return p.then((r) => {
return { state: "fulfilled", value: r };
}, (e) => {
return { state: "rejected", reason: e };
});
}
return stateWasSetDefer.promise.then(() => {
return q.allSettled(this._calcSavePromises());
return Promise.all(
this._calcSavePromises().map(mapPromiseToSnapshot),
);
});
},
@ -282,7 +298,7 @@ module.exports = React.createClass({
// color scheme
var p;
p = this.saveColor();
if (!q.isFulfilled(p)) {
if (!p.isFulfilled()) {
promises.push(p);
}
@ -294,7 +310,7 @@ module.exports = React.createClass({
// encryption
p = this.saveEnableEncryption();
if (!q.isFulfilled(p)) {
if (!p.isFulfilled()) {
promises.push(p);
}
@ -305,25 +321,25 @@ module.exports = React.createClass({
},
saveAliases: function() {
if (!this.refs.alias_settings) { return [q()]; }
if (!this.refs.alias_settings) { return [Promise.resolve()]; }
return this.refs.alias_settings.saveSettings();
},
saveColor: function() {
if (!this.refs.color_settings) { return q(); }
if (!this.refs.color_settings) { return Promise.resolve(); }
return this.refs.color_settings.saveSettings();
},
saveUrlPreviewSettings: function() {
if (!this.refs.url_preview_settings) { return q(); }
if (!this.refs.url_preview_settings) { return Promise.resolve(); }
return this.refs.url_preview_settings.saveSettings();
},
saveEnableEncryption: function() {
if (!this.refs.encrypt) { return q(); }
if (!this.refs.encrypt) { return Promise.resolve(); }
var encrypt = this.refs.encrypt.checked;
if (!encrypt) { return q(); }
if (!encrypt) { return Promise.resolve(); }
var roomId = this.props.room.roomId;
return MatrixClientPeg.get().sendStateEvent(

View file

@ -21,7 +21,7 @@ var MatrixClientPeg = require("../../../MatrixClientPeg");
var Modal = require("../../../Modal");
var sdk = require("../../../index");
import q from 'q';
import Promise from 'bluebird';
import AccessibleButton from '../elements/AccessibleButton';
import { _t } from '../../../languageHandler';
@ -161,7 +161,7 @@ module.exports = React.createClass({
},
_optionallySetEmail: function() {
const deferred = q.defer();
const deferred = Promise.defer();
// Ask for an email otherwise the user has no way to reset their password
const SetEmailDialog = sdk.getComponent("dialogs.SetEmailDialog");
Modal.createDialog(SetEmailDialog, {