Merge remote-tracking branch 'origin/develop' into dbkr/groupview_edit
This commit is contained in:
commit
7041106bf2
44 changed files with 363 additions and 203 deletions
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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(); },
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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, {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue