Merge branch 'develop' into luke/groups-are-communities

This commit is contained in:
Luke Barnard 2017-10-16 17:34:06 +01:00
commit 0c34e943fb
38 changed files with 642 additions and 58 deletions

View file

@ -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) 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) [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.10.5...v0.10.6)

View file

@ -1,6 +1,6 @@
{ {
"name": "matrix-react-sdk", "name": "matrix-react-sdk",
"version": "0.10.6", "version": "0.10.7",
"description": "SDK for matrix.org using React", "description": "SDK for matrix.org using React",
"author": "matrix.org", "author": "matrix.org",
"repository": { "repository": {
@ -67,7 +67,7 @@
"isomorphic-fetch": "^2.2.1", "isomorphic-fetch": "^2.2.1",
"linkifyjs": "^2.1.3", "linkifyjs": "^2.1.3",
"lodash": "^4.13.1", "lodash": "^4.13.1",
"matrix-js-sdk": "0.8.4", "matrix-js-sdk": "0.8.5",
"optimist": "^0.6.1", "optimist": "^0.6.1",
"prop-types": "^15.5.8", "prop-types": "^15.5.8",
"react": "^15.4.0", "react": "^15.4.0",

View file

@ -172,7 +172,7 @@ const sanitizeHtmlParams = {
// Lots of these won't come up by default because we don't allow them // 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'], selfClosing: ['img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta'],
// URL schemes we permit // URL schemes we permit
allowedSchemes: ['http', 'https', 'ftp', 'mailto'], allowedSchemes: ['http', 'https', 'ftp', 'mailto', 'magnet'],
allowProtocolRelative: false, allowProtocolRelative: false,
@ -385,10 +385,9 @@ class TextHighlighter extends BaseHighlighter {
* highlights: optional list of words to highlight, ordered by longest word first * highlights: optional list of words to highlight, ordered by longest word first
* *
* opts.highlightLink: optional href to add to highlighted words * 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) { export function bodyToHtml(content, highlights, opts={}) {
opts = opts || {};
const isHtml = (content.format === "org.matrix.custom.html"); const isHtml = (content.format === "org.matrix.custom.html");
const body = isHtml ? content.formatted_body : escape(content.body); const body = isHtml ? content.formatted_body : escape(content.body);
@ -418,7 +417,7 @@ export function bodyToHtml(content, highlights, opts) {
} }
let emojiBody = false; let emojiBody = false;
if (bodyHasEmoji) { if (!opts.disableBigEmoji && bodyHasEmoji) {
EMOJI_REGEX.lastIndex = 0; EMOJI_REGEX.lastIndex = 0;
const contentBodyTrimmed = content.body !== undefined ? content.body.trim() : ''; const contentBodyTrimmed = content.body !== undefined ? content.body.trim() : '';
const match = EMOJI_REGEX.exec(contentBodyTrimmed); const match = EMOJI_REGEX.exec(contentBodyTrimmed);

View file

@ -80,10 +80,11 @@ const Notifier = {
if (ev.getContent().body) msg = ev.getContent().body; if (ev.getContent().body) msg = ev.getContent().body;
} }
const avatarUrl = ev.sender ? Avatar.avatarUrlForMember( if (!this.isBodyEnabled()) {
ev.sender, 40, 40, 'crop', msg = '';
) : null; }
const avatarUrl = ev.sender ? Avatar.avatarUrlForMember(ev.sender, 40, 40, 'crop') : null;
const notif = plaf.displayNotification(title, msg, avatarUrl, room); const notif = plaf.displayNotification(title, msg, avatarUrl, room);
// if displayNotification returns non-null, the platform supports // if displayNotification returns non-null, the platform supports
@ -195,6 +196,19 @@ const Notifier = {
return enabled === 'true'; 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) { setAudioEnabled: function(enable) {
if (!global.localStorage) return; if (!global.localStorage) return;
global.localStorage.setItem('audio_notifications_enabled', global.localStorage.setItem('audio_notifications_enabled',

View file

@ -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) { function textForWidgetEvent(event) {
const senderName = event.getSender(); const senderName = event.getSender();
const {name: prevName, type: prevType, url: prevUrl} = event.getPrevContent(); const {name: prevName, type: prevType, url: prevUrl} = event.getPrevContent();
@ -304,6 +309,7 @@ const stateHandlers = {
'm.room.history_visibility': textForHistoryVisibilityEvent, 'm.room.history_visibility': textForHistoryVisibilityEvent,
'm.room.encryption': textForEncryptionEvent, 'm.room.encryption': textForEncryptionEvent,
'm.room.power_levels': textForPowerEvent, 'm.room.power_levels': textForPowerEvent,
'm.room.pinned_events': textForPinnedEvent,
'im.vector.modular.widgets': textForWidgetEvent, 'im.vector.modular.widgets': textForWidgetEvent,
}; };

View file

@ -30,6 +30,10 @@ const FEATURES = [
id: 'feature_groups', id: 'feature_groups',
name: _td("Communities"), name: _td("Communities"),
}, },
{
id: 'feature_pinning',
name: _td("Message Pinning"),
},
]; ];
export default { export default {
@ -98,6 +102,17 @@ export default {
Notifier.setEnabled(enable); Notifier.setEnabled(enable);
}, },
getEnableNotificationBody: function() {
return Notifier.isBodyEnabled();
},
setEnableNotificationBody: function(enable) {
if (!Notifier.supportsDesktopNotifications()) {
return;
}
Notifier.setBodyEnabled(enable);
},
getEnableAudioNotifications: function() { getEnableAudioNotifications: function() {
return Notifier.isAudioEnabled(); return Notifier.isAudioEnabled();
}, },

View file

@ -43,6 +43,10 @@ module.exports = React.createClass({
// the end of the live timeline. // the end of the live timeline.
atEndOfLiveTimeline: React.PropTypes.bool, 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 // 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 // the 'Active Call' text in the status bar if there is nothing
// more interesting) // more interesting)
@ -60,6 +64,14 @@ module.exports = React.createClass({
// 'unsent messages' bar // 'unsent messages' bar
onCancelAllClick: React.PropTypes.func, 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 // callback for when the user clicks on the 'scroll to bottom' button
onScrollToBottomClick: React.PropTypes.func, onScrollToBottomClick: React.PropTypes.func,
@ -140,7 +152,8 @@ module.exports = React.createClass({
(this.state.usersTyping.length > 0) || (this.state.usersTyping.length > 0) ||
this.props.numUnreadMessages || this.props.numUnreadMessages ||
!this.props.atEndOfLiveTimeline || !this.props.atEndOfLiveTimeline ||
this.props.hasActiveCall this.props.hasActiveCall ||
this.props.sentMessageAndIsAlone
) { ) {
return STATUS_BAR_EXPANDED; return STATUS_BAR_EXPANDED;
} else if (this.props.unsentMessageError) { } 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 (
<div className="mx_RoomStatusBar_isAlone">
{ _tJsx("There's no one else here! Would you like to <a>invite others</a> or <a>stop warning about the empty room</a>?",
[/<a>(.*?)<\/a>/, /<a>(.*?)<\/a>/],
[
(sub) => <a className="mx_RoomStatusBar_resend_link" key="invite" onClick={this.props.onInviteClick}>{ sub }</a>,
(sub) => <a className="mx_RoomStatusBar_resend_link" key="nowarn" onClick={this.props.onStopWarningClick}>{ sub }</a>,
],
) }
</div>
);
}
return null; return null;
}, },

View file

@ -117,6 +117,7 @@ module.exports = React.createClass({
guestsCanJoin: false, guestsCanJoin: false,
canPeek: false, canPeek: false,
showApps: false, showApps: false,
isAlone: false,
isPeeking: false, isPeeking: false,
// error object, as from the matrix client/server API // error object, as from the matrix client/server API
@ -461,6 +462,8 @@ module.exports = React.createClass({
switch (payload.action) { switch (payload.action) {
case 'message_send_failed': case 'message_send_failed':
case 'message_sent': case 'message_sent':
this._checkIfAlone(this.state.room);
// no break; to intentionally fall through
case 'message_send_cancelled': case 'message_send_cancelled':
this.setState({ this.setState({
unsentMessageError: this._getUnsentMessageError(this.state.room), unsentMessageError: this._getUnsentMessageError(this.state.room),
@ -740,6 +743,20 @@ module.exports = React.createClass({
} }
}, 500), }, 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) { _getUnsentMessageError: function(room) {
const unsentMessages = this._getUnsentMessages(room); const unsentMessages = this._getUnsentMessages(room);
if (!unsentMessages.length) return ""; if (!unsentMessages.length) return "";
@ -821,6 +838,22 @@ module.exports = React.createClass({
Resend.cancelUnsentEvents(this.state.room); 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) { onJoinButtonClicked: function(ev) {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
@ -1144,6 +1177,10 @@ module.exports = React.createClass({
return ret; return ret;
}, },
onPinnedClick: function() {
this.setState({showingPinned: !this.state.showingPinned, searching: false});
},
onSettingsClick: function() { onSettingsClick: function() {
this.showSettings(true); this.showSettings(true);
}, },
@ -1263,7 +1300,7 @@ module.exports = React.createClass({
}, },
onSearchClick: function() { onSearchClick: function() {
this.setState({ searching: true }); this.setState({ searching: true, showingPinned: false });
}, },
onCancelSearchClick: function() { onCancelSearchClick: function() {
@ -1462,6 +1499,7 @@ module.exports = React.createClass({
const RoomSettings = sdk.getComponent("rooms.RoomSettings"); const RoomSettings = sdk.getComponent("rooms.RoomSettings");
const AuxPanel = sdk.getComponent("rooms.AuxPanel"); const AuxPanel = sdk.getComponent("rooms.AuxPanel");
const SearchBar = sdk.getComponent("rooms.SearchBar"); const SearchBar = sdk.getComponent("rooms.SearchBar");
const PinnedEventsPanel = sdk.getComponent("rooms.PinnedEventsPanel");
const ScrollPanel = sdk.getComponent("structures.ScrollPanel"); const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
const TintableSvg = sdk.getComponent("elements.TintableSvg"); const TintableSvg = sdk.getComponent("elements.TintableSvg");
const RoomPreviewBar = sdk.getComponent("rooms.RoomPreviewBar"); const RoomPreviewBar = sdk.getComponent("rooms.RoomPreviewBar");
@ -1581,9 +1619,12 @@ module.exports = React.createClass({
numUnreadMessages={this.state.numUnreadMessages} numUnreadMessages={this.state.numUnreadMessages}
unsentMessageError={this.state.unsentMessageError} unsentMessageError={this.state.unsentMessageError}
atEndOfLiveTimeline={this.state.atEndOfLiveTimeline} atEndOfLiveTimeline={this.state.atEndOfLiveTimeline}
sentMessageAndIsAlone={this.state.isAlone}
hasActiveCall={inCall} hasActiveCall={inCall}
onResendAllClick={this.onResendAllClick} onResendAllClick={this.onResendAllClick}
onCancelAllClick={this.onCancelAllClick} onCancelAllClick={this.onCancelAllClick}
onInviteClick={this.onInviteButtonClick}
onStopWarningClick={this.onStopAloneWarningClick}
onScrollToBottomClick={this.jumpToLiveTimeline} onScrollToBottomClick={this.jumpToLiveTimeline}
onResize={this.onChildResize} onResize={this.onChildResize}
onVisible={this.onStatusBarVisible} onVisible={this.onStatusBarVisible}
@ -1603,6 +1644,9 @@ module.exports = React.createClass({
} else if (this.state.searching) { } else if (this.state.searching) {
hideCancel = true; // has own cancel hideCancel = true; // has own cancel
aux = <SearchBar ref="search_bar" searchInProgress={this.state.searchInProgress} onCancelClick={this.onCancelSearchClick} onSearch={this.onSearch} />; aux = <SearchBar ref="search_bar" searchInProgress={this.state.searchInProgress} onCancelClick={this.onCancelSearchClick} onSearch={this.onSearch} />;
} else if (this.state.showingPinned) {
hideCancel = true; // has own cancel
aux = <PinnedEventsPanel room={this.state.room} onCancelClick={this.onPinnedClick} />;
} else if (!myMember || myMember.membership !== "join") { } else if (!myMember || myMember.membership !== "join") {
// We do have a room object for this room, but we're not currently in it. // 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. // We may have a 3rd party invite to it.
@ -1776,6 +1820,7 @@ module.exports = React.createClass({
collapsedRhs={this.props.collapsedRhs} collapsedRhs={this.props.collapsedRhs}
onSearchClick={this.onSearchClick} onSearchClick={this.onSearchClick}
onSettingsClick={this.onSettingsClick} onSettingsClick={this.onSettingsClick}
onPinnedClick={this.onPinnedClick}
onSaveClick={this.onSettingsSaveClick} onSaveClick={this.onSettingsSaveClick}
onCancelClick={(aux && !hideCancel) ? this.onCancelClick : null} onCancelClick={(aux && !hideCancel) ? this.onCancelClick : null}
onForgetClick={(myMember && myMember.membership === "leave") ? this.onForgetClick : null} onForgetClick={(myMember && myMember.membership === "leave") ? this.onForgetClick : null}

View file

@ -114,6 +114,10 @@ const SETTINGS_LABELS = [
id: 'Pill.shouldHidePillAvatar', id: 'Pill.shouldHidePillAvatar',
label: _td('Hide avatars in user and room mentions'), label: _td('Hide avatars in user and room mentions'),
}, },
{
id: 'TextualBody.disableBigEmoji',
label: _td('Disable big emoji in chat'),
},
/* /*
{ {
id: 'useFixedWidthFont', 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) { onLogoutClicked: function(ev) {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createTrackedDialog('Logout E2E Export', '', QuestionDialog, { Modal.createTrackedDialog('Logout E2E Export', '', QuestionDialog, {
@ -1318,7 +1327,11 @@ module.exports = React.createClass({
</div> </div>
<div className="mx_UserSettings_avatarPicker"> <div className="mx_UserSettings_avatarPicker">
<div onClick={this.onAvatarPickerClick}> <div className="mx_UserSettings_avatarPicker_remove" onClick={this.onAvatarRemoveClick}>
<img src="img/cancel.svg" width="15" height="15"
alt={_t("Remove avatar")} title={_t("Remove avatar")} />
</div>
<div onClick={this.onAvatarPickerClick} className="mx_UserSettings_avatarPicker_imgContainer">
<ChangeAvatar ref="changeAvatar" initialAvatarUrl={avatarUrl} <ChangeAvatar ref="changeAvatar" initialAvatarUrl={avatarUrl}
showUploadSection={false} className="mx_UserSettings_avatarPicker_img" /> showUploadSection={false} className="mx_UserSettings_avatarPicker_img" />
</div> </div>

View file

@ -35,7 +35,9 @@ export default withMatrixClient(React.createClass({
getInitialState: function() { getInitialState: function() {
return { return {
fetching: false, fetching: false,
fetchingInvitedMembers: false,
members: null, members: null,
invitedMembers: null,
truncateAt: INITIAL_LOAD_NUM_MEMBERS, truncateAt: INITIAL_LOAD_NUM_MEMBERS,
}; };
}, },
@ -46,7 +48,10 @@ export default withMatrixClient(React.createClass({
}, },
_fetchMembers: function() { _fetchMembers: function() {
this.setState({fetching: true}); this.setState({
fetching: true,
fetchingInvitedMembers: true,
});
this.props.matrixClient.getGroupUsers(this.props.groupId).then((result) => { this.props.matrixClient.getGroupUsers(this.props.groupId).then((result) => {
this.setState({ this.setState({
members: result.chunk.map((apiMember) => { members: result.chunk.map((apiMember) => {
@ -58,6 +63,18 @@ export default withMatrixClient(React.createClass({
this.setState({fetching: false}); this.setState({fetching: false});
console.error("Failed to get group member list: " + e); 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) { _createOverflowTile: function(overflowCount, totalCount) {
@ -83,11 +100,10 @@ export default withMatrixClient(React.createClass({
this.setState({ searchQuery: ev.target.value }); this.setState({ searchQuery: ev.target.value });
}, },
makeGroupMemberTiles: function(query) { makeGroupMemberTiles: function(query, memberList) {
const GroupMemberTile = sdk.getComponent("groups.GroupMemberTile"); const GroupMemberTile = sdk.getComponent("groups.GroupMemberTile");
const TruncatedList = sdk.getComponent("elements.TruncatedList");
query = (query || "").toLowerCase(); query = (query || "").toLowerCase();
let memberList = this.state.members;
if (query) { if (query) {
memberList = memberList.filter((m) => { memberList = memberList.filter((m) => {
const matchesName = m.displayname.toLowerCase().indexOf(query) !== -1; const matchesName = m.displayname.toLowerCase().indexOf(query) !== -1;
@ -118,17 +134,19 @@ export default withMatrixClient(React.createClass({
} }
}); });
return memberList; return <TruncatedList className="mx_MemberList_wrapper" truncateAt={this.state.truncateAt}
createOverflowElement={this._createOverflowTile}
>
{ memberList }
</TruncatedList>;
}, },
render: function() { render: function() {
if (this.state.fetching) { if (this.state.fetching || this.state.fetchingInvitedMembers) {
const Spinner = sdk.getComponent("elements.Spinner"); const Spinner = sdk.getComponent("elements.Spinner");
return (<div className="mx_MemberList"> return (<div className="mx_MemberList">
<Spinner /> <Spinner />
</div>); </div>);
} else if (this.state.members === null) {
return null;
} }
const inputBox = ( const inputBox = (
@ -139,15 +157,21 @@ export default withMatrixClient(React.createClass({
</form> </form>
); );
const TruncatedList = sdk.getComponent("elements.TruncatedList"); const joined = this.state.members ? <div className="mx_MemberList_joined">
{ this.makeGroupMemberTiles(this.state.searchQuery, this.state.members) }
</div> : <div />;
const invited = this.state.invitedMembers ? <div className="mx_MemberList_invited">
<h2>{ _t("Invited") }</h2>
{ this.makeGroupMemberTiles(this.state.searchQuery, this.state.invitedMembers) }
</div> : <div />;
return ( return (
<div className="mx_MemberList"> <div className="mx_MemberList">
{ inputBox } { inputBox }
<GeminiScrollbar autoshow={true} className="mx_MemberList_joined mx_MemberList_outerWrapper"> <GeminiScrollbar autoshow={true} className="mx_MemberList_outerWrapper">
<TruncatedList className="mx_MemberList_wrapper" truncateAt={this.state.truncateAt} { joined }
createOverflowElement={this._createOverflowTile}> { invited }
{ this.makeGroupMemberTiles(this.state.searchQuery) }
</TruncatedList>
</GeminiScrollbar> </GeminiScrollbar>
</div> </div>
); );

View file

@ -70,9 +70,9 @@ module.exports = React.createClass({
// it sucks that _tJsx doesn't support normal _t substitutions :(( // it sucks that _tJsx doesn't support normal _t substitutions :((
return ( return (
<div className="mx_RoomAvatarEvent"> <div className="mx_RoomAvatarEvent">
{ _tJsx('$senderDisplayName changed the room avatar to <img/>', { _tJsx('%(senderDisplayName)s changed the room avatar to <img/>',
[ [
/\$senderDisplayName/, /%\(senderDisplayName\)s/,
/<img\/>/, /<img\/>/,
], ],
[ [

View file

@ -354,7 +354,9 @@ module.exports = React.createClass({
const mxEvent = this.props.mxEvent; const mxEvent = this.props.mxEvent;
const content = mxEvent.getContent(); 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) { if (this.props.highlightLink) {
body = <a href={this.props.highlightLink}>{ body }</a>; body = <a href={this.props.highlightLink}>{ body }</a>;

View file

@ -34,7 +34,7 @@ const ROOM_COLORS = [
["#dad658", "#f5f4ea"], ["#dad658", "#f5f4ea"],
["#80c553", "#eef5ea"], ["#80c553", "#eef5ea"],
["#bb814e", "#eee8e3"], ["#bb814e", "#eee8e3"],
["#595959", "#ececec"], //["#595959", "#ececec"], // Grey makes everything appear disabled, so remove it for now
]; ];
module.exports = React.createClass({ module.exports = React.createClass({

View file

@ -116,7 +116,9 @@ module.exports = React.createClass({
nameEl = ( nameEl = (
<div className="mx_EntityTile_details"> <div className="mx_EntityTile_details">
<img className="mx_EntityTile_chevron" src="img/member_chevron.png" width="8" height="12" /> <img className="mx_EntityTile_chevron" src="img/member_chevron.png" width="8" height="12" />
<EmojiText element="div" className="mx_EntityTile_name_hover" dir="auto">{ name }</EmojiText> <EmojiText element="div" className="mx_EntityTile_name mx_EntityTile_name_hover" dir="auto">
{ name }
</EmojiText>
<PresenceLabel activeAgo={activeAgo} <PresenceLabel activeAgo={activeAgo}
currentlyActive={this.props.presenceCurrentlyActive} currentlyActive={this.props.presenceCurrentlyActive}
presenceState={this.props.presenceState} /> presenceState={this.props.presenceState} />

View file

@ -44,6 +44,7 @@ const eventTileTypes = {
'm.room.history_visibility': 'messages.TextualEvent', 'm.room.history_visibility': 'messages.TextualEvent',
'm.room.encryption': 'messages.TextualEvent', 'm.room.encryption': 'messages.TextualEvent',
'm.room.power_levels': 'messages.TextualEvent', 'm.room.power_levels': 'messages.TextualEvent',
'm.room.pinned_events' : 'messages.TextualEvent',
'im.vector.modular.widgets': 'messages.TextualEvent', 'im.vector.modular.widgets': 'messages.TextualEvent',
}; };

View file

@ -625,22 +625,49 @@ module.exports = withMatrixClient(React.createClass({
}, },
_renderUserOptions: function() { _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; 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 = ( ignoreButton = (
<AccessibleButton onClick={this.onIgnoreToggle} className="mx_MemberInfo_field"> <AccessibleButton onClick={this.onIgnoreToggle} className="mx_MemberInfo_field">
{ this.state.isIgnoring ? _t("Unignore") : _t("Ignore") } { this.state.isIgnoring ? _t("Unignore") : _t("Ignore") }
</AccessibleButton> </AccessibleButton>
); );
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 = (
<AccessibleButton onClick={onReadReceiptButton} className="mx_MemberInfo_field">
{ _t('Jump to read receipt') }
</AccessibleButton>
);
}
} }
if (!ignoreButton) return null; if (!ignoreButton && !readReceiptButton) return null;
return ( return (
<div> <div>
<h3>{ _t("User Options") }</h3> <h3>{ _t("User Options") }</h3>
<div className="mx_MemberInfo_buttons"> <div className="mx_MemberInfo_buttons">
{ readReceiptButton }
{ ignoreButton } { ignoreButton }
</div> </div>
</div> </div>

View file

@ -146,8 +146,8 @@ module.exports = React.createClass({
const newState = { const newState = {
members: this.roomMembers(), members: this.roomMembers(),
}; };
newState.filteredJoinedMembers = this._filterMembers(newState.members, 'join'); newState.filteredJoinedMembers = this._filterMembers(newState.members, 'join', this.state.searchQuery);
newState.filteredInvitedMembers = this._filterMembers(newState.members, 'invite'); newState.filteredInvitedMembers = this._filterMembers(newState.members, 'invite', this.state.searchQuery);
this.setState(newState); this.setState(newState);
}, 500), }, 500),
@ -187,7 +187,7 @@ module.exports = React.createClass({
const user_id = all_user_ids[i]; const user_id = all_user_ids[i];
const m = all_members[user_id]; 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) { if ((ConferenceHandler && !ConferenceHandler.isConferenceUser(user_id)) || !ConferenceHandler) {
to_display.push(user_id); to_display.push(user_id);
++count; ++count;
@ -302,6 +302,7 @@ module.exports = React.createClass({
const m = this.memberDict[userId]; const m = this.memberDict[userId];
if (query) { if (query) {
query = query.toLowerCase();
const matchesName = m.name.toLowerCase().indexOf(query) !== -1; const matchesName = m.name.toLowerCase().indexOf(query) !== -1;
const matchesId = m.userId.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;
}); });
}, },

View file

@ -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 = (
<AccessibleButton onClick={this.onUnpinClicked} className="mx_PinnedEventTile_unpinButton">
<img src="img/cancel-red.svg" width="8" height="8" alt={_t('Unpin Message')} title={_t('Unpin Message')} />
</AccessibleButton>
);
}
return (
<div className="mx_PinnedEventTile">
<div className="mx_PinnedEventTile_actions">
<AccessibleButton className="mx_PinnedEventTile_gotoButton mx_textButton" onClick={this.onTileClicked}>
{ _t("Jump to message") }
</AccessibleButton>
{ unpinButton }
</div>
<MemberAvatar member={sender} width={avatarSize} height={avatarSize} />
<span className="mx_PinnedEventTile_sender">
{ sender.name }
</span>
<MessageEvent mxEvent={this.props.mxEvent} className="mx_PinnedEventTile_body" />
</div>
);
},
});

View file

@ -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 (<div>{ _t("No pinned messages.") }</div>);
}
return this.state.pinned.map((context) => {
return (<PinnedEventTile key={context.event.getId()}
mxRoom={this.props.room}
mxEvent={context.event}
onUnpinned={this._updatePinnedMessages} />);
});
},
render: function() {
let tiles = <div>{ _t("Loading...") }</div>;
if (this.state && !this.state.loading) {
tiles = this._getPinnedTiles();
}
return (
<div className="mx_PinnedEventsPanel">
<div className="mx_PinnedEventsPanel_body">
<AccessibleButton className="mx_PinnedEventsPanel_cancel" onClick={this.props.onCancelClick}><img src="img/cancel.svg" width="18" height="18" /></AccessibleButton>
<h3 className="mx_PinnedEventsPanel_header">{ _t("Pinned Messages") }</h3>
{ tiles }
</div>
</div>
);
},
});

View file

@ -31,6 +31,7 @@ import linkifyMatrix from '../../../linkify-matrix';
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
import ManageIntegsButton from '../elements/ManageIntegsButton'; import ManageIntegsButton from '../elements/ManageIntegsButton';
import {CancelButton} from './SimpleRoomHeader'; import {CancelButton} from './SimpleRoomHeader';
import UserSettingsStore from "../../../UserSettingsStore";
linkifyMatrix(linkify); linkifyMatrix(linkify);
@ -45,6 +46,7 @@ module.exports = React.createClass({
inRoom: React.PropTypes.bool, inRoom: React.PropTypes.bool,
collapsedRhs: React.PropTypes.bool, collapsedRhs: React.PropTypes.bool,
onSettingsClick: React.PropTypes.func, onSettingsClick: React.PropTypes.func,
onPinnedClick: React.PropTypes.func,
onSaveClick: React.PropTypes.func, onSaveClick: React.PropTypes.func,
onSearchClick: React.PropTypes.func, onSearchClick: React.PropTypes.func,
onLeaveClick: React.PropTypes.func, onLeaveClick: React.PropTypes.func,
@ -129,6 +131,10 @@ module.exports = React.createClass({
}).done(); }).done();
}, },
onAvatarRemoveClick: function() {
MatrixClientPeg.get().sendStateEvent(this.props.room.roomId, 'm.room.avatar', {url: null}, '');
},
onShowRhsClick: function(ev) { onShowRhsClick: function(ev) {
dis.dispatch({ action: 'show_right_panel' }); dis.dispatch({ action: 'show_right_panel' });
}, },
@ -172,6 +178,7 @@ module.exports = React.createClass({
let spinner = null; let spinner = null;
let saveButton = null; let saveButton = null;
let settingsButton = null; let settingsButton = null;
let pinnedEventsButton = null;
let canSetRoomName; let canSetRoomName;
let canSetRoomAvatar; let canSetRoomAvatar;
@ -268,11 +275,15 @@ module.exports = React.createClass({
<div className="mx_RoomHeader_avatarPicker_edit"> <div className="mx_RoomHeader_avatarPicker_edit">
<label htmlFor="avatarInput" ref="file_label"> <label htmlFor="avatarInput" ref="file_label">
<img src="img/camera.svg" <img src="img/camera.svg"
alt={_t("Upload avatar")} title={_t("Upload avatar")} alt={_t("Upload avatar")} title={_t("Upload avatar")}
width="17" height="15" /> width="17" height="15" />
</label> </label>
<input id="avatarInput" type="file" onChange={this.onAvatarSelected} /> <input id="avatarInput" type="file" onChange={this.onAvatarSelected} />
</div> </div>
<div className="mx_RoomHeader_avatarPicker_remove" onClick={this.onAvatarRemoveClick}>
<img src="img/cancel.svg" width="10"
alt={_t("Remove avatar")} title={_t("Remove avatar")} />
</div>
</div> </div>
); );
} else if (this.props.room || (this.props.oobData && this.props.oobData.name)) { } else if (this.props.room || (this.props.oobData && this.props.oobData.name)) {
@ -290,6 +301,13 @@ module.exports = React.createClass({
</AccessibleButton>; </AccessibleButton>;
} }
if (this.props.onPinnedClick && UserSettingsStore.isFeatureEnabled('feature_pinning')) {
pinnedEventsButton =
<AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onPinnedClick} title={_t("Pinned Messages")}>
<TintableSvg src="img/icons-pin.svg" width="16" height="16" />
</AccessibleButton>;
}
// var leave_button; // var leave_button;
// if (this.props.onLeaveClick) { // if (this.props.onLeaveClick) {
// leave_button = // leave_button =
@ -334,6 +352,7 @@ module.exports = React.createClass({
rightRow = rightRow =
<div className="mx_RoomHeader_rightRow"> <div className="mx_RoomHeader_rightRow">
{ settingsButton } { settingsButton }
{ pinnedEventsButton }
{ manageIntegsButton } { manageIntegsButton }
{ forgetButton } { forgetButton }
{ searchButton } { searchButton }

View file

@ -53,6 +53,10 @@ module.exports = React.createClass({
}; };
}, },
componentWillMount: function() {
MatrixClientPeg.get().on("RoomState.events", this.onRoomStateEvents);
},
componentWillReceiveProps: function(newProps) { componentWillReceiveProps: function(newProps) {
if (this.avatarSet) { if (this.avatarSet) {
// don't clobber what the user has just set // 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) { setAvatarFromFile: function(file) {
let newUrl = null; let newUrl = null;

View file

@ -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!", "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 <a>disabled</a> URL previews by default.": "Du hast die URL-Vorschau standardmäßig <a>deaktiviert</a>.", "You have <a>disabled</a> URL previews by default.": "Du hast die URL-Vorschau standardmäßig <a>deaktiviert</a>.",
"You have <a>enabled</a> URL previews by default.": "Du hast die URL-Vorschau standardmäßig <a>aktiviert</a>.", "You have <a>enabled</a> URL previews by default.": "Du hast die URL-Vorschau standardmäßig <a>aktiviert</a>.",
"$senderDisplayName changed the room avatar to <img/>": "$senderDisplayName hat das Raum-Bild geändert zu <img/>", "%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s hat das Raum-Bild geändert zu <img/>",
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s hat das Raum-Bild für %(roomName)s geändert", "%(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", "Hide removed messages": "Gelöschte Nachrichten verbergen",
"Start new chat": "Neuen Chat starten", "Start new chat": "Neuen Chat starten",

View file

@ -639,7 +639,7 @@
"Disable URL previews by default for participants in this room": "Απενεργοποίηση της προεπισκόπησης συνδέσμων για όλους τους συμμετέχοντες στο δωμάτιο", "Disable URL previews by default for participants in this room": "Απενεργοποίηση της προεπισκόπησης συνδέσμων για όλους τους συμμετέχοντες στο δωμάτιο",
"Disable URL previews for this room (affects only you)": "Απενεργοποίηση της προεπισκόπησης συνδέσμων για αυτό το δωμάτιο (επηρεάζει μόνο εσάς)", "Disable URL previews for this room (affects only you)": "Απενεργοποίηση της προεπισκόπησης συνδέσμων για αυτό το δωμάτιο (επηρεάζει μόνο εσάς)",
" (unsupported)": " (μη υποστηριζόμενο)", " (unsupported)": " (μη υποστηριζόμενο)",
"$senderDisplayName changed the room avatar to <img/>": "Ο $senderDisplayName άλλαξε την εικόνα του δωματίου σε <img/>", "%(senderDisplayName)s changed the room avatar to <img/>": "Ο %(senderDisplayName)s άλλαξε την εικόνα του δωματίου σε <img/>",
"Missing Media Permissions, click here to request.": "Λείπουν τα δικαιώματα πολύμεσων, κάντε κλικ για να ζητήσετε.", "Missing Media Permissions, click here to request.": "Λείπουν τα δικαιώματα πολύμεσων, κάντε κλικ για να ζητήσετε.",
"You may need to manually permit Riot to access your microphone/webcam": "Μπορεί να χρειαστεί να ορίσετε χειροκίνητα την πρόσβαση του Riot στο μικρόφωνο/κάμερα", "You may need to manually permit Riot to access your microphone/webcam": "Μπορεί να χρειαστεί να ορίσετε χειροκίνητα την πρόσβαση του Riot στο μικρόφωνο/κάμερα",
"Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "Δεν είναι δυνατή η σύνδεση στον διακομιστή - παρακαλούμε ελέγξτε την συνδεσιμότητα, βεβαιωθείτε ότι το <a>πιστοποιητικό SSL</a> του διακομιστή είναι έμπιστο και ότι κάποιο πρόσθετο περιηγητή δεν αποτρέπει τα αιτήματα.", "Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "Δεν είναι δυνατή η σύνδεση στον διακομιστή - παρακαλούμε ελέγξτε την συνδεσιμότητα, βεβαιωθείτε ότι το <a>πιστοποιητικό SSL</a> του διακομιστή είναι έμπιστο και ότι κάποιο πρόσθετο περιηγητή δεν αποτρέπει τα αιτήματα.",

View file

@ -252,6 +252,7 @@
"%(targetName)s joined the room.": "%(targetName)s joined the room.", "%(targetName)s joined the room.": "%(targetName)s joined the room.",
"Joins room with given alias": "Joins room with given alias", "Joins room with given alias": "Joins room with given alias",
"Jump to first unread message.": "Jump to first unread message.", "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.", "%(senderName)s kicked %(targetName)s.": "%(senderName)s kicked %(targetName)s.",
"Kick": "Kick", "Kick": "Kick",
"Kicks user with given id": "Kicks user with given id", "Kicks user with given id": "Kicks user with given id",
@ -289,6 +290,7 @@
"matrix-react-sdk version:": "matrix-react-sdk version:", "matrix-react-sdk version:": "matrix-react-sdk version:",
"Matrix Apps": "Matrix Apps", "Matrix Apps": "Matrix Apps",
"Members only": "Members only", "Members only": "Members only",
"Disable big emoji in chat": "Disable big emoji in chat",
"Disable Emoji suggestions while typing": "Disable Emoji suggestions while typing", "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", "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", "Missing room_id in request": "Missing room_id in request",
@ -609,6 +611,7 @@
"Room": "Room", "Room": "Room",
"Copied!": "Copied!", "Copied!": "Copied!",
"Failed to copy": "Failed to copy", "Failed to copy": "Failed to copy",
"There's no one else here! Would you like to <a>invite others</a> or <a>stop warning about the empty room</a>?": "There's no one else here! Would you like to <a>invite others</a> or <a>stop warning about the empty room</a>?",
"Connectivity to the server has been lost.": "Connectivity to the server has been lost.", "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.", "Sent messages will be stored until your connection has returned.": "Sent messages will be stored until your connection has returned.",
"<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.": "<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.", "<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.": "<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.",
@ -616,6 +619,7 @@
"(~%(count)s results)|other": "(~%(count)s results)", "(~%(count)s results)|other": "(~%(count)s results)",
"Cancel": "Cancel", "Cancel": "Cancel",
"or": "or", "or": "or",
"Message Pinning": "Message Pinning",
"Active call": "Active call", "Active call": "Active call",
"Monday": "Monday", "Monday": "Monday",
"Tuesday": "Tuesday", "Tuesday": "Tuesday",
@ -632,6 +636,7 @@
"quote": "quote", "quote": "quote",
"bullet": "bullet", "bullet": "bullet",
"numbullet": "numbullet", "numbullet": "numbullet",
"Remove avatar": "Remove avatar",
"%(severalUsers)sjoined %(repeats)s times": "%(severalUsers)sjoined %(repeats)s times", "%(severalUsers)sjoined %(repeats)s times": "%(severalUsers)sjoined %(repeats)s times",
"%(oneUser)sjoined %(repeats)s times": "%(oneUser)sjoined %(repeats)s times", "%(oneUser)sjoined %(repeats)s times": "%(oneUser)sjoined %(repeats)s times",
"%(severalUsers)sjoined": "%(severalUsers)sjoined", "%(severalUsers)sjoined": "%(severalUsers)sjoined",
@ -785,7 +790,7 @@
"Start chatting": "Start chatting", "Start chatting": "Start chatting",
"Start Chatting": "Start Chatting", "Start Chatting": "Start Chatting",
"Click on the button below to start chatting!": "Click on the button below to start chatting!", "Click on the button below to start chatting!": "Click on the button below to start chatting!",
"$senderDisplayName changed the room avatar to <img/>": "$senderDisplayName changed the room avatar to <img/>", "%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s changed the room avatar to <img/>",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removed the room avatar.", "%(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", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s changed the avatar for %(roomName)s",
"Username available": "Username available", "Username available": "Username available",
@ -885,6 +890,8 @@
"Add rooms to the group summary": "Add rooms to the group summary", "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?", "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", "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", "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 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", "Failed to remove the room from the summary of %(groupId)s": "Failed to remove the room from the summary of %(groupId)s",

View file

@ -705,7 +705,7 @@
"Idle": "Idle", "Idle": "Idle",
"Offline": "Offline", "Offline": "Offline",
"Disable URL previews for this room (affects only you)": "Disable URL previews for this room (affects only you)", "Disable URL previews for this room (affects only you)": "Disable URL previews for this room (affects only you)",
"$senderDisplayName changed the room avatar to <img/>": "$senderDisplayName changed the room avatar to <img/>", "%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s changed the room avatar to <img/>",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removed the room avatar.", "%(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", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s changed the avatar for %(roomName)s",
"Active call (%(roomName)s)": "Active call (%(roomName)s)", "Active call (%(roomName)s)": "Active call (%(roomName)s)",
@ -844,6 +844,7 @@
"+example:%(domain)s": "+example:%(domain)s", "+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", "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", "Room creation failed": "Room creation failed",
"Pinned Messages": "Pinned Messages",
"You are a member of these groups:": "You are a member of these groups:", "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.", "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", "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", "%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s",
"Robot check is currently unavailable on desktop - please use a <a>web browser</a>": "Robot check is currently unavailable on desktop - please use a <a>web browser</a>", "Robot check is currently unavailable on desktop - please use a <a>web browser</a>": "Robot check is currently unavailable on desktop - please use a <a>web browser</a>",
"Verifies a user, device, and pubkey tuple": "Verifies a user, device, and pubkey tuple", "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", "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 <i>+example:matrix.org</i>.": "To join an existing group you'll have to know its group identifier; this will look something like <i>+example:matrix.org</i>.", "To join an existing group you'll have to know its group identifier; this will look something like <i>+example:matrix.org</i>.": "To join an existing group you'll have to know its group identifier; this will look something like <i>+example:matrix.org</i>.",
"%(weekDayName)s, %(monthName)s %(day)s": "%(weekDayName)s, %(monthName)s %(day)s", "%(weekDayName)s, %(monthName)s %(day)s": "%(weekDayName)s, %(monthName)s %(day)s",

View file

@ -736,7 +736,7 @@
"Start chatting": "Hasi txateatzen", "Start chatting": "Hasi txateatzen",
"Start Chatting": "Hasi txateatzen", "Start Chatting": "Hasi txateatzen",
"Click on the button below to start chatting!": "Egin klik beheko botoian txateatzen hasteko!", "Click on the button below to start chatting!": "Egin klik beheko botoian txateatzen hasteko!",
"$senderDisplayName changed the room avatar to <img/>": "$senderDisplayName erabiltzaileak gelaren abatarra aldatu du beste honetara: <img/>", "%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s erabiltzaileak gelaren abatarra aldatu du beste honetara: <img/>",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s erabiltzaileak gelaren abatarra ezabatu du.", "%(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", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s erabiltzaileak %(roomName)s gelaren abatarra aldatu du",
"Username available": "Erabiltzaile-izena eskuragarri dago", "Username available": "Erabiltzaile-izena eskuragarri dago",

View file

@ -637,7 +637,7 @@
"for %(amount)sm": "depuis %(amount)sm", "for %(amount)sm": "depuis %(amount)sm",
"for %(amount)sh": "depuis %(amount)sh", "for %(amount)sh": "depuis %(amount)sh",
"for %(amount)sd": "depuis %(amount)sj", "for %(amount)sd": "depuis %(amount)sj",
"$senderDisplayName changed the room avatar to <img/>": "$senderDisplayName a changé lavatar du salon en <img/>", "%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s a changé lavatar du salon en <img/>",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s a supprimé l'avatar du salon.", "%(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é lavatar de %(roomName)s", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s a changé lavatar de %(roomName)s",
"Device already verified!": "Appareil déjà vérifié !", "Device already verified!": "Appareil déjà vérifié !",

View file

@ -723,7 +723,7 @@
"Start chatting": "Csevegés indítása", "Start chatting": "Csevegés indítása",
"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!", "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 <img/>": "$senderDisplayName megváltoztatta a szoba avatar képét: <img/>", "%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s megváltoztatta a szoba avatar képét: <img/>",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s törölte 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", "%(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", "Username available": "Szabad felhasználói név",

View file

@ -743,7 +743,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 <img/>": "$senderDisplayName님이 방 아바타를 <img/>로 바꾸셨어요", "%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s님이 방 아바타를 <img/>로 바꾸셨어요",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s님이 방 아바타를 지우셨어요.", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s님이 방 아바타를 지우셨어요.",
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s가 %(roomName)s 방의 아바타를 바꾸셨어요", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s가 %(roomName)s 방의 아바타를 바꾸셨어요",
"Username available": "쓸 수 있는 사용자 이름", "Username available": "쓸 수 있는 사용자 이름",

View file

@ -619,7 +619,7 @@
"Dec": "Dec.", "Dec": "Dec.",
"Set a display name:": "Iestatīt redzamo vārdu:", "Set a display name:": "Iestatīt redzamo vārdu:",
"This image cannot be displayed.": "Šo attēlu nav iespējams parādīt.", "This image cannot be displayed.": "Šo attēlu nav iespējams parādīt.",
"$senderDisplayName changed the room avatar to <img/>": "$senderDisplayName nomainīja istabas attēlu uz <img/>", "%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s nomainīja istabas attēlu uz <img/>",
"Upload an avatar:": "Augšuplādē profila attēlu:", "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.", "This server does not support authentication with a phone number.": "Šis serveris neatbalsta autentifikāciju pēc telefona numura.",
"Missing password.": "Trūkst parole.", "Missing password.": "Trūkst parole.",

View file

@ -746,7 +746,7 @@
"Start chatting": "Start met praten", "Start chatting": "Start met praten",
"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!", "Click on the button below to start chatting!": "Klik op de knop hieronder om te starten met praten!",
"$senderDisplayName changed the room avatar to <img/>": "$senderDisplayName heeft de ruimte avatar aangepast naar <img/>", "%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s heeft de ruimte avatar aangepast naar <img/>",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s heeft de ruimte avatar verwijderd.", "%(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", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s veranderde de avatar voor %(roomName)s",
"Username available": "Gebruikersnaam beschikbaar", "Username available": "Gebruikersnaam beschikbaar",

View file

@ -771,7 +771,7 @@
"for %(amount)sd": "%(amount)s dni", "for %(amount)sd": "%(amount)s dni",
"Idle": "Bezczynny", "Idle": "Bezczynny",
"Check for update": "Sprawdź aktualizacje", "Check for update": "Sprawdź aktualizacje",
"$senderDisplayName changed the room avatar to <img/>": "$senderDisplayName zmienił awatar pokoju na <img/>", "%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s zmienił awatar pokoju na <img/>",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s usunął awatar pokoju.", "%(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", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s zmienił awatar %(roomName)s",
"This will be your account name on the <span></span> homeserver, or you can pick a <a>different server</a>.": "To będzie twoja nazwa konta na <span></span> serwerze domowym; możesz też wybrać <a>inny serwer</a>.", "This will be your account name on the <span></span> homeserver, or you can pick a <a>different server</a>.": "To będzie twoja nazwa konta na <span></span> serwerze domowym; możesz też wybrać <a>inny serwer</a>.",

View file

@ -699,7 +699,7 @@
"for %(amount)sd": "por %(amount)sd", "for %(amount)sd": "por %(amount)sd",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removeu a imagem da sala.", "%(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)s changed the avatar for %(roomName)s": "%(senderDisplayName)s alterou a imagem da sala %(roomName)s",
"$senderDisplayName changed the room avatar to <img/>": "$senderDisplayName alterou a imagem da sala para <img/>", "%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s alterou a imagem da sala para <img/>",
"Missing Media Permissions, click here to request.": "Faltam permissões para uso de mídia no seu computador. Clique aqui para solicitá-las.", "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 Microphones detected": "Não foi detetado nenhum microfone",
"No Webcams detected": "Não foi detetada nenhuma Webcam", "No Webcams detected": "Não foi detetada nenhuma Webcam",

View file

@ -696,7 +696,7 @@
"for %(amount)sd": "por %(amount)sd", "for %(amount)sd": "por %(amount)sd",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removeu a imagem da sala.", "%(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)s changed the avatar for %(roomName)s": "%(senderDisplayName)s alterou a imagem da sala %(roomName)s",
"$senderDisplayName changed the room avatar to <img/>": "$senderDisplayName alterou a imagem da sala para <img/>", "%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s alterou a imagem da sala para <img/>",
"Missing Media Permissions, click here to request.": "Faltam permissões para uso de mídia no seu computador. Clique aqui para solicitá-las.", "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 Microphones detected": "Não foi detectado nenhum microfone",
"No Webcams detected": "Não foi detectada nenhuma Webcam", "No Webcams detected": "Não foi detectada nenhuma Webcam",

View file

@ -705,7 +705,7 @@
"Idle": "Неактивен", "Idle": "Неактивен",
"Offline": "Не в сети", "Offline": "Не в сети",
"Disable URL previews for this room (affects only you)": "Отключить предпросмотр URL-адресов для этой комнаты (влияет только на вас)", "Disable URL previews for this room (affects only you)": "Отключить предпросмотр URL-адресов для этой комнаты (влияет только на вас)",
"$senderDisplayName changed the room avatar to <img/>": "$senderDisplayName сменил аватар комнаты на <img/>", "%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s сменил аватар комнаты на <img/>",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s удалил аватар комнаты.", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s удалил аватар комнаты.",
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s сменил аватар для %(roomName)s", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s сменил аватар для %(roomName)s",
"Create new room": "Создать новую комнату", "Create new room": "Создать новую комнату",

View file

@ -738,7 +738,7 @@
"Start chatting": "Sohbeti başlat", "Start chatting": "Sohbeti başlat",
"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!", "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 <img/>": "$senderDisplayName odanın avatarını <img/> olarak çevirdi", "%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s odanın avatarını <img/> olarak çevirdi",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s odanın avatarını kaldırdı.", "%(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", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s %(roomName)s için avatarı değiştirdi",
"Username available": "Kullanıcı ismi uygun", "Username available": "Kullanıcı ismi uygun",

View file

@ -228,7 +228,7 @@
"Idle": "閒置", "Idle": "閒置",
"Offline": "下線", "Offline": "下線",
"Disable URL previews for this room (affects only you)": "在這個房間禁止URL預覽只影響你", "Disable URL previews for this room (affects only you)": "在這個房間禁止URL預覽只影響你",
"$senderDisplayName changed the room avatar to <img/>": "$senderDisplayName 更改了聊天室的圖像為 <img/>", "%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s 更改了聊天室的圖像為 <img/>",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s 移除了聊天室圖片。", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s 移除了聊天室圖片。",
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s 更改了聊天室 %(roomName)s 圖像", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s 更改了聊天室 %(roomName)s 圖像",
"Cancel": "取消", "Cancel": "取消",

View file

@ -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) // 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]; const output = [tJsxText];
for (let i = 0; i < patterns.length; i++) { for (let i = 0; i < patterns.length; i++) {
// convert the last element in 'output' into 3 elements (pre-text, sub function, post-text). // convert the last element in 'output' into 3 elements (pre-text, sub function, post-text).
// Rinse and repeat for other patterns (using post-text). // Rinse and repeat for other patterns (using post-text).