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 */} - {/* Edit */} + />} {/* 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]); }, }; }