diff --git a/CHANGELOG.md b/CHANGELOG.md index 97523e9189..2c2af18e43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,160 @@ +Changes in [0.10.7](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.10.7) (2017-10-16) +===================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.10.7-rc.3...v0.10.7) + + * Update to latest js-sdk + +Changes in [0.10.7-rc.3](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.10.7-rc.3) (2017-10-13) +=============================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.10.7-rc.2...v0.10.7-rc.3) + + * Fix the enableLabs flag, again + [\#1474](https://github.com/matrix-org/matrix-react-sdk/pull/1474) + +Changes in [0.10.7-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.10.7-rc.2) (2017-10-13) +=============================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.10.7-rc.1...v0.10.7-rc.2) + + * Honour the (now legacy) enableLabs flag + [\#1473](https://github.com/matrix-org/matrix-react-sdk/pull/1473) + * Don't show labs features by default + [\#1472](https://github.com/matrix-org/matrix-react-sdk/pull/1472) + * Make features disabled by default + [\#1470](https://github.com/matrix-org/matrix-react-sdk/pull/1470) + +Changes in [0.10.7-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.10.7-rc.1) (2017-10-13) +=============================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.10.6...v0.10.7-rc.1) + + * Add warm fuzzy dialog for inviting users to a group + [\#1459](https://github.com/matrix-org/matrix-react-sdk/pull/1459) + * enable/disable features in config.json + [\#1468](https://github.com/matrix-org/matrix-react-sdk/pull/1468) + * Update from Weblate. + [\#1469](https://github.com/matrix-org/matrix-react-sdk/pull/1469) + * Don't send RR or RM when peeking at a room + [\#1463](https://github.com/matrix-org/matrix-react-sdk/pull/1463) + * Fix bug that inserted emoji when typing + [\#1467](https://github.com/matrix-org/matrix-react-sdk/pull/1467) + * Ignore VS16 char in RTE + [\#1458](https://github.com/matrix-org/matrix-react-sdk/pull/1458) + * Show failures when sending messages + [\#1460](https://github.com/matrix-org/matrix-react-sdk/pull/1460) + * Run eslint --fix + [\#1461](https://github.com/matrix-org/matrix-react-sdk/pull/1461) + * Show who banned the user on hover + [\#1441](https://github.com/matrix-org/matrix-react-sdk/pull/1441) + * Enhancements to room power level settings + [\#1440](https://github.com/matrix-org/matrix-react-sdk/pull/1440) + * Added TextInputWithCheckbox dialog + [\#868](https://github.com/matrix-org/matrix-react-sdk/pull/868) + * Make it clearer which HS you're logging into + [\#1456](https://github.com/matrix-org/matrix-react-sdk/pull/1456) + * Remove redundant stale onKeyDown + [\#1451](https://github.com/matrix-org/matrix-react-sdk/pull/1451) + * Only allow event state event handlers on state events + [\#1453](https://github.com/matrix-org/matrix-react-sdk/pull/1453) + * Modify the group store to include group rooms + [\#1452](https://github.com/matrix-org/matrix-react-sdk/pull/1452) + * Factor-out GroupStore and create GroupStoreCache + [\#1449](https://github.com/matrix-org/matrix-react-sdk/pull/1449) + * Put related groups UI behind groups labs flag + [\#1448](https://github.com/matrix-org/matrix-react-sdk/pull/1448) + * Restrict Flair in the timeline to related groups of the room + [\#1447](https://github.com/matrix-org/matrix-react-sdk/pull/1447) + * Implement UI for editing related groups of a room + [\#1446](https://github.com/matrix-org/matrix-react-sdk/pull/1446) + * Fix a couple of bugs with EditableItemList + [\#1445](https://github.com/matrix-org/matrix-react-sdk/pull/1445) + * Factor out EditableItemList from AliasSettings + [\#1444](https://github.com/matrix-org/matrix-react-sdk/pull/1444) + * Add dummy translation function to mark translatable strings + [\#1421](https://github.com/matrix-org/matrix-react-sdk/pull/1421) + * Implement button to remove a room from a group + [\#1438](https://github.com/matrix-org/matrix-react-sdk/pull/1438) + * Fix showing 3pid invites in member list + [\#1443](https://github.com/matrix-org/matrix-react-sdk/pull/1443) + * Add button to get to MyGroups (view_my_groups or path #/groups) + [\#1435](https://github.com/matrix-org/matrix-react-sdk/pull/1435) + * Add eslint rule to disallow spaces inside of curly braces + [\#1436](https://github.com/matrix-org/matrix-react-sdk/pull/1436) + * Fix ability to invite existing mx users + [\#1437](https://github.com/matrix-org/matrix-react-sdk/pull/1437) + * Construct address picker message using provided `validAddressTypes` + [\#1434](https://github.com/matrix-org/matrix-react-sdk/pull/1434) + * Fix GroupView summary rooms displaying without avatars + [\#1433](https://github.com/matrix-org/matrix-react-sdk/pull/1433) + * Implement adding rooms to a group (or group summary) by room ID + [\#1432](https://github.com/matrix-org/matrix-react-sdk/pull/1432) + * Give flair avatars a tooltip = the group ID + [\#1431](https://github.com/matrix-org/matrix-react-sdk/pull/1431) + * Fix ability to feature self in a group summary + [\#1430](https://github.com/matrix-org/matrix-react-sdk/pull/1430) + * Implement "Add room to group" feature + [\#1429](https://github.com/matrix-org/matrix-react-sdk/pull/1429) + * Fix group membership publicity + [\#1428](https://github.com/matrix-org/matrix-react-sdk/pull/1428) + * Add support for Jitsi screensharing in electron app + [\#1355](https://github.com/matrix-org/matrix-react-sdk/pull/1355) + * Delint and DRY TextForEvent + [\#1424](https://github.com/matrix-org/matrix-react-sdk/pull/1424) + * Bust the flair caches after 30mins + [\#1427](https://github.com/matrix-org/matrix-react-sdk/pull/1427) + * Show displayname / avatar in group member info + [\#1426](https://github.com/matrix-org/matrix-react-sdk/pull/1426) + * Create GroupSummaryStore for storing group summary stuff + [\#1418](https://github.com/matrix-org/matrix-react-sdk/pull/1418) + * Add status & toggle for publicity + [\#1419](https://github.com/matrix-org/matrix-react-sdk/pull/1419) + * MemberList: show 100 more on overflow tile click + [\#1417](https://github.com/matrix-org/matrix-react-sdk/pull/1417) + * Fix NPE in MemberList + [\#1425](https://github.com/matrix-org/matrix-react-sdk/pull/1425) + * Fix incorrect variable in string + [\#1422](https://github.com/matrix-org/matrix-react-sdk/pull/1422) + * apply i18n _t to string which has already been translated + [\#1420](https://github.com/matrix-org/matrix-react-sdk/pull/1420) + * Make the invite section a truncatedlist too + [\#1416](https://github.com/matrix-org/matrix-react-sdk/pull/1416) + * Implement removal function of features users/rooms + [\#1415](https://github.com/matrix-org/matrix-react-sdk/pull/1415) + * Allow TruncatedList to get children via a callback + [\#1412](https://github.com/matrix-org/matrix-react-sdk/pull/1412) + * Experimental: Lazy load user autocomplete entries + [\#1413](https://github.com/matrix-org/matrix-react-sdk/pull/1413) + * Show displayname & avatar url in group member list + [\#1414](https://github.com/matrix-org/matrix-react-sdk/pull/1414) + * De-lint TruncatedList + [\#1411](https://github.com/matrix-org/matrix-react-sdk/pull/1411) + * Remove unneeded strings + [\#1409](https://github.com/matrix-org/matrix-react-sdk/pull/1409) + * Clean on prerelease + [\#1410](https://github.com/matrix-org/matrix-react-sdk/pull/1410) + * Redesign membership section in GroupView + [\#1408](https://github.com/matrix-org/matrix-react-sdk/pull/1408) + * Implement adding rooms to the group summary + [\#1406](https://github.com/matrix-org/matrix-react-sdk/pull/1406) + * Honour the is_privileged flag in GroupView + [\#1407](https://github.com/matrix-org/matrix-react-sdk/pull/1407) + * Update when a group arrives + [\#1405](https://github.com/matrix-org/matrix-react-sdk/pull/1405) + * Implement `view_group` dispatch when clicking flair + [\#1404](https://github.com/matrix-org/matrix-react-sdk/pull/1404) + * GroupView: Add a User + [\#1402](https://github.com/matrix-org/matrix-react-sdk/pull/1402) + * Track action button click event + [\#1403](https://github.com/matrix-org/matrix-react-sdk/pull/1403) + * Separate sender profile into elements with classes + [\#1401](https://github.com/matrix-org/matrix-react-sdk/pull/1401) + * Fix ugly integration button, use hover to show error + [\#1399](https://github.com/matrix-org/matrix-react-sdk/pull/1399) + * Fix promise error in flair + [\#1400](https://github.com/matrix-org/matrix-react-sdk/pull/1400) + * Flair! + [\#1351](https://github.com/matrix-org/matrix-react-sdk/pull/1351) + * Group Membership UI + [\#1328](https://github.com/matrix-org/matrix-react-sdk/pull/1328) + Changes in [0.10.6](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.10.6) (2017-09-21) ===================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.10.5...v0.10.6) diff --git a/package.json b/package.json index e185a9027d..8693e33471 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "0.10.6", + "version": "0.10.7", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { @@ -67,7 +67,7 @@ "isomorphic-fetch": "^2.2.1", "linkifyjs": "^2.1.3", "lodash": "^4.13.1", - "matrix-js-sdk": "0.8.4", + "matrix-js-sdk": "0.8.5", "optimist": "^0.6.1", "prop-types": "^15.5.8", "react": "^15.4.0", diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js index 5620bc06df..b306eab23c 100644 --- a/src/HtmlUtils.js +++ b/src/HtmlUtils.js @@ -172,7 +172,7 @@ const sanitizeHtmlParams = { // Lots of these won't come up by default because we don't allow them selfClosing: ['img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta'], // URL schemes we permit - allowedSchemes: ['http', 'https', 'ftp', 'mailto'], + allowedSchemes: ['http', 'https', 'ftp', 'mailto', 'magnet'], allowProtocolRelative: false, @@ -385,10 +385,9 @@ class TextHighlighter extends BaseHighlighter { * highlights: optional list of words to highlight, ordered by longest word first * * opts.highlightLink: optional href to add to highlighted words + * opts.disableBigEmoji: optional argument to disable the big emoji class. */ -export function bodyToHtml(content, highlights, opts) { - opts = opts || {}; - +export function bodyToHtml(content, highlights, opts={}) { const isHtml = (content.format === "org.matrix.custom.html"); const body = isHtml ? content.formatted_body : escape(content.body); @@ -418,7 +417,7 @@ export function bodyToHtml(content, highlights, opts) { } let emojiBody = false; - if (bodyHasEmoji) { + if (!opts.disableBigEmoji && bodyHasEmoji) { EMOJI_REGEX.lastIndex = 0; const contentBodyTrimmed = content.body !== undefined ? content.body.trim() : ''; const match = EMOJI_REGEX.exec(contentBodyTrimmed); diff --git a/src/Notifier.js b/src/Notifier.js index a2e80353e1..93ef192fe0 100644 --- a/src/Notifier.js +++ b/src/Notifier.js @@ -80,10 +80,11 @@ const Notifier = { if (ev.getContent().body) msg = ev.getContent().body; } - const avatarUrl = ev.sender ? Avatar.avatarUrlForMember( - ev.sender, 40, 40, 'crop', - ) : null; + if (!this.isBodyEnabled()) { + msg = ''; + } + const avatarUrl = ev.sender ? Avatar.avatarUrlForMember(ev.sender, 40, 40, 'crop') : null; const notif = plaf.displayNotification(title, msg, avatarUrl, room); // if displayNotification returns non-null, the platform supports @@ -195,6 +196,19 @@ const Notifier = { return enabled === 'true'; }, + setBodyEnabled: function(enable) { + if (!global.localStorage) return; + global.localStorage.setItem('notifications_body_enabled', enable ? 'true' : 'false'); + }, + + isBodyEnabled: function() { + if (!global.localStorage) return true; + const enabled = global.localStorage.getItem('notifications_body_enabled'); + // default to true if the popups are enabled + if (enabled === null) return this.isEnabled(); + return enabled === 'true'; + }, + setAudioEnabled: function(enable) { if (!global.localStorage) return; global.localStorage.setItem('audio_notifications_enabled', diff --git a/src/TextForEvent.js b/src/TextForEvent.js index 6345403f09..51e3eb8dc9 100644 --- a/src/TextForEvent.js +++ b/src/TextForEvent.js @@ -259,6 +259,11 @@ function textForPowerEvent(event) { }); } +function textForPinnedEvent(event) { + const senderName = event.getSender(); + return _t("%(senderName)s changed the pinned messages for the room.", {senderName}); +} + function textForWidgetEvent(event) { const senderName = event.getSender(); const {name: prevName, type: prevType, url: prevUrl} = event.getPrevContent(); @@ -304,6 +309,7 @@ const stateHandlers = { 'm.room.history_visibility': textForHistoryVisibilityEvent, 'm.room.encryption': textForEncryptionEvent, 'm.room.power_levels': textForPowerEvent, + 'm.room.pinned_events': textForPinnedEvent, 'im.vector.modular.widgets': textForWidgetEvent, }; diff --git a/src/UserSettingsStore.js b/src/UserSettingsStore.js index d2b5bca841..68f463c373 100644 --- a/src/UserSettingsStore.js +++ b/src/UserSettingsStore.js @@ -30,6 +30,10 @@ const FEATURES = [ id: 'feature_groups', name: _td("Communities"), }, + { + id: 'feature_pinning', + name: _td("Message Pinning"), + }, ]; export default { @@ -98,6 +102,17 @@ export default { Notifier.setEnabled(enable); }, + getEnableNotificationBody: function() { + return Notifier.isBodyEnabled(); + }, + + setEnableNotificationBody: function(enable) { + if (!Notifier.supportsDesktopNotifications()) { + return; + } + Notifier.setBodyEnabled(enable); + }, + getEnableAudioNotifications: function() { return Notifier.isAudioEnabled(); }, diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index 3a2ab33db8..cad55351d1 100644 --- a/src/components/structures/RoomStatusBar.js +++ b/src/components/structures/RoomStatusBar.js @@ -43,6 +43,10 @@ module.exports = React.createClass({ // the end of the live timeline. atEndOfLiveTimeline: React.PropTypes.bool, + // This is true when the user is alone in the room, but has also sent a message. + // Used to suggest to the user to invite someone + sentMessageAndIsAlone: React.PropTypes.bool, + // true if there is an active call in this room (means we show // the 'Active Call' text in the status bar if there is nothing // more interesting) @@ -60,6 +64,14 @@ module.exports = React.createClass({ // 'unsent messages' bar onCancelAllClick: React.PropTypes.func, + // callback for when the user clicks on the 'invite others' button in the + // 'you are alone' bar + onInviteClick: React.PropTypes.func, + + // callback for when the user clicks on the 'stop warning me' button in the + // 'you are alone' bar + onStopWarningClick: React.PropTypes.func, + // callback for when the user clicks on the 'scroll to bottom' button onScrollToBottomClick: React.PropTypes.func, @@ -140,7 +152,8 @@ module.exports = React.createClass({ (this.state.usersTyping.length > 0) || this.props.numUnreadMessages || !this.props.atEndOfLiveTimeline || - this.props.hasActiveCall + this.props.hasActiveCall || + this.props.sentMessageAndIsAlone ) { return STATUS_BAR_EXPANDED; } else if (this.props.unsentMessageError) { @@ -305,6 +318,21 @@ module.exports = React.createClass({ ); } + // If you're alone in the room, and have sent a message, suggest to invite someone + if (this.props.sentMessageAndIsAlone) { + return ( +
+ { _tJsx("There's no one else here! Would you like to invite others or stop warning about the empty room?", + [/(.*?)<\/a>/, /(.*?)<\/a>/], + [ + (sub) => { sub }, + (sub) => { sub }, + ], + ) } +
+ ); + } + return null; }, diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 3d3537e067..83ca987276 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -117,6 +117,7 @@ module.exports = React.createClass({ guestsCanJoin: false, canPeek: false, showApps: false, + isAlone: false, isPeeking: false, // error object, as from the matrix client/server API @@ -461,6 +462,8 @@ module.exports = React.createClass({ switch (payload.action) { case 'message_send_failed': case 'message_sent': + this._checkIfAlone(this.state.room); + // no break; to intentionally fall through case 'message_send_cancelled': this.setState({ unsentMessageError: this._getUnsentMessageError(this.state.room), @@ -740,6 +743,20 @@ module.exports = React.createClass({ } }, 500), + _checkIfAlone: function(room) { + let warnedAboutLonelyRoom = false; + if (localStorage) { + warnedAboutLonelyRoom = localStorage.getItem('mx_user_alone_warned_' + this.state.room.roomId); + } + if (warnedAboutLonelyRoom) { + if (this.state.isAlone) this.setState({isAlone: false}); + return; + } + + const joinedMembers = room.currentState.getMembers().filter(m => m.membership === "join" || m.membership === "invite"); + this.setState({isAlone: joinedMembers.length === 1}); + }, + _getUnsentMessageError: function(room) { const unsentMessages = this._getUnsentMessages(room); if (!unsentMessages.length) return ""; @@ -821,6 +838,22 @@ module.exports = React.createClass({ Resend.cancelUnsentEvents(this.state.room); }, + onInviteButtonClick: function() { + // call AddressPickerDialog + dis.dispatch({ + action: 'view_invite', + roomId: this.state.room.roomId, + }); + this.setState({isAlone: false}); // there's a good chance they'll invite someone + }, + + onStopAloneWarningClick: function() { + if (localStorage) { + localStorage.setItem('mx_user_alone_warned_' + this.state.room.roomId, true); + } + this.setState({isAlone: false}); + }, + onJoinButtonClicked: function(ev) { const cli = MatrixClientPeg.get(); @@ -1144,6 +1177,10 @@ module.exports = React.createClass({ return ret; }, + onPinnedClick: function() { + this.setState({showingPinned: !this.state.showingPinned, searching: false}); + }, + onSettingsClick: function() { this.showSettings(true); }, @@ -1263,7 +1300,7 @@ module.exports = React.createClass({ }, onSearchClick: function() { - this.setState({ searching: true }); + this.setState({ searching: true, showingPinned: false }); }, onCancelSearchClick: function() { @@ -1462,6 +1499,7 @@ module.exports = React.createClass({ const RoomSettings = sdk.getComponent("rooms.RoomSettings"); const AuxPanel = sdk.getComponent("rooms.AuxPanel"); const SearchBar = sdk.getComponent("rooms.SearchBar"); + const PinnedEventsPanel = sdk.getComponent("rooms.PinnedEventsPanel"); const ScrollPanel = sdk.getComponent("structures.ScrollPanel"); const TintableSvg = sdk.getComponent("elements.TintableSvg"); const RoomPreviewBar = sdk.getComponent("rooms.RoomPreviewBar"); @@ -1581,9 +1619,12 @@ module.exports = React.createClass({ numUnreadMessages={this.state.numUnreadMessages} unsentMessageError={this.state.unsentMessageError} atEndOfLiveTimeline={this.state.atEndOfLiveTimeline} + sentMessageAndIsAlone={this.state.isAlone} hasActiveCall={inCall} onResendAllClick={this.onResendAllClick} onCancelAllClick={this.onCancelAllClick} + onInviteClick={this.onInviteButtonClick} + onStopWarningClick={this.onStopAloneWarningClick} onScrollToBottomClick={this.jumpToLiveTimeline} onResize={this.onChildResize} onVisible={this.onStatusBarVisible} @@ -1603,6 +1644,9 @@ module.exports = React.createClass({ } else if (this.state.searching) { hideCancel = true; // has own cancel aux = ; + } else if (this.state.showingPinned) { + hideCancel = true; // has own cancel + aux = ; } else if (!myMember || myMember.membership !== "join") { // We do have a room object for this room, but we're not currently in it. // We may have a 3rd party invite to it. @@ -1776,6 +1820,7 @@ module.exports = React.createClass({ collapsedRhs={this.props.collapsedRhs} onSearchClick={this.onSearchClick} onSettingsClick={this.onSettingsClick} + onPinnedClick={this.onPinnedClick} onSaveClick={this.onSettingsSaveClick} onCancelClick={(aux && !hideCancel) ? this.onCancelClick : null} onForgetClick={(myMember && myMember.membership === "leave") ? this.onForgetClick : null} diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index d83434f5a8..b69bea9282 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -114,6 +114,10 @@ const SETTINGS_LABELS = [ id: 'Pill.shouldHidePillAvatar', label: _td('Hide avatars in user and room mentions'), }, + { + id: 'TextualBody.disableBigEmoji', + label: _td('Disable big emoji in chat'), + }, /* { id: 'useFixedWidthFont', @@ -423,6 +427,11 @@ module.exports = React.createClass({ }); }, + onAvatarRemoveClick: function() { + MatrixClientPeg.get().setAvatarUrl(null); + this.setState({avatarUrl: null}); // the avatar update will complete async for us + }, + onLogoutClicked: function(ev) { const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); Modal.createTrackedDialog('Logout E2E Export', '', QuestionDialog, { @@ -1318,7 +1327,11 @@ module.exports = React.createClass({
-
+
+ {_t("Remove +
+
diff --git a/src/components/views/groups/GroupMemberList.js b/src/components/views/groups/GroupMemberList.js index 6a257259f9..f800130a32 100644 --- a/src/components/views/groups/GroupMemberList.js +++ b/src/components/views/groups/GroupMemberList.js @@ -35,7 +35,9 @@ export default withMatrixClient(React.createClass({ getInitialState: function() { return { fetching: false, + fetchingInvitedMembers: false, members: null, + invitedMembers: null, truncateAt: INITIAL_LOAD_NUM_MEMBERS, }; }, @@ -46,7 +48,10 @@ export default withMatrixClient(React.createClass({ }, _fetchMembers: function() { - this.setState({fetching: true}); + this.setState({ + fetching: true, + fetchingInvitedMembers: true, + }); this.props.matrixClient.getGroupUsers(this.props.groupId).then((result) => { this.setState({ members: result.chunk.map((apiMember) => { @@ -58,6 +63,18 @@ export default withMatrixClient(React.createClass({ this.setState({fetching: false}); console.error("Failed to get group member list: " + e); }); + + this.props.matrixClient.getGroupInvitedUsers(this.props.groupId).then((result) => { + this.setState({ + invitedMembers: result.chunk.map((apiMember) => { + return groupMemberFromApiObject(apiMember); + }), + fetchingInvitedMembers: false, + }); + }).catch((e) => { + this.setState({fetchingInvitedMembers: false}); + console.error("Failed to get group invited member list: " + e); + }); }, _createOverflowTile: function(overflowCount, totalCount) { @@ -83,11 +100,10 @@ export default withMatrixClient(React.createClass({ this.setState({ searchQuery: ev.target.value }); }, - makeGroupMemberTiles: function(query) { + makeGroupMemberTiles: function(query, memberList) { const GroupMemberTile = sdk.getComponent("groups.GroupMemberTile"); + const TruncatedList = sdk.getComponent("elements.TruncatedList"); query = (query || "").toLowerCase(); - - let memberList = this.state.members; if (query) { memberList = memberList.filter((m) => { const matchesName = m.displayname.toLowerCase().indexOf(query) !== -1; @@ -118,17 +134,19 @@ export default withMatrixClient(React.createClass({ } }); - return memberList; + return + { memberList } + ; }, render: function() { - if (this.state.fetching) { + if (this.state.fetching || this.state.fetchingInvitedMembers) { const Spinner = sdk.getComponent("elements.Spinner"); return (
); - } else if (this.state.members === null) { - return null; } const inputBox = ( @@ -139,15 +157,21 @@ export default withMatrixClient(React.createClass({ ); - const TruncatedList = sdk.getComponent("elements.TruncatedList"); + const joined = this.state.members ?
+ { this.makeGroupMemberTiles(this.state.searchQuery, this.state.members) } +
:
; + + const invited = this.state.invitedMembers ?
+

{ _t("Invited") }

+ { this.makeGroupMemberTiles(this.state.searchQuery, this.state.invitedMembers) } +
:
; + return (
{ inputBox } - - - { this.makeGroupMemberTiles(this.state.searchQuery) } - + + { joined } + { invited }
); diff --git a/src/components/views/messages/RoomAvatarEvent.js b/src/components/views/messages/RoomAvatarEvent.js index 453394249f..995d5f8531 100644 --- a/src/components/views/messages/RoomAvatarEvent.js +++ b/src/components/views/messages/RoomAvatarEvent.js @@ -70,9 +70,9 @@ module.exports = React.createClass({ // it sucks that _tJsx doesn't support normal _t substitutions :(( return (
- { _tJsx('$senderDisplayName changed the room avatar to ', + { _tJsx('%(senderDisplayName)s changed the room avatar to ', [ - /\$senderDisplayName/, + /%\(senderDisplayName\)s/, //, ], [ diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index c0468c38c2..64b23238e5 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -354,7 +354,9 @@ module.exports = React.createClass({ const mxEvent = this.props.mxEvent; const content = mxEvent.getContent(); - let body = HtmlUtils.bodyToHtml(content, this.props.highlights, {}); + let body = HtmlUtils.bodyToHtml(content, this.props.highlights, { + disableBigEmoji: UserSettingsStore.getSyncedSetting('TextualBody.disableBigEmoji', false), + }); if (this.props.highlightLink) { body = { body }; diff --git a/src/components/views/room_settings/ColorSettings.js b/src/components/views/room_settings/ColorSettings.js index d914390bd8..bfdaa49f65 100644 --- a/src/components/views/room_settings/ColorSettings.js +++ b/src/components/views/room_settings/ColorSettings.js @@ -34,7 +34,7 @@ const ROOM_COLORS = [ ["#dad658", "#f5f4ea"], ["#80c553", "#eef5ea"], ["#bb814e", "#eee8e3"], - ["#595959", "#ececec"], + //["#595959", "#ececec"], // Grey makes everything appear disabled, so remove it for now ]; module.exports = React.createClass({ diff --git a/src/components/views/rooms/EntityTile.js b/src/components/views/rooms/EntityTile.js index 64934f3cca..1df19340cd 100644 --- a/src/components/views/rooms/EntityTile.js +++ b/src/components/views/rooms/EntityTile.js @@ -116,7 +116,9 @@ module.exports = React.createClass({ nameEl = (
- { name } + + { name } + diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index f92dc0b97a..499d0ec09a 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -44,6 +44,7 @@ const eventTileTypes = { 'm.room.history_visibility': 'messages.TextualEvent', 'm.room.encryption': 'messages.TextualEvent', 'm.room.power_levels': 'messages.TextualEvent', + 'm.room.pinned_events' : 'messages.TextualEvent', 'im.vector.modular.widgets': 'messages.TextualEvent', }; diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 856d3ebad4..2f8bd30a72 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -625,22 +625,49 @@ module.exports = withMatrixClient(React.createClass({ }, _renderUserOptions: function() { - // Only allow the user to ignore the user if its not ourselves + const cli = this.props.matrixClient; + const member = this.props.member; + let ignoreButton = null; - if (this.props.member.userId !== this.props.matrixClient.getUserId()) { + let readReceiptButton = null; + + // Only allow the user to ignore the user if its not ourselves + // same goes for jumping to read receipt + if (member.userId !== cli.getUserId()) { ignoreButton = ( { this.state.isIgnoring ? _t("Unignore") : _t("Ignore") } ); + + if (member.roomId) { + const room = cli.getRoom(member.roomId); + const eventId = room.getEventReadUpTo(member.userId); + + const onReadReceiptButton = function() { + dis.dispatch({ + action: 'view_room', + highlighted: true, + event_id: eventId, + room_id: member.roomId, + }); + }; + + readReceiptButton = ( + + { _t('Jump to read receipt') } + + ); + } } - if (!ignoreButton) return null; + if (!ignoreButton && !readReceiptButton) return null; return (

{ _t("User Options") }

+ { readReceiptButton } { ignoreButton }
diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index 0713a4ae8a..04b6089559 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -146,8 +146,8 @@ module.exports = React.createClass({ const newState = { members: this.roomMembers(), }; - newState.filteredJoinedMembers = this._filterMembers(newState.members, 'join'); - newState.filteredInvitedMembers = this._filterMembers(newState.members, 'invite'); + newState.filteredJoinedMembers = this._filterMembers(newState.members, 'join', this.state.searchQuery); + newState.filteredInvitedMembers = this._filterMembers(newState.members, 'invite', this.state.searchQuery); this.setState(newState); }, 500), @@ -187,7 +187,7 @@ module.exports = React.createClass({ const user_id = all_user_ids[i]; const m = all_members[user_id]; - if (m.membership == 'join' || m.membership == 'invite') { + if (m.membership === 'join' || m.membership === 'invite') { if ((ConferenceHandler && !ConferenceHandler.isConferenceUser(user_id)) || !ConferenceHandler) { to_display.push(user_id); ++count; @@ -302,6 +302,7 @@ module.exports = React.createClass({ const m = this.memberDict[userId]; if (query) { + query = query.toLowerCase(); const matchesName = m.name.toLowerCase().indexOf(query) !== -1; const matchesId = m.userId.toLowerCase().indexOf(query) !== -1; @@ -310,7 +311,7 @@ module.exports = React.createClass({ } } - return m.membership == membership; + return m.membership === membership; }); }, diff --git a/src/components/views/rooms/PinnedEventTile.js b/src/components/views/rooms/PinnedEventTile.js new file mode 100644 index 0000000000..65b41d946a --- /dev/null +++ b/src/components/views/rooms/PinnedEventTile.js @@ -0,0 +1,90 @@ +/* +Copyright 2017 Travis Ralston + +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 MatrixClientPeg from "../../../MatrixClientPeg"; +import dis from "../../../dispatcher"; +import AccessibleButton from "../elements/AccessibleButton"; +import MessageEvent from "../messages/MessageEvent"; +import MemberAvatar from "../avatars/MemberAvatar"; +import { _t } from '../../../languageHandler'; + +module.exports = React.createClass({ + displayName: 'PinnedEventTile', + propTypes: { + mxRoom: React.PropTypes.object.isRequired, + mxEvent: React.PropTypes.object.isRequired, + onUnpinned: React.PropTypes.func, + }, + onTileClicked: function() { + dis.dispatch({ + action: 'view_room', + event_id: this.props.mxEvent.getId(), + highlighted: true, + room_id: this.props.mxEvent.getRoomId(), + }); + }, + onUnpinClicked: function() { + const pinnedEvents = this.props.mxRoom.currentState.getStateEvents("m.room.pinned_events", ""); + if (!pinnedEvents || !pinnedEvents.getContent().pinned) { + // Nothing to do: already unpinned + if (this.props.onUnpinned) this.props.onUnpinned(); + } else { + const pinned = pinnedEvents.getContent().pinned; + const index = pinned.indexOf(this.props.mxEvent.getId()); + if (index !== -1) { + pinned.splice(index, 1); + MatrixClientPeg.get().sendStateEvent(this.props.mxRoom.roomId, 'm.room.pinned_events', {pinned}, '') + .then(() => { + if (this.props.onUnpinned) this.props.onUnpinned(); + }); + } else if (this.props.onUnpinned) this.props.onUnpinned(); + } + }, + _canUnpin: function() { + return this.props.mxRoom.currentState.mayClientSendStateEvent('m.room.pinned_events', MatrixClientPeg.get()); + }, + render: function() { + const sender = this.props.mxRoom.getMember(this.props.mxEvent.getSender()); + const avatarSize = 40; + + let unpinButton = null; + if (this._canUnpin()) { + unpinButton = ( + + {_t('Unpin + + ); + } + + return ( +
+
+ + { _t("Jump to message") } + + { unpinButton } +
+ + + + { sender.name } + + +
+ ); + }, +}); diff --git a/src/components/views/rooms/PinnedEventsPanel.js b/src/components/views/rooms/PinnedEventsPanel.js new file mode 100644 index 0000000000..deea03f030 --- /dev/null +++ b/src/components/views/rooms/PinnedEventsPanel.js @@ -0,0 +1,105 @@ +/* +Copyright 2017 Travis Ralston + +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 MatrixClientPeg from "../../../MatrixClientPeg"; +import AccessibleButton from "../elements/AccessibleButton"; +import PinnedEventTile from "./PinnedEventTile"; +import { _t } from '../../../languageHandler'; + +module.exports = React.createClass({ + displayName: 'PinnedEventsPanel', + propTypes: { + // The Room from the js-sdk we're going to show pinned events for + room: React.PropTypes.object.isRequired, + + onCancelClick: React.PropTypes.func, + }, + + getInitialState: function() { + return { + loading: true, + }; + }, + + componentDidMount: function() { + this._updatePinnedMessages(); + }, + + _updatePinnedMessages: function() { + const pinnedEvents = this.props.room.currentState.getStateEvents("m.room.pinned_events", ""); + if (!pinnedEvents || !pinnedEvents.getContent().pinned) { + this.setState({ loading: false, pinned: [] }); + } else { + const promises = []; + const cli = MatrixClientPeg.get(); + + pinnedEvents.getContent().pinned.map((eventId) => { + promises.push(cli.getEventTimeline(this.props.room.getUnfilteredTimelineSet(), eventId, 0).then( + (timeline) => { + const event = timeline.getEvents().find((e) => e.getId() === eventId); + return {eventId, timeline, event}; + }).catch((err) => { + console.error("Error looking up pinned event " + eventId + " in room " + this.props.room.roomId); + console.error(err); + return null; // return lack of context to avoid unhandled errors + })); + }); + + Promise.all(promises).then((contexts) => { + // Filter out the messages before we try to render them + const pinned = contexts.filter((context) => { + if (!context) return false; // no context == not applicable for the room + if (context.event.getType() !== "m.room.message") return false; + if (context.event.isRedacted()) return false; + return true; + }); + + this.setState({ loading: false, pinned }); + }); + } + }, + + _getPinnedTiles: function() { + if (this.state.pinned.length == 0) { + return (
{ _t("No pinned messages.") }
); + } + + return this.state.pinned.map((context) => { + return (); + }); + }, + + render: function() { + let tiles =
{ _t("Loading...") }
; + if (this.state && !this.state.loading) { + tiles = this._getPinnedTiles(); + } + + return ( +
+
+ +

{ _t("Pinned Messages") }

+ { tiles } +
+
+ ); + }, +}); diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 42cbb90cd9..4df0ff738c 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -31,6 +31,7 @@ import linkifyMatrix from '../../../linkify-matrix'; import AccessibleButton from '../elements/AccessibleButton'; import ManageIntegsButton from '../elements/ManageIntegsButton'; import {CancelButton} from './SimpleRoomHeader'; +import UserSettingsStore from "../../../UserSettingsStore"; linkifyMatrix(linkify); @@ -45,6 +46,7 @@ module.exports = React.createClass({ inRoom: React.PropTypes.bool, collapsedRhs: React.PropTypes.bool, onSettingsClick: React.PropTypes.func, + onPinnedClick: React.PropTypes.func, onSaveClick: React.PropTypes.func, onSearchClick: React.PropTypes.func, onLeaveClick: React.PropTypes.func, @@ -129,6 +131,10 @@ module.exports = React.createClass({ }).done(); }, + onAvatarRemoveClick: function() { + MatrixClientPeg.get().sendStateEvent(this.props.room.roomId, 'm.room.avatar', {url: null}, ''); + }, + onShowRhsClick: function(ev) { dis.dispatch({ action: 'show_right_panel' }); }, @@ -172,6 +178,7 @@ module.exports = React.createClass({ let spinner = null; let saveButton = null; let settingsButton = null; + let pinnedEventsButton = null; let canSetRoomName; let canSetRoomAvatar; @@ -268,11 +275,15 @@ module.exports = React.createClass({
+
+ {_t("Remove +
); } else if (this.props.room || (this.props.oobData && this.props.oobData.name)) { @@ -290,6 +301,13 @@ module.exports = React.createClass({ ; } + if (this.props.onPinnedClick && UserSettingsStore.isFeatureEnabled('feature_pinning')) { + pinnedEventsButton = + + + ; + } + // var leave_button; // if (this.props.onLeaveClick) { // leave_button = @@ -334,6 +352,7 @@ module.exports = React.createClass({ rightRow =
{ settingsButton } + { pinnedEventsButton } { manageIntegsButton } { forgetButton } { searchButton } diff --git a/src/components/views/settings/ChangeAvatar.js b/src/components/views/settings/ChangeAvatar.js index b3204ab86e..a363dc2c60 100644 --- a/src/components/views/settings/ChangeAvatar.js +++ b/src/components/views/settings/ChangeAvatar.js @@ -53,6 +53,10 @@ module.exports = React.createClass({ }; }, + componentWillMount: function() { + MatrixClientPeg.get().on("RoomState.events", this.onRoomStateEvents); + }, + componentWillReceiveProps: function(newProps) { if (this.avatarSet) { // don't clobber what the user has just set @@ -63,6 +67,28 @@ module.exports = React.createClass({ }); }, + componentWillUnmount: function() { + if (MatrixClientPeg.get()) { + MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents); + } + }, + + onRoomStateEvents: function(ev) { + if (!this.props.room) { + return; + } + + if (ev.getRoomId() !== this.props.room.roomId || ev.getType() !== 'm.room.avatar' + || ev.getSender() !== MatrixClientPeg.get().getUserId()) { + return; + } + + if (!ev.getContent().url) { + this.avatarSet = false; + this.setState({}); // force update + } + }, + setAvatarFromFile: function(file) { let newUrl = null; diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 63f21e80a1..c7b05de215 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -728,7 +728,7 @@ "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "WARNUNG: SCHLÜSSEL-VERIFIZIERUNG FEHLGESCHLAGEN! Der Signatur-Schlüssel für %(userId)s und das Gerät %(deviceId)s ist \"%(fprint)s\", welcher nicht mit dem bereitgestellten Schlüssel \"%(fingerprint)s\" übereinstimmt. Dies kann bedeuten, dass deine Kommunikation abgehört wird!", "You have disabled URL previews by default.": "Du hast die URL-Vorschau standardmäßig deaktiviert.", "You have enabled URL previews by default.": "Du hast die URL-Vorschau standardmäßig aktiviert.", - "$senderDisplayName changed the room avatar to ": "$senderDisplayName hat das Raum-Bild geändert zu ", + "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s hat das Raum-Bild geändert zu ", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s hat das Raum-Bild für %(roomName)s geändert", "Hide removed messages": "Gelöschte Nachrichten verbergen", "Start new chat": "Neuen Chat starten", diff --git a/src/i18n/strings/el.json b/src/i18n/strings/el.json index 99ae411d94..06009b0b3b 100644 --- a/src/i18n/strings/el.json +++ b/src/i18n/strings/el.json @@ -639,7 +639,7 @@ "Disable URL previews by default for participants in this room": "Απενεργοποίηση της προεπισκόπησης συνδέσμων για όλους τους συμμετέχοντες στο δωμάτιο", "Disable URL previews for this room (affects only you)": "Απενεργοποίηση της προεπισκόπησης συνδέσμων για αυτό το δωμάτιο (επηρεάζει μόνο εσάς)", " (unsupported)": " (μη υποστηριζόμενο)", - "$senderDisplayName changed the room avatar to ": "Ο $senderDisplayName άλλαξε την εικόνα του δωματίου σε ", + "%(senderDisplayName)s changed the room avatar to ": "Ο %(senderDisplayName)s άλλαξε την εικόνα του δωματίου σε ", "Missing Media Permissions, click here to request.": "Λείπουν τα δικαιώματα πολύμεσων, κάντε κλικ για να ζητήσετε.", "You may need to manually permit Riot to access your microphone/webcam": "Μπορεί να χρειαστεί να ορίσετε χειροκίνητα την πρόσβαση του Riot στο μικρόφωνο/κάμερα", "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Δεν είναι δυνατή η σύνδεση στον διακομιστή - παρακαλούμε ελέγξτε την συνδεσιμότητα, βεβαιωθείτε ότι το πιστοποιητικό SSL του διακομιστή είναι έμπιστο και ότι κάποιο πρόσθετο περιηγητή δεν αποτρέπει τα αιτήματα.", diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ff571cfe42..a28e532073 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -252,6 +252,7 @@ "%(targetName)s joined the room.": "%(targetName)s joined the room.", "Joins room with given alias": "Joins room with given alias", "Jump to first unread message.": "Jump to first unread message.", + "Jump to read receipt": "Jump to read receipt", "%(senderName)s kicked %(targetName)s.": "%(senderName)s kicked %(targetName)s.", "Kick": "Kick", "Kicks user with given id": "Kicks user with given id", @@ -289,6 +290,7 @@ "matrix-react-sdk version:": "matrix-react-sdk version:", "Matrix Apps": "Matrix Apps", "Members only": "Members only", + "Disable big emoji in chat": "Disable big emoji in chat", "Disable Emoji suggestions while typing": "Disable Emoji suggestions while typing", "Message not sent due to unknown devices being present": "Message not sent due to unknown devices being present", "Missing room_id in request": "Missing room_id in request", @@ -609,6 +611,7 @@ "Room": "Room", "Copied!": "Copied!", "Failed to copy": "Failed to copy", + "There's no one else here! Would you like to invite others or stop warning about the empty room?": "There's no one else here! Would you like to invite others or stop warning about the empty room?", "Connectivity to the server has been lost.": "Connectivity to the server has been lost.", "Sent messages will be stored until your connection has returned.": "Sent messages will be stored until your connection has returned.", "Resend all or cancel all now. You can also select individual messages to resend or cancel.": "Resend all or cancel all now. You can also select individual messages to resend or cancel.", @@ -616,6 +619,7 @@ "(~%(count)s results)|other": "(~%(count)s results)", "Cancel": "Cancel", "or": "or", + "Message Pinning": "Message Pinning", "Active call": "Active call", "Monday": "Monday", "Tuesday": "Tuesday", @@ -632,6 +636,7 @@ "quote": "quote", "bullet": "bullet", "numbullet": "numbullet", + "Remove avatar": "Remove avatar", "%(severalUsers)sjoined %(repeats)s times": "%(severalUsers)sjoined %(repeats)s times", "%(oneUser)sjoined %(repeats)s times": "%(oneUser)sjoined %(repeats)s times", "%(severalUsers)sjoined": "%(severalUsers)sjoined", @@ -785,7 +790,7 @@ "Start chatting": "Start chatting", "Start Chatting": "Start Chatting", "Click on the button below to start chatting!": "Click on the button below to start chatting!", - "$senderDisplayName changed the room avatar to ": "$senderDisplayName changed the room avatar to ", + "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s changed the room avatar to ", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removed the room avatar.", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s changed the avatar for %(roomName)s", "Username available": "Username available", @@ -885,6 +890,8 @@ "Add rooms to the group summary": "Add rooms to the group summary", "Which rooms would you like to add to this summary?": "Which rooms would you like to add to this summary?", "Room name or alias": "Room name or alias", + "Pinned Messages": "Pinned Messages", + "%(senderName)s changed the pinned messages for the room.": "%(senderName)s changed the pinned messages for the room.", "You are an administrator of this group": "You are an administrator of this group", "Failed to add the following rooms to the summary of %(groupId)s:": "Failed to add the following rooms to the summary of %(groupId)s:", "Failed to remove the room from the summary of %(groupId)s": "Failed to remove the room from the summary of %(groupId)s", diff --git a/src/i18n/strings/en_US.json b/src/i18n/strings/en_US.json index 5236e959d4..7326b54799 100644 --- a/src/i18n/strings/en_US.json +++ b/src/i18n/strings/en_US.json @@ -705,7 +705,7 @@ "Idle": "Idle", "Offline": "Offline", "Disable URL previews for this room (affects only you)": "Disable URL previews for this room (affects only you)", - "$senderDisplayName changed the room avatar to ": "$senderDisplayName changed the room avatar to ", + "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s changed the room avatar to ", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removed the room avatar.", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s changed the avatar for %(roomName)s", "Active call (%(roomName)s)": "Active call (%(roomName)s)", @@ -844,6 +844,7 @@ "+example:%(domain)s": "+example:%(domain)s", "Group IDs must be of the form +localpart:%(domain)s": "Group IDs must be of the form +localpart:%(domain)s", "Room creation failed": "Room creation failed", + "Pinned Messages": "Pinned Messages", "You are a member of these groups:": "You are a member of these groups:", "Create a group to represent your community! Define a set of rooms and your own custom homepage to mark out your space in the Matrix universe.": "Create a group to represent your community! Define a set of rooms and your own custom homepage to mark out your space in the Matrix universe.", "Join an existing group": "Join an existing group", @@ -859,6 +860,7 @@ "%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s", "Robot check is currently unavailable on desktop - please use a web browser": "Robot check is currently unavailable on desktop - please use a web browser", "Verifies a user, device, and pubkey tuple": "Verifies a user, device, and pubkey tuple", + "%(senderName)s changed the pinned messages for the room.": "%(senderName)s changed the pinned messages for the room.", "It is currently only possible to create groups on your own home server: use a group ID ending with %(domain)s": "It is currently only possible to create groups on your own home server: use a group ID ending with %(domain)s", "To join an existing group you'll have to know its group identifier; this will look something like +example:matrix.org.": "To join an existing group you'll have to know its group identifier; this will look something like +example:matrix.org.", "%(weekDayName)s, %(monthName)s %(day)s": "%(weekDayName)s, %(monthName)s %(day)s", diff --git a/src/i18n/strings/eu.json b/src/i18n/strings/eu.json index fcad984ff5..825940fdc4 100644 --- a/src/i18n/strings/eu.json +++ b/src/i18n/strings/eu.json @@ -736,7 +736,7 @@ "Start chatting": "Hasi txateatzen", "Start Chatting": "Hasi txateatzen", "Click on the button below to start chatting!": "Egin klik beheko botoian txateatzen hasteko!", - "$senderDisplayName changed the room avatar to ": "$senderDisplayName erabiltzaileak gelaren abatarra aldatu du beste honetara: ", + "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s erabiltzaileak gelaren abatarra aldatu du beste honetara: ", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s erabiltzaileak gelaren abatarra ezabatu du.", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s erabiltzaileak %(roomName)s gelaren abatarra aldatu du", "Username available": "Erabiltzaile-izena eskuragarri dago", diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index d07a162f8d..3a45793b7c 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -637,7 +637,7 @@ "for %(amount)sm": "depuis %(amount)sm", "for %(amount)sh": "depuis %(amount)sh", "for %(amount)sd": "depuis %(amount)sj", - "$senderDisplayName changed the room avatar to ": "$senderDisplayName a changé l’avatar du salon en ", + "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s a changé l’avatar du salon en ", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s a supprimé l'avatar du salon.", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s a changé l’avatar de %(roomName)s", "Device already verified!": "Appareil déjà vérifié !", diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index bcd3452a6c..4e2c39b27b 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -723,7 +723,7 @@ "Start chatting": "Csevegés indítása", "Start Chatting": "Csevegés indítása", "Click on the button below to start chatting!": "Csevegés indításához kattints a gombra alább!", - "$senderDisplayName changed the room avatar to ": "$senderDisplayName megváltoztatta a szoba avatar képét: ", + "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s megváltoztatta a szoba avatar képét: ", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s törölte a szoba avatar képét.", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s megváltoztatta %(roomName)s szoba avatar képét", "Username available": "Szabad felhasználói név", diff --git a/src/i18n/strings/ko.json b/src/i18n/strings/ko.json index 307ee762ef..98a07e629d 100644 --- a/src/i18n/strings/ko.json +++ b/src/i18n/strings/ko.json @@ -743,7 +743,7 @@ "Start chatting": "이야기하기", "Start Chatting": "이야기하기", "Click on the button below to start chatting!": "이야기하려면 아래 버튼을 누르세요!", - "$senderDisplayName changed the room avatar to ": "$senderDisplayName님이 방 아바타를 로 바꾸셨어요", + "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s님이 방 아바타를 로 바꾸셨어요", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s님이 방 아바타를 지우셨어요.", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s가 %(roomName)s 방의 아바타를 바꾸셨어요", "Username available": "쓸 수 있는 사용자 이름", diff --git a/src/i18n/strings/lv.json b/src/i18n/strings/lv.json index 0a5e999015..164b6226d9 100644 --- a/src/i18n/strings/lv.json +++ b/src/i18n/strings/lv.json @@ -619,7 +619,7 @@ "Dec": "Dec.", "Set a display name:": "Iestatīt redzamo vārdu:", "This image cannot be displayed.": "Šo attēlu nav iespējams parādīt.", - "$senderDisplayName changed the room avatar to ": "$senderDisplayName nomainīja istabas attēlu uz ", + "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s nomainīja istabas attēlu uz ", "Upload an avatar:": "Augšuplādē profila attēlu:", "This server does not support authentication with a phone number.": "Šis serveris neatbalsta autentifikāciju pēc telefona numura.", "Missing password.": "Trūkst parole.", diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json index 38ca640fc8..c12a2ad81c 100644 --- a/src/i18n/strings/nl.json +++ b/src/i18n/strings/nl.json @@ -746,7 +746,7 @@ "Start chatting": "Start met praten", "Start Chatting": "Start Met Praten", "Click on the button below to start chatting!": "Klik op de knop hieronder om te starten met praten!", - "$senderDisplayName changed the room avatar to ": "$senderDisplayName heeft de ruimte avatar aangepast naar ", + "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s heeft de ruimte avatar aangepast naar ", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s heeft de ruimte avatar verwijderd.", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s veranderde de avatar voor %(roomName)s", "Username available": "Gebruikersnaam beschikbaar", diff --git a/src/i18n/strings/pl.json b/src/i18n/strings/pl.json index 0b11e26c44..66e7b2aa28 100644 --- a/src/i18n/strings/pl.json +++ b/src/i18n/strings/pl.json @@ -771,7 +771,7 @@ "for %(amount)sd": "%(amount)s dni", "Idle": "Bezczynny", "Check for update": "Sprawdź aktualizacje", - "$senderDisplayName changed the room avatar to ": "$senderDisplayName zmienił awatar pokoju na ", + "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s zmienił awatar pokoju na ", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s usunął awatar pokoju.", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s zmienił awatar %(roomName)s", "This will be your account name on the homeserver, or you can pick a different server.": "To będzie twoja nazwa konta na serwerze domowym; możesz też wybrać inny serwer.", diff --git a/src/i18n/strings/pt.json b/src/i18n/strings/pt.json index 56beb43e33..73d7e6ca68 100644 --- a/src/i18n/strings/pt.json +++ b/src/i18n/strings/pt.json @@ -699,7 +699,7 @@ "for %(amount)sd": "por %(amount)sd", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removeu a imagem da sala.", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s alterou a imagem da sala %(roomName)s", - "$senderDisplayName changed the room avatar to ": "$senderDisplayName alterou a imagem da sala para ", + "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s alterou a imagem da sala para ", "Missing Media Permissions, click here to request.": "Faltam permissões para uso de mídia no seu computador. Clique aqui para solicitá-las.", "No Microphones detected": "Não foi detetado nenhum microfone", "No Webcams detected": "Não foi detetada nenhuma Webcam", diff --git a/src/i18n/strings/pt_BR.json b/src/i18n/strings/pt_BR.json index 109c0bb6c2..691cf8d9bf 100644 --- a/src/i18n/strings/pt_BR.json +++ b/src/i18n/strings/pt_BR.json @@ -696,7 +696,7 @@ "for %(amount)sd": "por %(amount)sd", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removeu a imagem da sala.", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s alterou a imagem da sala %(roomName)s", - "$senderDisplayName changed the room avatar to ": "$senderDisplayName alterou a imagem da sala para ", + "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s alterou a imagem da sala para ", "Missing Media Permissions, click here to request.": "Faltam permissões para uso de mídia no seu computador. Clique aqui para solicitá-las.", "No Microphones detected": "Não foi detectado nenhum microfone", "No Webcams detected": "Não foi detectada nenhuma Webcam", diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index ab09fd1700..fffce4f328 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -705,7 +705,7 @@ "Idle": "Неактивен", "Offline": "Не в сети", "Disable URL previews for this room (affects only you)": "Отключить предпросмотр URL-адресов для этой комнаты (влияет только на вас)", - "$senderDisplayName changed the room avatar to ": "$senderDisplayName сменил аватар комнаты на ", + "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s сменил аватар комнаты на ", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s удалил аватар комнаты.", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s сменил аватар для %(roomName)s", "Create new room": "Создать новую комнату", diff --git a/src/i18n/strings/tr.json b/src/i18n/strings/tr.json index 98be1c9a64..fdc06f741c 100644 --- a/src/i18n/strings/tr.json +++ b/src/i18n/strings/tr.json @@ -738,7 +738,7 @@ "Start chatting": "Sohbeti başlat", "Start Chatting": "Sohbeti Başlat", "Click on the button below to start chatting!": "Sohbeti başlatmak için aşağıdaki butona tıklayın!", - "$senderDisplayName changed the room avatar to ": "$senderDisplayName odanın avatarını olarak çevirdi", + "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s odanın avatarını olarak çevirdi", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s odanın avatarını kaldırdı.", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s %(roomName)s için avatarı değiştirdi", "Username available": "Kullanıcı ismi uygun", diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 83aadd5178..35e18b9793 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -228,7 +228,7 @@ "Idle": "閒置", "Offline": "下線", "Disable URL previews for this room (affects only you)": "在這個房間禁止URL預覽(只影響你)", - "$senderDisplayName changed the room avatar to ": "$senderDisplayName 更改了聊天室的圖像為 ", + "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s 更改了聊天室的圖像為 ", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s 移除了聊天室圖片。", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s 更改了聊天室 %(roomName)s 圖像", "Cancel": "取消", diff --git a/src/languageHandler.js b/src/languageHandler.js index b2fc65c46d..a90b78c40e 100644 --- a/src/languageHandler.js +++ b/src/languageHandler.js @@ -109,8 +109,9 @@ export function _tJsx(jsxText, patterns, subs) { } // The translation returns text so there's no XSS vector here (no unsafe HTML, no code execution) - const tJsxText = _t(jsxText); + const tJsxText = _t(jsxText, {interpolate: false}); const output = [tJsxText]; + for (let i = 0; i < patterns.length; i++) { // convert the last element in 'output' into 3 elements (pre-text, sub function, post-text). // Rinse and repeat for other patterns (using post-text).