diff --git a/CHANGELOG.md b/CHANGELOG.md index 89f3741535..b161a9d908 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,71 @@ +Changes in [0.12.5](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.12.5) (2018-05-17) +===================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.12.4...v0.12.5) + + * Fix image size jumping regression + [\#1909](https://github.com/matrix-org/matrix-react-sdk/pull/1909) + +Changes in [0.12.4](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.12.4) (2018-05-16) +===================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.12.4-rc.6...v0.12.4) + + * No changes from rc.5 + +Changes in [0.12.4-rc.6](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.12.4-rc.6) (2018-05-15) +=============================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.12.4-rc.5...v0.12.4-rc.6) + + * Wait for deletion of widgets as well addition + [\#1907](https://github.com/matrix-org/matrix-react-sdk/pull/1907) + +Changes in [0.12.4-rc.5](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.12.4-rc.5) (2018-05-15) +=============================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.12.4-rc.4...v0.12.4-rc.5) + + * Wait for echo from server when adding user widgets + [\#1905](https://github.com/matrix-org/matrix-react-sdk/pull/1905) + +Changes in [0.12.4-rc.4](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.12.4-rc.4) (2018-05-14) +=============================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.12.4-rc.3...v0.12.4-rc.4) + + * Update from Weblate. + [\#1904](https://github.com/matrix-org/matrix-react-sdk/pull/1904) + * Correctly identify sticker picker widgets + [\#1894](https://github.com/matrix-org/matrix-react-sdk/pull/1894) + * Quick fix for sticker picker position + [\#1903](https://github.com/matrix-org/matrix-react-sdk/pull/1903) + * Remove redundant logging (currently shown on every render when no stiā€¦ + [\#1901](https://github.com/matrix-org/matrix-react-sdk/pull/1901) + * Fix stickers briefly being 2x the size + [\#1899](https://github.com/matrix-org/matrix-react-sdk/pull/1899) + * Send required properties when making requests to widgets over postMessage + [\#1891](https://github.com/matrix-org/matrix-react-sdk/pull/1891) + * Fix room widget second load infini spinner + [\#1897](https://github.com/matrix-org/matrix-react-sdk/pull/1897) + * Update widget state when account data changes + [\#1896](https://github.com/matrix-org/matrix-react-sdk/pull/1896) + * Remove margins when in a ReplyThread to stop them taking so much space + [\#1882](https://github.com/matrix-org/matrix-react-sdk/pull/1882) + * Add setting to enable widget screenshots (if widgets declare support) + [\#1892](https://github.com/matrix-org/matrix-react-sdk/pull/1892) + * T3chguy/replies html tag + [\#1889](https://github.com/matrix-org/matrix-react-sdk/pull/1889) + * Instant Sticker Picker + [\#1888](https://github.com/matrix-org/matrix-react-sdk/pull/1888) + * Update widget 'widgetData' key to 'data' to match spec. + [\#1887](https://github.com/matrix-org/matrix-react-sdk/pull/1887) + * Fix 'state_key' field name. + [\#1886](https://github.com/matrix-org/matrix-react-sdk/pull/1886) + * Improve appearance of short-lived app loading spinner + [\#1885](https://github.com/matrix-org/matrix-react-sdk/pull/1885) + * Take feature_sticker_messagse out of labs + [\#1883](https://github.com/matrix-org/matrix-react-sdk/pull/1883) + * Fix issue incorrect positioning with widget loading indicator + [\#1884](https://github.com/matrix-org/matrix-react-sdk/pull/1884) + * Users should always be able to edit their user/non-room widgets + [\#1879](https://github.com/matrix-org/matrix-react-sdk/pull/1879) + Changes in [0.12.4-rc.3](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.12.4-rc.3) (2018-05-11) =============================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.12.4-rc.2...v0.12.4-rc.3) diff --git a/package.json b/package.json index c25cf6ae43..dfc02a7e26 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "0.12.4-rc.3", + "version": "0.12.5", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js index 58572cf144..7ca404be31 100644 --- a/src/HtmlUtils.js +++ b/src/HtmlUtils.js @@ -413,12 +413,13 @@ class TextHighlighter extends BaseHighlighter { * opts.stripReplyFallback: optional argument specifying the event is a reply and so fallback needs removing */ export function bodyToHtml(content, highlights, opts={}) { - let isHtml = content.format === "org.matrix.custom.html" && content.formatted_body; + const isHtmlMessage = content.format === "org.matrix.custom.html" && content.formatted_body; let bodyHasEmoji = false; let strippedBody; let safeBody; + let isDisplayedWithHtml; // XXX: We sanitize the HTML whilst also highlighting its text nodes, to avoid accidentally trying // to highlight HTML tags themselves. However, this does mean that we don't highlight textnodes which // are interrupted by HTML tags (not that we did before) - e.g. foobar won't get highlighted @@ -439,17 +440,18 @@ export function bodyToHtml(content, highlights, opts={}) { if (opts.stripReplyFallback && formattedBody) formattedBody = ReplyThread.stripHTMLReply(formattedBody); strippedBody = opts.stripReplyFallback ? ReplyThread.stripPlainReply(content.body) : content.body; - bodyHasEmoji = containsEmoji(isHtml ? formattedBody : content.body); + bodyHasEmoji = containsEmoji(isHtmlMessage ? formattedBody : content.body); // Only generate safeBody if the message was sent as org.matrix.custom.html - if (isHtml) { + if (isHtmlMessage) { + isDisplayedWithHtml = true; safeBody = sanitizeHtml(formattedBody, sanitizeHtmlParams); } else { // ... or if there are emoji, which we insert as HTML alongside the // escaped plaintext body. if (bodyHasEmoji) { - isHtml = true; + isDisplayedWithHtml = true; safeBody = sanitizeHtml(escape(strippedBody), sanitizeHtmlParams); } } @@ -475,10 +477,10 @@ export function bodyToHtml(content, highlights, opts={}) { const className = classNames({ 'mx_EventTile_body': true, 'mx_EventTile_bigEmoji': emojiBody, - 'markdown-body': isHtml, + 'markdown-body': isHtmlMessage, }); - return isHtml ? + return isDisplayedWithHtml ? : { strippedBody }; } diff --git a/src/RoomInvite.js b/src/RoomInvite.js index 31541148d9..0bcc08eb06 100644 --- a/src/RoomInvite.js +++ b/src/RoomInvite.js @@ -57,6 +57,7 @@ export function showStartChatInviteDialog() { title: _t('Start a chat'), description: _t("Who would you like to communicate with?"), placeholder: _t("Email, name or matrix ID"), + validAddressTypes: ['mx-user-id', 'email'], button: _t("Start Chat"), onFinished: _onStartChatFinished, }); diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js index a163bf7bbd..9457e6ccfb 100644 --- a/src/ScalarMessaging.js +++ b/src/ScalarMessaging.js @@ -286,6 +286,51 @@ function inviteUser(event, roomId, userId) { }); } +/** + * Returns a promise that resolves when a widget with the given + * ID has been added as a user widget (ie. the accountData event + * arrives) or rejects after a timeout + * + * @param {string} widgetId The ID of the widget to wait for + * @param {boolean} add True to wait for the widget to be added, + * false to wait for it to be deleted. + * @returns {Promise} that resolves when the widget is available + */ +function waitForUserWidget(widgetId, add) { + return new Promise((resolve, reject) => { + const currentAccountDataEvent = MatrixClientPeg.get().getAccountData('m.widgets'); + + // Tests an account data event, returning true if it's in the state + // we're waiting for it to be in + function eventInIntendedState(ev) { + if (!ev || !currentAccountDataEvent.getContent()) return false; + if (add) { + return ev.getContent()[widgetId] !== undefined; + } else { + return ev.getContent()[widgetId] === undefined; + } + } + + if (eventInIntendedState(currentAccountDataEvent)) { + resolve(); + return; + } + + function onAccountData(ev) { + if (eventInIntendedState(currentAccountDataEvent)) { + MatrixClientPeg.get().removeListener('accountData', onAccountData); + clearTimeout(timerId); + resolve(); + } + } + const timerId = setTimeout(() => { + MatrixClientPeg.get().removeListener('accountData', onAccountData); + reject(new Error("Timed out waiting for widget ID " + widgetId + " to appear")); + }, 10000); + MatrixClientPeg.get().on('accountData', onAccountData); + }); +} + function setWidget(event, roomId) { const widgetId = event.data.widget_id; const widgetType = event.data.type; @@ -355,12 +400,20 @@ function setWidget(event, roomId) { }; } + // This starts listening for when the echo comes back from the server + // since the widget won't appear added until this happens. If we don't + // wait for this, the action will complete but if the user is fast enough, + // the widget still won't actually be there. client.setAccountData('m.widgets', userWidgets).then(() => { + return waitForUserWidget(widgetId, widgetUrl !== null); + }).then(() => { sendResponse(event, { success: true, }); dis.dispatch({ action: "user_widget_updated" }); + }).catch((e) => { + sendError(event, _t('Unable to create widget.'), e); }); } else { // Room widget if (!roomId) { @@ -373,6 +426,8 @@ function setWidget(event, roomId) { // TODO - Room widgets need to be moved to 'm.widget' state events // https://docs.google.com/document/d/1uPF7XWY_dXTKVKV7jZQ2KmsI19wn9-kFRgQ1tFQP7wQ/edit?usp=sharing client.sendStateEvent(roomId, "im.vector.modular.widgets", content, widgetId).done(() => { + // XXX: We should probably wait for the echo of the state event to come back from the server, + // as we do with user widgets. sendResponse(event, { success: true, }); diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index d9ac9de693..2dd5a75c47 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -266,6 +266,7 @@ const LoggedInView = React.createClass({ const GroupView = sdk.getComponent('structures.GroupView'); const MyGroups = sdk.getComponent('structures.MyGroups'); const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar'); + const CookieBar = sdk.getComponent('globals.CookieBar'); const NewVersionBar = sdk.getComponent('globals.NewVersionBar'); const UpdateCheckBar = sdk.getComponent('globals.UpdateCheckBar'); const PasswordNagBar = sdk.getComponent('globals.PasswordNagBar'); @@ -353,7 +354,12 @@ const LoggedInView = React.createClass({ let topBar; const isGuest = this.props.matrixClient.isGuest(); - if (this.props.hasNewVersion) { + if (this.props.showCookieBar && + this.props.config.piwik + ) { + const policyUrl = this.props.config.piwik.policyUrl || null; + topBar = ; + } else if (this.props.hasNewVersion) { topBar = ; diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 3005bc86ad..051b9ed10b 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -165,6 +165,8 @@ export default React.createClass({ newVersionReleaseNotes: null, checkingForUpdate: null, + showCookieBar: false, + // Parameters used in the registration dance with the IS register_client_secret: null, register_session_id: null, @@ -227,8 +229,6 @@ export default React.createClass({ componentWillMount: function() { SdkConfig.put(this.props.config); - if (!SettingsStore.getValue("analyticsOptOut")) Analytics.enable(); - // Used by _viewRoom before getting state from sync this.firstSyncComplete = false; this.firstSyncPromise = Promise.defer(); @@ -361,6 +361,16 @@ export default React.createClass({ // loadSession as there's logic there to ask the user if they want // to try logging out. }); + + if (SettingsStore.getValue("showCookieBar")) { + this.setState({ + showCookieBar: true, + }); + } + + if (SettingsStore.getValue("analyticsOptIn")) { + Analytics.enable(); + } }, componentWillUnmount: function() { @@ -673,6 +683,23 @@ export default React.createClass({ hideToSRUsers: false, }); break; + case 'accept_cookies': + SettingsStore.setValue("analyticsOptIn", null, SettingLevel.DEVICE, true); + SettingsStore.setValue("showCookieBar", null, SettingLevel.DEVICE, false); + + this.setState({ + showCookieBar: false, + }); + Analytics.enable(); + break; + case 'reject_cookies': + SettingsStore.setValue("analyticsOptIn", null, SettingLevel.DEVICE, false); + SettingsStore.setValue("showCookieBar", null, SettingLevel.DEVICE, false); + + this.setState({ + showCookieBar: false, + }); + break; } }, @@ -1621,6 +1648,7 @@ export default React.createClass({ onRegistered={this.onRegistered} currentRoomId={this.state.currentRoomId} teamToken={this._teamToken} + showCookieBar={this.state.showCookieBar} {...this.props} {...this.state} /> diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index 35a55284fd..c8ce79905d 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -86,9 +86,9 @@ const SIMPLE_SETTINGS = [ // These settings must be defined in SettingsStore const ANALYTICS_SETTINGS = [ { - id: 'analyticsOptOut', + id: 'analyticsOptIn', fn: function(checked) { - Analytics[checked ? 'disable' : 'enable'](); + checked ? Analytics.enable() : Analytics.disable(); }, }, ]; diff --git a/src/components/views/elements/TagTile.js b/src/components/views/elements/TagTile.js index c5fdea0a54..fb3ddee093 100644 --- a/src/components/views/elements/TagTile.js +++ b/src/components/views/elements/TagTile.js @@ -21,7 +21,7 @@ import { MatrixClient } from 'matrix-js-sdk'; import sdk from '../../../index'; import dis from '../../../dispatcher'; import { isOnlyCtrlOrCmdIgnoreShiftKeyEvent } from '../../../Keyboard'; -import ContextualMenu from '../../structures/ContextualMenu'; +import * as ContextualMenu from '../../structures/ContextualMenu'; import FlairStore from '../../../stores/FlairStore'; import GroupStore from '../../../stores/GroupStore'; diff --git a/src/components/views/globals/CookieBar.js b/src/components/views/globals/CookieBar.js new file mode 100644 index 0000000000..8fab64be67 --- /dev/null +++ b/src/components/views/globals/CookieBar.js @@ -0,0 +1,78 @@ +/* +Copyright 2018 New Vector Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; +import dis from '../../../dispatcher'; +import { _t } from '../../../languageHandler'; +import sdk from '../../../index'; + +export default class CookieBar extends React.Component { + static propTypes = { + policyUrl: PropTypes.string, + } + + constructor() { + super(); + } + + onAccept() { + dis.dispatch({ + action: 'accept_cookies', + }); + } + + onReject() { + dis.dispatch({ + action: 'reject_cookies', + }); + } + + render() { + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + const toolbarClasses = "mx_MatrixToolbar"; + return ( +
+ Warning +
+ { this.props.policyUrl ? _t( + "Help improve Riot by sending usage data? " + + "This will use a cookie. " + + "(See our cookie and privacy policies).", + {}, + { + // XXX: We need to link to the page that explains our cookies + 'PolicyLink': (sub) => + { sub } + + , + }, + ) : _t("Help improve Riot by sending usage data? This will use a cookie.") } +
+ + { _t("Yes please") } + + + + +
+ ); + } +} diff --git a/src/components/views/messages/MImageBody.js b/src/components/views/messages/MImageBody.js index 6cc492acf8..8045d43104 100644 --- a/src/components/views/messages/MImageBody.js +++ b/src/components/views/messages/MImageBody.js @@ -1,5 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd +Copyright 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -140,7 +141,6 @@ export default class extends React.Component { } onImageLoad() { - this.fixupHeight(); this.props.onWidgetLoad(); } @@ -207,6 +207,7 @@ export default class extends React.Component { }); }).done(); } + this.fixupHeight(); this._afterComponentDidMount(); } diff --git a/src/components/views/messages/MStickerBody.js b/src/components/views/messages/MStickerBody.js index 3a412fc2e2..aaad5ba75e 100644 --- a/src/components/views/messages/MStickerBody.js +++ b/src/components/views/messages/MStickerBody.js @@ -40,7 +40,6 @@ export default class MStickerBody extends MImageBody { } _onImageLoad() { - this.fixupHeight(); this.setState({ placeholderClasses: 'mx_MStickerBody_placeholder_invisible', }); diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index a4a4b4ebe8..bc2a715d31 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -32,7 +32,7 @@ import SdkConfig from '../../../SdkConfig'; import dis from '../../../dispatcher'; import { _t } from '../../../languageHandler'; import MatrixClientPeg from '../../../MatrixClientPeg'; -import ContextualMenu from '../../structures/ContextualMenu'; +import * as ContextualMenu from '../../structures/ContextualMenu'; import SettingsStore from "../../../settings/SettingsStore"; import PushProcessor from 'matrix-js-sdk/lib/pushprocessor'; import ReplyThread from "../elements/ReplyThread"; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9b932ef2b6..239b45c32e 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -190,7 +190,6 @@ "Message Replies": "Message Replies", "Message Pinning": "Message Pinning", "Tag Panel": "Tag Panel", - "Sticker Messages": "Sticker Messages", "Disable Emoji suggestions while typing": "Disable Emoji suggestions while typing", "Use compact timeline layout": "Use compact timeline layout", "Hide removed messages": "Hide removed messages", @@ -210,7 +209,7 @@ "Mirror local video feed": "Mirror local video feed", "Disable Community Filter Panel": "Disable Community Filter Panel", "Disable Peer-to-Peer for 1:1 calls": "Disable Peer-to-Peer for 1:1 calls", - "Opt out of analytics": "Opt out of analytics", + "Send analytics data": "Send analytics data", "Never send encrypted messages to unverified devices from this device": "Never send encrypted messages to unverified devices from this device", "Never send encrypted messages to unverified devices in this room from this device": "Never send encrypted messages to unverified devices in this room from this device", "Enable inline URL previews by default": "Enable inline URL previews by default", @@ -566,8 +565,6 @@ "Download %(text)s": "Download %(text)s", "Invalid file%(extra)s": "Invalid file%(extra)s", "Error decrypting image": "Error decrypting image", - "This image cannot be displayed.": "This image cannot be displayed.", - "Image '%(Body)s' cannot be displayed.": "Image '%(Body)s' cannot be displayed.", "Error decrypting video": "Error decrypting video", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s changed the avatar for %(roomName)s", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removed the room avatar.", @@ -637,6 +634,9 @@ "Something went wrong when trying to get your communities.": "Something went wrong when trying to get your communities.", "Display your community flair in rooms configured to show it.": "Display your community flair in rooms configured to show it.", "You're not currently a member of any communities.": "You're not currently a member of any communities.", + "Help improve Riot by sending usage data? This will use a cookie. (See our cookie and privacy policies).": "Help improve Riot by sending usage data? This will use a cookie. (See our cookie and privacy policies).", + "Help improve Riot by sending usage data? This will use a cookie.": "Help improve Riot by sending usage data? This will use a cookie.", + "Yes please": "Yes please", "You are not receiving desktop notifications": "You are not receiving desktop notifications", "Enable them now": "Enable them now", "What's New": "What's New", @@ -815,8 +815,8 @@ "Encryption key request": "Encryption key request", "Sign out": "Sign out", "Log out and remove encryption keys?": "Log out and remove encryption keys?", - "Send Logs": "Send Logs", "Clear Storage and Sign Out": "Clear Storage and Sign Out", + "Send Logs": "Send Logs", "Refresh": "Refresh", "Unable to restore session": "Unable to restore session", "We encountered an error trying to restore your previous session.": "We encountered an error trying to restore your previous session.", diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 663318f990..b1bc4161fd 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -213,11 +213,15 @@ export const SETTINGS = { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, default: "en", }, - "analyticsOptOut": { + "analyticsOptIn": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, - displayName: _td('Opt out of analytics'), + displayName: _td('Send analytics data'), default: false, }, + "showCookieBar": { + supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, + default: true, + }, "autocompleteDelay": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, default: 200,