diff --git a/.babelrc b/.babelrc
index 8c7b66269d..6ba0e0dae0 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,4 +1,4 @@
{
"presets": ["react", "es2015", "es2016"],
- "plugins": ["transform-class-properties", "transform-object-rest-spread", "transform-async-to-generator", "transform-runtime", "add-module-exports"]
+ "plugins": ["transform-class-properties", "transform-object-rest-spread", "transform-async-to-bluebird", "transform-runtime", "add-module-exports"]
}
diff --git a/package.json b/package.json
index 2b637f68c6..9daaa38da7 100644
--- a/package.json
+++ b/package.json
@@ -46,6 +46,7 @@
},
"dependencies": {
"babel-runtime": "^6.11.6",
+ "bluebird": "^3.5.0",
"blueimp-canvas-to-blob": "^3.5.0",
"browser-encrypt-attachment": "^0.3.0",
"browser-request": "^0.3.3",
@@ -68,7 +69,6 @@
"matrix-js-sdk": "matrix-org/matrix-js-sdk#develop",
"optimist": "^0.6.1",
"prop-types": "^15.5.8",
- "q": "^1.4.1",
"react": "^15.4.0",
"react-addons-css-transition-group": "15.3.2",
"react-dom": "^15.4.0",
@@ -85,7 +85,7 @@
"babel-eslint": "^6.1.2",
"babel-loader": "^6.2.5",
"babel-plugin-add-module-exports": "^0.2.1",
- "babel-plugin-transform-async-to-generator": "^6.16.0",
+ "babel-plugin-transform-async-to-bluebird": "^1.1.1",
"babel-plugin-transform-class-properties": "^6.16.0",
"babel-plugin-transform-object-rest-spread": "^6.16.0",
"babel-plugin-transform-runtime": "^6.15.0",
diff --git a/src/ContentMessages.js b/src/ContentMessages.js
index 315c312b9f..9239de9d8f 100644
--- a/src/ContentMessages.js
+++ b/src/ContentMessages.js
@@ -16,7 +16,7 @@ limitations under the License.
'use strict';
-var q = require('q');
+import Promise from 'bluebird';
var extend = require('./extend');
var dis = require('./dispatcher');
var MatrixClientPeg = require('./MatrixClientPeg');
@@ -52,7 +52,7 @@ const MAX_HEIGHT = 600;
* and a thumbnail key.
*/
function createThumbnail(element, inputWidth, inputHeight, mimeType) {
- const deferred = q.defer();
+ const deferred = Promise.defer();
var targetWidth = inputWidth;
var targetHeight = inputHeight;
@@ -95,7 +95,7 @@ function createThumbnail(element, inputWidth, inputHeight, mimeType) {
* @return {Promise} A promise that resolves with the html image element.
*/
function loadImageElement(imageFile) {
- const deferred = q.defer();
+ const deferred = Promise.defer();
// Load the file into an html element
const img = document.createElement("img");
@@ -154,7 +154,7 @@ function infoForImageFile(matrixClient, roomId, imageFile) {
* @return {Promise} A promise that resolves with the video image element.
*/
function loadVideoElement(videoFile) {
- const deferred = q.defer();
+ const deferred = Promise.defer();
// Load the file into an html element
const video = document.createElement("video");
@@ -210,7 +210,7 @@ function infoForVideoFile(matrixClient, roomId, videoFile) {
* is read.
*/
function readFileAsArrayBuffer(file) {
- const deferred = q.defer();
+ const deferred = Promise.defer();
const reader = new FileReader();
reader.onload = function(e) {
deferred.resolve(e.target.result);
@@ -229,11 +229,13 @@ function readFileAsArrayBuffer(file) {
* @param {MatrixClient} matrixClient The matrix client to upload the file with.
* @param {String} roomId The ID of the room being uploaded to.
* @param {File} file The file to upload.
+ * @param {Function?} progressHandler optional callback to be called when a chunk of
+ * data is uploaded.
* @return {Promise} A promise that resolves with an object.
* If the file is unencrypted then the object will have a "url" key.
* If the file is encrypted then the object will have a "file" key.
*/
-function uploadFile(matrixClient, roomId, file) {
+function uploadFile(matrixClient, roomId, file, progressHandler) {
if (matrixClient.isRoomEncrypted(roomId)) {
// If the room is encrypted then encrypt the file before uploading it.
// First read the file into memory.
@@ -245,7 +247,9 @@ function uploadFile(matrixClient, roomId, file) {
const encryptInfo = encryptResult.info;
// Pass the encrypted data as a Blob to the uploader.
const blob = new Blob([encryptResult.data]);
- return matrixClient.uploadContent(blob).then(function(url) {
+ return matrixClient.uploadContent(blob, {
+ progressHandler: progressHandler,
+ }).then(function(url) {
// If the attachment is encrypted then bundle the URL along
// with the information needed to decrypt the attachment and
// add it under a file key.
@@ -257,7 +261,9 @@ function uploadFile(matrixClient, roomId, file) {
});
});
} else {
- const basePromise = matrixClient.uploadContent(file);
+ const basePromise = matrixClient.uploadContent(file, {
+ progressHandler: progressHandler,
+ });
const promise1 = basePromise.then(function(url) {
// If the attachment isn't encrypted then include the URL directly.
return {"url": url};
@@ -288,7 +294,7 @@ class ContentMessages {
content.info.mimetype = file.type;
}
- const def = q.defer();
+ const def = Promise.defer();
if (file.type.indexOf('image/') == 0) {
content.msgtype = 'm.image';
infoForImageFile(matrixClient, roomId, file).then(imageInfo=>{
@@ -326,23 +332,24 @@ class ContentMessages {
dis.dispatch({action: 'upload_started'});
var error;
+
+ function onProgress(ev) {
+ upload.total = ev.total;
+ upload.loaded = ev.loaded;
+ dis.dispatch({action: 'upload_progress', upload: upload});
+ }
+
return def.promise.then(function() {
// XXX: upload.promise must be the promise that
// is returned by uploadFile as it has an abort()
// method hacked onto it.
upload.promise = uploadFile(
- matrixClient, roomId, file
+ matrixClient, roomId, file, onProgress,
);
return upload.promise.then(function(result) {
content.file = result.file;
content.url = result.url;
});
- }).progress(function(ev) {
- if (ev) {
- upload.total = ev.total;
- upload.loaded = ev.loaded;
- dis.dispatch({action: 'upload_progress', upload: upload});
- }
}).then(function(url) {
return matrixClient.sendMessage(roomId, content);
}, function(err) {
diff --git a/src/Lifecycle.js b/src/Lifecycle.js
index 00a82bad21..eb2156e780 100644
--- a/src/Lifecycle.js
+++ b/src/Lifecycle.js
@@ -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 Matrix from 'matrix-js-sdk';
import MatrixClientPeg from './MatrixClientPeg';
@@ -116,12 +116,12 @@ export function loadSession(opts) {
*/
export function attemptTokenLogin(queryParams, defaultDeviceDisplayName) {
if (!queryParams.loginToken) {
- return q(false);
+ return Promise.resolve(false);
}
if (!queryParams.homeserver) {
console.warn("Cannot log in with token: can't determine HS URL to use");
- return q(false);
+ return Promise.resolve(false);
}
// create a temporary MatrixClient to do the login
@@ -197,7 +197,7 @@ function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
// localStorage (e.g. teamToken, isGuest etc.)
function _restoreFromLocalStorage() {
if (!localStorage) {
- return q(false);
+ return Promise.resolve(false);
}
const hsUrl = localStorage.getItem("mx_hs_url");
const isUrl = localStorage.getItem("mx_is_url") || 'https://matrix.org';
@@ -229,14 +229,14 @@ function _restoreFromLocalStorage() {
}
} else {
console.log("No previous session found.");
- return q(false);
+ return Promise.resolve(false);
}
}
function _handleRestoreFailure(e) {
console.log("Unable to restore session", e);
- const def = q.defer();
+ const def = Promise.defer();
const SessionRestoreErrorDialog =
sdk.getComponent('views.dialogs.SessionRestoreErrorDialog');
diff --git a/src/Login.js b/src/Login.js
index 8225509919..049b79c2f4 100644
--- a/src/Login.js
+++ b/src/Login.js
@@ -18,7 +18,7 @@ limitations under the License.
import Matrix from "matrix-js-sdk";
import { _t } from "./languageHandler";
-import q from 'q';
+import Promise from 'bluebird';
import url from 'url';
export default class Login {
@@ -144,7 +144,7 @@ export default class Login {
const client = this._createTemporaryClient();
return client.login('m.login.password', loginParams).then(function(data) {
- return q({
+ return Promise.resolve({
homeserverUrl: self._hsUrl,
identityServerUrl: self._isUrl,
userId: data.user_id,
@@ -160,7 +160,7 @@ export default class Login {
});
return fbClient.login('m.login.password', loginParams).then(function(data) {
- return q({
+ return Promise.resolve({
homeserverUrl: self._fallbackHsUrl,
identityServerUrl: self._isUrl,
userId: data.user_id,
diff --git a/src/RoomNotifs.js b/src/RoomNotifs.js
index 88b6e56c7f..5cc078dc59 100644
--- a/src/RoomNotifs.js
+++ b/src/RoomNotifs.js
@@ -16,7 +16,7 @@ limitations under the License.
import MatrixClientPeg from './MatrixClientPeg';
import PushProcessor from 'matrix-js-sdk/lib/pushprocessor';
-import q from 'q';
+import Promise from 'bluebird';
export const ALL_MESSAGES_LOUD = 'all_messages_loud';
export const ALL_MESSAGES = 'all_messages';
@@ -87,7 +87,7 @@ function setRoomNotifsStateMuted(roomId) {
],
}));
- return q.all(promises);
+ return Promise.all(promises);
}
function setRoomNotifsStateUnmuted(roomId, newState) {
@@ -126,7 +126,7 @@ function setRoomNotifsStateUnmuted(roomId, newState) {
promises.push(cli.setPushRuleEnabled('global', 'room', roomId, true));
}
- return q.all(promises);
+ return Promise.all(promises);
}
function findOverrideMuteRule(roomId) {
diff --git a/src/Rooms.js b/src/Rooms.js
index 3ac7c68533..2e3f4457f0 100644
--- a/src/Rooms.js
+++ b/src/Rooms.js
@@ -15,7 +15,7 @@ limitations under the License.
*/
import MatrixClientPeg from './MatrixClientPeg';
-import q from 'q';
+import Promise from 'bluebird';
/**
* Given a room object, return the alias we should use for it,
@@ -102,7 +102,7 @@ export function guessAndSetDMRoom(room, isDirect) {
*/
export function setDMRoom(roomId, userId) {
if (MatrixClientPeg.get().isGuest()) {
- return q();
+ return Promise.resolve();
}
const mDirectEvent = MatrixClientPeg.get().getAccountData('m.direct');
diff --git a/src/ScalarAuthClient.js b/src/ScalarAuthClient.js
index 6908a7f67d..b1d17b93a9 100644
--- a/src/ScalarAuthClient.js
+++ b/src/ScalarAuthClient.js
@@ -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 request = require('browser-request');
var SdkConfig = require('./SdkConfig');
@@ -39,7 +39,7 @@ class ScalarAuthClient {
// Returns a scalar_token string
getScalarToken() {
var tok = window.localStorage.getItem("mx_scalar_token");
- if (tok) return q(tok);
+ if (tok) return Promise.resolve(tok);
// No saved token, so do the dance to get one. First, we
// need an openid bearer token from the HS.
@@ -53,7 +53,7 @@ class ScalarAuthClient {
}
exchangeForScalarToken(openid_token_object) {
- var defer = q.defer();
+ var defer = Promise.defer();
var scalar_rest_url = SdkConfig.get().integrations_rest_url;
request({
diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js
index 88a78595d6..d14d439d66 100644
--- a/src/ScalarMessaging.js
+++ b/src/ScalarMessaging.js
@@ -491,7 +491,7 @@ function canSendEvent(event, roomId) {
}
if (!canSend) {
- sendError(event, _t('You do not have permission in this room.'));
+ sendError(event, _t('You do not have permission to do that in this room.'));
return;
}
diff --git a/src/UserSettingsStore.js b/src/UserSettingsStore.js
index fef84468ec..4c66c90598 100644
--- a/src/UserSettingsStore.js
+++ b/src/UserSettingsStore.js
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import q from 'q';
+import Promise from 'bluebird';
import MatrixClientPeg from './MatrixClientPeg';
import Notifier from './Notifier';
import { _t } from './languageHandler';
@@ -48,7 +48,7 @@ export default {
loadThreePids: function() {
if (MatrixClientPeg.get().isGuest()) {
- return q({
+ return Promise.resolve({
threepids: [],
}); // guests can't poke 3pid endpoint
}
diff --git a/src/autocomplete/Autocompleter.js b/src/autocomplete/Autocompleter.js
index 62b5a870f3..e47c5dd59c 100644
--- a/src/autocomplete/Autocompleter.js
+++ b/src/autocomplete/Autocompleter.js
@@ -22,7 +22,7 @@ import DuckDuckGoProvider from './DuckDuckGoProvider';
import RoomProvider from './RoomProvider';
import UserProvider from './UserProvider';
import EmojiProvider from './EmojiProvider';
-import Q from 'q';
+import Promise from 'bluebird';
export type SelectionRange = {
start: number,
@@ -52,28 +52,31 @@ export async function getCompletions(query: string, selection: SelectionRange, f
otherwise, we run into a condition where new completions are displayed
while the user is interacting with the list, which makes it difficult
to predict whether an action will actually do what is intended
-
- It ends up containing a list of Q promise states, which are objects with
- state (== "fulfilled" || "rejected") and value. */
- const completionsList = await Q.allSettled(
- PROVIDERS.map(provider => {
- return Q(provider.getCompletions(query, selection, force))
- .timeout(PROVIDER_COMPLETION_TIMEOUT);
+ */
+ const completionsList = await Promise.all(
+ // Array of inspections of promises that might timeout. Instead of allowing a
+ // single timeout to reject the Promise.all, reflect each one and once they've all
+ // settled, filter for the fulfilled ones
+ PROVIDERS.map((provider) => {
+ return provider
+ .getCompletions(query, selection, force)
+ .timeout(PROVIDER_COMPLETION_TIMEOUT)
+ .reflect();
}),
);
- return completionsList
- .filter(completion => completion.state === "fulfilled")
- .map((completionsState, i) => {
- return {
- completions: completionsState.value,
- provider: PROVIDERS[i],
+ return completionsList.filter(
+ (inspection) => inspection.isFulfilled(),
+ ).map((completionsState, i) => {
+ return {
+ completions: completionsState.value(),
+ provider: PROVIDERS[i],
- /* the currently matched "command" the completer tried to complete
- * we pass this through so that Autocomplete can figure out when to
- * re-show itself once hidden.
- */
- command: PROVIDERS[i].getCurrentCommand(query, selection, force),
- };
- });
+ /* the currently matched "command" the completer tried to complete
+ * we pass this through so that Autocomplete can figure out when to
+ * re-show itself once hidden.
+ */
+ command: PROVIDERS[i].getCurrentCommand(query, selection, force),
+ };
+ });
}
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index 75a15d71ee..b90cb53435 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -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
diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index f12e80b944..094251f4c1 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -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();
});
},
diff --git a/src/components/structures/ScrollPanel.js b/src/components/structures/ScrollPanel.js
index f035efee92..a8a2ec181b 100644
--- a/src/components/structures/ScrollPanel.js
+++ b/src/components/structures/ScrollPanel.js
@@ -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) {
diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js
index cdee03ab63..dc9c0fc9ba 100644
--- a/src/components/structures/TimelinePanel.js
+++ b/src/components/structures/TimelinePanel.js
@@ -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);
diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js
index 14432dff81..45c52c7f1d 100644
--- a/src/components/structures/UserSettings.js
+++ b/src/components/structures/UserSettings.js
@@ -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() {
diff --git a/src/components/structures/login/Registration.js b/src/components/structures/login/Registration.js
index 388198bb02..fe05ba4cfd 100644
--- a/src/components/structures/login/Registration.js
+++ b/src/components/structures/login/Registration.js
@@ -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;
diff --git a/src/components/views/dialogs/ChatInviteDialog.js b/src/components/views/dialogs/ChatInviteDialog.js
index a85efadef7..d3a208a785 100644
--- a/src/components/views/dialogs/ChatInviteDialog.js
+++ b/src/components/views/dialogs/ChatInviteDialog.js
@@ -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) => {
diff --git a/src/components/views/dialogs/SetMxIdDialog.js b/src/components/views/dialogs/SetMxIdDialog.js
index d428223ad6..4d4f672f2b 100644
--- a/src/components/views/dialogs/SetMxIdDialog.js
+++ b/src/components/views/dialogs/SetMxIdDialog.js
@@ -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';
diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js
index 3c3f01c1cd..effe50643c 100644
--- a/src/components/views/elements/AppTile.js
+++ b/src/components/views/elements/AppTile.js
@@ -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({
Loading...
);
} 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({
);
}
+
+ // editing is done in scalar
+ const showEditButton = Boolean(this._scalarClient);
+
return (
{this.formatAppTileName()}
{/* Edit widget */}
- {/* */}
+ />}
{/* Delete widget */}
{
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,
diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js
index 63737d5fad..37a9bcd0cd 100644
--- a/src/components/views/rooms/MemberList.js
+++ b/src/components/views/rooms/MemberList.js
@@ -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");
diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js
index e4ba6bbe81..14f52706ec 100644
--- a/src/components/views/rooms/MessageComposer.js
+++ b/src/components/views/rooms/MessageComposer.js
@@ -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) {
diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js
index e9dc216fa7..d4ae55f03a 100644
--- a/src/components/views/rooms/MessageComposerInput.js
+++ b/src/components/views/rooms/MessageComposerInput.js
@@ -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 ? : 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 ? : null;
+ }
+
+ if (isUserPill || isRoomPill) {
+ return (
+
+ {avatar}
+ {props.children}
+
+ );
+ }
+
+ return (
+
+ {props.children}
+
+ );
+ },
+ });
+ 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),
diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js
index d255670a52..d6a973f648 100644
--- a/src/components/views/rooms/RoomSettings.js
+++ b/src/components/views/rooms/RoomSettings.js
@@ -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(
diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js
index 099ae2deae..14ec9806b4 100644
--- a/src/components/views/settings/ChangePassword.js
+++ b/src/components/views/settings/ChangePassword.js
@@ -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, {
diff --git a/src/createRoom.js b/src/createRoom.js
index 916405776d..74e4b3c2fc 100644
--- a/src/createRoom.js
+++ b/src/createRoom.js
@@ -21,7 +21,7 @@ import { _t } from './languageHandler';
import dis from "./dispatcher";
import * as Rooms from "./Rooms";
-import q from 'q';
+import Promise from 'bluebird';
/**
* Create a new room, and switch to it.
@@ -42,7 +42,7 @@ function createRoom(opts) {
const client = MatrixClientPeg.get();
if (client.isGuest()) {
dis.dispatch({action: 'view_set_mxid'});
- return q(null);
+ return Promise.resolve(null);
}
const defaultPreset = opts.dmUserId ? 'trusted_private_chat' : 'private_chat';
@@ -92,7 +92,7 @@ function createRoom(opts) {
if (opts.dmUserId) {
return Rooms.setDMRoom(roomId, opts.dmUserId);
} else {
- return q();
+ return Promise.resolve();
}
}).then(function() {
// NB createRoom doesn't block on the client seeing the echo that the
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 4e40415589..8ba15045a8 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -956,5 +956,6 @@
"Featured Rooms:": "Featured Rooms:",
"Error whilst fetching joined groups": "Error whilst fetching joined groups",
"Featured Users:": "Featured Users:",
- "Edit Group": "Edit Group"
+ "Edit Group": "Edit Group",
+ "Automatically replace plain text Emoji": "Automatically replace plain text Emoji"
}
diff --git a/src/languageHandler.js b/src/languageHandler.js
index 1f0e6326e0..e956d4f8bd 100644
--- a/src/languageHandler.js
+++ b/src/languageHandler.js
@@ -17,7 +17,7 @@ limitations under the License.
import request from 'browser-request';
import counterpart from 'counterpart';
-import q from 'q';
+import Promise from 'bluebird';
import React from 'react';
import UserSettingsStore from './UserSettingsStore';
@@ -231,7 +231,7 @@ export function getCurrentLanguage() {
}
function getLangsJson() {
- const deferred = q.defer();
+ const deferred = Promise.defer();
request(
{ method: "GET", url: i18nFolder + 'languages.json' },
@@ -247,7 +247,7 @@ function getLangsJson() {
}
function getLanguage(langPath) {
- const deferred = q.defer();
+ const deferred = Promise.defer();
let response_return = {};
request(
diff --git a/src/utils/DecryptFile.js b/src/utils/DecryptFile.js
index 8b5f014f2c..04496e4204 100644
--- a/src/utils/DecryptFile.js
+++ b/src/utils/DecryptFile.js
@@ -20,7 +20,7 @@ import encrypt from 'browser-encrypt-attachment';
import 'isomorphic-fetch';
// Grab the client so that we can turn mxc:// URLs into https:// URLS.
import MatrixClientPeg from '../MatrixClientPeg';
-import q from 'q';
+import Promise from 'bluebird';
/**
@@ -28,7 +28,7 @@ import q from 'q';
* @return {Promise} A promise that resolves with the data:// URI.
*/
export function readBlobAsDataUri(file) {
- var deferred = q.defer();
+ var deferred = Promise.defer();
var reader = new FileReader();
reader.onload = function(e) {
deferred.resolve(e.target.result);
@@ -53,7 +53,7 @@ export function readBlobAsDataUri(file) {
export function decryptFile(file) {
const url = MatrixClientPeg.get().mxcUrlToHttp(file.url);
// Download the encrypted file as an array buffer.
- return q(fetch(url)).then(function(response) {
+ return Promise.resolve(fetch(url)).then(function(response) {
return response.arrayBuffer();
}).then(function(responseData) {
// Decrypt the array buffer using the information taken from
diff --git a/src/utils/MultiInviter.js b/src/utils/MultiInviter.js
index 68a0800ed7..c26cc6b52e 100644
--- a/src/utils/MultiInviter.js
+++ b/src/utils/MultiInviter.js
@@ -15,7 +15,7 @@ limitations under the License.
*/
import {getAddressType, inviteToRoom} from '../Invite';
-import q from 'q';
+import Promise from 'bluebird';
/**
* Invites multiple addresses to a room, handling rate limiting from the server
@@ -55,7 +55,7 @@ export default class MultiInviter {
this.errorTexts[addr] = 'Unrecognised address';
}
}
- this.deferred = q.defer();
+ this.deferred = Promise.defer();
this._inviteMore(0);
return this.deferred.promise;
diff --git a/test/components/structures/ScrollPanel-test.js b/test/components/structures/ScrollPanel-test.js
index a783f424e7..e7d3d7b0e6 100644
--- a/test/components/structures/ScrollPanel-test.js
+++ b/test/components/structures/ScrollPanel-test.js
@@ -18,7 +18,7 @@ var React = require('react');
var ReactDOM = require("react-dom");
var ReactTestUtils = require('react-addons-test-utils');
var expect = require('expect');
-var q = require('q');
+import Promise from 'bluebird';
var sdk = require('matrix-react-sdk');
@@ -58,7 +58,7 @@ var Tester = React.createClass({
if (handler) {
res = handler();
} else {
- res = q(false);
+ res = Promise.resolve(false);
}
if (defer) {
@@ -74,7 +74,7 @@ var Tester = React.createClass({
/* returns a promise which will resolve when the fill happens */
awaitFill: function(dir) {
console.log("ScrollPanel Tester: awaiting " + dir + " fill");
- var defer = q.defer();
+ var defer = Promise.defer();
this._fillDefers[dir] = defer;
return defer.promise;
},
@@ -94,7 +94,7 @@ var Tester = React.createClass({
/* returns a promise which will resolve when a scroll event happens */
awaitScroll: function() {
console.log("Awaiting scroll");
- this._scrollDefer = q.defer();
+ this._scrollDefer = Promise.defer();
return this._scrollDefer.promise;
},
@@ -168,7 +168,7 @@ describe('ScrollPanel', function() {
const sp = tester.scrollPanel();
let retriesRemaining = 1;
const awaitReady = function() {
- return q().then(() => {
+ return Promise.resolve().then(() => {
if (sp._pendingFillRequests.b === false &&
sp._pendingFillRequests.f === false
) {
@@ -195,7 +195,7 @@ describe('ScrollPanel', function() {
it('should handle scrollEvent strangeness', function() {
const events = [];
- return q().then(() => {
+ return Promise.resolve().then(() => {
// initialise with a load of events
for (let i = 0; i < 20; i++) {
events.push(i+80);
@@ -227,7 +227,7 @@ describe('ScrollPanel', function() {
it('should not get stuck in #528 workaround', function(done) {
var events = [];
- q().then(() => {
+ Promise.resolve().then(() => {
// initialise with a bunch of events
for (var i = 0; i < 40; i++) {
events.push(i);
diff --git a/test/components/structures/TimelinePanel-test.js b/test/components/structures/TimelinePanel-test.js
index 748e37bee3..98ec65b8e8 100644
--- a/test/components/structures/TimelinePanel-test.js
+++ b/test/components/structures/TimelinePanel-test.js
@@ -18,7 +18,7 @@ var React = require('react');
var ReactDOM = require('react-dom');
var ReactTestUtils = require('react-addons-test-utils');
var expect = require('expect');
-var q = require('q');
+import Promise from 'bluebird';
var sinon = require('sinon');
var jssdk = require('matrix-js-sdk');
@@ -145,20 +145,20 @@ describe('TimelinePanel', function() {
// panel isn't paginating
var awaitPaginationCompletion = function() {
if(!panel.state.forwardPaginating)
- return q();
+ return Promise.resolve();
else
- return q.delay(0).then(awaitPaginationCompletion);
+ return Promise.delay(0).then(awaitPaginationCompletion);
};
// helper function which will return a promise which resolves when
// the TimelinePanel fires a scroll event
var awaitScroll = function() {
- scrollDefer = q.defer();
+ scrollDefer = Promise.defer();
return scrollDefer.promise;
};
// let the first round of pagination finish off
- q.delay(5).then(() => {
+ Promise.delay(5).then(() => {
expect(panel.state.canBackPaginate).toBe(false);
expect(scryEventTiles(panel).length).toEqual(N_EVENTS);
@@ -214,7 +214,7 @@ describe('TimelinePanel', function() {
client.paginateEventTimeline = sinon.spy((tl, opts) => {
console.log("paginate:", opts);
expect(opts.backwards).toBe(true);
- return q(true);
+ return Promise.resolve(true);
});
var rendered = ReactDOM.render(
@@ -279,7 +279,7 @@ describe('TimelinePanel', function() {
// helper function which will return a promise which resolves when
// the TimelinePanel fires a scroll event
var awaitScroll = function() {
- scrollDefer = q.defer();
+ scrollDefer = Promise.defer();
return scrollDefer.promise.then(() => {
console.log("got scroll event; scrollTop now " +
diff --git a/test/components/views/dialogs/InteractiveAuthDialog-test.js b/test/components/views/dialogs/InteractiveAuthDialog-test.js
index ecfaba4e2c..f606793132 100644
--- a/test/components/views/dialogs/InteractiveAuthDialog-test.js
+++ b/test/components/views/dialogs/InteractiveAuthDialog-test.js
@@ -15,7 +15,7 @@ limitations under the License.
*/
import expect from 'expect';
-import q from 'q';
+import Promise from 'bluebird';
import React from 'react';
import ReactDOM from 'react-dom';
import ReactTestUtils from 'react-addons-test-utils';
@@ -50,7 +50,7 @@ describe('InteractiveAuthDialog', function () {
it('Should successfully complete a password flow', function() {
const onFinished = sinon.spy();
- const doRequest = sinon.stub().returns(q({a:1}));
+ const doRequest = sinon.stub().returns(Promise.resolve({a:1}));
// tell the stub matrixclient to return a real userid
var client = MatrixClientPeg.get();
@@ -110,7 +110,7 @@ describe('InteractiveAuthDialog', function () {
);
// let the request complete
- return q.delay(1);
+ return Promise.delay(1);
}).then(() => {
expect(onFinished.callCount).toEqual(1);
expect(onFinished.calledWithExactly(true, {a:1})).toBe(true);
diff --git a/test/components/views/rooms/MessageComposerInput-test.js b/test/components/views/rooms/MessageComposerInput-test.js
index 6d4b4e69cc..fe379afcff 100644
--- a/test/components/views/rooms/MessageComposerInput-test.js
+++ b/test/components/views/rooms/MessageComposerInput-test.js
@@ -3,7 +3,7 @@ import ReactTestUtils from 'react-addons-test-utils';
import ReactDOM from 'react-dom';
import expect, {createSpy} from 'expect';
import sinon from 'sinon';
-import Q from 'q';
+import Promise from 'bluebird';
import * as testUtils from '../../../test-utils';
import sdk from 'matrix-react-sdk';
import UserSettingsStore from '../../../../src/UserSettingsStore';
@@ -47,7 +47,7 @@ describe('MessageComposerInput', () => {
// warnings
// (please can we make the components not setState() after
// they are unmounted?)
- Q.delay(10).done(() => {
+ Promise.delay(10).done(() => {
if (parentDiv) {
ReactDOM.unmountComponentAtNode(parentDiv);
parentDiv.remove();
diff --git a/test/stores/RoomViewStore-test.js b/test/stores/RoomViewStore-test.js
index adcae90e9b..be598de8da 100644
--- a/test/stores/RoomViewStore-test.js
+++ b/test/stores/RoomViewStore-test.js
@@ -7,7 +7,7 @@ import RoomViewStore from '../../src/stores/RoomViewStore';
import peg from '../../src/MatrixClientPeg';
import * as testUtils from '../test-utils';
-import q from 'q';
+import Promise from 'bluebird';
const dispatch = testUtils.getDispatchForStore(RoomViewStore);
@@ -39,7 +39,7 @@ describe('RoomViewStore', function() {
});
it('can be used to view a room by alias and join', function(done) {
- peg.get().getRoomIdForAlias.returns(q({room_id: "!randomcharacters:aser.ver"}));
+ peg.get().getRoomIdForAlias.returns(Promise.resolve({room_id: "!randomcharacters:aser.ver"}));
peg.get().joinRoom = (roomAddress) => {
expect(roomAddress).toBe("#somealias2:aser.ver");
done();
diff --git a/test/test-utils.js b/test/test-utils.js
index bc6b484bb5..23f16a2e4c 100644
--- a/test/test-utils.js
+++ b/test/test-utils.js
@@ -1,7 +1,7 @@
"use strict";
import sinon from 'sinon';
-import q from 'q';
+import Promise from 'bluebird';
import peg from '../src/MatrixClientPeg';
import dis from '../src/dispatcher';
@@ -75,12 +75,12 @@ export function createTestClient() {
on: sinon.stub(),
removeListener: sinon.stub(),
isRoomEncrypted: sinon.stub().returns(false),
- peekInRoom: sinon.stub().returns(q(mkStubRoom())),
+ peekInRoom: sinon.stub().returns(Promise.resolve(mkStubRoom())),
- paginateEventTimeline: sinon.stub().returns(q()),
- sendReadReceipt: sinon.stub().returns(q()),
- getRoomIdForAlias: sinon.stub().returns(q()),
- getProfileInfo: sinon.stub().returns(q({})),
+ paginateEventTimeline: sinon.stub().returns(Promise.resolve()),
+ sendReadReceipt: sinon.stub().returns(Promise.resolve()),
+ getRoomIdForAlias: sinon.stub().returns(Promise.resolve()),
+ getProfileInfo: sinon.stub().returns(Promise.resolve({})),
getAccountData: (type) => {
return mkEvent({
type,
@@ -89,9 +89,9 @@ export function createTestClient() {
});
},
setAccountData: sinon.stub(),
- sendTyping: sinon.stub().returns(q({})),
- sendTextMessage: () => q({}),
- sendHtmlMessage: () => q({}),
+ sendTyping: sinon.stub().returns(Promise.resolve({})),
+ sendTextMessage: () => Promise.resolve({}),
+ sendHtmlMessage: () => Promise.resolve({}),
getSyncState: () => "SYNCING",
generateClientSecret: () => "t35tcl1Ent5ECr3T",
isGuest: () => false,
@@ -101,13 +101,13 @@ export function createTestClient() {
export function createTestRtsClient(teamMap, sidMap) {
return {
getTeamsConfig() {
- return q(Object.keys(teamMap).map((token) => teamMap[token]));
+ return Promise.resolve(Object.keys(teamMap).map((token) => teamMap[token]));
},
trackReferral(referrer, emailSid, clientSecret) {
- return q({team_token: sidMap[emailSid]});
+ return Promise.resolve({team_token: sidMap[emailSid]});
},
getTeam(teamToken) {
- return q(teamMap[teamToken]);
+ return Promise.resolve(teamMap[teamToken]);
},
};
}