From 1f44233e05d2a9ef1bf00c51004f0d395b9f9fb3 Mon Sep 17 00:00:00 2001 From: Stefan Parviainen Date: Thu, 12 Oct 2017 21:24:45 +0200 Subject: [PATCH 001/365] Better translations in RoomList.js Signed-off-by: Stefan Parviainen --- src/components/views/rooms/RoomList.js | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index da77174dff..56589353f9 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -34,27 +34,18 @@ const Receipt = require('../../../utils/Receipt'); const HIDE_CONFERENCE_CHANS = true; function phraseForSection(section) { - // These would probably be better as individual strings, - // but for some reason we have translations for these strings - // as-is, so keeping it like this for now. - let verb; switch (section) { case 'm.favourite': - verb = _t('to favourite'); - break; + return _t('Drop here to favourite'); case 'im.vector.fake.direct': - verb = _t('to tag direct chat'); - break; + return _t('Drop here to tag direct chat'); case 'im.vector.fake.recent': - verb = _t('to restore'); - break; + return _t('Drop here to restore'); case 'm.lowpriority': - verb = _t('to demote'); - break; + return _t('Drop here to demote'); default: return _t('Drop here to tag %(section)s', {section: section}); } - return _t('Drop here %(toAction)s', {toAction: verb}); } module.exports = React.createClass({ From 9495ccdbb53f8bf7e30388cdb39b61fe6b2dadb2 Mon Sep 17 00:00:00 2001 From: Stefan Parviainen Date: Thu, 12 Oct 2017 21:37:12 +0200 Subject: [PATCH 002/365] Don't hardcode ConfirmUserActionDialog title Signed-off-by: Stefan Parviainen --- src/components/views/dialogs/ConfirmUserActionDialog.js | 4 ++-- src/components/views/groups/GroupMemberInfo.js | 1 + src/components/views/rooms/MemberInfo.js | 5 +++-- src/components/views/rooms/RoomSettings.js | 1 + 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/views/dialogs/ConfirmUserActionDialog.js b/src/components/views/dialogs/ConfirmUserActionDialog.js index 9091d8975e..64e25df5f1 100644 --- a/src/components/views/dialogs/ConfirmUserActionDialog.js +++ b/src/components/views/dialogs/ConfirmUserActionDialog.js @@ -36,6 +36,7 @@ export default React.createClass({ // group member object. Supply either this or 'member' groupMember: GroupMemberType, action: React.PropTypes.string.isRequired, // eg. 'Ban' + title: React.PropTypes.string.isRequired, // eg. 'Ban this user?' // Whether to display a text field for a reason // If true, the second argument to onFinished will @@ -75,7 +76,6 @@ export default React.createClass({ const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar"); const BaseAvatar = sdk.getComponent("views.avatars.BaseAvatar"); - const title = _t("%(actionVerb)s this person?", { actionVerb: this.props.action}); const confirmButtonClass = classnames({ 'mx_Dialog_primary': true, 'danger': this.props.danger, @@ -113,7 +113,7 @@ export default React.createClass({ return (
diff --git a/src/components/views/groups/GroupMemberInfo.js b/src/components/views/groups/GroupMemberInfo.js index 6f1a370f26..aca2b1b222 100644 --- a/src/components/views/groups/GroupMemberInfo.js +++ b/src/components/views/groups/GroupMemberInfo.js @@ -69,6 +69,7 @@ module.exports = withMatrixClient(React.createClass({ Modal.createDialog(ConfirmUserActionDialog, { groupMember: this.props.groupMember, action: _t('Remove from group'), + title: _t('Remove this user from group?'), danger: true, onFinished: (proceed) => { if (!proceed) return; diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 856d3ebad4..180db1d5dd 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -247,11 +247,11 @@ module.exports = withMatrixClient(React.createClass({ onKick: function() { const membership = this.props.member.membership; - const kickLabel = membership === "invite" ? _t("Disinvite") : _t("Kick"); const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog"); Modal.createTrackedDialog('Confirm User Action Dialog', 'onKick', ConfirmUserActionDialog, { member: this.props.member, - action: kickLabel, + action: membership === "invite" ? _t("Disinvite") : _t("Kick"), + title: membership === "invite" ? _t("Disinvite this user?") : _t("Kick this user?"), askReason: membership == "join", danger: true, onFinished: (proceed, reason) => { @@ -285,6 +285,7 @@ module.exports = withMatrixClient(React.createClass({ Modal.createTrackedDialog('Confirm User Action Dialog', 'onBanOrUnban', ConfirmUserActionDialog, { member: this.props.member, action: this.props.member.membership == 'ban' ? _t("Unban") : _t("Ban"), + title: this.props.member.membership == 'ban' ? _t("Unban this user?") : _t("Ban this user?"), askReason: this.props.member.membership != 'ban', danger: this.props.member.membership != 'ban', onFinished: (proceed, reason) => { diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index 9934456597..b1a2f41cec 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -72,6 +72,7 @@ const BannedUser = React.createClass({ Modal.createTrackedDialog('Confirm User Action Dialog', 'onUnbanClick', ConfirmUserActionDialog, { member: this.props.member, action: _t('Unban'), + title: _t('Unban this user?'), danger: false, onFinished: (proceed) => { if (!proceed) return; From 3b91ada4c8806fedada2d3d0f1fd809070044ecc Mon Sep 17 00:00:00 2001 From: Stefan Parviainen Date: Fri, 13 Oct 2017 20:44:01 +0200 Subject: [PATCH 003/365] Departify sending emails and text messages Signed-off-by: Stefan Parviainen --- src/components/structures/login/ForgotPassword.js | 2 +- src/components/views/login/InteractiveAuthEntryComponents.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/structures/login/ForgotPassword.js b/src/components/structures/login/ForgotPassword.js index 3e76291d20..4500e385e5 100644 --- a/src/components/structures/login/ForgotPassword.js +++ b/src/components/structures/login/ForgotPassword.js @@ -166,7 +166,7 @@ module.exports = React.createClass({ } else if (this.state.progress === "sent_email") { resetPasswordJsx = (
- { _t('An email has been sent to') } { this.state.email }. { _t("Once you've followed the link it contains, click below") }. + { _t("An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.", { emailAddress: this.state.email }) }
diff --git a/src/components/views/login/InteractiveAuthEntryComponents.js b/src/components/views/login/InteractiveAuthEntryComponents.js index 4c53c23f76..d0cd3931e4 100644 --- a/src/components/views/login/InteractiveAuthEntryComponents.js +++ b/src/components/views/login/InteractiveAuthEntryComponents.js @@ -256,7 +256,7 @@ export const EmailIdentityAuthEntry = React.createClass({ } else { return (
-

{ _t("An email has been sent to") } { this.props.inputs.emailAddress }

+

{ _t("An email has been sent to %(emailAddress)s", { emailAddress: '' + this.props.inputs.emailAddress + '' }) }

{ _t("Please check your email to continue registration.") }

); @@ -370,7 +370,7 @@ export const MsisdnAuthEntry = React.createClass({ }); return (
-

{ _t("A text message has been sent to") } +{ this._msisdn }

+

{ _t("A text message has been sent to %(msisdn)s", { msisdn: '' + this._msisdn + '' }) }

{ _t("Please enter the code it contains:") }

From a84b42bf24883dc57160f315027cf0802ec7bf49 Mon Sep 17 00:00:00 2001 From: Stefan Parviainen Date: Fri, 13 Oct 2017 21:10:50 +0200 Subject: [PATCH 004/365] Departify RoomSettings Signed-off-by: Stefan Parviainen --- src/components/views/rooms/RoomSettings.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index b1a2f41cec..0cb12002e7 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -868,21 +868,21 @@ module.exports = React.createClass({ disabled={!roomState.mayClientSendStateEvent("m.room.history_visibility", cli)} checked={historyVisibility === "shared"} onChange={this._onHistoryRadioToggle} /> - { _t('Members only') } ({ _t('since the point in time of selecting this option') }) + { _t('Members only since the point in time of selecting this option') })
From 6cd07731c4b35bcaae70d026d3890f7b36a6e87e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 14 Oct 2017 14:37:47 -0600 Subject: [PATCH 005/365] Add MemberPresenceAvatar and control presence ourselves Includes rudimentary support for custom statuses and user-controlled status. Some minor tweaks have also been made to better control how we advertise our presence. Signed-off-by: Travis Ralston --- src/MatrixClientPeg.js | 1 + src/Presence.js | 39 ++++- .../views/avatars/MemberPresenceAvatar.js | 135 ++++++++++++++++++ src/components/views/rooms/MessageComposer.js | 4 +- 4 files changed, 173 insertions(+), 6 deletions(-) create mode 100644 src/components/views/avatars/MemberPresenceAvatar.js diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js index 0c3d5b3775..7a4f0b99b0 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.js @@ -93,6 +93,7 @@ class MatrixClientPeg { const opts = utils.deepCopy(this.opts); // the react sdk doesn't work without this, so don't allow opts.pendingEventOrdering = "detached"; + opts.disablePresence = true; // we do this manually try { const promise = this.matrixClient.store.startup(); diff --git a/src/Presence.js b/src/Presence.js index fab518e1cb..2652c64c96 100644 --- a/src/Presence.js +++ b/src/Presence.js @@ -56,13 +56,27 @@ class Presence { return this.state; } + /** + * Get the current status message. + * @returns {String} the status message, may be null + */ + getStatusMessage() { + return this.statusMessage; + } + /** * Set the presence state. * If the state has changed, the Home Server will be notified. * @param {string} newState the new presence state (see PRESENCE enum) + * @param {String} statusMessage an optional status message for the presence + * @param {boolean} maintain true to have this status maintained by this tracker */ - setState(newState) { - if (newState === this.state) { + setState(newState, statusMessage=null, maintain=false) { + if (this.maintain) { + // Don't update presence if we're maintaining a particular status + return; + } + if (newState === this.state && statusMessage === this.statusMessage) { return; } if (PRESENCE_STATES.indexOf(newState) === -1) { @@ -72,21 +86,37 @@ class Presence { return; } const old_state = this.state; + const old_message = this.statusMessage; this.state = newState; + this.statusMessage = statusMessage; + this.maintain = maintain; if (MatrixClientPeg.get().isGuest()) { return; // don't try to set presence when a guest; it won't work. } + const updateContent = { + presence: this.state, + status_msg: this.statusMessage ? this.statusMessage : '', + }; + const self = this; - MatrixClientPeg.get().setPresence(this.state).done(function() { + MatrixClientPeg.get().setPresence(updateContent).done(function() { console.log("Presence: %s", newState); + + // We have to dispatch because the js-sdk is unreliable at telling us about our own presence + dis.dispatch({action: "self_presence_updated", statusInfo: updateContent}); }, function(err) { console.error("Failed to set presence: %s", err); self.state = old_state; + self.statusMessage = old_message; }); } + stopMaintainingStatus() { + this.maintain = false; + } + /** * Callback called when the user made no action on the page for UNAVAILABLE_TIME ms. * @private @@ -95,7 +125,8 @@ class Presence { this.setState("unavailable"); } - _onUserActivity() { + _onUserActivity(payload) { + if (payload.action === "sync_state" || payload.action === "self_presence_updated") return; this._resetTimer(); } diff --git a/src/components/views/avatars/MemberPresenceAvatar.js b/src/components/views/avatars/MemberPresenceAvatar.js new file mode 100644 index 0000000000..e90f2e3e62 --- /dev/null +++ b/src/components/views/avatars/MemberPresenceAvatar.js @@ -0,0 +1,135 @@ +/* + 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. + */ + +'use strict'; + +import React from "react"; +import * as sdk from "../../../index"; +import MatrixClientPeg from "../../../MatrixClientPeg"; +import AccessibleButton from '../elements/AccessibleButton'; +import Presence from "../../../Presence"; +import dispatcher from "../../../dispatcher"; + +module.exports = React.createClass({ + displayName: 'MemberPresenceAvatar', + + propTypes: { + member: React.PropTypes.object.isRequired, + width: React.PropTypes.number, + height: React.PropTypes.number, + resizeMethod: React.PropTypes.string, + }, + + getDefaultProps: function() { + return { + width: 40, + height: 40, + resizeMethod: 'crop', + }; + }, + + getInitialState: function() { + const presenceState = this.props.member.user.presence; + return { + status: presenceState, + }; + }, + + componentWillMount: function() { + MatrixClientPeg.get().on("User.presence", this.onUserPresence); + this.dispatcherRef = dispatcher.register(this.onAction); + }, + + componentWillUnmount: function() { + if (MatrixClientPeg.get()) { + MatrixClientPeg.get().removeListener("User.presence", this.onUserPresence); + } + dispatcher.unregister(this.dispatcherRef); + }, + + onAction: function(payload) { + if (payload.action !== "self_presence_updated") return; + if (this.props.member.userId !== MatrixClientPeg.get().getUserId()) return; + this.setState({ + status: payload.statusInfo.presence, + message: payload.statusInfo.status_msg, + }); + }, + + onUserPresence: function(event, user) { + if (user.userId !== MatrixClientPeg.get().getUserId()) return; + this.setState({ + status: user.presence, + message: user.presenceStatusMsg, + }); + }, + + onClick: function() { + if (Presence.getState() === "online") { + Presence.setState("unavailable", "This is a message", true); + } else { + Presence.stopMaintainingStatus(); + } + console.log("CLICK"); + + const presenceState = this.props.member.user.presence; + const presenceLastActiveAgo = this.props.member.user.lastActiveAgo; + const presenceLastTs = this.props.member.user.lastPresenceTs; + const presenceCurrentlyActive = this.props.member.user.currentlyActive; + const presenceMessage = this.props.member.user.presenceStatusMsg; + + console.log({ + presenceState, + presenceLastActiveAgo, + presenceLastTs, + presenceCurrentlyActive, + presenceMessage, + }); + }, + + render: function() { + const MemberAvatar = sdk.getComponent("avatars.MemberAvatar"); + + let onClickFn = null; + if (this.props.member.userId === MatrixClientPeg.get().getUserId()) { + onClickFn = this.onClick; + } + + const avatarNode = ( + + ); + const statusNode = ( + + ); + + let avatar = ( +
+ {avatarNode} + {statusNode} +
+ ); + if (onClickFn) { + avatar = ( + + {avatarNode} + {statusNode} + + ); + } + return avatar; + }, +}); diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 8e27520d89..d06cd76bdb 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -238,7 +238,7 @@ export default class MessageComposer extends React.Component { render() { const me = this.props.room.getMember(MatrixClientPeg.get().credentials.userId); const uploadInputStyle = {display: 'none'}; - const MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); + const MemberPresenceAvatar = sdk.getComponent('avatars.MemberPresenceAvatar'); const TintableSvg = sdk.getComponent("elements.TintableSvg"); const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput"); @@ -246,7 +246,7 @@ export default class MessageComposer extends React.Component { controls.push(
- +
, ); From 0b20681f6a2963a260f4c2031123a5b3975995d4 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 14 Oct 2017 19:13:46 -0600 Subject: [PATCH 006/365] Put presence management behind a labs setting Signed-off-by: Travis Ralston --- src/UserSettingsStore.js | 4 ++++ src/components/views/avatars/MemberPresenceAvatar.js | 9 ++++++++- src/i18n/strings/en_EN.json | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/UserSettingsStore.js b/src/UserSettingsStore.js index b274e6a594..cb4d184eff 100644 --- a/src/UserSettingsStore.js +++ b/src/UserSettingsStore.js @@ -34,6 +34,10 @@ const FEATURES = [ id: 'feature_pinning', name: _td("Message Pinning"), }, + { + id: 'feature_presence_management', + name: _td("Presence Management"), + }, ]; export default { diff --git a/src/components/views/avatars/MemberPresenceAvatar.js b/src/components/views/avatars/MemberPresenceAvatar.js index e90f2e3e62..486688250e 100644 --- a/src/components/views/avatars/MemberPresenceAvatar.js +++ b/src/components/views/avatars/MemberPresenceAvatar.js @@ -22,6 +22,7 @@ import MatrixClientPeg from "../../../MatrixClientPeg"; import AccessibleButton from '../elements/AccessibleButton'; import Presence from "../../../Presence"; import dispatcher from "../../../dispatcher"; +import UserSettingsStore from "../../../UserSettingsStore"; module.exports = React.createClass({ displayName: 'MemberPresenceAvatar', @@ -112,10 +113,16 @@ module.exports = React.createClass({ ); - const statusNode = ( + let statusNode = ( ); + // LABS: Disable presence management functions for now + if (!UserSettingsStore.isFeatureEnabled("feature_presence_management")) { + statusNode = null; + onClickFn = null; + } + let avatar = (
{avatarNode} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index df236636a2..1a9e59e3cd 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -620,6 +620,7 @@ "Cancel": "Cancel", "or": "or", "Message Pinning": "Message Pinning", + "Presence Management": "Presence Management", "Active call": "Active call", "Monday": "Monday", "Tuesday": "Tuesday", From 788e16a716dcef588f5d73cfc799df3dd91972c4 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 14 Oct 2017 20:23:50 -0600 Subject: [PATCH 007/365] Linting Signed-off-by: Travis Ralston --- src/components/views/avatars/MemberPresenceAvatar.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/views/avatars/MemberPresenceAvatar.js b/src/components/views/avatars/MemberPresenceAvatar.js index 486688250e..8005dcd405 100644 --- a/src/components/views/avatars/MemberPresenceAvatar.js +++ b/src/components/views/avatars/MemberPresenceAvatar.js @@ -111,10 +111,10 @@ module.exports = React.createClass({ const avatarNode = ( + resizeMethod={this.props.resizeMethod} /> ); let statusNode = ( - + ); // LABS: Disable presence management functions for now @@ -125,15 +125,15 @@ module.exports = React.createClass({ let avatar = (
- {avatarNode} - {statusNode} + { avatarNode } + { statusNode }
); if (onClickFn) { avatar = ( - {avatarNode} - {statusNode} + { avatarNode } + { statusNode } ); } From 03800b747608873f242446a330ba2387db2f871d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 14 Oct 2017 21:43:47 -0600 Subject: [PATCH 008/365] Support more positioning options on context menus Signed-off-by: Travis Ralston --- src/components/structures/ContextualMenu.js | 50 ++++++++++++++------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/src/components/structures/ContextualMenu.js b/src/components/structures/ContextualMenu.js index c3ad7f9cd1..3c2308e6a7 100644 --- a/src/components/structures/ContextualMenu.js +++ b/src/components/structures/ContextualMenu.js @@ -33,6 +33,7 @@ module.exports = { menuHeight: React.PropTypes.number, chevronOffset: React.PropTypes.number, menuColour: React.PropTypes.string, + chevronFace: React.PropTypes.string, // top, bottom, left, right }, getOrCreateContainer: function() { @@ -58,12 +59,30 @@ module.exports = { } }; - const position = { - top: props.top, - }; + const position = {}; + let chevronFace = null; + + if (props.top) { + position.top = props.top; + } else { + position.bottom = props.bottom; + } + + if (props.left) { + position.left = props.left; + chevronFace = 'left'; + } else { + position.right = props.right; + chevronFace = 'right'; + } const chevronOffset = {}; - if (props.chevronOffset) { + if (props.chevronFace) { + chevronFace = props.chevronFace; + } + if (chevronFace === 'top' || chevronFace === 'bottom') { + chevronOffset.left = props.chevronOffset; + } else { chevronOffset.top = props.chevronOffset; } @@ -74,28 +93,27 @@ module.exports = { .mx_ContextualMenu_chevron_left:after { border-right-color: ${props.menuColour}; } - .mx_ContextualMenu_chevron_right:after { border-left-color: ${props.menuColour}; } + .mx_ContextualMenu_chevron_top:after { + border-left-color: ${props.menuColour}; + } + .mx_ContextualMenu_chevron_bottom:after { + border-left-color: ${props.menuColour}; + } `; } - let chevron = null; - if (props.left) { - chevron =
; - position.left = props.left; - } else { - chevron =
; - position.right = props.right; - } - + const chevron =
; const className = 'mx_ContextualMenu_wrapper'; const menuClasses = classNames({ 'mx_ContextualMenu': true, - 'mx_ContextualMenu_left': props.left, - 'mx_ContextualMenu_right': !props.left, + 'mx_ContextualMenu_left': chevronFace === 'left', + 'mx_ContextualMenu_right': chevronFace === 'right', + 'mx_ContextualMenu_top': chevronFace === 'top', + 'mx_ContextualMenu_bottom': chevronFace === 'bottom', }); const menuStyle = {}; From c4837172821bed0be652abd64663c46c031f60c0 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 14 Oct 2017 21:44:07 -0600 Subject: [PATCH 009/365] Make onClick be a context menu for presence Signed-off-by: Travis Ralston --- .../views/avatars/MemberPresenceAvatar.js | 50 ++++++++++++------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/src/components/views/avatars/MemberPresenceAvatar.js b/src/components/views/avatars/MemberPresenceAvatar.js index 8005dcd405..de7c28154f 100644 --- a/src/components/views/avatars/MemberPresenceAvatar.js +++ b/src/components/views/avatars/MemberPresenceAvatar.js @@ -23,6 +23,7 @@ import AccessibleButton from '../elements/AccessibleButton'; import Presence from "../../../Presence"; import dispatcher from "../../../dispatcher"; import UserSettingsStore from "../../../UserSettingsStore"; +import * as ContextualMenu from "../../structures/ContextualMenu"; module.exports = React.createClass({ displayName: 'MemberPresenceAvatar', @@ -44,8 +45,10 @@ module.exports = React.createClass({ getInitialState: function() { const presenceState = this.props.member.user.presence; + const presenceMessage = this.props.member.user.presenceStatusMsg; return { status: presenceState, + message: presenceMessage, }; }, @@ -78,27 +81,38 @@ module.exports = React.createClass({ }); }, - onClick: function() { - if (Presence.getState() === "online") { - Presence.setState("unavailable", "This is a message", true); - } else { - Presence.stopMaintainingStatus(); - } - console.log("CLICK"); + onStatusChange: function(newStatus) { + console.log(this.state); + console.log(newStatus); + }, - const presenceState = this.props.member.user.presence; - const presenceLastActiveAgo = this.props.member.user.lastActiveAgo; - const presenceLastTs = this.props.member.user.lastPresenceTs; - const presenceCurrentlyActive = this.props.member.user.currentlyActive; - const presenceMessage = this.props.member.user.presenceStatusMsg; + onClick: function(e) { + const PresenceContextMenu = sdk.getComponent('context_menus.PresenceContextMenu'); + const elementRect = e.target.getBoundingClientRect(); - console.log({ - presenceState, - presenceLastActiveAgo, - presenceLastTs, - presenceCurrentlyActive, - presenceMessage, + // The window X and Y offsets are to adjust position when zoomed in to page + const x = (elementRect.left + window.pageXOffset) - (elementRect.width / 2) + 3; + const chevronOffset = 12; + let y = elementRect.top + (elementRect.height / 2) + window.pageYOffset; + y = y - (chevronOffset + 4); // where 4 is 1/4 the height of the chevron + + const self = this; + ContextualMenu.createMenu(PresenceContextMenu, { + chevronOffset: chevronOffset, + chevronFace: 'bottom', + left: x, + top: y, + menuWidth: 300, + currentStatus: this.state.status, + onChange: this.onStatusChange, }); + + e.stopPropagation(); + // const presenceState = this.props.member.user.presence; + // const presenceLastActiveAgo = this.props.member.user.lastActiveAgo; + // const presenceLastTs = this.props.member.user.lastPresenceTs; + // const presenceCurrentlyActive = this.props.member.user.currentlyActive; + // const presenceMessage = this.props.member.user.presenceStatusMsg; }, render: function() { From 7307bc412f794e06f4035c9210e6394f9158ce17 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 14 Oct 2017 23:16:12 -0600 Subject: [PATCH 010/365] Respond to updates from presence context menu Signed-off-by: Travis Ralston --- src/components/views/avatars/MemberPresenceAvatar.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/views/avatars/MemberPresenceAvatar.js b/src/components/views/avatars/MemberPresenceAvatar.js index de7c28154f..19342f3492 100644 --- a/src/components/views/avatars/MemberPresenceAvatar.js +++ b/src/components/views/avatars/MemberPresenceAvatar.js @@ -82,8 +82,10 @@ module.exports = React.createClass({ }, onStatusChange: function(newStatus) { - console.log(this.state); - console.log(newStatus); + Presence.stopMaintainingStatus(); + if (newStatus === "online") { + Presence.setState(newStatus); + } else Presence.setState(newStatus, null, true); }, onClick: function(e) { @@ -96,13 +98,12 @@ module.exports = React.createClass({ let y = elementRect.top + (elementRect.height / 2) + window.pageYOffset; y = y - (chevronOffset + 4); // where 4 is 1/4 the height of the chevron - const self = this; ContextualMenu.createMenu(PresenceContextMenu, { chevronOffset: chevronOffset, chevronFace: 'bottom', left: x, top: y, - menuWidth: 300, + menuWidth: 125, currentStatus: this.state.status, onChange: this.onStatusChange, }); From 15d1dc1f3b0c441b6feef2b23ccb92325dd538d7 Mon Sep 17 00:00:00 2001 From: Stefan Parviainen Date: Sun, 15 Oct 2017 16:57:13 +0200 Subject: [PATCH 011/365] Fix indentation Signed-off-by: Stefan Parviainen --- src/components/views/rooms/RoomList.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index 56589353f9..e689579650 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -36,13 +36,13 @@ const HIDE_CONFERENCE_CHANS = true; function phraseForSection(section) { switch (section) { case 'm.favourite': - return _t('Drop here to favourite'); + return _t('Drop here to favourite'); case 'im.vector.fake.direct': - return _t('Drop here to tag direct chat'); + return _t('Drop here to tag direct chat'); case 'im.vector.fake.recent': - return _t('Drop here to restore'); + return _t('Drop here to restore'); case 'm.lowpriority': - return _t('Drop here to demote'); + return _t('Drop here to demote'); default: return _t('Drop here to tag %(section)s', {section: section}); } From ad2f54f8ab228383eb602c9cead1083ad16dec61 Mon Sep 17 00:00:00 2001 From: Stefan Parviainen Date: Sun, 15 Oct 2017 18:01:57 +0200 Subject: [PATCH 012/365] Fix italics and parens Signed-off-by: Stefan Parviainen --- .../views/login/InteractiveAuthEntryComponents.js | 6 +++--- src/components/views/rooms/RoomSettings.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/views/login/InteractiveAuthEntryComponents.js b/src/components/views/login/InteractiveAuthEntryComponents.js index d0cd3931e4..5f5a74ccd1 100644 --- a/src/components/views/login/InteractiveAuthEntryComponents.js +++ b/src/components/views/login/InteractiveAuthEntryComponents.js @@ -20,7 +20,7 @@ import url from 'url'; import classnames from 'classnames'; import sdk from '../../../index'; -import { _t } from '../../../languageHandler'; +import { _t, _tJsx } from '../../../languageHandler'; /* This file contains a collection of components which are used by the * InteractiveAuth to prompt the user to enter the information needed @@ -256,7 +256,7 @@ export const EmailIdentityAuthEntry = React.createClass({ } else { return (
-

{ _t("An email has been sent to %(emailAddress)s", { emailAddress: '' + this.props.inputs.emailAddress + '' }) }

+

{ _tJsx("An email has been sent to %(emailAddress)s", /%\(emailAddress\)s/, (sub) => {this.props.inputs.emailAddress}) }

{ _t("Please check your email to continue registration.") }

); @@ -370,7 +370,7 @@ export const MsisdnAuthEntry = React.createClass({ }); return (
-

{ _t("A text message has been sent to %(msisdn)s", { msisdn: '' + this._msisdn + '' }) }

+

{ _tJsx("A text message has been sent to %(msisdn)s", /%\(msisdn\)s/, (sub) => {this._msisdn}) }

{ _t("Please enter the code it contains:") }

diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index 0cb12002e7..50478eaf43 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -868,21 +868,21 @@ module.exports = React.createClass({ disabled={!roomState.mayClientSendStateEvent("m.room.history_visibility", cli)} checked={historyVisibility === "shared"} onChange={this._onHistoryRadioToggle} /> - { _t('Members only since the point in time of selecting this option') }) + { _t('Members only (since the point in time of selecting this option)') }
From 4f6cd6b23a8474550cdd745feafeb69dbc88bbf7 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sun, 15 Oct 2017 21:17:43 -0600 Subject: [PATCH 013/365] Add a small indicator for when a new event is pinned Signed-off-by: Travis Ralston --- .../views/rooms/PinnedEventsPanel.js | 19 ++++++++++ src/components/views/rooms/RoomHeader.js | 36 ++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/PinnedEventsPanel.js b/src/components/views/rooms/PinnedEventsPanel.js index deea03f030..5a99d9ab2d 100644 --- a/src/components/views/rooms/PinnedEventsPanel.js +++ b/src/components/views/rooms/PinnedEventsPanel.js @@ -71,6 +71,25 @@ module.exports = React.createClass({ this.setState({ loading: false, pinned }); }); } + + this._updateReadState(); + }, + + _updateReadState: function() { + const pinnedEvents = this.props.room.currentState.getStateEvents("m.room.pinned_events", ""); + if (!pinnedEvents) return; // nothing to read + + let lastReadEvent = null; + const readPinsEvent = this.props.room.getAccountData("im.vector.room.read_pins"); + if (readPinsEvent) { + lastReadEvent = readPinsEvent.getContent().last_read_id; + } + + if (lastReadEvent !== pinnedEvents.getId()) { + MatrixClientPeg.get().setRoomAccountData(this.props.room.roomId, "im.vector.room.read_pins", { + last_read_id: pinnedEvents.getId(), + }); + } }, _getPinnedTiles: function() { diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 4df0ff738c..ea5748db60 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -65,6 +65,7 @@ module.exports = React.createClass({ componentDidMount: function() { const cli = MatrixClientPeg.get(); cli.on("RoomState.events", this._onRoomStateEvents); + cli.on("Room.accountData", this._onRoomAccountData); // When a room name occurs, RoomState.events is fired *before* // room.name is updated. So we have to listen to Room.name as well as @@ -87,6 +88,7 @@ module.exports = React.createClass({ const cli = MatrixClientPeg.get(); if (cli) { cli.removeListener("RoomState.events", this._onRoomStateEvents); + cli.removeListener("Room.accountData", this._onRoomAccountData); } }, @@ -99,6 +101,13 @@ module.exports = React.createClass({ this._rateLimitedUpdate(); }, + _onRoomAccountData: function(event, room) { + if (!this.props.room || room.roomId !== this.props.room.roomId) return; + if (event.getType() !== "im.vector.room.read_pins") return; + + this._rateLimitedUpdate(); + }, + _rateLimitedUpdate: new RateLimitedFunc(function() { /* eslint-disable babel/no-invalid-this */ this.forceUpdate(); @@ -139,6 +148,25 @@ module.exports = React.createClass({ dis.dispatch({ action: 'show_right_panel' }); }, + _hasUnreadPins: function() { + const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", ''); + if (!currentPinEvent) return false; + if (currentPinEvent.getContent().pinned && currentPinEvent.getContent().pinned.length <= 0) { + return false; // no pins == nothing to read + } + + const readPinsEvent = this.props.room.getAccountData("im.vector.room.read_pins"); + if (readPinsEvent) { + const lastReadEvent = readPinsEvent.getContent().last_read_id; + if (lastReadEvent) { + return currentPinEvent.getId() !== lastReadEvent; + } + } + + // There's pins, and we haven't read any of them + return true; + }, + /** * After editing the settings, get the new name for the room * @@ -302,8 +330,14 @@ module.exports = React.createClass({ } if (this.props.onPinnedClick && UserSettingsStore.isFeatureEnabled('feature_pinning')) { + let newPinsNotification = null; + if (this._hasUnreadPins()) { + newPinsNotification = (
); + } pinnedEventsButton = - + + { newPinsNotification } ; } From 8083dccfa5239340dc8994d743e629fa8b055bd2 Mon Sep 17 00:00:00 2001 From: Stefan Parviainen Date: Sun, 15 Oct 2017 21:08:41 +0200 Subject: [PATCH 014/365] De-partify SenderProfile Signed-off-by: Stefan Parviainen Also, text does not need to be EmojiText --- .../views/messages/SenderProfile.js | 38 ++++++++++++++----- src/components/views/rooms/EventTile.js | 12 +++--- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/components/views/messages/SenderProfile.js b/src/components/views/messages/SenderProfile.js index 63e3144115..f6940cd4b3 100644 --- a/src/components/views/messages/SenderProfile.js +++ b/src/components/views/messages/SenderProfile.js @@ -19,6 +19,7 @@ import React from 'react'; import sdk from '../../../index'; import Flair from '../elements/Flair.js'; +import { _tJsx } from '../../../languageHandler'; export default function SenderProfile(props) { const EmojiText = sdk.getComponent('elements.EmojiText'); @@ -26,27 +27,44 @@ export default function SenderProfile(props) { const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender(); const {msgtype} = mxEvent.getContent(); + // Display sender name by default if nothing else is given + const text = props.text ? props.text : '%(senderName)s'; + if (msgtype === 'm.emote') { return ; // emote message must include the name so don't duplicate it } + // Name + flair + const nameElem = [ + { name || '' }, + props.enableFlair ? + + : null, + ] + + if(props.text) { + // Replace senderName, and wrap surrounding text in spans with the right class + content = _tJsx(props.text, /^(.*)\%\(senderName\)s(.*)$/m, (p1, p2) => [ + p1 ? {p1} : null, + nameElem, + p2 ? {p2} : null, + ]); + } else { + content = nameElem; + } + return (
- { name || '' } - { props.enableFlair ? - - : null - } - { props.aux ? { props.aux } : null } + { content }
); } SenderProfile.propTypes = { mxEvent: React.PropTypes.object.isRequired, // event whose sender we're showing - aux: React.PropTypes.string, // stuff to go after the sender name, if anything + text: React.PropTypes.string, // Text to show. Defaults to sender name onClick: React.PropTypes.func, }; diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 499d0ec09a..812d72a26a 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -19,7 +19,7 @@ limitations under the License. const React = require('react'); const classNames = require("classnames"); -import { _t } from '../../../languageHandler'; +import { _t, _td } from '../../../languageHandler'; const Modal = require('../../../Modal'); const sdk = require('../../../index'); @@ -502,12 +502,12 @@ module.exports = withMatrixClient(React.createClass({ } if (needsSenderProfile) { - let aux = null; + let text = null; if (!this.props.tileShape) { - if (msgtype === 'm.image') aux = _t('sent an image'); - else if (msgtype === 'm.video') aux = _t('sent a video'); - else if (msgtype === 'm.file') aux = _t('uploaded a file'); - sender = ; + if (msgtype === 'm.image') text = _td('%(senderName)s sent an image'); + else if (msgtype === 'm.video') text = _td('%(senderName)s sent a video'); + else if (msgtype === 'm.file') text = _td('%(senderName)s uploaded a file'); + sender = ; } else { sender = ; } From 468a05c6f1f2e14218cbf7fa083dd9ab9fb73612 Mon Sep 17 00:00:00 2001 From: Stefan Parviainen Date: Tue, 17 Oct 2017 21:32:35 +0200 Subject: [PATCH 015/365] Fix SenderProfile Signed-off-by: Stefan Parviainen --- src/components/views/messages/SenderProfile.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/components/views/messages/SenderProfile.js b/src/components/views/messages/SenderProfile.js index f6940cd4b3..afdb97272f 100644 --- a/src/components/views/messages/SenderProfile.js +++ b/src/components/views/messages/SenderProfile.js @@ -27,30 +27,29 @@ export default function SenderProfile(props) { const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender(); const {msgtype} = mxEvent.getContent(); - // Display sender name by default if nothing else is given - const text = props.text ? props.text : '%(senderName)s'; - if (msgtype === 'm.emote') { return ; // emote message must include the name so don't duplicate it } // Name + flair const nameElem = [ - { name || '' }, + { name || '' }, props.enableFlair ? - : null, - ] + ]; + + let content = ''; if(props.text) { // Replace senderName, and wrap surrounding text in spans with the right class content = _tJsx(props.text, /^(.*)\%\(senderName\)s(.*)$/m, (p1, p2) => [ - p1 ? {p1} : null, + p1 ? { p1 } : null, nameElem, - p2 ? {p2} : null, + p2 ? { p2 } : null, ]); } else { content = nameElem; From fc860c66bc0f1d7cb5b4785a1372bbb422fd47e9 Mon Sep 17 00:00:00 2001 From: Stefan Parviainen Date: Tue, 17 Oct 2017 22:03:49 +0200 Subject: [PATCH 016/365] De-partify RoomPreviewBar Signed-off-by: Stefan Parviainen --- src/components/views/rooms/RoomPreviewBar.js | 22 +++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/components/views/rooms/RoomPreviewBar.js b/src/components/views/rooms/RoomPreviewBar.js index 368d81e606..0c0601a504 100644 --- a/src/components/views/rooms/RoomPreviewBar.js +++ b/src/components/views/rooms/RoomPreviewBar.js @@ -83,10 +83,8 @@ module.exports = React.createClass({ } }, - _roomNameElement: function(fallback) { - fallback = fallback || _t('a room'); - const name = this.props.room ? this.props.room.name : (this.props.room_alias || ""); - return name ? name : fallback; + _roomNameElement: function() { + return this.props.room ? this.props.room.name : (this.props.room_alias || ""); }, render: function() { @@ -150,7 +148,7 @@ module.exports = React.createClass({
); } else if (kicked || banned) { - const roomName = this._roomNameElement(_t('This room')); + const roomName = this._roomNameElement(); const kickerMember = this.props.room.currentState.getMember( myMember.events.member.getSender(), ); @@ -167,9 +165,17 @@ module.exports = React.createClass({ let actionText; if (kicked) { - actionText = _t("You have been kicked from %(roomName)s by %(userName)s.", {roomName: roomName, userName: kickerName}); + if(roomName) { + actionText = _t("You have been kicked from %(roomName)s by %(userName)s.", {roomName: roomName, userName: kickerName}); + } else { + actionText = _t("You have been kicked from this room by %(userName)s.", {userName: kickerName}); + } } else if (banned) { - actionText = _t("You have been banned from %(roomName)s by %(userName)s.", {roomName: roomName, userName: kickerName}); + if(roomName) { + actionText = _t("You have been banned from %(roomName)s by %(userName)s.", {roomName: roomName, userName: kickerName}); + } else { + actionText = _t("You have been banned from this room by %(userName)s.", {userName: kickerName}); + } } // no other options possible due to the kicked || banned check above. joinBlock = ( @@ -203,7 +209,7 @@ module.exports = React.createClass({ joinBlock = (
- { _t('You are trying to access %(roomName)s.', {roomName: name}) } + { name ? _t('You are trying to access %(roomName)s.', {roomName: name}) : _t('You are trying to access a room.') }
{ _tJsx("Click here to join the discussion!", /(.*?)<\/a>/, From 7eeed3e0932841fea286d5a7bdad86cfc412c98e Mon Sep 17 00:00:00 2001 From: Stefan Parviainen Date: Tue, 17 Oct 2017 23:46:23 +0200 Subject: [PATCH 017/365] Simplify MemberEventListSummary by using pluralization provided by the i18n library Signed-off-by: Stefan Parviainen --- .../views/elements/MemberEventListSummary.js | 154 +++++------------- 1 file changed, 40 insertions(+), 114 deletions(-) diff --git a/src/components/views/elements/MemberEventListSummary.js b/src/components/views/elements/MemberEventListSummary.js index 596838febe..9b303b4bd9 100644 --- a/src/components/views/elements/MemberEventListSummary.js +++ b/src/components/views/elements/MemberEventListSummary.js @@ -86,7 +86,6 @@ module.exports = React.createClass({ const summaries = orderedTransitionSequences.map((transitions) => { const userNames = eventAggregates[transitions]; const nameList = this._renderNameList(userNames); - const plural = userNames.length > 1; const splitTransitions = transitions.split(','); @@ -101,7 +100,7 @@ module.exports = React.createClass({ const descs = coalescedTransitions.map((t) => { return this._getDescriptionForTransition( - t.transitionType, plural, t.repeats, + t.transitionType, userNames.length, t.repeats, ); }); @@ -208,148 +207,75 @@ module.exports = React.createClass({ * For a certain transition, t, describe what happened to the users that * underwent the transition. * @param {string} t the transition type. - * @param {boolean} plural whether there were multiple users undergoing the same - * transition. + * @param {integer} userCount number of usernames * @param {number} repeats the number of times the transition was repeated in a row. * @returns {string} the written Human Readable equivalent of the transition. */ - _getDescriptionForTransition(t, plural, repeats) { + _getDescriptionForTransition(t, userCount, repeats) { // The empty interpolations 'severalUsers' and 'oneUser' // are there only to show translators to non-English languages // that the verb is conjugated to plural or singular Subject. let res = null; switch(t) { case "joined": - if (repeats > 1) { - res = (plural) - ? _t("%(severalUsers)sjoined %(repeats)s times", { severalUsers: "", repeats: repeats }) - : _t("%(oneUser)sjoined %(repeats)s times", { oneUser: "", repeats: repeats }); - } else { - res = (plural) - ? _t("%(severalUsers)sjoined", { severalUsers: "" }) - : _t("%(oneUser)sjoined", { oneUser: "" }); - } + res = (userCount > 1) + ? _t("%(severalUsers)sjoined %(count)s times", { severalUsers: "", count: repeats }) + : _t("%(oneUser)sjoined %(count)s times", { oneUser: "", count: repeats }); break; case "left": - if (repeats > 1) { - res = (plural) - ? _t("%(severalUsers)sleft %(repeats)s times", { severalUsers: "", repeats: repeats }) - : _t("%(oneUser)sleft %(repeats)s times", { oneUser: "", repeats: repeats }); - } else { - res = (plural) - ? _t("%(severalUsers)sleft", { severalUsers: "" }) - : _t("%(oneUser)sleft", { oneUser: "" }); - } - break; + res = (userCount > 1) + ? _t("%(severalUsers)sleft %(count)s times", { severalUsers: "", count: repeats }) + : _t("%(oneUser)sleft %(count)s times", { oneUser: "", count: repeats }); + break; case "joined_and_left": - if (repeats > 1) { - res = (plural) - ? _t("%(severalUsers)sjoined and left %(repeats)s times", { severalUsers: "", repeats: repeats }) - : _t("%(oneUser)sjoined and left %(repeats)s times", { oneUser: "", repeats: repeats }); - } else { - res = (plural) - ? _t("%(severalUsers)sjoined and left", { severalUsers: "" }) - : _t("%(oneUser)sjoined and left", { oneUser: "" }); - } + res = (userCount > 1) + ? _t("%(severalUsers)sjoined and left %(count)s times", { severalUsers: "", count: repeats }) + : _t("%(oneUser)sjoined and left %(count)s times", { oneUser: "", count: repeats }); break; case "left_and_joined": - if (repeats > 1) { - res = (plural) - ? _t("%(severalUsers)sleft and rejoined %(repeats)s times", { severalUsers: "", repeats: repeats }) - : _t("%(oneUser)sleft and rejoined %(repeats)s times", { oneUser: "", repeats: repeats }); - } else { - res = (plural) - ? _t("%(severalUsers)sleft and rejoined", { severalUsers: "" }) - : _t("%(oneUser)sleft and rejoined", { oneUser: "" }); - } + res = (userCount > 1) + ? _t("%(severalUsers)sleft and rejoined %(count)s times", { severalUsers: "", count: repeats }) + : _t("%(oneUser)sleft and rejoined %(count)s times", { oneUser: "", count: repeats }); break; case "invite_reject": - if (repeats > 1) { - res = (plural) - ? _t("%(severalUsers)srejected their invitations %(repeats)s times", { severalUsers: "", repeats: repeats }) - : _t("%(oneUser)srejected their invitation %(repeats)s times", { oneUser: "", repeats: repeats }); - } else { - res = (plural) - ? _t("%(severalUsers)srejected their invitations", { severalUsers: "" }) - : _t("%(oneUser)srejected their invitation", { oneUser: "" }); - } + res = (userCount > 1) + ? _t("%(severalUsers)srejected their invitations %(count)s times", { severalUsers: "", count: repeats }) + : _t("%(oneUser)srejected their invitation %(count)s times", { oneUser: "", count: repeats }); break; case "invite_withdrawal": - if (repeats > 1) { - res = (plural) - ? _t("%(severalUsers)shad their invitations withdrawn %(repeats)s times", { severalUsers: "", repeats: repeats }) - : _t("%(oneUser)shad their invitation withdrawn %(repeats)s times", { oneUser: "", repeats: repeats }); - } else { - res = (plural) - ? _t("%(severalUsers)shad their invitations withdrawn", { severalUsers: "" }) - : _t("%(oneUser)shad their invitation withdrawn", { oneUser: "" }); - } + res = (userCount > 1) + ? _t("%(severalUsers)shad their invitations withdrawn %(count)s times", { severalUsers: "", count: repeats }) + : _t("%(oneUser)shad their invitation withdrawn %(count)s times", { oneUser: "", count: repeats }); break; case "invited": - if (repeats > 1) { - res = (plural) - ? _t("were invited %(repeats)s times", { repeats: repeats }) - : _t("was invited %(repeats)s times", { repeats: repeats }); - } else { - res = (plural) - ? _t("were invited") - : _t("was invited"); - } + res = (userCount > 1) + ? _t("were invited %(count)s times", { count: repeats }) + : _t("was invited %(count)s times", { count: repeats }); break; case "banned": - if (repeats > 1) { - res = (plural) - ? _t("were banned %(repeats)s times", { repeats: repeats }) - : _t("was banned %(repeats)s times", { repeats: repeats }); - } else { - res = (plural) - ? _t("were banned") - : _t("was banned"); - } + res = (userCount > 1) + ? _t("were banned %(count)s times", { count: repeats }) + : _t("was banned %(count)s times", { count: repeats }); break; case "unbanned": - if (repeats > 1) { - res = (plural) - ? _t("were unbanned %(repeats)s times", { repeats: repeats }) - : _t("was unbanned %(repeats)s times", { repeats: repeats }); - } else { - res = (plural) - ? _t("were unbanned") - : _t("was unbanned"); - } + res = (userCount > 1) + ? _t("were unbanned %(count)s times", { count: repeats }) + : _t("was unbanned %(count)s times", { count: repeats }); break; case "kicked": - if (repeats > 1) { - res = (plural) - ? _t("were kicked %(repeats)s times", { repeats: repeats }) - : _t("was kicked %(repeats)s times", { repeats: repeats }); - } else { - res = (plural) - ? _t("were kicked") - : _t("was kicked"); - } + res = (userCount > 1) + ? _t("were kicked %(count)s times", { count: repeats }) + : _t("was kicked %(count)s times", { count: repeats }); break; case "changed_name": - if (repeats > 1) { - res = (plural) - ? _t("%(severalUsers)schanged their name %(repeats)s times", { severalUsers: "", repeats: repeats }) - : _t("%(oneUser)schanged their name %(repeats)s times", { oneUser: "", repeats: repeats }); - } else { - res = (plural) - ? _t("%(severalUsers)schanged their name", { severalUsers: "" }) - : _t("%(oneUser)schanged their name", { oneUser: "" }); - } + res = (userCount > 1) + ? _t("%(severalUsers)schanged their name %(count)s times", { severalUsers: "", count: repeats }) + : _t("%(oneUser)schanged their name %(count)s times", { oneUser: "", count: repeats }); break; case "changed_avatar": - if (repeats > 1) { - res = (plural) - ? _t("%(severalUsers)schanged their avatar %(repeats)s times", { severalUsers: "", repeats: repeats }) - : _t("%(oneUser)schanged their avatar %(repeats)s times", { oneUser: "", repeats: repeats }); - } else { - res = (plural) - ? _t("%(severalUsers)schanged their avatar", { severalUsers: "" }) - : _t("%(oneUser)schanged their avatar", { oneUser: "" }); - } + res = (userCount > 1) + ? _t("%(severalUsers)schanged their avatar %(count)s times", { severalUsers: "", count: repeats }) + : _t("%(oneUser)schanged their avatar %(count)s times", { oneUser: "", count: repeats }); break; } From 4d57140529b63f3612efcf24c082cdb523b0dbe1 Mon Sep 17 00:00:00 2001 From: Krombel Date: Fri, 20 Oct 2017 11:57:44 +0000 Subject: [PATCH 018/365] Translated using Weblate (German) Currently translated at 100.0% (904 of 904 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 47 ++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 8ff76a239e..6cc338c05b 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -857,5 +857,50 @@ "Guests can join": "Gäste können beitreten", "No rooms to show": "Keine Räume anzuzeigen", "Community Settings": "Community-Einstellungen", - "Community Member Settings": "Community-Mitglieder-Einstellungen" + "Community Member Settings": "Community-Mitglieder-Einstellungen", + "Who would you like to add to this community?": "Wen möchtest du zu dieser Community hinzufügen?", + "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Warnung: Jede Person, die du einer Community hinzufügst, wird für alle, die die Community-ID kennen, öffentlich sichtbar sein", + "Invite new community members": "Lade neue Community-Mitglieder ein", + "Invite to Community": "Lade zur Community ein", + "Which rooms would you like to add to this community?": "Welche Räume würdest du dieser Community hinzufügen wollen?", + "Warning: any room you add to a community will be publicly visible to anyone who knows the community ID": "Warnung: Jeder Raum, den du einer Community hinzufügst, wird für alle, die die Community-ID kennen, öffentlich sichtbar sein", + "Add rooms to the community": "Füge Raum zur Community hinzu", + "Add to community": "Füge zur Community hinzu", + "Your community invitations have been sent.": "Deine Community-Einladungen wurden gesendet.", + "Failed to invite users to community": "Einladen von Nutzern in die Community ist fehlgeschlagen", + "Communities": "Communities", + "Invalid community ID": "Invalide Community-ID", + "'%(groupId)s' is not a valid community ID": "'%(groupId)s' ist keine valide Community-ID", + "Related Communities": "Verknüpfte Communities", + "Related communities for this room:": "Verknüpfte Communities für diesen Raum:", + "This room has no related communities": "Dieser Raum hat keine verknüpften Communities", + "New community ID (e.g. +foo:%(localDomain)s)": "Neue Community-ID (z.B. +foo:%(localDomain)s)", + "Remove from community": "Von Community entfernen", + "Failed to remove user from community": "Entfernen des Nutzers von der Community fehlgeschlagen", + "Filter community members": "Filtere Community-Mitglieder", + "Filter community rooms": "Filtere Community-Räume", + "Failed to remove room from community": "Entfernen des Raumes von der Community fehlgeschlagen", + "Removing a room from the community will also remove it from the community page.": "Ein Entfernen eines Raumes aus der Community wird ihn auch von der Community-Seite entfernen.", + "Community IDs may only contain alphanumeric characters": "Community-ID darf nur alphanumerische Zeichen enthalten", + "Create Community": "Community erzeugen", + "Community Name": "Community-Name", + "Community ID": "Community-ID", + "example": "Beispiel", + "Add rooms to the community summary": "Fügt Räume zur Community-Übersicht hinzu", + "Add users to the community summary": "Fügt Benutzer zur Community-Übersicht hinzu", + "Failed to update community": "Aktualisieren der Community fehlgeschlagen", + "Leave Community": "Verlasse Community", + "Add rooms to this community": "Füge Räume dieser Community hinzu", + "%(inviter)s has invited you to join this community": "%(inviter)s hat dich eingeladen dieser Community beizutreten", + "You are a member of this community": "Du bist ein Mitglied dieser Community", + "You are an administrator of this community": "Du bist ein Administrator dieser Community", + "Community %(groupId)s not found": "Community '%(groupId)s' nicht gefunden", + "This Home server does not support communities": "Dieser Heimserver unterstützt keine Communities", + "Failed to load %(groupId)s": "Laden von '%(groupId)s' fehlgeschlagen", + "Error whilst fetching joined communities": "Fehler beim laden beigetretener Communities", + "Create a new community": "Erzeuge neue Community", + "Create a community to represent your community! Define a set of rooms and your own custom homepage to mark out your space in the Matrix universe.": "Erzeuge eine Community um deine Community zu repräsentieren! Definiere eine Menge von Räumen und deine eigene angepasste Startseite um dein Revier im Matrix-Universum zu markieren.", + "Join an existing community": "Trete einer existierenden Community bei", + "To join an existing community you'll have to know its community identifier; this will look something like +example:matrix.org.": "Um einer existierenden Community beizutreten, musst du die Community-ID kennen. Diese sieht z.B. aus wie +example:matrix.org.", + "Your Communities": "Deine Communities" } From 43517964a8a9a7a7a6ae8043ab1e446334c6b260 Mon Sep 17 00:00:00 2001 From: Krombel Date: Fri, 20 Oct 2017 11:59:11 +0000 Subject: [PATCH 019/365] Translated using Weblate (German) Currently translated at 100.0% (904 of 904 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 6cc338c05b..20dc4e3a2c 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -739,7 +739,7 @@ "Changes colour scheme of current room": "Ändere Farbschema des aktuellen Raumes", "Delete widget": "Widget entfernen", "Define the power level of a user": "Setze das Berechtigungslevel eines Benutzers", - "Edit": "Bearbeiten", + "Edit": "Editieren", "Enable automatic language detection for syntax highlighting": "Automatische Spracherkennung für die Syntax-Hervorhebung aktivieren", "Hide Apps": "Apps verbergen", "Hide join/leave messages (invites/kicks/bans unaffected)": "Betreten-/Verlassen-Benachrichtigungen verbergen (gilt nicht für Einladungen/Kicks/Bans)", From 7c7c9d43fce6977b406be83ead082d6d134e0c07 Mon Sep 17 00:00:00 2001 From: Bamstam Date: Fri, 20 Oct 2017 12:34:03 +0000 Subject: [PATCH 020/365] Translated using Weblate (German) Currently translated at 100.0% (904 of 904 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 20dc4e3a2c..2f46941a97 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -21,10 +21,10 @@ "User ID": "Benutzer-ID", "Curve25519 identity key": "Curve25519-Identitäts-Schlüssel", "Claimed Ed25519 fingerprint key": "Geforderter Ed25519-Fingerprint-Schlüssel", - "none": "keiner", + "none": "nicht vorhanden", "Algorithm": "Algorithmus", "unencrypted": "unverschlüsselt", - "Decryption error": "Entschlüsselungs Fehler", + "Decryption error": "Fehler beim Entschlüsseln", "Session ID": "Sitzungs-ID", "End-to-end encryption information": "Informationen zur Ende-zu-Ende-Verschlüsselung", "Event information": "Ereignis-Information", @@ -158,7 +158,7 @@ "This room is not accessible by remote Matrix servers": "Remote-Matrix-Server können auf diesen Raum nicht zugreifen", "This room's internal ID is": "Die interne ID dieses Raumes ist", "Admin": "Administrator", - "Server may be unavailable, overloaded, or you hit a bug.": "Server ist nicht verfügbar, überlastet oder du bist auf einen Fehler gestoßen.", + "Server may be unavailable, overloaded, or you hit a bug.": "Server ist nicht verfügbar, überlastet oder du bist auf einen Softwarefehler gestoßen.", "Could not connect to the integration server": "Konnte keine Verbindung zum Integrations-Server herstellen", "Disable inline URL previews by default": "URL-Vorschau im Chat standardmäßig deaktivieren", "Labs": "Labor", @@ -869,8 +869,8 @@ "Your community invitations have been sent.": "Deine Community-Einladungen wurden gesendet.", "Failed to invite users to community": "Einladen von Nutzern in die Community ist fehlgeschlagen", "Communities": "Communities", - "Invalid community ID": "Invalide Community-ID", - "'%(groupId)s' is not a valid community ID": "'%(groupId)s' ist keine valide Community-ID", + "Invalid community ID": "Ungültige Community-ID", + "'%(groupId)s' is not a valid community ID": "'%(groupId)s' ist keine gültige Community-ID", "Related Communities": "Verknüpfte Communities", "Related communities for this room:": "Verknüpfte Communities für diesen Raum:", "This room has no related communities": "Dieser Raum hat keine verknüpften Communities", @@ -891,13 +891,13 @@ "Failed to update community": "Aktualisieren der Community fehlgeschlagen", "Leave Community": "Verlasse Community", "Add rooms to this community": "Füge Räume dieser Community hinzu", - "%(inviter)s has invited you to join this community": "%(inviter)s hat dich eingeladen dieser Community beizutreten", + "%(inviter)s has invited you to join this community": "%(inviter)s hat dich eingeladen, dieser Community beizutreten", "You are a member of this community": "Du bist ein Mitglied dieser Community", "You are an administrator of this community": "Du bist ein Administrator dieser Community", "Community %(groupId)s not found": "Community '%(groupId)s' nicht gefunden", "This Home server does not support communities": "Dieser Heimserver unterstützt keine Communities", "Failed to load %(groupId)s": "Laden von '%(groupId)s' fehlgeschlagen", - "Error whilst fetching joined communities": "Fehler beim laden beigetretener Communities", + "Error whilst fetching joined communities": "Fehler beim Laden beigetretener Communities", "Create a new community": "Erzeuge neue Community", "Create a community to represent your community! Define a set of rooms and your own custom homepage to mark out your space in the Matrix universe.": "Erzeuge eine Community um deine Community zu repräsentieren! Definiere eine Menge von Räumen und deine eigene angepasste Startseite um dein Revier im Matrix-Universum zu markieren.", "Join an existing community": "Trete einer existierenden Community bei", From f2e12c9edf165cdd88d9231d13cef53adbe13c62 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Fri, 20 Oct 2017 13:50:13 +0000 Subject: [PATCH 021/365] Translated using Weblate (Hungarian) Currently translated at 100.0% (904 of 904 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 1af47c90dd..b5c895098f 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -312,7 +312,7 @@ "Reason: %(reasonText)s": "Ok: %(reasonText)s", "Revoke Moderator": "Moderátor visszahívása", "Refer a friend to Riot:": "Ismerős meghívása a Riotba:", - "Register": "Regisztráció", + "Register": "Regisztrál", "%(targetName)s rejected the invitation.": "%(targetName)s elutasította a meghívót.", "Reject invitation": "Meghívó elutasítása", "Rejoin": "Újracsatlakozás", From 37fd19290f0cdbe07052cee2b31e9696d69a6a47 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Fri, 20 Oct 2017 18:42:29 +0100 Subject: [PATCH 022/365] concept of default theme --- src/components/structures/MatrixChat.js | 2 +- src/components/structures/UserSettings.js | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 3fa628b8a3..9e476714ec 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -888,7 +888,7 @@ module.exports = React.createClass({ */ _onSetTheme: function(theme) { if (!theme) { - theme = 'light'; + theme = this.props.config.default_theme || 'light'; } // look for the stylesheet elements. diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index b69bea9282..e98bb844cc 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -179,6 +179,11 @@ const THEMES = [ label: _td('Dark theme'), value: 'dark', }, + { + id: 'theme', + label: _td('Status.im theme'), + value: 'status', + }, ]; const IgnoredUser = React.createClass({ @@ -279,7 +284,7 @@ module.exports = React.createClass({ const syncedSettings = UserSettingsStore.getSyncedSettings(); if (!syncedSettings.theme) { - syncedSettings.theme = 'light'; + syncedSettings.theme = SdkConfig.get().default_theme || 'light'; } this._syncedSettings = syncedSettings; From e3487fed7927980505caa6b369e9b0d7a633cf95 Mon Sep 17 00:00:00 2001 From: Walter Date: Fri, 20 Oct 2017 18:53:26 +0000 Subject: [PATCH 023/365] Translated using Weblate (Russian) Currently translated at 95.6% (866 of 905 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index de9aa13c77..e6d4dd7f20 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -857,5 +857,12 @@ "Community Member Settings": "Настройки участников сообщества", "Publish this community on your profile": "Опубликовать это сообщество в вашем профиле", "Long Description (HTML)": "Длинное описание (HTML)", - "Community Settings": "Настройки сообщества" + "Community Settings": "Настройки сообщества", + "Invite to Community": "Пригласить в сообщество", + "Add to community": "Добавить к сообществу", + "Add rooms to the community": "Добавить комнату к сообществу", + "Which rooms would you like to add to this community?": "Какую комнату хотели бы вы добавить к этому сообществу?", + "Who would you like to add to this community?": "Кого бы вы хотели добавить в это сообщество?", + "Invite new community members": "Пригласить новых членов сообщества", + "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Внимание: любой человек, которого вы добавляете в сообщество, будет общедоступным для всех, кто знает ID сообщества" } From 2ee07b69658b14d9e8aa45aa1664963dacc00057 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 20 Oct 2017 19:49:48 +0000 Subject: [PATCH 024/365] Translated using Weblate (Russian) Currently translated at 100.0% (905 of 905 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 49 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index e6d4dd7f20..1c0efcec17 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -740,7 +740,7 @@ "Delete widget": "Удалить виджет", "Define the power level of a user": "Определить уровень доступа пользователя", "Do you want to load widget from URL:": "Загрузить виджет из URL-адреса:", - "Edit": "Изменить", + "Edit": "Редактировать", "Enable automatic language detection for syntax highlighting": "Включить автоматическое определение языка для подсветки синтаксиса", "Hide Apps": "Скрыть приложения", "Hide join/leave messages (invites/kicks/bans unaffected)": "Скрыть сообщения о входе/выходе (приглашениях/выкидываниях/банах)", @@ -859,10 +859,49 @@ "Long Description (HTML)": "Длинное описание (HTML)", "Community Settings": "Настройки сообщества", "Invite to Community": "Пригласить в сообщество", - "Add to community": "Добавить к сообществу", - "Add rooms to the community": "Добавить комнату к сообществу", - "Which rooms would you like to add to this community?": "Какую комнату хотели бы вы добавить к этому сообществу?", + "Add to community": "Добавить в сообщество", + "Add rooms to the community": "Добавление комнат в сообщество", + "Which rooms would you like to add to this community?": "Какие комнаты вы хотите добавить в это сообщество?", "Who would you like to add to this community?": "Кого бы вы хотели добавить в это сообщество?", "Invite new community members": "Пригласить новых членов сообщества", - "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Внимание: любой человек, которого вы добавляете в сообщество, будет общедоступным для всех, кто знает ID сообщества" + "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Предупреждение: любой, кого вы добавляете в сообщество, будет виден всем, кто знает ID сообщества", + "Warning: any room you add to a community will be publicly visible to anyone who knows the community ID": "Предупреждение: любая комната, добавляемая в сообщество, будет видна всем, кто знает ID сообщества", + "Add rooms to this community": "Добавить комнаты в это сообщество", + "Your community invitations have been sent.": "Ваши приглашения в сообщество были отправлены.", + "Failed to invite users to community": "Не удалось пригласить пользователей в сообщество", + "Communities": "Сообщества", + "Invalid community ID": "Недопустимый ID сообщества", + "'%(groupId)s' is not a valid community ID": "'%(groupId)s' - недействительный ID сообщества", + "Related Communities": "Связанные сообщества", + "Related communities for this room:": "Связанные сообщества для этой комнаты:", + "This room has no related communities": "Эта комната не имеет связанных сообществ", + "New community ID (e.g. +foo:%(localDomain)s)": "Новый ID сообщества (напр. +foo:%(localDomain)s)", + "Remove from community": "Удалить из сообщества", + "Failed to remove user from community": "Не удалось удалить пользователя из сообщества", + "Filter community members": "Фильтровать участников сообщества", + "Filter community rooms": "Фильтровать комнаты сообщества", + "Failed to remove room from community": "Не удалось удалить комнату из сообщества", + "Removing a room from the community will also remove it from the community page.": "Удаление комнаты из сообщества также удалит ее со страницы сообщества.", + "Community IDs may only contain alphanumeric characters": "ID сообщества могут содержать только буквенно-цифровые символы", + "Create Community": "Создать сообщество", + "Community Name": "Имя сообщества", + "Community ID": "ID сообщества", + "example": "пример", + "Add rooms to the community summary": "Добавить комнаты в сводку сообщества", + "Add users to the community summary": "Добавить пользователей в сводку сообщества", + "Failed to update community": "Не удалось обновить сообщество", + "Leave Community": "Покинуть сообщество", + "%(inviter)s has invited you to join this community": "%(inviter)s пригласил(а) вас присоединиться к этому сообществу", + "You are a member of this community": "Вы являетесь участником этого сообщества", + "You are an administrator of this community": "Вы являетесь администратором этого сообщества", + "Community %(groupId)s not found": "Сообщество %(groupId)s не найдено", + "This Home server does not support communities": "Этот домашний сервер не поддерживает сообщества", + "Failed to load %(groupId)s": "Ошибка загрузки %(groupId)s", + "Your Communities": "Ваши сообщества", + "You're not currently a member of any communities.": "В настоящее время вы не являетесь членом каких-либо сообществ.", + "Error whilst fetching joined communities": "Ошибка при загрузке сообществ", + "Create a new community": "Создать новое сообщество", + "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Создайте сообщество для объединения пользователей и комнат! Создайте собственную домашнюю страницу, чтобы выделить свое пространство во вселенной Matrix.", + "Join an existing community": "Присоединиться к существующему сообществу", + "To join an existing community you'll have to know its community identifier; this will look something like +example:matrix.org.": "Чтобы присоединиться к существующему сообществу, вам нужно знать его ID; это будет выглядеть примерно так+primer:matrix.org." } From 01e5d74be3f487f8a1511b34da16a4a8ca642048 Mon Sep 17 00:00:00 2001 From: Rinat Date: Sat, 21 Oct 2017 16:45:10 +0000 Subject: [PATCH 025/365] Translated using Weblate (Russian) Currently translated at 100.0% (905 of 905 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 1c0efcec17..43eacd175d 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -878,8 +878,8 @@ "New community ID (e.g. +foo:%(localDomain)s)": "Новый ID сообщества (напр. +foo:%(localDomain)s)", "Remove from community": "Удалить из сообщества", "Failed to remove user from community": "Не удалось удалить пользователя из сообщества", - "Filter community members": "Фильтровать участников сообщества", - "Filter community rooms": "Фильтровать комнаты сообщества", + "Filter community members": "Поиск участников", + "Filter community rooms": "Поиск комнат", "Failed to remove room from community": "Не удалось удалить комнату из сообщества", "Removing a room from the community will also remove it from the community page.": "Удаление комнаты из сообщества также удалит ее со страницы сообщества.", "Community IDs may only contain alphanumeric characters": "ID сообщества могут содержать только буквенно-цифровые символы", From eb7d5fb90f520cc3ff26670892c60bbd3e940f87 Mon Sep 17 00:00:00 2001 From: Krombel Date: Sun, 22 Oct 2017 12:29:08 +0000 Subject: [PATCH 026/365] Translated using Weblate (German) Currently translated at 100.0% (905 of 905 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 2f46941a97..75bf36cce2 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -902,5 +902,7 @@ "Create a community to represent your community! Define a set of rooms and your own custom homepage to mark out your space in the Matrix universe.": "Erzeuge eine Community um deine Community zu repräsentieren! Definiere eine Menge von Räumen und deine eigene angepasste Startseite um dein Revier im Matrix-Universum zu markieren.", "Join an existing community": "Trete einer existierenden Community bei", "To join an existing community you'll have to know its community identifier; this will look something like +example:matrix.org.": "Um einer existierenden Community beizutreten, musst du die Community-ID kennen. Diese sieht z.B. aus wie +example:matrix.org.", - "Your Communities": "Deine Communities" + "Your Communities": "Deine Communities", + "You're not currently a member of any communities.": "Du bist aktuell kein Mitglied einer Community.", + "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Erzeuge eine Community um Nutzer und Räume zu gruppieren! Erzeuge eine angepasste Homepage um dein Revier im Matrix-Universum zu markieren." } From f09fbccc1917ff6d9da5d5169c795a9826c84381 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 23 Oct 2017 00:56:15 +0100 Subject: [PATCH 027/365] WIP --- src/UserSettingsStore.js | 15 ++++++ src/components/structures/login/Login.js | 37 ++++++++++----- src/components/views/login/LoginPageFooter.js | 30 ++++++++++++ src/components/views/login/LoginPageHeader.js | 47 +++++++++++++++++++ src/i18n/strings/en_EN.json | 1 + 5 files changed, 117 insertions(+), 13 deletions(-) create mode 100644 src/components/views/login/LoginPageFooter.js create mode 100644 src/components/views/login/LoginPageHeader.js diff --git a/src/UserSettingsStore.js b/src/UserSettingsStore.js index 68f463c373..ede5a157a8 100644 --- a/src/UserSettingsStore.js +++ b/src/UserSettingsStore.js @@ -180,6 +180,21 @@ export default { }); }, + getTheme: function() { + let syncedSettings; + let theme; + if (MatrixClientPeg.get()) { + syncedSettings = this.getSyncedSettings(); + } + if (!syncedSettings || !syncedSettings.theme) { + theme = SdkConfig.get().default_theme || 'light'; + } + else { + theme = syncedSettings.theme; + } + return theme; + }, + getSyncedSettings: function() { const event = MatrixClientPeg.get().getAccountData('im.vector.web.settings'); return event ? event.getContent() : {}; diff --git a/src/components/structures/login/Login.js b/src/components/structures/login/Login.js index 8ee6eafad4..eb131989a8 100644 --- a/src/components/structures/login/Login.js +++ b/src/components/structures/login/Login.js @@ -329,13 +329,17 @@ module.exports = React.createClass({ render: function() { const Loader = sdk.getComponent("elements.Spinner"); + const LoginPageHeader = sdk.getComponent("login.LoginPageHeader"); + const LoginPageFooter = sdk.getComponent("login.LoginPageFooter"); const LoginHeader = sdk.getComponent("login.LoginHeader"); const LoginFooter = sdk.getComponent("login.LoginFooter"); const ServerConfig = sdk.getComponent("login.ServerConfig"); const loader = this.state.busy ?
: null; + const theme = UserSettingsStore.getTheme(); + let loginAsGuestJsx; - if (this.props.enableGuest) { + if (this.props.enableGuest && theme !== 'status') { loginAsGuestJsx =
{ _t('Login as guest') } @@ -343,42 +347,49 @@ module.exports = React.createClass({ } let returnToAppJsx; - if (this.props.onCancelClick) { + if (this.props.onCancelClick && theme !== 'status') { returnToAppJsx = { _t('Return to app') } ; } + let serverConfig; + if (theme !== 'status') { + serverConfig = ; + } + return (
+
-

{ _t('Sign in') } +

{ theme !== 'status' ? _t('Sign in') : _t('Sign in to get started') } { loader }

- { this.componentForStep(this.state.currentFlow) } -
{ this.state.errorText }
+ { this.componentForStep(this.state.currentFlow) } + { serverConfig } { _t('Create an account') } { loginAsGuestJsx } { returnToAppJsx } - { this._renderLanguageSetting() } + { theme !== 'status' ? this._renderLanguageSetting() : '' }
+
); }, diff --git a/src/components/views/login/LoginPageFooter.js b/src/components/views/login/LoginPageFooter.js new file mode 100644 index 0000000000..e4eca9b8b3 --- /dev/null +++ b/src/components/views/login/LoginPageFooter.js @@ -0,0 +1,30 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +'use strict'; + +import { _t } from '../../../languageHandler'; +import React from 'react'; + +module.exports = React.createClass({ + displayName: 'LoginPageFooter', + + render: function() { + return ( +
+ ); + }, +}); diff --git a/src/components/views/login/LoginPageHeader.js b/src/components/views/login/LoginPageHeader.js new file mode 100644 index 0000000000..bef7fa5e0b --- /dev/null +++ b/src/components/views/login/LoginPageHeader.js @@ -0,0 +1,47 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +'use strict'; + +import UserSettingsStore from '../../../UserSettingsStore'; + +const React = require('react'); + +module.exports = React.createClass({ + displayName: 'LoginPageHeader', + + render: function() { + let themeBranding; + if (UserSettingsStore.getTheme() === 'status') { + themeBranding =
+
+ Status +
+
+

Status Community Chat

+
+ A safer, decentralised communication platform powered by Riot +
+
+
; + } + else { + themeBranding =
; + } + + return themeBranding; + }, +}); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f071d42f56..db13972c9f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -475,6 +475,7 @@ "Sign in with": "Sign in with", "Email address": "Email address", "Sign in": "Sign in", + "Sign in to get started": "Sign in to get started", "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?", "Email address (optional)": "Email address (optional)", "You are registering with %(SelectedTeamName)s": "You are registering with %(SelectedTeamName)s", From 18e50653d75967eaeb9d73828898795bf8253862 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Mon, 23 Oct 2017 01:09:22 +0000 Subject: [PATCH 028/365] Translated using Weblate (Chinese (Traditional)) Currently translated at 85.7% (776 of 905 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index a10092e3d2..6e0cfd3892 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -239,7 +239,7 @@ "Device ID:": "裝置 ID:", "device id: ": "裝置 ID: ", "Reason": "原因", - "Register": "注冊", + "Register": "註冊", "Default server": "預設伺服器", "Custom server": "自定的伺服器", "Home server URL": "自家伺服器網址", @@ -774,5 +774,6 @@ "Robot check is currently unavailable on desktop - please use a web browser": "機器人檢查目前在桌面端不可用 ── 請使用網路瀏覽器", "%(widgetName)s widget modified by %(senderName)s": "%(widgetName)s 小工具已被 %(senderName)s 修改", "Copied!": "已複製!", - "Failed to copy": "複製失敗" + "Failed to copy": "複製失敗", + "Add rooms to this community": "新增聊天室到此社群" } From 39b424bf267e53499e5c24acde9a03e09e05bac7 Mon Sep 17 00:00:00 2001 From: Bamstam Date: Sun, 22 Oct 2017 13:45:44 +0000 Subject: [PATCH 029/365] Translated using Weblate (German) Currently translated at 100.0% (905 of 905 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 38 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 75bf36cce2..444f05704b 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -855,16 +855,16 @@ "Unnamed room": "Unbenannter Raum", "World readable": "Lesbar für die Welt", "Guests can join": "Gäste können beitreten", - "No rooms to show": "Keine Räume anzuzeigen", + "No rooms to show": "Keine anzeigbaren Räume vorhanden", "Community Settings": "Community-Einstellungen", "Community Member Settings": "Community-Mitglieder-Einstellungen", "Who would you like to add to this community?": "Wen möchtest du zu dieser Community hinzufügen?", "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Warnung: Jede Person, die du einer Community hinzufügst, wird für alle, die die Community-ID kennen, öffentlich sichtbar sein", - "Invite new community members": "Lade neue Community-Mitglieder ein", - "Invite to Community": "Lade zur Community ein", - "Which rooms would you like to add to this community?": "Welche Räume würdest du dieser Community hinzufügen wollen?", + "Invite new community members": "Neue Community-Mitglieder einladen", + "Invite to Community": "In die Community einladen", + "Which rooms would you like to add to this community?": "Welche Räume möchtest du zu dieser Community hinzufügen?", "Warning: any room you add to a community will be publicly visible to anyone who knows the community ID": "Warnung: Jeder Raum, den du einer Community hinzufügst, wird für alle, die die Community-ID kennen, öffentlich sichtbar sein", - "Add rooms to the community": "Füge Raum zur Community hinzu", + "Add rooms to the community": "Räume zur Community hinzufügen", "Add to community": "Füge zur Community hinzu", "Your community invitations have been sent.": "Deine Community-Einladungen wurden gesendet.", "Failed to invite users to community": "Einladen von Nutzern in die Community ist fehlgeschlagen", @@ -874,34 +874,34 @@ "Related Communities": "Verknüpfte Communities", "Related communities for this room:": "Verknüpfte Communities für diesen Raum:", "This room has no related communities": "Dieser Raum hat keine verknüpften Communities", - "New community ID (e.g. +foo:%(localDomain)s)": "Neue Community-ID (z.B. +foo:%(localDomain)s)", - "Remove from community": "Von Community entfernen", - "Failed to remove user from community": "Entfernen des Nutzers von der Community fehlgeschlagen", - "Filter community members": "Filtere Community-Mitglieder", - "Filter community rooms": "Filtere Community-Räume", - "Failed to remove room from community": "Entfernen des Raumes von der Community fehlgeschlagen", + "New community ID (e.g. +foo:%(localDomain)s)": "Neue Community-ID (z. B. +foo:%(localDomain)s)", + "Remove from community": "Aus Community entfernen", + "Failed to remove user from community": "Entfernen des Benutzers aus der Community fehlgeschlagen", + "Filter community members": "Community-Mitglieder filtern", + "Filter community rooms": "Community-Räume filtern", + "Failed to remove room from community": "Entfernen des Raumes aus der Community fehlgeschlagen", "Removing a room from the community will also remove it from the community page.": "Ein Entfernen eines Raumes aus der Community wird ihn auch von der Community-Seite entfernen.", - "Community IDs may only contain alphanumeric characters": "Community-ID darf nur alphanumerische Zeichen enthalten", - "Create Community": "Community erzeugen", + "Community IDs may only contain alphanumeric characters": "Community-IDs dürfen nur alphanumerische Zeichen enthalten", + "Create Community": "Community erstellen", "Community Name": "Community-Name", "Community ID": "Community-ID", "example": "Beispiel", "Add rooms to the community summary": "Fügt Räume zur Community-Übersicht hinzu", "Add users to the community summary": "Fügt Benutzer zur Community-Übersicht hinzu", "Failed to update community": "Aktualisieren der Community fehlgeschlagen", - "Leave Community": "Verlasse Community", - "Add rooms to this community": "Füge Räume dieser Community hinzu", - "%(inviter)s has invited you to join this community": "%(inviter)s hat dich eingeladen, dieser Community beizutreten", + "Leave Community": "Community verlassen", + "Add rooms to this community": "Räume zu dieser Community hinzufügen", + "%(inviter)s has invited you to join this community": "%(inviter)s hat dich in diese Community eingeladen", "You are a member of this community": "Du bist ein Mitglied dieser Community", "You are an administrator of this community": "Du bist ein Administrator dieser Community", "Community %(groupId)s not found": "Community '%(groupId)s' nicht gefunden", "This Home server does not support communities": "Dieser Heimserver unterstützt keine Communities", "Failed to load %(groupId)s": "Laden von '%(groupId)s' fehlgeschlagen", "Error whilst fetching joined communities": "Fehler beim Laden beigetretener Communities", - "Create a new community": "Erzeuge neue Community", + "Create a new community": "Neue Community erstellen", "Create a community to represent your community! Define a set of rooms and your own custom homepage to mark out your space in the Matrix universe.": "Erzeuge eine Community um deine Community zu repräsentieren! Definiere eine Menge von Räumen und deine eigene angepasste Startseite um dein Revier im Matrix-Universum zu markieren.", - "Join an existing community": "Trete einer existierenden Community bei", - "To join an existing community you'll have to know its community identifier; this will look something like +example:matrix.org.": "Um einer existierenden Community beizutreten, musst du die Community-ID kennen. Diese sieht z.B. aus wie +example:matrix.org.", + "Join an existing community": "Einer bestehenden Community beitreten", + "To join an existing community you'll have to know its community identifier; this will look something like +example:matrix.org.": "Um einer bereits bestehenden Community beitreten zu können, musst dir deren Community-ID bekannt sein. Diese sieht z. B. aus wie +example:matrix.org.", "Your Communities": "Deine Communities", "You're not currently a member of any communities.": "Du bist aktuell kein Mitglied einer Community.", "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Erzeuge eine Community um Nutzer und Räume zu gruppieren! Erzeuge eine angepasste Homepage um dein Revier im Matrix-Universum zu markieren." From 48396e1483a1ef69c4c317fefdae18c8f666efc9 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Fri, 20 Oct 2017 19:53:09 +0000 Subject: [PATCH 030/365] Translated using Weblate (Hungarian) Currently translated at 100.0% (905 of 905 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index b5c895098f..8646d448b5 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -902,5 +902,7 @@ "To join an existing community you'll have to know its community identifier; this will look something like +example:matrix.org.": "Ahhoz hogy csatlakozni tudj egy meglévő közösséghez ismerned kell a közösségi azonosítót ami például így nézhet ki: +pelda:matrix.org.", "example": "példa", "Failed to load %(groupId)s": "Nem sikerült betölteni: %(groupId)s", - "Your Communities": "Közösségeid" + "Your Communities": "Közösségeid", + "You're not currently a member of any communities.": "Nem vagy tagja egyetlen közösségnek sem.", + "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Készíts közösséget hogy egybegyűjtsd a felhasználókat és szobákat! Készíts egy saját kezdőlapot amivel meghatározhatod magad a Matrix univerzumában." } From 5e134f244b07bc91a20d7afa93f1368bd43ffa96 Mon Sep 17 00:00:00 2001 From: Andrey Date: Sat, 21 Oct 2017 19:53:32 +0000 Subject: [PATCH 031/365] Translated using Weblate (Russian) Currently translated at 100.0% (905 of 905 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 43eacd175d..47016eabf2 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -878,8 +878,8 @@ "New community ID (e.g. +foo:%(localDomain)s)": "Новый ID сообщества (напр. +foo:%(localDomain)s)", "Remove from community": "Удалить из сообщества", "Failed to remove user from community": "Не удалось удалить пользователя из сообщества", - "Filter community members": "Поиск участников", - "Filter community rooms": "Поиск комнат", + "Filter community members": "Фильтр участников сообщества", + "Filter community rooms": "Фильтр комнат сообщества", "Failed to remove room from community": "Не удалось удалить комнату из сообщества", "Removing a room from the community will also remove it from the community page.": "Удаление комнаты из сообщества также удалит ее со страницы сообщества.", "Community IDs may only contain alphanumeric characters": "ID сообщества могут содержать только буквенно-цифровые символы", From 23dd059a7f4f5789dee18585cc0b7498426c73c2 Mon Sep 17 00:00:00 2001 From: Andrey Date: Sat, 21 Oct 2017 19:53:32 +0000 Subject: [PATCH 032/365] Translated using Weblate (Russian) Currently translated at 100.0% (905 of 905 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 47016eabf2..ccd304c235 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -903,5 +903,6 @@ "Create a new community": "Создать новое сообщество", "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Создайте сообщество для объединения пользователей и комнат! Создайте собственную домашнюю страницу, чтобы выделить свое пространство во вселенной Matrix.", "Join an existing community": "Присоединиться к существующему сообществу", - "To join an existing community you'll have to know its community identifier; this will look something like +example:matrix.org.": "Чтобы присоединиться к существующему сообществу, вам нужно знать его ID; это будет выглядеть примерно так+primer:matrix.org." + "To join an existing community you'll have to know its community identifier; this will look something like +example:matrix.org.": "Чтобы присоединиться к существующему сообществу, вам нужно знать его ID; это будет выглядеть примерно так+primer:matrix.org.", + "Something went wrong whilst creating your community": "При создании сообщества что-то пошло не так" } From 015fa776fb96c59c1cd47f9c20ba1e748c62cbe7 Mon Sep 17 00:00:00 2001 From: Jan Kudrik Date: Mon, 23 Oct 2017 15:23:42 +0000 Subject: [PATCH 033/365] Translated using Weblate (Czech) Currently translated at 52.9% (479 of 905 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/cs/ --- src/i18n/strings/cs.json | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json index 91a71937d4..0cc1f5b475 100644 --- a/src/i18n/strings/cs.json +++ b/src/i18n/strings/cs.json @@ -450,5 +450,33 @@ "(no answer)": "(žádná odpověď)", "(unknown failure: %(reason)s)": "(neznámá chyba: %(reason)s)", "(warning: cannot be disabled again!)": "(varování: nemůže být opět zakázáno!)", - "WARNING: Device already verified, but keys do NOT MATCH!": "VAROVÁNÍ: Zařízení ověřeno, ale klíče se NESHODUJÍ!" + "WARNING: Device already verified, but keys do NOT MATCH!": "VAROVÁNÍ: Zařízení ověřeno, ale klíče se NESHODUJÍ!", + "The remote side failed to pick up": "Vzdálené straně se nepodařilo hovor přijmout", + "Who would you like to add to this community?": "Koho chcete přidat do této komunity?", + "Invite new community members": "Pozvěte nové členy komunity", + "Name or matrix ID": "Jméno nebo matrix ID", + "Invite to Community": "Pozvat do komunity", + "Which rooms would you like to add to this community?": "Které místnosti chcete přidat do této komunity?", + "Warning: any room you add to a community will be publicly visible to anyone who knows the community ID": "Varování: místnost, kterou přidáte do této komunity, bude veřejně viditelná každému, kdo zná ID komunity", + "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Varování: osoba, kterou přidáte do této komunity, bude veřejně viditelná každému, kdo zná ID komunity", + "Add rooms to the community": "Přidat místnosti do komunity", + "Room name or alias": "Název místnosti nebo přezdívka", + "Add to community": "Přidat do komunity", + "Failed to invite the following users to %(groupId)s:": "Následující uživatele se nepodařilo přidat do %(groupId)s:", + "Invites sent": "Pozvánky odeslány", + "Your community invitations have been sent.": "Vaše komunitní pozvánky byly odeslány.", + "Failed to invite users to community": "Nepodařilo se pozvat uživatele do komunity", + "Failed to invite users to %(groupId)s": "Nepodařilo se pozvat uživatele do %(groupId)s", + "%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s", + "%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s", + "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s", + "Failed to add the following rooms to %(groupId)s:": "Nepodařilo se přidat následující místnosti do %(groupId)s:", + "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Vaše e-mailová adresa zřejmě nepatří k žádnému Matrix ID na tomto domovském serveru.", + "Send Invites": "Odeslat pozvánky", + "Failed to invite user": "Nepodařilo se pozvat uživatele", + "Failed to invite": "Pozvání se nezdařilo", + "Failed to invite the following users to the %(roomName)s room:": "Do místnosti %(roomName)s se nepodařilo pozvat následující uživatele:", + "You need to be logged in.": "Musíte být přihlášen/a.", + "You are now ignoring %(userId)s": "Nyní ignorujete %(userId)s", + "You are no longer ignoring %(userId)s": "Už neignorujete %(userId)s" } From d7c9109a66a1dd85a20741189e056cd0715dfe67 Mon Sep 17 00:00:00 2001 From: Bamstam Date: Sun, 22 Oct 2017 13:45:44 +0000 Subject: [PATCH 034/365] Translated using Weblate (German) Currently translated at 100.0% (905 of 905 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 444f05704b..18fa49b0a1 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -904,5 +904,6 @@ "To join an existing community you'll have to know its community identifier; this will look something like +example:matrix.org.": "Um einer bereits bestehenden Community beitreten zu können, musst dir deren Community-ID bekannt sein. Diese sieht z. B. aus wie +example:matrix.org.", "Your Communities": "Deine Communities", "You're not currently a member of any communities.": "Du bist aktuell kein Mitglied einer Community.", - "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Erzeuge eine Community um Nutzer und Räume zu gruppieren! Erzeuge eine angepasste Homepage um dein Revier im Matrix-Universum zu markieren." + "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Erzeuge eine Community um Nutzer und Räume zu gruppieren! Erzeuge eine angepasste Homepage um dein Revier im Matrix-Universum zu markieren.", + "Something went wrong whilst creating your community": "Beim Erstellen deiner Community ist ein Fehler aufgetreten" } From 64e998cdf957bedc39543b5c44fd94c437db1353 Mon Sep 17 00:00:00 2001 From: Jan Kudrik Date: Mon, 23 Oct 2017 15:45:49 +0000 Subject: [PATCH 035/365] Translated using Weblate (Czech) Currently translated at 53.9% (488 of 905 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/cs/ --- src/i18n/strings/cs.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json index 0cc1f5b475..fc18928eda 100644 --- a/src/i18n/strings/cs.json +++ b/src/i18n/strings/cs.json @@ -478,5 +478,14 @@ "Failed to invite the following users to the %(roomName)s room:": "Do místnosti %(roomName)s se nepodařilo pozvat následující uživatele:", "You need to be logged in.": "Musíte být přihlášen/a.", "You are now ignoring %(userId)s": "Nyní ignorujete %(userId)s", - "You are no longer ignoring %(userId)s": "Už neignorujete %(userId)s" + "You are no longer ignoring %(userId)s": "Už neignorujete %(userId)s", + "Add rooms to this community": "Přidat místnosti do této komunity", + "Unpin Message": "Odepnout zprávu", + "Ignored user": "Ignorovaný uživatel", + "Unignored user": "Odignorovaný uživatel", + "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!": "VAROVÁNÍ: OVĚŘENÍ KLÍČE SELHALO! Podepisovací klíč uživatele %(userId)s a zařízení %(deviceId)s je \"%(fprint)s\", což nesouhlasí s dodaným klíčem \"%(fingerprint)s\". Toto může znamenat, že vaše komunikace je odposlouchávána!", + "Reason": "Důvod", + "VoIP conference started.": "VoIP konference započata.", + "VoIP conference finished.": "VoIP konference ukončena.", + "%(targetName)s left the room.": "%(targetName)s opustil/a místnost." } From ef30ba889b617a22a46c2173d0d388bc480cd305 Mon Sep 17 00:00:00 2001 From: Stefan Parviainen Date: Mon, 23 Oct 2017 19:55:40 +0200 Subject: [PATCH 036/365] Make MemberEventListSummary more translatable Signed-off-by: Stefan Parviainen --- src/components/views/elements/MemberEventListSummary.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/MemberEventListSummary.js b/src/components/views/elements/MemberEventListSummary.js index 9b303b4bd9..6e75c32e0d 100644 --- a/src/components/views/elements/MemberEventListSummary.js +++ b/src/components/views/elements/MemberEventListSummary.js @@ -106,7 +106,7 @@ module.exports = React.createClass({ const desc = this._renderCommaSeparatedList(descs); - return nameList + " " + desc; + return _t('%(nameList)s %(transitionList)s', { nameList: nameList, transitionList: desc }); }); if (!summaries) { From 2e7df425b9cef7530c15ce0698ac7ee5ec292e97 Mon Sep 17 00:00:00 2001 From: Tirifto Date: Tue, 24 Oct 2017 07:58:57 +0000 Subject: [PATCH 037/365] Translated using Weblate (Esperanto) Currently translated at 4.5% (41 of 905 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/eo/ --- src/i18n/strings/eo.json | 44 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/eo.json b/src/i18n/strings/eo.json index 0967ef424b..363b61faad 100644 --- a/src/i18n/strings/eo.json +++ b/src/i18n/strings/eo.json @@ -1 +1,43 @@ -{} +{ + "This email address is already in use": "Tiu ĉi retpoŝtadreso jam estas uzata", + "This phone number is already in use": "Tiu ĉi telefona numero jam estas uzata", + "Failed to verify email address: make sure you clicked the link in the email": "Kontrolo de via retpoŝtadreso malsukcesis; certigu, ke vi alklakis la ligilon en la retletero", + "Call Timeout": "Voka Tempolimo", + "The remote side failed to pick up": "Kunvokonto malsukcesis respondi", + "Unable to capture screen": "Ekrano ne registreblas", + "You cannot place a call with yourself.": "Vi ne povas voki vin mem.", + "Warning!": "Averto!", + "Sign in with CAS": "Saluti per CAS", + "Sign in with": "Saluti per", + "Sign in": "Saluti", + "For security, this session has been signed out. Please sign in again.": "Pro sekurecaj kialoj, la seanco finiĝis. Bonvolu resaluti.", + "Upload Failed": "Alŝuto malsukcesis", + "Sun": "Dim", + "Mon": "Lun", + "Tue": "Mar", + "Wed": "Mer", + "Thu": "Ĵaŭ", + "Fri": "Ven", + "Sat": "Sab", + "Jan": "Jan", + "Feb": "Feb", + "Mar": "Mar", + "Apr": "Apr", + "May": "Maj", + "Jun": "Jun", + "Jul": "Jul", + "Aug": "Aŭg", + "Sep": "Sep", + "Oct": "Okt", + "Nov": "Nov", + "Dec": "Dec", + "PM": "ptm", + "AM": "atm", + "%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s", + "%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s", + "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(fullYear)s %(monthName)s %(day)s %(time)s", + "Who would you like to add to this community?": "Kiun vi volas aldoni al tiu ĉi komunumo?", + "Invite new community members": "Invitu novajn komunumanojn", + "Name or matrix ID": "Nomo aŭ Matrix-identigilo", + "Invite to Community": "Inviti al komunumo" +} From 825e7eb8ab007d6ebab0b95261d5b64b7955c9a3 Mon Sep 17 00:00:00 2001 From: Jan Kudrik Date: Tue, 24 Oct 2017 14:49:57 +0000 Subject: [PATCH 038/365] Translated using Weblate (Czech) Currently translated at 56.0% (507 of 905 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/cs/ --- src/i18n/strings/cs.json | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json index fc18928eda..72ac75aba1 100644 --- a/src/i18n/strings/cs.json +++ b/src/i18n/strings/cs.json @@ -487,5 +487,26 @@ "Reason": "Důvod", "VoIP conference started.": "VoIP konference započata.", "VoIP conference finished.": "VoIP konference ukončena.", - "%(targetName)s left the room.": "%(targetName)s opustil/a místnost." + "%(targetName)s left the room.": "%(targetName)s opustil/a místnost.", + "You are already in a call.": "Již máte probíhající hovor.", + "%(senderName)s requested a VoIP conference.": "%(senderName)s požádal/a o VoIP konferenci.", + "%(senderName)s removed their profile picture.": "%(senderName)s odstranil/a svůj profilový obrázek.", + "%(targetName)s rejected the invitation.": "%(targetName)s odmítl/a pozvání.", + "Communities": "Komunity", + "Message Pinning": "Připínání zpráv", + "Your browser does not support the required cryptography extensions": "Váš prohlížeč nepodporuje požadovaná kryptografická rozšíření", + "Do you want to set an email address?": "Chcete nastavit e-mailovou adresu?", + "New Password": "Nové heslo", + "Device Name": "Název zařízení", + "Unignore": "Odignorovat", + "Ignore": "Ignorovat", + "Admin Tools": "Nástroje pro správce", + "bold": "tučně", + "italic": "kurzíva", + "strike": "přeškrtnutí", + "underline": "podtržení", + "code": "kód", + "quote": "citace", + "bullet": "odrážka", + "numbullet": "číselný seznam" } From 2b0cb7e28e3798905815519cb4a28008b610de78 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 24 Oct 2017 15:26:16 +0000 Subject: [PATCH 039/365] Translated using Weblate (Russian) Currently translated at 100.0% (905 of 905 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index ccd304c235..1b372058d5 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -904,5 +904,7 @@ "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Создайте сообщество для объединения пользователей и комнат! Создайте собственную домашнюю страницу, чтобы выделить свое пространство во вселенной Matrix.", "Join an existing community": "Присоединиться к существующему сообществу", "To join an existing community you'll have to know its community identifier; this will look something like +example:matrix.org.": "Чтобы присоединиться к существующему сообществу, вам нужно знать его ID; это будет выглядеть примерно так+primer:matrix.org.", - "Something went wrong whilst creating your community": "При создании сообщества что-то пошло не так" + "Something went wrong whilst creating your community": "При создании сообщества что-то пошло не так", + "%(names)s and %(count)s others are typing|other": "%(names)s и %(count)s другие печатают", + "And %(count)s more...|other": "И %(count)s больше..." } From 5d53f30619282913e3ca568d3a6e6772f259405b Mon Sep 17 00:00:00 2001 From: Jan Kudrik Date: Tue, 24 Oct 2017 15:03:29 +0000 Subject: [PATCH 040/365] Translated using Weblate (Czech) Currently translated at 57.2% (518 of 905 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/cs/ --- src/i18n/strings/cs.json | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json index 72ac75aba1..7d87a250b7 100644 --- a/src/i18n/strings/cs.json +++ b/src/i18n/strings/cs.json @@ -493,7 +493,7 @@ "%(senderName)s removed their profile picture.": "%(senderName)s odstranil/a svůj profilový obrázek.", "%(targetName)s rejected the invitation.": "%(targetName)s odmítl/a pozvání.", "Communities": "Komunity", - "Message Pinning": "Připínání zpráv", + "Message Pinning": "Připíchnutí zprávy", "Your browser does not support the required cryptography extensions": "Váš prohlížeč nepodporuje požadovaná kryptografická rozšíření", "Do you want to set an email address?": "Chcete nastavit e-mailovou adresu?", "New Password": "Nové heslo", @@ -508,5 +508,16 @@ "code": "kód", "quote": "citace", "bullet": "odrážka", - "numbullet": "číselný seznam" + "numbullet": "číselný seznam", + "No pinned messages.": "Žádné připíchnuté zprávy.", + "Pinned Messages": "Připíchnuté zprávy", + "%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s odstranil/a svoje zobrazované jméno (%(oldDisplayName)s).", + "%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s odvolal/a pozvánku pro %(targetName)s.", + "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s učinil/a budoucí historii místnosti viditelnou všem členům, a to od chvíle jejich pozvání.", + "%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s učinil/a budoucí historii místnosti viditelnou všem členům, a to od chvíle jejich vstupu do místnosti.", + "%(senderName)s made future room history visible to all room members.": "%(senderName)s učinil/a budoucí historii místnosti viditelnou všem členům.", + "%(senderName)s made future room history visible to anyone.": "%(senderName)s učinil/a budoucí historii místnosti viditelnou komukoliv.", + "%(senderName)s changed the pinned messages for the room.": "%(senderName)s změnil/a připíchnuté zprávy této místnosti.", + "%(names)s and %(count)s others are typing|other": "%(names)s a %(count)s další píší", + "Authentication check failed: incorrect password?": "Kontrola ověření selhala: špatné heslo?" } From 6406fc3865ff3fe434b5e7c341bcf7a5792e63e0 Mon Sep 17 00:00:00 2001 From: Stefan Parviainen Date: Tue, 24 Oct 2017 18:32:50 +0200 Subject: [PATCH 041/365] Use plurals in WhoIsTyping --- src/WhoIsTyping.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/WhoIsTyping.js b/src/WhoIsTyping.js index 6bea2cbb92..0edad8d4a5 100644 --- a/src/WhoIsTyping.js +++ b/src/WhoIsTyping.js @@ -68,10 +68,8 @@ module.exports = { const names = whoIsTyping.map(function(m) { return m.name; }); - if (othersCount==1) { - return _t('%(names)s and one other are typing', {names: names.slice(0, limit - 1).join(', ')}); - } else if (othersCount>1) { - return _t('%(names)s and %(count)s others are typing', {names: names.slice(0, limit - 1).join(', '), count: othersCount}); + if (othersCount>=1) { + return _t('%(names)s and %(count)s others are typing', {names: names.slice(0, limit - 1).join(', '), count: othersCount}); } else { const lastPerson = names.pop(); return _t('%(names)s and %(lastPerson)s are typing', {names: names.join(', '), lastPerson: lastPerson}); From b5024cca750d546eb917ff48f41abb1e613d943a Mon Sep 17 00:00:00 2001 From: Stefan Parviainen Date: Tue, 24 Oct 2017 19:34:08 +0200 Subject: [PATCH 042/365] Further simplify MemberEventListSummary a bit Signed-off-by: Stefan Parviainen --- src/components/views/elements/MemberEventListSummary.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/views/elements/MemberEventListSummary.js b/src/components/views/elements/MemberEventListSummary.js index 6e75c32e0d..6a1566c961 100644 --- a/src/components/views/elements/MemberEventListSummary.js +++ b/src/components/views/elements/MemberEventListSummary.js @@ -302,11 +302,9 @@ module.exports = React.createClass({ return ""; } else if (items.length === 1) { return items[0]; - } else if (remaining) { + } else if (remaining >= 0) { items = items.slice(0, itemLimit); - return (remaining > 1) - ? _t("%(items)s and %(remaining)s others", { items: items.join(', '), remaining: remaining } ) - : _t("%(items)s and one other", { items: items.join(', ') }); + return _t("%(items)s and %(count)s others", { items: items.join(', '), count: remaining } ) } else { const lastItem = items.pop(); return _t("%(items)s and %(lastItem)s", { items: items.join(', '), lastItem: lastItem }); From bc034f3083796384f42999bc355534052f149d63 Mon Sep 17 00:00:00 2001 From: Stefan Parviainen Date: Tue, 24 Oct 2017 19:34:32 +0200 Subject: [PATCH 043/365] Update strings Signed-off-by: Stefan Parviainen --- src/i18n/strings/en_EN.json | 145 +++++++++++++++++++----------------- 1 file changed, 75 insertions(+), 70 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 492113989b..6955ed053a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -153,8 +153,8 @@ "Communities": "Communities", "Message Pinning": "Message Pinning", "%(displayName)s is typing": "%(displayName)s is typing", - "%(names)s and one other are typing": "%(names)s and one other are typing", "%(names)s and %(count)s others are typing|other": "%(names)s and %(count)s others are typing", + "%(names)s and %(count)s others are typing|one": "%(names)s and one other is typing", "%(names)s and %(lastPerson)s are typing": "%(names)s and %(lastPerson)s are typing", "Failure to create room": "Failure to create room", "Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.", @@ -210,9 +210,9 @@ " (unsupported)": " (unsupported)", "Join as voice or video.": "Join as voice or video.", "Ongoing conference call%(supportedText)s.": "Ongoing conference call%(supportedText)s.", - "sent an image": "sent an image", - "sent a video": "sent a video", - "uploaded a file": "uploaded a file", + "%(senderName)s sent an image": "%(senderName)s sent an image", + "%(senderName)s sent a video": "%(senderName)s sent a video", + "%(senderName)s uploaded a file": "%(senderName)s uploaded a file", "Options": "Options", "Undecryptable": "Undecryptable", "Encrypted by a verified device": "Encrypted by a verified device", @@ -225,9 +225,13 @@ "device id: ": "device id: ", "Disinvite": "Disinvite", "Kick": "Kick", + "Disinvite this user?": "Disinvite this user?", + "Kick this user?": "Kick this user?", "Failed to kick": "Failed to kick", "Unban": "Unban", "Ban": "Ban", + "Unban this user?": "Unban this user?", + "Ban this user?": "Ban this user?", "Failed to ban user": "Failed to ban user", "Failed to mute user": "Failed to mute user", "Failed to toggle moderator status": "Failed to toggle moderator status", @@ -312,12 +316,11 @@ "Forget room": "Forget room", "Search": "Search", "Show panel": "Show panel", - "to favourite": "to favourite", - "to tag direct chat": "to tag direct chat", - "to restore": "to restore", - "to demote": "to demote", + "Drop here to favourite": "Drop here to favourite", + "Drop here to tag direct chat": "Drop here to tag direct chat", + "Drop here to restore": "Drop here to restore", + "Drop here to demote": "Drop here to demote", "Drop here to tag %(section)s": "Drop here to tag %(section)s", - "Drop here %(toAction)s": "Drop here %(toAction)s", "Press to start a chat with someone": "Press to start a chat with someone", "You're not in any rooms yet! Press to make a room or to browse the directory": "You're not in any rooms yet! Press to make a room or to browse the directory", "Invites": "Invites", @@ -327,20 +330,22 @@ "Low priority": "Low priority", "Historical": "Historical", "Unnamed Room": "Unnamed Room", - "a room": "a room", "Unable to ascertain that the address this invite was sent to matches one associated with your account.": "Unable to ascertain that the address this invite was sent to matches one associated with your account.", "This invitation was sent to an email address which is not associated with this account:": "This invitation was sent to an email address which is not associated with this account:", "You may wish to login with a different account, or add this email to this account.": "You may wish to login with a different account, or add this email to this account.", "You have been invited to join this room by %(inviterName)s": "You have been invited to join this room by %(inviterName)s", "Would you like to accept or decline this invitation?": "Would you like to accept or decline this invitation?", - "This room": "This room", "Reason: %(reasonText)s": "Reason: %(reasonText)s", "Rejoin": "Rejoin", "You have been kicked from %(roomName)s by %(userName)s.": "You have been kicked from %(roomName)s by %(userName)s.", + "You have been kicked from this room by %(userName)s.": "You have been kicked from this room by %(userName)s.", "You have been banned from %(roomName)s by %(userName)s.": "You have been banned from %(roomName)s by %(userName)s.", + "You have been banned from this room by %(userName)s.": "You have been banned from this room by %(userName)s.", + "This room": "This room", "%(roomName)s does not exist.": "%(roomName)s does not exist.", "%(roomName)s is not accessible at this time.": "%(roomName)s is not accessible at this time.", "You are trying to access %(roomName)s.": "You are trying to access %(roomName)s.", + "You are trying to access a room.": "You are trying to access a room.", "Click here to join the discussion!": "Click here to join the discussion!", "This is a preview of this room. Room interactions have been disabled": "This is a preview of this room. Room interactions have been disabled", "To change the room's avatar, you must be a": "To change the room's avatar, you must be a", @@ -385,10 +390,9 @@ "Publish this room to the public in %(domain)s's room directory?": "Publish this room to the public in %(domain)s's room directory?", "Who can read history?": "Who can read history?", "Anyone": "Anyone", - "Members only": "Members only", - "since the point in time of selecting this option": "since the point in time of selecting this option", - "since they were invited": "since they were invited", - "since they joined": "since they joined", + "Members only (since the point in time of selecting this option)": "Members only (since the point in time of selecting this option)", + "Members only (since they were invited)": "Members only (since they were invited)", + "Members only (since they joined)": "Members only (since they joined)", "Room Colour": "Room Colour", "Permissions": "Permissions", "The default role for new room members is": "The default role for new room members is", @@ -460,10 +464,10 @@ "Dismiss": "Dismiss", "To continue, please enter your password.": "To continue, please enter your password.", "Password:": "Password:", - "An email has been sent to": "An email has been sent to", + "An email has been sent to %(emailAddress)s": "An email has been sent to %(emailAddress)s", "Please check your email to continue registration.": "Please check your email to continue registration.", "Token incorrect": "Token incorrect", - "A text message has been sent to": "A text message has been sent to", + "A text message has been sent to %(msisdn)s": "A text message has been sent to %(msisdn)s", "Please enter the code it contains:": "Please enter the code it contains:", "Start authentication": "Start authentication", "powered by Matrix": "powered by Matrix", @@ -485,6 +489,7 @@ "Identity server URL": "Identity server URL", "What does this mean?": "What does this mean?", "Remove from community": "Remove from community", + "Remove this user from community?": "Remove this user from community?", "Failed to remove user from community": "Failed to remove user from community", "Filter community members": "Filter community members", "Filter community rooms": "Filter community rooms", @@ -510,56 +515,57 @@ "Integrations Error": "Integrations Error", "Could not connect to the integration server": "Could not connect to the integration server", "Manage Integrations": "Manage Integrations", - "%(severalUsers)sjoined %(repeats)s times": "%(severalUsers)sjoined %(repeats)s times", - "%(oneUser)sjoined %(repeats)s times": "%(oneUser)sjoined %(repeats)s times", - "%(severalUsers)sjoined": "%(severalUsers)sjoined", - "%(oneUser)sjoined": "%(oneUser)sjoined", - "%(severalUsers)sleft %(repeats)s times": "%(severalUsers)sleft %(repeats)s times", - "%(oneUser)sleft %(repeats)s times": "%(oneUser)sleft %(repeats)s times", - "%(severalUsers)sleft": "%(severalUsers)sleft", - "%(oneUser)sleft": "%(oneUser)sleft", - "%(severalUsers)sjoined and left %(repeats)s times": "%(severalUsers)sjoined and left %(repeats)s times", - "%(oneUser)sjoined and left %(repeats)s times": "%(oneUser)sjoined and left %(repeats)s times", - "%(severalUsers)sjoined and left": "%(severalUsers)sjoined and left", - "%(oneUser)sjoined and left": "%(oneUser)sjoined and left", - "%(severalUsers)sleft and rejoined %(repeats)s times": "%(severalUsers)sleft and rejoined %(repeats)s times", - "%(oneUser)sleft and rejoined %(repeats)s times": "%(oneUser)sleft and rejoined %(repeats)s times", - "%(severalUsers)sleft and rejoined": "%(severalUsers)sleft and rejoined", - "%(oneUser)sleft and rejoined": "%(oneUser)sleft and rejoined", - "%(severalUsers)srejected their invitations %(repeats)s times": "%(severalUsers)srejected their invitations %(repeats)s times", - "%(oneUser)srejected their invitation %(repeats)s times": "%(oneUser)srejected their invitation %(repeats)s times", - "%(severalUsers)srejected their invitations": "%(severalUsers)srejected their invitations", - "%(oneUser)srejected their invitation": "%(oneUser)srejected their invitation", - "%(severalUsers)shad their invitations withdrawn %(repeats)s times": "%(severalUsers)shad their invitations withdrawn %(repeats)s times", - "%(oneUser)shad their invitation withdrawn %(repeats)s times": "%(oneUser)shad their invitation withdrawn %(repeats)s times", - "%(severalUsers)shad their invitations withdrawn": "%(severalUsers)shad their invitations withdrawn", - "%(oneUser)shad their invitation withdrawn": "%(oneUser)shad their invitation withdrawn", - "were invited %(repeats)s times": "were invited %(repeats)s times", - "was invited %(repeats)s times": "was invited %(repeats)s times", - "were invited": "were invited", - "was invited": "was invited", - "were banned %(repeats)s times": "were banned %(repeats)s times", - "was banned %(repeats)s times": "was banned %(repeats)s times", - "were banned": "were banned", - "was banned": "was banned", - "were unbanned %(repeats)s times": "were unbanned %(repeats)s times", - "was unbanned %(repeats)s times": "was unbanned %(repeats)s times", - "were unbanned": "were unbanned", - "was unbanned": "was unbanned", - "were kicked %(repeats)s times": "were kicked %(repeats)s times", - "was kicked %(repeats)s times": "was kicked %(repeats)s times", - "were kicked": "were kicked", - "was kicked": "was kicked", - "%(severalUsers)schanged their name %(repeats)s times": "%(severalUsers)schanged their name %(repeats)s times", - "%(oneUser)schanged their name %(repeats)s times": "%(oneUser)schanged their name %(repeats)s times", - "%(severalUsers)schanged their name": "%(severalUsers)schanged their name", - "%(oneUser)schanged their name": "%(oneUser)schanged their name", - "%(severalUsers)schanged their avatar %(repeats)s times": "%(severalUsers)schanged their avatar %(repeats)s times", - "%(oneUser)schanged their avatar %(repeats)s times": "%(oneUser)schanged their avatar %(repeats)s times", - "%(severalUsers)schanged their avatar": "%(severalUsers)schanged their avatar", - "%(oneUser)schanged their avatar": "%(oneUser)schanged their avatar", - "%(items)s and %(remaining)s others": "%(items)s and %(remaining)s others", - "%(items)s and one other": "%(items)s and one other", + "%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s", + "%(severalUsers)sjoined %(count)s times|other": "%(severalUsers)sjoined %(count)s times", + "%(severalUsers)sjoined %(count)s times|one": "%(severalUsers)sjoined", + "%(oneUser)sjoined %(count)s times|other": "%(oneUser)sjoined %(count)s times", + "%(oneUser)sjoined %(count)s times|one": "%(oneUser)sjoined", + "%(severalUsers)sleft %(count)s times|other": "%(severalUsers)sleft %(count)s times", + "%(severalUsers)sleft %(count)s times|one": "%(severalUsers)sleft", + "%(oneUser)sleft %(count)s times|other": "%(oneUser)sleft %(count)s times", + "%(oneUser)sleft %(count)s times|one": "%(oneUser)sleft", + "%(severalUsers)sjoined and left %(count)s times|other": "%(severalUsers)sjoined and left %(count)s times", + "%(severalUsers)sjoined and left %(count)s times|one": "%(severalUsers)sjoined and left", + "%(oneUser)sjoined and left %(count)s times|other": "%(oneUser)sjoined and left %(count)s times", + "%(oneUser)sjoined and left %(count)s times|one": "%(oneUser)sjoined and left", + "%(severalUsers)sleft and rejoined %(count)s times|other": "%(severalUsers)sleft and rejoined %(count)s times", + "%(severalUsers)sleft and rejoined %(count)s times|one": "%(severalUsers)sleft and rejoined", + "%(oneUser)sleft and rejoined %(count)s times|other": "%(oneUser)sleft and rejoined %(count)s times", + "%(oneUser)sleft and rejoined %(count)s times|one": "%(oneUser)sleft and rejoined", + "%(severalUsers)srejected their invitations %(count)s times|other": "%(severalUsers)srejected their invitations %(count)s times", + "%(severalUsers)srejected their invitations %(count)s times|one": "%(severalUsers)srejected their invitations", + "%(oneUser)srejected their invitation %(count)s times|other": "%(oneUser)srejected their invitation %(count)s times", + "%(oneUser)srejected their invitation %(count)s times|one": "%(oneUser)srejected their invitation", + "%(severalUsers)shad their invitations withdrawn %(count)s times|other": "%(severalUsers)shad their invitations withdrawn %(count)s times", + "%(severalUsers)shad their invitations withdrawn %(count)s times|one": "%(severalUsers)shad their invitations withdrawn", + "%(oneUser)shad their invitation withdrawn %(count)s times|other": "%(oneUser)shad their invitation withdrawn %(count)s times", + "%(oneUser)shad their invitation withdrawn %(count)s times|one": "%(oneUser)shad their invitation withdrawn", + "were invited %(count)s times|other": "were invited %(count)s times", + "were invited %(count)s times|one": "were invited", + "was invited %(count)s times|other": "was invited %(count)s times", + "was invited %(count)s times|one": "was invited", + "were banned %(count)s times|other": "were banned %(count)s times", + "were banned %(count)s times|one": "were banned", + "was banned %(count)s times|other": "was banned %(count)s times", + "was banned %(count)s times|one": "was banned", + "were unbanned %(count)s times|other": "were unbanned %(count)s times", + "were unbanned %(count)s times|one": "were unbanned", + "was unbanned %(count)s times|other": "was unbanned %(count)s times", + "was unbanned %(count)s times|one": "was unbanned", + "were kicked %(count)s times|other": "were kicked %(count)s times", + "were kicked %(count)s times|one": "were kicked", + "was kicked %(count)s times|other": "was kicked %(count)s times", + "was kicked %(count)s times|one": "was kicked", + "%(severalUsers)schanged their name %(count)s times|other": "%(severalUsers)schanged their name %(count)s times", + "%(severalUsers)schanged their name %(count)s times|one": "%(severalUsers)schanged their name", + "%(oneUser)schanged their name %(count)s times|other": "%(oneUser)schanged their name %(count)s times", + "%(oneUser)schanged their name %(count)s times|one": "%(oneUser)schanged their name", + "%(severalUsers)schanged their avatar %(count)s times|other": "%(severalUsers)schanged their avatar %(count)s times", + "%(severalUsers)schanged their avatar %(count)s times|one": "%(severalUsers)schanged their avatar", + "%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)schanged their avatar %(count)s times", + "%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)schanged their avatar", + "%(items)s and %(count)s others|other": "%(items)s and %(count)s others", + "%(items)s and %(count)s others|one": "%(items)s and one other", "%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s", "Custom level": "Custom level", "Room directory": "Room directory", @@ -581,7 +587,6 @@ "Start Chatting": "Start Chatting", "Confirm Removal": "Confirm Removal", "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.", - "%(actionVerb)s this person?": "%(actionVerb)s this person?", "Community IDs may only contain alphanumeric characters": "Community IDs may only contain alphanumeric characters", "Something went wrong whilst creating your community": "Something went wrong whilst creating your community", "Create Community": "Create Community", @@ -676,6 +681,7 @@ "Leave %(groupName)s?": "Leave %(groupName)s?", "Leave": "Leave", "Unable to leave room": "Unable to leave room", + "Community Settings": "Community Settings", "Add rooms to this community": "Add rooms to this community", "Featured Rooms:": "Featured Rooms:", "Featured Users:": "Featured Users:", @@ -686,7 +692,6 @@ "Publish this community on your profile": "Publish this community on your profile", "Long Description (HTML)": "Long Description (HTML)", "Description": "Description", - "Community Settings": "Community Settings", "Community %(groupId)s not found": "Community %(groupId)s not found", "This Home server does not support communities": "This Home server does not support communities", "Failed to load %(groupId)s": "Failed to load %(groupId)s", @@ -824,7 +829,7 @@ "A new password must be entered.": "A new password must be entered.", "New passwords must match each other.": "New passwords must match each other.", "Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.", - "Once you've followed the link it contains, click below": "Once you've followed the link it contains, click below", + "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.", "I have verified my email address": "I have verified my email address", "Your password has been reset": "Your password has been reset", "You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device": "You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device", From b0d4fb09c64612ef63e029441eeab9003e138e09 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Tue, 24 Oct 2017 17:48:13 +0000 Subject: [PATCH 044/365] Translated using Weblate (Hungarian) Currently translated at 100.0% (905 of 905 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 8646d448b5..5e8b44d2a5 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -904,5 +904,8 @@ "Failed to load %(groupId)s": "Nem sikerült betölteni: %(groupId)s", "Your Communities": "Közösségeid", "You're not currently a member of any communities.": "Nem vagy tagja egyetlen közösségnek sem.", - "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Készíts közösséget hogy egybegyűjtsd a felhasználókat és szobákat! Készíts egy saját kezdőlapot amivel meghatározhatod magad a Matrix univerzumában." + "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Készíts közösséget hogy egybegyűjtsd a felhasználókat és szobákat! Készíts egy saját kezdőlapot amivel meghatározhatod magad a Matrix univerzumában.", + "%(names)s and %(count)s others are typing|other": "%(names)s és még %(count)s felhasználó gépel", + "And %(count)s more...|other": "És még %(count)s...", + "Something went wrong whilst creating your community": "Valami nem sikerült a közösség létrehozásánál" } From 88fd60066fd011d8cb475961e9ae398413974820 Mon Sep 17 00:00:00 2001 From: Stefan Parviainen Date: Tue, 24 Oct 2017 20:07:57 +0200 Subject: [PATCH 045/365] Fix typo Signed-off-by: Stefan Parviainen --- src/components/views/elements/MemberEventListSummary.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/MemberEventListSummary.js b/src/components/views/elements/MemberEventListSummary.js index 6a1566c961..de6f801a21 100644 --- a/src/components/views/elements/MemberEventListSummary.js +++ b/src/components/views/elements/MemberEventListSummary.js @@ -302,7 +302,7 @@ module.exports = React.createClass({ return ""; } else if (items.length === 1) { return items[0]; - } else if (remaining >= 0) { + } else if (remaining > 0) { items = items.slice(0, itemLimit); return _t("%(items)s and %(count)s others", { items: items.join(', '), count: remaining } ) } else { From afdc6b523ec528634c8e82854cd160b314bd3ff8 Mon Sep 17 00:00:00 2001 From: Ruben Barkow Date: Tue, 24 Oct 2017 22:08:28 +0000 Subject: [PATCH 046/365] Translated using Weblate (German) Currently translated at 99.7% (903 of 905 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 18fa49b0a1..792078604a 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -851,9 +851,9 @@ "Jump to message": "Zur Nachricht springen", "No pinned messages.": "Keine angehefteten Nachrichten.", "Loading...": "Lade...", - "Unpin Message": "Nachricht losheften", + "Unpin Message": "Nachricht lösen", "Unnamed room": "Unbenannter Raum", - "World readable": "Lesbar für die Welt", + "World readable": "Lesbar für alle", "Guests can join": "Gäste können beitreten", "No rooms to show": "Keine anzeigbaren Räume vorhanden", "Community Settings": "Community-Einstellungen", From f64a5a3abd35b9a45ed77fb74a14efa18a3f2398 Mon Sep 17 00:00:00 2001 From: Bamstam Date: Tue, 24 Oct 2017 22:12:19 +0000 Subject: [PATCH 047/365] Translated using Weblate (German) Currently translated at 100.0% (905 of 905 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 792078604a..ffeabc323e 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -905,5 +905,7 @@ "Your Communities": "Deine Communities", "You're not currently a member of any communities.": "Du bist aktuell kein Mitglied einer Community.", "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Erzeuge eine Community um Nutzer und Räume zu gruppieren! Erzeuge eine angepasste Homepage um dein Revier im Matrix-Universum zu markieren.", - "Something went wrong whilst creating your community": "Beim Erstellen deiner Community ist ein Fehler aufgetreten" + "Something went wrong whilst creating your community": "Beim Erstellen deiner Community ist ein Fehler aufgetreten", + "%(names)s and %(count)s others are typing|other": "%(names)s und %(count)s weitere schreiben", + "And %(count)s more...|other": "Und %(count)s weitere..." } From 26e8b2c1b3af8d6a2b8559ec3b4644d18746b7c2 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 24 Oct 2017 23:37:26 +0100 Subject: [PATCH 048/365] switch to a LoginPage wrapper component as it's much nicer in the CSS to wrap the LoginBox as needed rather than have separate header & footer divs floating above and below it which need to be correctly vertically centered --- .../structures/login/ForgotPassword.js | 5 +- src/components/structures/login/Login.js | 9 +-- .../structures/login/PostRegistration.js | 5 +- .../structures/login/Registration.js | 5 +- src/components/views/login/LoginPage.js | 58 +++++++++++++++++++ src/components/views/login/LoginPageFooter.js | 30 ---------- src/components/views/login/LoginPageHeader.js | 47 --------------- 7 files changed, 70 insertions(+), 89 deletions(-) create mode 100644 src/components/views/login/LoginPage.js delete mode 100644 src/components/views/login/LoginPageFooter.js delete mode 100644 src/components/views/login/LoginPageHeader.js diff --git a/src/components/structures/login/ForgotPassword.js b/src/components/structures/login/ForgotPassword.js index 3e76291d20..f14d64528f 100644 --- a/src/components/structures/login/ForgotPassword.js +++ b/src/components/structures/login/ForgotPassword.js @@ -154,6 +154,7 @@ module.exports = React.createClass({ }, render: function() { + const LoginPage = sdk.getComponent("login.LoginPage"); const LoginHeader = sdk.getComponent("login.LoginHeader"); const LoginFooter = sdk.getComponent("login.LoginFooter"); const ServerConfig = sdk.getComponent("login.ServerConfig"); @@ -233,12 +234,12 @@ module.exports = React.createClass({ return ( -
+
{ resetPasswordJsx }
-
+ ); }, }); diff --git a/src/components/structures/login/Login.js b/src/components/structures/login/Login.js index eb131989a8..f5f4f8cd69 100644 --- a/src/components/structures/login/Login.js +++ b/src/components/structures/login/Login.js @@ -329,8 +329,7 @@ module.exports = React.createClass({ render: function() { const Loader = sdk.getComponent("elements.Spinner"); - const LoginPageHeader = sdk.getComponent("login.LoginPageHeader"); - const LoginPageFooter = sdk.getComponent("login.LoginPageFooter"); + const LoginPage = sdk.getComponent("login.LoginPage"); const LoginHeader = sdk.getComponent("login.LoginHeader"); const LoginFooter = sdk.getComponent("login.LoginFooter"); const ServerConfig = sdk.getComponent("login.ServerConfig"); @@ -367,8 +366,7 @@ module.exports = React.createClass({ } return ( -
- +
@@ -389,8 +387,7 @@ module.exports = React.createClass({
- -
+ ); }, }); diff --git a/src/components/structures/login/PostRegistration.js b/src/components/structures/login/PostRegistration.js index 194995150c..184356e852 100644 --- a/src/components/structures/login/PostRegistration.js +++ b/src/components/structures/login/PostRegistration.js @@ -59,9 +59,10 @@ module.exports = React.createClass({ render: function() { const ChangeDisplayName = sdk.getComponent('settings.ChangeDisplayName'); const ChangeAvatar = sdk.getComponent('settings.ChangeAvatar'); + const LoginPage = sdk.getComponent('login.LoginPage'); const LoginHeader = sdk.getComponent('login.LoginHeader'); return ( -
+
@@ -74,7 +75,7 @@ module.exports = React.createClass({ { this.state.errorString }
-
+ ); }, }); diff --git a/src/components/structures/login/Registration.js b/src/components/structures/login/Registration.js index db488ea237..8151c1e65c 100644 --- a/src/components/structures/login/Registration.js +++ b/src/components/structures/login/Registration.js @@ -322,6 +322,7 @@ module.exports = React.createClass({ render: function() { const LoginHeader = sdk.getComponent('login.LoginHeader'); const LoginFooter = sdk.getComponent('login.LoginFooter'); + const LoginPage = sdk.getComponent('login.LoginPage'); const InteractiveAuth = sdk.getComponent('structures.InteractiveAuth'); const Spinner = sdk.getComponent("elements.Spinner"); const ServerConfig = sdk.getComponent('views.login.ServerConfig'); @@ -385,7 +386,7 @@ module.exports = React.createClass({ ); } return ( -
+
-
+ ); }, }); diff --git a/src/components/views/login/LoginPage.js b/src/components/views/login/LoginPage.js new file mode 100644 index 0000000000..a07997727b --- /dev/null +++ b/src/components/views/login/LoginPage.js @@ -0,0 +1,58 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +'use strict'; + +import UserSettingsStore from '../../../UserSettingsStore'; + +const React = require('react'); + +module.exports = React.createClass({ + displayName: 'LoginPage', + + render: function() { + if (UserSettingsStore.getTheme() === 'status') { + return ( +
+
+ Status +
+
+
+

Status Community Chat

+
+ A safer, decentralised communication platform powered by Riot +
+
+ { this.props.children } +
+

This channel is for our development community.

+

Interested in SNT and discussions on the cryptocurrency market?

+

Join Telegram Chat

+
+
+
+ ); + } + else { + return ( +
+ { this.props.children } +
+ ); + } + }, +}); diff --git a/src/components/views/login/LoginPageFooter.js b/src/components/views/login/LoginPageFooter.js deleted file mode 100644 index e4eca9b8b3..0000000000 --- a/src/components/views/login/LoginPageFooter.js +++ /dev/null @@ -1,30 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -'use strict'; - -import { _t } from '../../../languageHandler'; -import React from 'react'; - -module.exports = React.createClass({ - displayName: 'LoginPageFooter', - - render: function() { - return ( -
- ); - }, -}); diff --git a/src/components/views/login/LoginPageHeader.js b/src/components/views/login/LoginPageHeader.js deleted file mode 100644 index bef7fa5e0b..0000000000 --- a/src/components/views/login/LoginPageHeader.js +++ /dev/null @@ -1,47 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -'use strict'; - -import UserSettingsStore from '../../../UserSettingsStore'; - -const React = require('react'); - -module.exports = React.createClass({ - displayName: 'LoginPageHeader', - - render: function() { - let themeBranding; - if (UserSettingsStore.getTheme() === 'status') { - themeBranding =
-
- Status -
-
-

Status Community Chat

-
- A safer, decentralised communication platform powered by Riot -
-
-
; - } - else { - themeBranding =
; - } - - return themeBranding; - }, -}); From 59b2189909ba9ff074c613987e18b5ca01d3c591 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 24 Oct 2017 23:58:54 +0100 Subject: [PATCH 049/365] unbreak tests --- src/UserSettingsStore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UserSettingsStore.js b/src/UserSettingsStore.js index ede5a157a8..eca6d916a9 100644 --- a/src/UserSettingsStore.js +++ b/src/UserSettingsStore.js @@ -187,7 +187,7 @@ export default { syncedSettings = this.getSyncedSettings(); } if (!syncedSettings || !syncedSettings.theme) { - theme = SdkConfig.get().default_theme || 'light'; + theme = (SdkConfig.get() ? SdkConfig.get().default_theme : undefined) || 'light'; } else { theme = syncedSettings.theme; From f032284eff219c279939740d65216cb49e50dfa8 Mon Sep 17 00:00:00 2001 From: "Andrew (anoa)" Date: Tue, 24 Oct 2017 16:21:46 -0700 Subject: [PATCH 050/365] Remember whether widget drawer was hidden per-room Fixes #4850 Signed-off-by: Andrew (anoa) --- src/components/structures/RoomView.js | 16 +++++++++++++++- src/components/views/rooms/AppsDrawer.js | 15 +++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 83ca987276..583ce78785 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -281,7 +281,7 @@ module.exports = React.createClass({ this.setState({ isPeeking: false, }); - + // This won't necessarily be a MatrixError, but we duck-type // here and say if it's got an 'errcode' key with the right value, // it means we can't peek. @@ -305,6 +305,20 @@ module.exports = React.createClass({ _shouldShowApps: function(room) { if (!BROWSER_SUPPORTS_SANDBOX) return false; + // Check if user has prompted to close this app before + // If so, do not show apps + let showWidget = localStorage.getItem( + room.roomId + "_show_widget_drawer"); + + console.warn(room); + console.warn("Key is: " + room.roomId + "_show_widget_drawer"); + console.warn("showWidget is: " + showWidget); + + if (showWidget == "false") { + console.warn("We're blocking the widget from loading."); + return false; + } + const appsStateEvents = room.currentState.getStateEvents('im.vector.modular.widgets'); // any valid widget = show apps for (let i = 0; i < appsStateEvents.length; i++) { diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 1c9296228d..9bc946bc4b 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -83,14 +83,25 @@ module.exports = React.createClass({ onAction: function(action) { switch (action.action) { case 'appsDrawer': - // When opening the app draw when there aren't any apps, auto-launch the - // integrations manager to skip the awkward click on "Add widget" + // When opening the app drawer when there aren't any apps, + // auto-launch the integrations manager to skip the awkward + // click on "Add widget" + let widgetStateKey = this.props.room.roomId + "_show_widget_drawer"; if (action.show) { const apps = this._getApps(); if (apps.length === 0) { this._launchManageIntegrations(); } + + localStorage.removeItem(widgetStateKey); + } else { + // Store hidden state of widget + // Don't show if previously hidden + console.warn("Storing hidden widget state for room - ", + this.props.room.roomId); + localStorage.setItem(widgetStateKey, false); } + break; } }, From 9821f0d459766dc2e1a3436c0978371981b3e42b Mon Sep 17 00:00:00 2001 From: "Andrew (anoa)" Date: Tue, 24 Oct 2017 16:37:23 -0700 Subject: [PATCH 051/365] Fix linting Signed-off-by: Andrew (anoa) --- src/components/views/rooms/AppsDrawer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 9bc946bc4b..09bf4e616b 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -81,12 +81,12 @@ module.exports = React.createClass({ }, onAction: function(action) { + const widgetStateKey = this.props.room.roomId + "_show_widget_drawer"; switch (action.action) { case 'appsDrawer': // When opening the app drawer when there aren't any apps, // auto-launch the integrations manager to skip the awkward // click on "Add widget" - let widgetStateKey = this.props.room.roomId + "_show_widget_drawer"; if (action.show) { const apps = this._getApps(); if (apps.length === 0) { From adbea44d70826dad20ffbcdf50098ff807248173 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Wed, 25 Oct 2017 01:30:09 +0100 Subject: [PATCH 052/365] hide login options for status --- src/components/views/login/PasswordLogin.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/components/views/login/PasswordLogin.js b/src/components/views/login/PasswordLogin.js index 484ee01f4e..eb89bc00f1 100644 --- a/src/components/views/login/PasswordLogin.js +++ b/src/components/views/login/PasswordLogin.js @@ -20,7 +20,7 @@ import classNames from 'classnames'; import sdk from '../../../index'; import { _t } from '../../../languageHandler'; import {field_input_incorrect} from '../../../UiEffects'; - +import UserSettingsStore from '../../../UserSettingsStore'; /** * A pure UI component which displays a username/password form. @@ -210,9 +210,10 @@ class PasswordLogin extends React.Component { const loginField = this.renderLoginField(this.state.loginType, matrixIdText === ''); - return ( -
- + const theme = UserSettingsStore.getTheme(); + let loginType; + if (theme !== 'status') { + loginType = (
{ _t('Phone') }
+ ); + } + + return ( +
+ + { loginType } { loginField } {this._passwordField = e;}} type="password" name="password" From 67cc02df3b3a938972fcf33969fa9bdb00b8e841 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Wed, 25 Oct 2017 01:37:06 +0100 Subject: [PATCH 053/365] hide header when error is up --- src/components/structures/login/Login.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/components/structures/login/Login.js b/src/components/structures/login/Login.js index f5f4f8cd69..c31bdb1cca 100644 --- a/src/components/structures/login/Login.js +++ b/src/components/structures/login/Login.js @@ -354,6 +354,7 @@ module.exports = React.createClass({ } let serverConfig; + let header; if (theme !== 'status') { serverConfig = ; + + header =

{ _t('Sign in') }

; + } + else { + if (!this.state.errorText) { + header =

{ _t('Sign in to get started') }

; + } } return ( @@ -370,11 +378,9 @@ module.exports = React.createClass({
-

{ theme !== 'status' ? _t('Sign in') : _t('Sign in to get started') } - { loader } -

+ { header }
- { this.state.errorText } + { this.state.errorText }
{ this.componentForStep(this.state.currentFlow) } { serverConfig } From eb4b7c78a048b5ad2494e48ec6ab34c4d170bf52 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Wed, 25 Oct 2017 02:04:02 +0100 Subject: [PATCH 054/365] skin register screen --- .../structures/login/Registration.js | 46 +++++++++++------ .../views/login/RegistrationForm.js | 50 +++++++++++-------- 2 files changed, 59 insertions(+), 37 deletions(-) diff --git a/src/components/structures/login/Registration.js b/src/components/structures/login/Registration.js index 8151c1e65c..15e6b536c4 100644 --- a/src/components/structures/login/Registration.js +++ b/src/components/structures/login/Registration.js @@ -26,6 +26,7 @@ import MatrixClientPeg from '../../../MatrixClientPeg'; import RegistrationForm from '../../views/login/RegistrationForm'; import RtsClient from '../../../RtsClient'; import { _t } from '../../../languageHandler'; +import UserSettingsStore from '../../../UserSettingsStore'; const MIN_PASSWORD_LENGTH = 6; @@ -327,6 +328,8 @@ module.exports = React.createClass({ const Spinner = sdk.getComponent("elements.Spinner"); const ServerConfig = sdk.getComponent('views.login.ServerConfig'); + const theme = UserSettingsStore.getTheme(); + let registerBody; if (this.state.doingUIAuth) { registerBody = ( @@ -345,9 +348,19 @@ module.exports = React.createClass({ } else if (this.state.busy || this.state.teamServerBusy) { registerBody = ; } else { - let errorSection; - if (this.state.errorText) { - errorSection =
{ this.state.errorText }
; + let serverConfigSection; + if (theme !== 'status') { + serverConfigSection = ( + + ); } registerBody = (
@@ -363,16 +376,7 @@ module.exports = React.createClass({ onRegisterClick={this.onFormSubmit} onTeamSelected={this.onTeamSelected} /> - { errorSection } - + { serverConfigSection }
); } @@ -385,6 +389,17 @@ module.exports = React.createClass({ ); } + + let header; + let errorText; + if (theme === 'status' && this.state.errorText) { + header =
{ this.state.errorText }
; + } + else { + header =

{ _t('Create an account') }

; + errorText =
{ this.state.errorText }
; + } + return (
@@ -394,11 +409,12 @@ module.exports = React.createClass({ this.state.teamSelected.domain + "/icon.png" : null} /> -

{ _t('Create an account') }

+ { header } { registerBody } - { _t('I already have an account') } + { theme === 'status' ? _t('Sign in') : _t('I already have an account') } + { errorText } { returnToAppJsx }
diff --git a/src/components/views/login/RegistrationForm.js b/src/components/views/login/RegistrationForm.js index 9c7c75b125..7574b68418 100644 --- a/src/components/views/login/RegistrationForm.js +++ b/src/components/views/login/RegistrationForm.js @@ -22,6 +22,7 @@ import Email from '../../../email'; import { looksValid as phoneNumberLooksValid } from '../../../phonenumber'; import Modal from '../../../Modal'; import { _t } from '../../../languageHandler'; +import UserSettingsStore from '../../../UserSettingsStore'; const FIELD_EMAIL = 'field_email'; const FIELD_PHONE_COUNTRY = 'field_phone_country'; @@ -305,29 +306,34 @@ module.exports = React.createClass({ } } + const theme = UserSettingsStore.getTheme(); + const CountryDropdown = sdk.getComponent('views.login.CountryDropdown'); - const phoneSection = ( -
- - -
- ); + let phoneSection; + if (theme !== "status") { + phoneSection = ( +
+ + +
+ ); + } const registerButton = ( From ae40ef44606a34e362229a2b1746cd71456ee548 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Wed, 25 Oct 2017 02:18:14 +0100 Subject: [PATCH 055/365] fix error layouts --- src/components/structures/login/Login.js | 13 ++++++++++--- src/components/structures/login/Registration.js | 4 +++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/components/structures/login/Login.js b/src/components/structures/login/Login.js index c31bdb1cca..44363d83ad 100644 --- a/src/components/structures/login/Login.js +++ b/src/components/structures/login/Login.js @@ -373,15 +373,22 @@ module.exports = React.createClass({ } } + let errorTextSection; + if (this.state.errorText) { + errorTextSection = ( +
+ { this.state.errorText } +
+ ); + } + return (
{ header } -
- { this.state.errorText } -
+ { errorTextSection } { this.componentForStep(this.state.currentFlow) } { serverConfig } diff --git a/src/components/structures/login/Registration.js b/src/components/structures/login/Registration.js index 15e6b536c4..4e95b62be3 100644 --- a/src/components/structures/login/Registration.js +++ b/src/components/structures/login/Registration.js @@ -397,7 +397,9 @@ module.exports = React.createClass({ } else { header =

{ _t('Create an account') }

; - errorText =
{ this.state.errorText }
; + if (this.state.errorText) { + errorText =
{ this.state.errorText }
; + } } return ( From 6beb604cd0dd14172ab7655c4027597cb84d305f Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Wed, 25 Oct 2017 02:31:30 +0100 Subject: [PATCH 056/365] fascist linting >:( --- src/UserSettingsStore.js | 3 ++- src/components/views/login/LoginPage.js | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/UserSettingsStore.js b/src/UserSettingsStore.js index eca6d916a9..ec6184f61a 100644 --- a/src/UserSettingsStore.js +++ b/src/UserSettingsStore.js @@ -189,7 +189,8 @@ export default { if (!syncedSettings || !syncedSettings.theme) { theme = (SdkConfig.get() ? SdkConfig.get().default_theme : undefined) || 'light'; } - else { + else + { theme = syncedSettings.theme; } return theme; diff --git a/src/components/views/login/LoginPage.js b/src/components/views/login/LoginPage.js index a07997727b..fa51426059 100644 --- a/src/components/views/login/LoginPage.js +++ b/src/components/views/login/LoginPage.js @@ -28,13 +28,14 @@ module.exports = React.createClass({ return (
- Status + Status
{ this.props.children } @@ -47,7 +48,8 @@ module.exports = React.createClass({
); } - else { + else + { return (
{ this.props.children } From 1476af9c8f1c73a7cea55ed13fbf4cdc54d882ed Mon Sep 17 00:00:00 2001 From: Bamstam Date: Tue, 24 Oct 2017 22:18:58 +0000 Subject: [PATCH 057/365] Translated using Weblate (German) Currently translated at 100.0% (905 of 905 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index ffeabc323e..3755d30bb0 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -851,7 +851,7 @@ "Jump to message": "Zur Nachricht springen", "No pinned messages.": "Keine angehefteten Nachrichten.", "Loading...": "Lade...", - "Unpin Message": "Nachricht lösen", + "Unpin Message": "Nachricht nicht mehr anheften", "Unnamed room": "Unbenannter Raum", "World readable": "Lesbar für alle", "Guests can join": "Gäste können beitreten", From 45632b419176446e79cb02770386854168415d0a Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 25 Oct 2017 15:48:10 +0000 Subject: [PATCH 058/365] Translated using Weblate (Russian) Currently translated at 100.0% (907 of 907 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 1b372058d5..efc3bf3067 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -906,5 +906,7 @@ "To join an existing community you'll have to know its community identifier; this will look something like +example:matrix.org.": "Чтобы присоединиться к существующему сообществу, вам нужно знать его ID; это будет выглядеть примерно так+primer:matrix.org.", "Something went wrong whilst creating your community": "При создании сообщества что-то пошло не так", "%(names)s and %(count)s others are typing|other": "%(names)s и %(count)s другие печатают", - "And %(count)s more...|other": "И %(count)s больше..." + "And %(count)s more...|other": "И %(count)s больше...", + "Delete Widget": "Удалить виджет", + "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Удаление виджета удаляет его для всех пользователей этой комнаты. Вы действительно хотите удалить этот виджет?" } From 9b93804afad5c57efdcdf79464ba04fd7c5a1061 Mon Sep 17 00:00:00 2001 From: Jan Kudrik Date: Wed, 25 Oct 2017 16:13:26 +0000 Subject: [PATCH 059/365] Translated using Weblate (Czech) Currently translated at 60.1% (546 of 907 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/cs/ --- src/i18n/strings/cs.json | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json index 7d87a250b7..484cd1f40b 100644 --- a/src/i18n/strings/cs.json +++ b/src/i18n/strings/cs.json @@ -44,7 +44,7 @@ "Create new room": "Založit novou místnost", "Room directory": "Adresář místností", "Start chat": "Začít chat", - "Options": "Možnosti", + "Options": "Volby", "Register": "Zaregistrovat", "Cancel": "Storno", "Error": "Chyba", @@ -140,7 +140,7 @@ "Decline": "Odmítnout", "Decrypt %(text)s": "Dešifrovat %(text)s", "Decryption error": "Chyba dešifrování", - "Delete": "Vymazat", + "Delete": "Smazat", "Delete widget": "Vymazat widget", "Default": "Výchozí", "Device already verified!": "Zařízení již bylo ověřeno!", @@ -307,10 +307,10 @@ "Send anyway": "Přesto poslat", "Sender device information": "Informace o odesilatelově zařízení", "Send Reset Email": "Poslat resetovací e-mail", - "sent an image": "poslat obrázek", + "sent an image": "poslal/a obrázek", "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s poslal/a obrázek.", "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s poslal/a %(targetDisplayName)s pozvánku ke vstupu do místnosti.", - "sent a video": "poslat video", + "sent a video": "poslal/a video", "Server error": "Chyba serveru", "Server may be unavailable or overloaded": "Server může být nedostupný nebo přetížený", "Server may be unavailable, overloaded, or search timed out :(": "Server může být nedostupný, přetížený nebo vyhledávání vypršelo :(", @@ -519,5 +519,33 @@ "%(senderName)s made future room history visible to anyone.": "%(senderName)s učinil/a budoucí historii místnosti viditelnou komukoliv.", "%(senderName)s changed the pinned messages for the room.": "%(senderName)s změnil/a připíchnuté zprávy této místnosti.", "%(names)s and %(count)s others are typing|other": "%(names)s a %(count)s další píší", - "Authentication check failed: incorrect password?": "Kontrola ověření selhala: špatné heslo?" + "Authentication check failed: incorrect password?": "Kontrola ověření selhala: špatné heslo?", + "You need to be able to invite users to do that.": "Pro tuto akci musíte mít právo zvát uživatele.", + "Delete Widget": "Smazat widget", + "Error decrypting image": "Chyba při dešifrování obrázku", + "Image '%(Body)s' cannot be displayed.": "Obrázek '%(Body)s' nemůže být zobrazen.", + "This image cannot be displayed.": "Tento obrázek nemůže být zobrazen.", + "Error decrypting video": "Chyba při dešifrování videa", + "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s odstranil/a avatar místnosti.", + "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s změnil/a avatar místnosti na ", + "Copied!": "Zkopírováno!", + "Failed to copy": "Nepodařilo se zkopírovat", + "Removed or unknown message type": "Zpráva odstraněna nebo neznámého typu", + "Message removed by %(userId)s": "Zprávu odstranil/a %(userId)s", + "This Home Server would like to make sure you are not a robot": "Tento domovský server by se rád přesvědčil, že nejste robot", + "You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.": "Přes vlastní serverové volby se můžete přihlásit k dalším Matrix serverům tak, že zadáte jinou adresu domovského serveru.", + "Identity server URL": "Adresa serveru identity", + "You can also set a custom identity server but this will typically prevent interaction with users based on email address.": "Taktéž můžete zadat vlastní server identity, ale to vám zpravidla znemožní interagovat s uživateli na základě e-mailové adresy.", + "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Smazáním widgetu jej odstraníte všem uživatelům v této místnosti. Určitě chcete tento widget smazat?", + "The maximum permitted number of widgets have already been added to this room.": "V této místnosti již bylo dosaženo limitu pro maximální počet widgetů.", + "Drop file here to upload": "Přetažením sem nahrajete", + "uploaded a file": "nahrál/a soubor", + "Example": "Příklad", + "Create Community": "Vytvořit komunitu", + "Community Name": "Název komunity", + "Community ID": "ID komunity", + "example": "příklad", + "Create": "Vytvořit", + "Advanced options": "Pokročilé volby", + "User Options": "Volby uživatele" } From 841e274b07e62422f646de576f719a237366b52e Mon Sep 17 00:00:00 2001 From: Bamstam Date: Wed, 25 Oct 2017 16:01:05 +0000 Subject: [PATCH 060/365] Translated using Weblate (German) Currently translated at 99.8% (906 of 907 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 3755d30bb0..be2061f1d9 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -907,5 +907,6 @@ "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Erzeuge eine Community um Nutzer und Räume zu gruppieren! Erzeuge eine angepasste Homepage um dein Revier im Matrix-Universum zu markieren.", "Something went wrong whilst creating your community": "Beim Erstellen deiner Community ist ein Fehler aufgetreten", "%(names)s and %(count)s others are typing|other": "%(names)s und %(count)s weitere schreiben", - "And %(count)s more...|other": "Und %(count)s weitere..." + "And %(count)s more...|other": "Und %(count)s weitere...", + "Delete Widget": "Widget löschen" } From 5c9970950bde6581fae7623fd8b656b47e81f2ba Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 25 Oct 2017 11:21:12 -0600 Subject: [PATCH 061/365] Undo i18n change to make merge easier Signed-off-by: Travis Ralston --- src/i18n/strings/en_EN.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 1a9e59e3cd..df236636a2 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -620,7 +620,6 @@ "Cancel": "Cancel", "or": "or", "Message Pinning": "Message Pinning", - "Presence Management": "Presence Management", "Active call": "Active call", "Monday": "Monday", "Tuesday": "Tuesday", From 24000ee45689d397f35249ec8c66418e10b5741b Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 25 Oct 2017 11:23:04 -0600 Subject: [PATCH 062/365] Re-add i18n for labs Signed-off-by: Travis Ralston --- src/i18n/strings/en_EN.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index fbff51299a..a33560a052 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -152,6 +152,7 @@ "%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s", "Communities": "Communities", "Message Pinning": "Message Pinning", + "Presence Management": "Presence Management", "%(displayName)s is typing": "%(displayName)s is typing", "%(names)s and one other are typing": "%(names)s and one other are typing", "%(names)s and %(count)s others are typing|other": "%(names)s and %(count)s others are typing", From 935e6baf880cefab50cc3072bc50dbbb62a6ae55 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 25 Oct 2017 15:48:44 +0000 Subject: [PATCH 063/365] Translated using Weblate (Russian) Currently translated at 100.0% (908 of 908 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index efc3bf3067..01d8458d47 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -908,5 +908,6 @@ "%(names)s and %(count)s others are typing|other": "%(names)s и %(count)s другие печатают", "And %(count)s more...|other": "И %(count)s больше...", "Delete Widget": "Удалить виджет", - "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Удаление виджета удаляет его для всех пользователей этой комнаты. Вы действительно хотите удалить этот виджет?" + "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Удаление виджета удаляет его для всех пользователей этой комнаты. Вы действительно хотите удалить этот виджет?", + "Message removed": "Сообщение удалено" } From b3a85934682b54e56734d6a26c909c25bffc355e Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Wed, 25 Oct 2017 22:48:27 +0100 Subject: [PATCH 064/365] make tinter coarsely theme aware --- src/Tinter.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Tinter.js b/src/Tinter.js index 6b23df8c9b..81cc48c7c0 100644 --- a/src/Tinter.js +++ b/src/Tinter.js @@ -1,5 +1,6 @@ /* Copyright 2015 OpenMarket Ltd +Copyright 2017 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,6 +15,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +import UserSettingsStore from './UserSettingsStore'; + // FIXME: these vars should be bundled up and attached to // module.exports otherwise this will break when included by both // react-sdk and apps layered on top. @@ -178,8 +181,11 @@ module.exports = { } if (!primaryColor) { - primaryColor = "#76CFA6"; // Vector green - secondaryColor = "#EAF5F0"; // Vector light green + const theme = UserSettingsStore.getTheme(); + // FIXME: get this out of the theme CSS itself somehow? + // we could store it in a string CSS attrib somewhere we could sniff... + primaryColor = theme === 'status' ? "#6CC1F6" : "#76CFA6"; // Vector green + secondaryColor = theme === 'status' ? "#586C7B" : "#EAF5F0"; // Vector light green } if (!secondaryColor) { From ac7a94afb22a77f61973c2d5e5c3fe82fc4ecd29 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 26 Oct 2017 01:43:42 +0100 Subject: [PATCH 065/365] apply theme tint at launch --- src/components/structures/MatrixChat.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 9e476714ec..d4759a0e23 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -276,6 +276,9 @@ module.exports = React.createClass({ this._windowWidth = 10000; this.handleResize(); window.addEventListener('resize', this.handleResize); + + // check we have the right tint applied for this theme + Tinter.tint(); }, componentDidMount: function() { From 5a9dae5ff1f1e990e05c132a404502eb58ee82a5 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 26 Oct 2017 01:44:05 +0100 Subject: [PATCH 066/365] use generic 'text button' for delete buttons in UserSettings --- src/components/structures/UserSettings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index e98bb844cc..beaf1b04b5 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -205,7 +205,7 @@ const IgnoredUser = React.createClass({ render: function() { return (
  • - + { _t("Unignore") } { this.props.userId } From deb0f902e31541f83eeaa84b482a053c7e2006c1 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 26 Oct 2017 01:59:18 +0100 Subject: [PATCH 067/365] linting --- src/UserSettingsStore.js | 4 +--- src/components/structures/UserSettings.js | 2 +- src/components/views/login/LoginPage.js | 4 +--- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/UserSettingsStore.js b/src/UserSettingsStore.js index 65973c0763..c1d77b120b 100644 --- a/src/UserSettingsStore.js +++ b/src/UserSettingsStore.js @@ -188,9 +188,7 @@ export default { } if (!syncedSettings || !syncedSettings.theme) { theme = (SdkConfig.get() ? SdkConfig.get().default_theme : undefined) || 'light'; - } - else - { + } else { theme = syncedSettings.theme; } return theme; diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index beaf1b04b5..30828bdc85 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -181,7 +181,7 @@ const THEMES = [ }, { id: 'theme', - label: _td('Status.im theme'), + label: 'Status.im theme', value: 'status', }, ]; diff --git a/src/components/views/login/LoginPage.js b/src/components/views/login/LoginPage.js index fa51426059..a1a5000227 100644 --- a/src/components/views/login/LoginPage.js +++ b/src/components/views/login/LoginPage.js @@ -47,9 +47,7 @@ module.exports = React.createClass({
  • ); - } - else - { + } else { return (
    { this.props.children } From 5c32b5b11a94cef53b7bf31f64eeef9f00446b7a Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 26 Oct 2017 02:10:03 +0100 Subject: [PATCH 068/365] fix i18n --- src/components/structures/UserSettings.js | 2 +- src/i18n/strings/en_EN.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index 30828bdc85..beaf1b04b5 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -181,7 +181,7 @@ const THEMES = [ }, { id: 'theme', - label: 'Status.im theme', + label: _td('Status.im theme'), value: 'status', }, ]; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d69374acd1..cf6ee70ddf 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -904,5 +904,6 @@ "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.", "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.", "File to import": "File to import", - "Import": "Import" + "Import": "Import", + "Status.im theme": "Status.im theme" } From e6c3483c8b37df93f239aa1735d4858ac43a7d73 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 26 Oct 2017 09:58:05 +0100 Subject: [PATCH 069/365] Add correct telegram link --- src/components/views/login/LoginPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/login/LoginPage.js b/src/components/views/login/LoginPage.js index a1a5000227..32f090d361 100644 --- a/src/components/views/login/LoginPage.js +++ b/src/components/views/login/LoginPage.js @@ -42,7 +42,7 @@ module.exports = React.createClass({

    This channel is for our development community.

    Interested in SNT and discussions on the cryptocurrency market?

    -

    Join Telegram Chat

    +

    Join Telegram Chat

    From d0ba79996f77657fe642a4db81c67a3ff3d8908f Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Wed, 25 Oct 2017 18:29:05 +0000 Subject: [PATCH 070/365] Translated using Weblate (German) Currently translated at 99.8% (907 of 908 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index be2061f1d9..75c383289b 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -908,5 +908,6 @@ "Something went wrong whilst creating your community": "Beim Erstellen deiner Community ist ein Fehler aufgetreten", "%(names)s and %(count)s others are typing|other": "%(names)s und %(count)s weitere schreiben", "And %(count)s more...|other": "Und %(count)s weitere...", - "Delete Widget": "Widget löschen" + "Delete Widget": "Widget löschen", + "Message removed": "Nachricht entfernt" } From 93008272dd377c303b065c2130c2cebce865e412 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 25 Oct 2017 18:32:38 +0000 Subject: [PATCH 071/365] Translated using Weblate (Russian) Currently translated at 100.0% (908 of 908 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 01d8458d47..56e560f48c 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -8,7 +8,7 @@ "An email has been sent to": "Email был отправлен", "A new password must be entered.": "Введите новый пароль.", "Anyone who knows the room's link, apart from guests": "Любой, кто знает ссылку на комнату, кроме гостей", - "Anyone who knows the room's link, including guests": "Любой, кто знает ссылку комнаты, включая гостей", + "Anyone who knows the room's link, including guests": "Любой, кто знает ссылку на комнату, включая гостей", "Are you sure you want to reject the invitation?": "Вы уверены что вы хотите отклонить приглашение?", "Are you sure you want to upload the following files?": "Вы уверены что вы хотите отправить следующие файлы?", "Banned users": "Заблокированные пользователи", @@ -62,7 +62,7 @@ "Filter room members": "Фильтр участников комнаты", "Forget room": "Забыть комнату", "Forgot your password?": "Забыли пароль?", - "For security, this session has been signed out. Please sign in again.": "По соображениям безопасности, эта сессия была прекращена. Пожалуйста, войдите снова.", + "For security, this session has been signed out. Please sign in again.": "Для обеспечения безопасности ваша сессия была завершена. Пожалуйста, войдите снова.", "Found a bug?": "Нашли ошибку?", "Hangup": "Закончить", "Historical": "Архив", @@ -74,7 +74,7 @@ "Invite new room members": "Пригласить новых участников в комнату", "Invites": "Приглашает", "Invites user with given id to current room": "Приглашает пользователя с заданным ID в текущую комнату", - "Sign in with": "Войти с помощью", + "Sign in with": "Войти, используя", "Joins room with given alias": "Входит в комнату с заданным псевдонимом", "Kicks user with given id": "Выкидывает пользователя с заданным ID", "Labs": "Лаборатория", @@ -183,11 +183,11 @@ "%(targetName)s joined the room.": "%(targetName)s вошел(ла) в комнату.", "%(senderName)s kicked %(targetName)s.": "%(senderName)s выкинул %(targetName)s.", "%(targetName)s left the room.": "%(targetName)s покинул комнату.", - "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s сделал будущую историю комнаты видимой все участники комнаты, с момента приглашения.", - "%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s сделал будущую историю комнаты видимой все участники комнаты, с момента входа.", - "%(senderName)s made future room history visible to all room members.": "%(senderName)s сделал будущую историю комнаты видимой все участники комнаты.", - "%(senderName)s made future room history visible to anyone.": "%(senderName)s сделал будущую историю комнаты видимой любой.", - "%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s сделал будущую историю комнаты видимой неизвестный (%(visibility)s).", + "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s сделал(а) историю комнаты видимой для всех участников комнаты с момента их приглашения.", + "%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s сделал(а) историю комнаты видимой для всех участников комнаты с момента их входа.", + "%(senderName)s made future room history visible to all room members.": "%(senderName)s сделал(а) историю комнаты видимой для всех участников комнаты.", + "%(senderName)s made future room history visible to anyone.": "%(senderName)s сделал(а) историю комнаты видимой для всех.", + "%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s сделал(а) историю комнаты видимой для неизвестного (%(visibility)s).", "Missing room_id in request": "Отсутствует room_id в запросе", "Missing user_id in request": "Отсутствует user_id в запросе", "Must be viewing a room": "Необходимо посмотреть комнату", @@ -306,14 +306,14 @@ "'%(alias)s' is not a valid format for an alias": "'%(alias)s' недопустимый формат псевдонима", "Join Room": "Войти в комнату", "Kick": "Выгнать", - "Local addresses for this room:": "Локальный адрес этой комнаты:", + "Local addresses for this room:": "Локальные адреса этой комнаты:", "Markdown is disabled": "Markdown отключен", "Markdown is enabled": "Markdown включен", "matrix-react-sdk version:": "версия matrix-react-sdk:", "New address (e.g. #foo:%(localDomain)s)": "Новый адрес (например, #foo:%(localDomain)s)", "New passwords don't match": "Новые пароли не совпадают", "not set": "не задано", - "not specified": "не определено", + "not specified": "не определен", "No devices with registered encryption keys": "Нет устройств с зарегистрированными ключами шифрования", "No more results": "Больше никаких результатов", "No results": "Нет результатов", @@ -334,7 +334,7 @@ "Report it": "Сообщить об этом", "Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Сброс пароля на данный момент сбрасывает ключи шифрования на всех устройствах, делая зашифрованную историю чатов нечитаемой. Чтобы избежать этого, экспортируйте ключи комнат и импортируйте их после сброса пароля. В будущем это будет исправлено.", "Return to app": "Вернуться в приложение", - "Riot does not have permission to send you notifications - please check your browser settings": "Riot не имеет разрешение на отправку уведомлений, проверьте параметры своего браузера", + "Riot does not have permission to send you notifications - please check your browser settings": "У Riot нет разрешений на отправку уведомлений - проверьте настройки браузера", "Riot was not given permission to send notifications - please try again": "Riot не получил разрешение на отправку уведомлений, пожалуйста, попробуйте снова", "riot-web version:": "версия riot-web:", "Room %(roomId)s not visible": "Комната %(roomId)s невидима", @@ -509,7 +509,7 @@ "Session ID": "ID сессии", "%(senderName)s set a profile picture.": "%(senderName)s установил изображение профиля.", "%(senderName)s set their display name to %(displayName)s.": "%(senderName)s изменил отображаемое имя на %(displayName)s.", - "Signed Out": "Вышли", + "Signed Out": "Выполнен выход", "Sorry, this homeserver is using a login which is not recognised ": "К сожалению, этот домашний сервер использует неизвестный метод авторизации ", "Tagged as: ": "Теги: ", "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "Предоставленный ключ подписи соответствует ключу, полученному от %(userId)s с устройства %(deviceId)s. Устройство помечено как проверенное.", @@ -586,7 +586,7 @@ "ex. @bob:example.com": "например @bob:example.com", "Add User": "Добавить пользователя", "This Home Server would like to make sure you are not a robot": "Этот домашний сервер хочет убедиться, что вы не робот", - "Sign in with CAS": "Войти с помощью CAS", + "Sign in with CAS": "Войти, используя CAS", "You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.": "Вы можете использовать настраиваемые параметры сервера для входа на другие серверы Matrix, указав другой URL-адрес домашнего сервера.", "This allows you to use this app with an existing Matrix account on a different home server.": "Это позволяет использовать это приложение с существующей учетной записью Matrix на другом домашнем сервере.", "You can also set a custom identity server but this will typically prevent interaction with users based on email address.": "Вы также можете установить другой сервер идентификации, но это, как правило, будет препятствовать взаимодействию с пользователями на основе адреса email.", @@ -612,7 +612,7 @@ "Disable URL previews by default for participants in this room": "Отключить предпросмотр URL-адресов по умолчанию для участников этой комнаты", "URL previews are %(globalDisableUrlPreview)s by default for participants in this room.": "Предварительный просмотр URL-адресов %(globalDisableUrlPreview)s по умолчанию для участников этой комнаты.", "URL Previews": "Предварительный просмотр URL-адресов", - "Enable URL previews for this room (affects only you)": "Включить предпросмотр URL-адресов для этой комнаты (влияет только на вас)", + "Enable URL previews for this room (affects only you)": "Включить предпросмотр URL-адресов для этой комнаты (касается только вас)", "Drop file here to upload": "Перетащите файл сюда для отправки", " (unsupported)": " (не поддерживается)", "Ongoing conference call%(supportedText)s.": "Установлен групповой вызов %(supportedText)s.", @@ -623,7 +623,7 @@ "Online": "В сети", "Idle": "Неактивен", "Offline": "Не в сети", - "Disable URL previews for this room (affects only you)": "Отключить предпросмотр URL-адресов для этой комнаты (влияет только на вас)", + "Disable URL previews for this room (affects only you)": "Отключить предпросмотр URL-адресов для этой комнаты (касается только вас)", "%(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", @@ -743,7 +743,7 @@ "Edit": "Редактировать", "Enable automatic language detection for syntax highlighting": "Включить автоматическое определение языка для подсветки синтаксиса", "Hide Apps": "Скрыть приложения", - "Hide join/leave messages (invites/kicks/bans unaffected)": "Скрыть сообщения о входе/выходе (приглашениях/выкидываниях/банах)", + "Hide join/leave messages (invites/kicks/bans unaffected)": "Скрыть сообщения о входе/выходе (не применяется к приглашениям/выкидываниям/банам)", "Hide avatar and display name changes": "Скрыть сообщения об изменении аватаров и отображаемых имен", "Integrations Error": "Ошибка интеграции", "AM": "AM", From 3a8b036687394554d101ea60ed94584d8055d596 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 25 Oct 2017 18:32:38 +0000 Subject: [PATCH 072/365] Translated using Weblate (Russian) Currently translated at 100.0% (909 of 909 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 56e560f48c..efa1617c97 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -909,5 +909,6 @@ "And %(count)s more...|other": "И %(count)s больше...", "Delete Widget": "Удалить виджет", "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Удаление виджета удаляет его для всех пользователей этой комнаты. Вы действительно хотите удалить этот виджет?", - "Message removed": "Сообщение удалено" + "Message removed": "Сообщение удалено", + "Mirror local video feed": "Зеркальное отображение видео" } From 8a9750bccbffcfe97845c2965686fa98c3fbb755 Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 26 Oct 2017 10:17:25 +0000 Subject: [PATCH 073/365] Translated using Weblate (Russian) Currently translated at 100.0% (910 of 910 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index efa1617c97..9824788844 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -910,5 +910,6 @@ "Delete Widget": "Удалить виджет", "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Удаление виджета удаляет его для всех пользователей этой комнаты. Вы действительно хотите удалить этот виджет?", "Message removed": "Сообщение удалено", - "Mirror local video feed": "Зеркальное отображение видео" + "Mirror local video feed": "Зеркальное отображение видео", + "Invite": "Пригласить" } From a0509d8b5d056a0d6957325db5a9dec83cc3da74 Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 26 Oct 2017 10:44:03 +0000 Subject: [PATCH 074/365] Translated using Weblate (Russian) Currently translated at 100.0% (911 of 911 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 9824788844..95dbea1f47 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -911,5 +911,6 @@ "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Удаление виджета удаляет его для всех пользователей этой комнаты. Вы действительно хотите удалить этот виджет?", "Message removed": "Сообщение удалено", "Mirror local video feed": "Зеркальное отображение видео", - "Invite": "Пригласить" + "Invite": "Пригласить", + "Remove this room from the community": "Удалить эту комнату из сообщества" } From 14d9743e303097f26182f80d45d0e956a15cebbe Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 26 Oct 2017 17:22:17 +0100 Subject: [PATCH 075/365] unbreak reg --- src/components/views/login/RegistrationForm.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/login/RegistrationForm.js b/src/components/views/login/RegistrationForm.js index 7574b68418..07418c4843 100644 --- a/src/components/views/login/RegistrationForm.js +++ b/src/components/views/login/RegistrationForm.js @@ -123,7 +123,7 @@ module.exports = React.createClass({ password: this.refs.password.value.trim(), email: email, phoneCountry: this.state.phoneCountry, - phoneNumber: this.refs.phoneNumber.value.trim(), + phoneNumber: this.refs.phoneNumber ? this.refs.phoneNumber.value.trim() : '', }); if (promise) { @@ -181,7 +181,7 @@ module.exports = React.createClass({ this.markFieldValid(field_id, emailValid, "RegistrationForm.ERR_EMAIL_INVALID"); break; case FIELD_PHONE_NUMBER: - const phoneNumber = this.refs.phoneNumber.value; + const phoneNumber = this.refs.phoneNumber ? this.refs.phoneNumber.value : ''; const phoneNumberValid = phoneNumber === '' || phoneNumberLooksValid(phoneNumber); this.markFieldValid(field_id, phoneNumberValid, "RegistrationForm.ERR_PHONE_NUMBER_INVALID"); break; From 655d0c615a6bfb627440971cc337de307c22a024 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 26 Oct 2017 17:57:49 +0100 Subject: [PATCH 076/365] remove spurious Sign In button and legacy Return to App buttons --- src/components/structures/login/Login.js | 5 ++++- src/components/structures/login/Registration.js | 16 +++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/components/structures/login/Login.js b/src/components/structures/login/Login.js index 44363d83ad..789b066074 100644 --- a/src/components/structures/login/Login.js +++ b/src/components/structures/login/Login.js @@ -346,12 +346,15 @@ module.exports = React.createClass({ } let returnToAppJsx; - if (this.props.onCancelClick && theme !== 'status') { + /* + // with the advent of ILAG I don't think we need this any more + if (this.props.onCancelClick) { returnToAppJsx = { _t('Return to app') } ; } + */ let serverConfig; let header; diff --git a/src/components/structures/login/Registration.js b/src/components/structures/login/Registration.js index 4e95b62be3..5634c28197 100644 --- a/src/components/structures/login/Registration.js +++ b/src/components/structures/login/Registration.js @@ -382,6 +382,8 @@ module.exports = React.createClass({ } let returnToAppJsx; + /* + // with the advent of ILAG I don't think we need this any more if (this.props.onCancelClick) { returnToAppJsx = ( @@ -389,6 +391,7 @@ module.exports = React.createClass({ ); } + */ let header; let errorText; @@ -402,6 +405,15 @@ module.exports = React.createClass({ } } + let signIn; + if (!this.state.doingUIAuth) { + signIn = ( + + { theme === 'status' ? _t('Sign in') : _t('I already have an account') } + + ); + } + return (
    @@ -413,9 +425,7 @@ module.exports = React.createClass({ /> { header } { registerBody } - - { theme === 'status' ? _t('Sign in') : _t('I already have an account') } - + { signIn } { errorText } { returnToAppJsx } From 6bac1ad755281d9a52240cea7a9d0be140360739 Mon Sep 17 00:00:00 2001 From: Jan Kudrik Date: Thu, 26 Oct 2017 14:39:46 +0000 Subject: [PATCH 077/365] Translated using Weblate (Czech) Currently translated at 62.8% (573 of 911 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/cs/ --- src/i18n/strings/cs.json | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json index 484cd1f40b..c3907c5e22 100644 --- a/src/i18n/strings/cs.json +++ b/src/i18n/strings/cs.json @@ -547,5 +547,32 @@ "example": "příklad", "Create": "Vytvořit", "Advanced options": "Pokročilé volby", - "User Options": "Volby uživatele" + "User Options": "Volby uživatele", + "Please select the destination room for this message": "Vyberte prosím pro tuto zprávu cílovou místnost", + "No devices with registered encryption keys": "Žádná zařízení se zaregistrovanými šifrovacími klíči", + "Jump to read receipt": "Přeskočit na potvrzení o přečtení", + "Invite": "Pozvat", + "Revoke Moderator": "Odebrat moderátorství", + "Make Moderator": "Udělit moderátorství", + "and %(count)s others...|one": "a někdo další...", + "Hangup": "Zavěsit", + "Hide Apps": "Skrýt aplikace", + "Show Text Formatting Toolbar": "Zobrazit nástroje formátování textu", + "Hide Text Formatting Toolbar": "Skrýt nástroje formátování textu", + "Jump to message": "Přeskočit na zprávu", + "Loading...": "Načítání...", + "Loading device info...": "Načítá se info o zařízení...", + "You seem to be uploading files, are you sure you want to quit?": "Zřejmě právě nahráváte soubory. Chcete přesto odejít?", + "You seem to be in a call, are you sure you want to quit?": "Zřejmě máte probíhající hovor. Chcete přesto odejít?", + "Idle": "Nečinný/á", + "Unknown": "Neznámý", + "Seen by %(userName)s at %(dateTime)s": "Spatřen uživatelem %(userName)s v %(dateTime)s", + "Unnamed room": "Nepojmenovaná místnost", + "World readable": "Světu čitelné", + "Guests can join": "Hosté mohou vstoupit", + "No rooms to show": "Žádné místnosti k zobrazení", + "(~%(count)s results)|other": "(~%(count)s výsledků)", + "(~%(count)s results)|one": "(~%(count)s výsledek)", + "Upload avatar": "Nahrát avatar", + "Remove avatar": "Odstranit avatar" } From b4868a6846461918836f91b3b1364ad6c690785a Mon Sep 17 00:00:00 2001 From: "Andrew (anoa)" Date: Thu, 26 Oct 2017 11:17:13 -0700 Subject: [PATCH 078/365] showWidget->hideWidgetDrawer and remove logs Signed-off-by: Andrew (anoa) --- src/components/structures/RoomView.js | 15 +++++---------- src/components/views/rooms/AppsDrawer.js | 8 +++----- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 583ce78785..38603f1805 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -305,17 +305,12 @@ module.exports = React.createClass({ _shouldShowApps: function(room) { if (!BROWSER_SUPPORTS_SANDBOX) return false; - // Check if user has prompted to close this app before - // If so, do not show apps - let showWidget = localStorage.getItem( - room.roomId + "_show_widget_drawer"); + // Check if user has previously chosen to hide the app drawer for this + // room. If so, do not show apps + let hideWidgetDrawer = localStorage.getItem( + room.roomId + "_hide_widget_drawer"); - console.warn(room); - console.warn("Key is: " + room.roomId + "_show_widget_drawer"); - console.warn("showWidget is: " + showWidget); - - if (showWidget == "false") { - console.warn("We're blocking the widget from loading."); + if (hideWidgetDrawer === "true") { return false; } diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 09bf4e616b..9a3ba5f329 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -81,7 +81,7 @@ module.exports = React.createClass({ }, onAction: function(action) { - const widgetStateKey = this.props.room.roomId + "_show_widget_drawer"; + const hideWidgetKey = this.props.room.roomId + "_hide_widget_drawer"; switch (action.action) { case 'appsDrawer': // When opening the app drawer when there aren't any apps, @@ -93,13 +93,11 @@ module.exports = React.createClass({ this._launchManageIntegrations(); } - localStorage.removeItem(widgetStateKey); + localStorage.removeItem(hideWidgetKey); } else { // Store hidden state of widget // Don't show if previously hidden - console.warn("Storing hidden widget state for room - ", - this.props.room.roomId); - localStorage.setItem(widgetStateKey, false); + localStorage.setItem(hideWidgetKey, true); } break; From de2bad2af40a57d2b4a880c458057db31d98ba84 Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 26 Oct 2017 14:06:02 +0000 Subject: [PATCH 079/365] Translated using Weblate (Russian) Currently translated at 100.0% (912 of 912 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 95dbea1f47..b81d958bf9 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -912,5 +912,6 @@ "Message removed": "Сообщение удалено", "Mirror local video feed": "Зеркальное отображение видео", "Invite": "Пригласить", - "Remove this room from the community": "Удалить эту комнату из сообщества" + "Remove this room from the community": "Удалить эту комнату из сообщества", + "Mention": "Упоминание" } From 015aed05973e28c7b3f75ed415af0479f72277e2 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Fri, 27 Oct 2017 01:03:04 +0100 Subject: [PATCH 080/365] hide optionality of email for status --- src/components/views/login/RegistrationForm.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/views/login/RegistrationForm.js b/src/components/views/login/RegistrationForm.js index 07418c4843..426cc41dea 100644 --- a/src/components/views/login/RegistrationForm.js +++ b/src/components/views/login/RegistrationForm.js @@ -274,10 +274,13 @@ module.exports = React.createClass({ render: function() { const self = this; + const theme = UserSettingsStore.getTheme(); + const emailPlaceholder = theme === 'status' ? _t("Email address") : _t("Email address (optional)"); + const emailSection = (
    Date: Fri, 27 Oct 2017 01:09:37 +0100 Subject: [PATCH 081/365] target blank for tg --- src/components/views/login/LoginPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/login/LoginPage.js b/src/components/views/login/LoginPage.js index 32f090d361..b5e2faedec 100644 --- a/src/components/views/login/LoginPage.js +++ b/src/components/views/login/LoginPage.js @@ -42,7 +42,7 @@ module.exports = React.createClass({

    This channel is for our development community.

    Interested in SNT and discussions on the cryptocurrency market?

    -

    Join Telegram Chat

    +

    Join Telegram Chat

    From 1bf3ef6de4128a1bf2211c5f6d11db6aed05869b Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Fri, 27 Oct 2017 01:23:50 +0100 Subject: [PATCH 082/365] fix password reset --- .../structures/login/ForgotPassword.js | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/components/structures/login/ForgotPassword.js b/src/components/structures/login/ForgotPassword.js index f14d64528f..0e49a59936 100644 --- a/src/components/structures/login/ForgotPassword.js +++ b/src/components/structures/login/ForgotPassword.js @@ -17,13 +17,14 @@ limitations under the License. 'use strict'; -const React = require('react'); +import React from 'react'; import { _t } from '../../../languageHandler'; -const sdk = require('../../../index'); -const Modal = require("../../../Modal"); -const MatrixClientPeg = require('../../../MatrixClientPeg'); +import sdk from '../../../index'; +import Modal from "../../../Modal"; +import MatrixClientPeg from "../../../MatrixClientPeg"; -const PasswordReset = require("../../../PasswordReset"); +import PasswordReset from "../../../PasswordReset"; +import UserSettingsStore from "../../../UserSettingsStore"; module.exports = React.createClass({ displayName: 'ForgotPassword', @@ -183,6 +184,22 @@ module.exports = React.createClass({
    ); } else { + let theme = UserSettingsStore.getTheme(); + + let serverConfigSection; + if (theme !== 'status') { + serverConfigSection = ( + + ); + } + resetPasswordJsx = (
    @@ -210,16 +227,7 @@ module.exports = React.createClass({
    - -
    -
    + { serverConfigSection } { _t('Return to login screen') } From e3f896c5e091cb0199afe9d9766c844afea7f3ba Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Fri, 27 Oct 2017 01:35:21 +0100 Subject: [PATCH 083/365] don't forget login prompt class --- src/components/structures/login/ForgotPassword.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/login/ForgotPassword.js b/src/components/structures/login/ForgotPassword.js index 0e49a59936..851d13b23e 100644 --- a/src/components/structures/login/ForgotPassword.js +++ b/src/components/structures/login/ForgotPassword.js @@ -167,7 +167,7 @@ module.exports = React.createClass({ resetPasswordJsx = ; } else if (this.state.progress === "sent_email") { resetPasswordJsx = ( -
    +
    { _t('An email has been sent to') } { this.state.email }. { _t("Once you've followed the link it contains, click below") }.
    +

    { _t('Your password has been reset') }.

    { _t('You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device') }.

    Date: Fri, 27 Oct 2017 09:50:03 +0000 Subject: [PATCH 084/365] Translated using Weblate (Hungarian) Currently translated at 99.5% (908 of 912 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 5e8b44d2a5..39c6748530 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -907,5 +907,8 @@ "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Készíts közösséget hogy egybegyűjtsd a felhasználókat és szobákat! Készíts egy saját kezdőlapot amivel meghatározhatod magad a Matrix univerzumában.", "%(names)s and %(count)s others are typing|other": "%(names)s és még %(count)s felhasználó gépel", "And %(count)s more...|other": "És még %(count)s...", - "Something went wrong whilst creating your community": "Valami nem sikerült a közösség létrehozásánál" + "Something went wrong whilst creating your community": "Valami nem sikerült a közösség létrehozásánál", + "Mention": "Említ", + "Invite": "Meghív", + "Message removed": "Üzenet eltávolítva" } From 86e587aefd2edcab4d22664498b41c3d67a600fb Mon Sep 17 00:00:00 2001 From: Szimszon Date: Fri, 27 Oct 2017 11:03:05 +0000 Subject: [PATCH 085/365] Translated using Weblate (Hungarian) Currently translated at 100.0% (912 of 912 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 39c6748530..aa9467734f 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -910,5 +910,9 @@ "Something went wrong whilst creating your community": "Valami nem sikerült a közösség létrehozásánál", "Mention": "Említ", "Invite": "Meghív", - "Message removed": "Üzenet eltávolítva" + "Message removed": "Üzenet eltávolítva", + "Remove this room from the community": "A szoba törlése a közösségből", + "Delete Widget": "Kisalkalmazás törlése", + "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "A kisalkalmazás törlése minden felhasználót érint a szobában. Tényleg törölni szeretnéd?", + "Mirror local video feed": "Helyi videó folyam tükrözése" } From 7662b5ac8f5d8db849730a0e0c74b4535aaf6c13 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Fri, 27 Oct 2017 13:47:51 +0100 Subject: [PATCH 086/365] Unfinished, non-working changes to try and handle URL changes gracefully --- src/components/views/elements/AppTile.js | 26 +++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 0070af1fb2..7da0407c00 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -99,14 +99,27 @@ export default React.createClass({ }, componentWillMount: function() { + window.addEventListener('message', this._onMessage, false); + this.updateWidgetContent(); + }, + + // Update widget content + updateWidgetContent() { + this.setScalarToken(); + this.setState({ + loading: true, + }); + }, + + // Adds a scalar token to the widget URL, if required + setScalarToken() { if (!this.isScalarUrl()) { return; } // Fetch the token before loading the iframe as we need to mangle the URL - this.setState({ - loading: true, - }); - this._scalarClient = new ScalarAuthClient(); + if(! this._scalarClient) { + this._scalarClient = new ScalarAuthClient(); + } this._scalarClient.getScalarToken().done((token) => { // Append scalar_token as a query param this._scalarClient.scalarToken = token; @@ -128,13 +141,16 @@ export default React.createClass({ loading: false, }); }); - window.addEventListener('message', this._onMessage, false); }, componentWillUnmount() { window.removeEventListener('message', this._onMessage); }, + componentWillReceiveProps(nextProps) { + console.warn("Apptile", this.id, "got new props", this.url, nextProps.url); + }, + _onMessage(event) { if (this.props.type !== 'jitsi') { return; From 5d0629ff73203b946e3a864b967d428b81c7dcfe Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Fri, 27 Oct 2017 14:23:16 +0100 Subject: [PATCH 087/365] resolve matrix.status.im v. matrix.org confusion --- src/components/structures/login/Login.js | 12 +++++++++++- src/components/views/login/PasswordLogin.js | 4 +++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/components/structures/login/Login.js b/src/components/structures/login/Login.js index 789b066074..fa1bd2e6b5 100644 --- a/src/components/structures/login/Login.js +++ b/src/components/structures/login/Login.js @@ -105,7 +105,17 @@ module.exports = React.createClass({ if (error.httpStatus == 400 && usingEmail) { errorText = _t('This Home Server does not support login using email address.'); } else if (error.httpStatus === 401 || error.httpStatus === 403) { - errorText = _t('Incorrect username and/or password.'); + const theme = UserSettingsStore.getTheme(); + if (theme === "status") { + errorText = ( +
    +
    Incorrect username and/or password.
    +
    Please note you are logging into the matrix.status.im server, not matrix.org.
    +
    + ); + } else { + errorText = _t('Incorrect username and/or password.'); + } } else { // other errors, not specific to doing a password login errorText = this._errorTextFromError(error); diff --git a/src/components/views/login/PasswordLogin.js b/src/components/views/login/PasswordLogin.js index eb89bc00f1..8af148dc6c 100644 --- a/src/components/views/login/PasswordLogin.js +++ b/src/components/views/login/PasswordLogin.js @@ -122,6 +122,8 @@ class PasswordLogin extends React.Component { mx_Login_field_disabled: disabled, }; + const theme = UserSettingsStore.getTheme(); + switch(loginType) { case PasswordLogin.LOGIN_FIELD_EMAIL: classes.mx_Login_email = true; @@ -144,7 +146,7 @@ class PasswordLogin extends React.Component { type="text" name="username" // make it a little easier for browser's remember-password onChange={this.onUsernameChanged} - placeholder={_t('User name')} + placeholder={theme === 'status' ? "Username on matrix.status.im" : _t("User name")} value={this.state.username} autoFocus disabled={disabled} From 3ceb63b97e23b074f48c77505794207fb14f7e74 Mon Sep 17 00:00:00 2001 From: Krombel Date: Fri, 27 Oct 2017 13:48:35 +0000 Subject: [PATCH 088/365] Translated using Weblate (German) Currently translated at 100.0% (912 of 912 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 75c383289b..00cf1099d7 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -909,5 +909,10 @@ "%(names)s and %(count)s others are typing|other": "%(names)s und %(count)s weitere schreiben", "And %(count)s more...|other": "Und %(count)s weitere...", "Delete Widget": "Widget löschen", - "Message removed": "Nachricht entfernt" + "Message removed": "Nachricht entfernt", + "Mention": "Erwähnung", + "Invite": "Einladung", + "Remove this room from the community": "Entferne diesen Raum von der Community", + "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Das Löschen eines Widgets entfernt es für alle Nutzer in diesem Raum. Bist du sicher, dass du dieses Widget löschen willst?", + "Mirror local video feed": "Spiegele lokalen Video-Feed" } From 1a1874e1b5d6a4fc90f52df06d1b86c7f6e1b613 Mon Sep 17 00:00:00 2001 From: Bamstam Date: Fri, 27 Oct 2017 14:00:49 +0000 Subject: [PATCH 089/365] Translated using Weblate (German) Currently translated at 100.0% (912 of 912 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 00cf1099d7..c03f385631 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -798,7 +798,7 @@ "Unignore": "Entignorieren", "User Options": "Benutzer-Optionen", "Unignored user": "Benutzer entignoriert", - "Ignored user": "Benutzer ignoriert", + "Ignored user": "Ignorierter Benutzer", "Stops ignoring a user, showing their messages going forward": "Beendet das Ignorieren eines Benutzers, nachfolgende Nachrichten werden wieder angezeigt", "Ignores a user, hiding their messages from you": "Ignoriert einen Benutzer und verbirgt dessen Nachrichten", "Disable Emoji suggestions while typing": "Emoji-Vorschläge während des Schreibens deaktivieren", @@ -863,11 +863,11 @@ "Invite new community members": "Neue Community-Mitglieder einladen", "Invite to Community": "In die Community einladen", "Which rooms would you like to add to this community?": "Welche Räume möchtest du zu dieser Community hinzufügen?", - "Warning: any room you add to a community will be publicly visible to anyone who knows the community ID": "Warnung: Jeder Raum, den du einer Community hinzufügst, wird für alle, die die Community-ID kennen, öffentlich sichtbar sein", + "Warning: any room you add to a community will be publicly visible to anyone who knows the community ID": "Warnung: Jeder Raum, den du zu einer Community hinzufügst, wird für alle, die die Community-ID kennen, öffentlich sichtbar sein", "Add rooms to the community": "Räume zur Community hinzufügen", - "Add to community": "Füge zur Community hinzu", + "Add to community": "Zur Community hinzufügen", "Your community invitations have been sent.": "Deine Community-Einladungen wurden gesendet.", - "Failed to invite users to community": "Einladen von Nutzern in die Community ist fehlgeschlagen", + "Failed to invite users to community": "Benutzer konnten nicht in die Community eingeladen werden", "Communities": "Communities", "Invalid community ID": "Ungültige Community-ID", "'%(groupId)s' is not a valid community ID": "'%(groupId)s' ist keine gültige Community-ID", From e8db013d4b703a82af6e5907dae3b64643fbaec0 Mon Sep 17 00:00:00 2001 From: Krombel Date: Fri, 27 Oct 2017 14:00:53 +0000 Subject: [PATCH 090/365] Translated using Weblate (German) Currently translated at 100.0% (912 of 912 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index c03f385631..92b87ded71 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -798,7 +798,7 @@ "Unignore": "Entignorieren", "User Options": "Benutzer-Optionen", "Unignored user": "Benutzer entignoriert", - "Ignored user": "Ignorierter Benutzer", + "Ignored user": "Ignorierte Benutzer", "Stops ignoring a user, showing their messages going forward": "Beendet das Ignorieren eines Benutzers, nachfolgende Nachrichten werden wieder angezeigt", "Ignores a user, hiding their messages from you": "Ignoriert einen Benutzer und verbirgt dessen Nachrichten", "Disable Emoji suggestions while typing": "Emoji-Vorschläge während des Schreibens deaktivieren", From e2cd2e9524242a93d8139d1ca9ffddb61ed67e35 Mon Sep 17 00:00:00 2001 From: Bamstam Date: Fri, 27 Oct 2017 14:03:50 +0000 Subject: [PATCH 091/365] Translated using Weblate (German) Currently translated at 100.0% (912 of 912 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 92b87ded71..40b1ee0ef2 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -778,7 +778,7 @@ "Failed to copy": "Kopieren fehlgeschlagen", "Ignored Users": "Ignorierte Benutzer", "Ignore": "Ignorieren", - "You are now ignoring %(userId)s": "Du ignorierst jetzt %(userId)s", + "You are now ignoring %(userId)s": "%(userId)s wird jetzt ignoriert", "You are no longer ignoring %(userId)s": "%(userId)s wird nicht mehr ignoriert", "Message removed by %(userId)s": "Nachricht wurde von %(userId)s entfernt", "Name or matrix ID": "Name oder Matrix-ID", @@ -797,7 +797,7 @@ "This setting cannot be changed later!": "Diese Einstellung kann nachträglich nicht mehr geändert werden!", "Unignore": "Entignorieren", "User Options": "Benutzer-Optionen", - "Unignored user": "Benutzer entignoriert", + "Unignored user": "Benutzer wird nicht mehr ignoriert", "Ignored user": "Ignorierte Benutzer", "Stops ignoring a user, showing their messages going forward": "Beendet das Ignorieren eines Benutzers, nachfolgende Nachrichten werden wieder angezeigt", "Ignores a user, hiding their messages from you": "Ignoriert einen Benutzer und verbirgt dessen Nachrichten", From be2935f8140812f11d40e201b4174f0aca5f4de6 Mon Sep 17 00:00:00 2001 From: Krombel Date: Fri, 27 Oct 2017 14:04:58 +0000 Subject: [PATCH 092/365] Translated using Weblate (German) Currently translated at 100.0% (912 of 912 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 40b1ee0ef2..d716a3b1cf 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -797,7 +797,7 @@ "This setting cannot be changed later!": "Diese Einstellung kann nachträglich nicht mehr geändert werden!", "Unignore": "Entignorieren", "User Options": "Benutzer-Optionen", - "Unignored user": "Benutzer wird nicht mehr ignoriert", + "Unignored user": "Benutzer nicht mehr ignoriert", "Ignored user": "Ignorierte Benutzer", "Stops ignoring a user, showing their messages going forward": "Beendet das Ignorieren eines Benutzers, nachfolgende Nachrichten werden wieder angezeigt", "Ignores a user, hiding their messages from you": "Ignoriert einen Benutzer und verbirgt dessen Nachrichten", From a281a1d2781b71e21faf600bfb7e0e927f4e949f Mon Sep 17 00:00:00 2001 From: Bamstam Date: Fri, 27 Oct 2017 14:46:11 +0000 Subject: [PATCH 093/365] Translated using Weblate (German) Currently translated at 100.0% (912 of 912 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index d716a3b1cf..49db6f8ded 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -793,7 +793,7 @@ "You have entered an invalid address.": "Du hast eine ungültige Adresse eingegeben.", "Matrix ID": "Matrix-ID", "Advanced options": "Erweiterte Optionen", - "Block users on other matrix homeservers from joining this room": "Blockiere Nutzer anderer Matrix-Heimserver die diesen Raum betreten wollen", + "Block users on other matrix homeservers from joining this room": "Benutzer anderer Matrix-Heimserver das Betreten dieses Raumes verbieten", "This setting cannot be changed later!": "Diese Einstellung kann nachträglich nicht mehr geändert werden!", "Unignore": "Entignorieren", "User Options": "Benutzer-Optionen", @@ -802,21 +802,21 @@ "Stops ignoring a user, showing their messages going forward": "Beendet das Ignorieren eines Benutzers, nachfolgende Nachrichten werden wieder angezeigt", "Ignores a user, hiding their messages from you": "Ignoriert einen Benutzer und verbirgt dessen Nachrichten", "Disable Emoji suggestions while typing": "Emoji-Vorschläge während des Schreibens deaktivieren", - "Banned by %(displayName)s": "Gebannt von %(displayName)s", - "To send messages, you must be a": "Um Nachrichten zu senden musst du sein ein", + "Banned by %(displayName)s": "Verbannt von %(displayName)s", + "To send messages, you must be a": "Notwendiges Berechtigungslevel, um Nachrichten zu senden", "To invite users into the room, you must be a": "Notwendiges Berechtigungslevel, um Benutzer in diesen Raum einladen zu können:", "To configure the room, you must be a": "Notwendiges Berechtigungslevel, um diesen Raum konfigurieren:", "To kick users, you must be a": "Notwendiges Berechtigungslevel, um Benutzer zu kicken:", "To ban users, you must be a": "Notwendiges Berechtigungslevel, um einen Benutzer zu verbannen:", - "To remove other users' messages, you must be a": "Um Nachrichten von Benutzern zu löschen, musst du sein ein", - "To send events of type , you must be a": "Um Ereignisse desTyps zu senden, musst du sein ein", - "To change the room's avatar, you must be a": "Um das Raumbild zu ändern, musst du sein ein", - "To change the room's name, you must be a": "Um den Raumnamen zu ändern, musst du sein ein", - "To change the room's main address, you must be a": "Um die Hauptadresse des Raumes zu ändern, musst du sein ein", - "To change the room's history visibility, you must be a": "Um die Sichtbarkeit des bisherigen Chatverlaufs zu ändern, musst du sein ein", - "To change the permissions in the room, you must be a": "Um Berechtigungen in diesem Raum zu ändern, musst du sein ein", - "To change the topic, you must be a": "Um das Thema zu ändern, musst du sein ein", - "To modify widgets in the room, you must be a": "Um Widgets in dem Raum zu ändern, musst du sein ein", + "To remove other users' messages, you must be a": "Notwendiges Berechtigungslevel, um Nachrichten von anderen Benutzern zu löschen", + "To send events of type , you must be a": "Notwendiges Berechtigungslevel, um Ereignisse des Typs zu senden", + "To change the room's avatar, you must be a": "Notwendiges Berechtigungslevel, um das Raumbild zu ändern", + "To change the room's name, you must be a": "Notwendiges Berechtigungslevel, um den Raumnamen zu ändern", + "To change the room's main address, you must be a": "Notwendiges Berechtigungslevel, um die Hauptadresse des Raumes zu ändern", + "To change the room's history visibility, you must be a": "Notwendiges Berechtigungslevel, um die Sichtbarkeit des bisherigen Chatverlaufs zu ändern", + "To change the permissions in the room, you must be a": "Notwendiges Berechtigungslevel, um Berechtigungen in diesem Raum zu ändern", + "To change the topic, you must be a": "Notwendiges Berechtigungslevel, um das Thema zu ändern", + "To modify widgets in the room, you must be a": "Notwendiges Berechtigungslevel, um Widgets in diesem Raum zu ändern", "Description": "Beschreibung", "Unable to accept invite": "Einladung kann nicht akzeptiert werden", "Failed to invite users to %(groupId)s": "Benutzer konnten nicht in %(groupId)s eingeladen werden", @@ -835,7 +835,7 @@ "Failed to add the following rooms to %(groupId)s:": "Die folgenden Räume konnten %(groupId)s nicht hinzugefügt werden:", "Matrix Room ID": "Matrix-Raum-ID", "email address": "E-Mail-Adresse", - "Try using one of the following valid address types: %(validTypesList)s.": "Versuche eine der folgenden validen Adresstypen zu benutzen: %(validTypesList)s.", + "Try using one of the following valid address types: %(validTypesList)s.": "Bitte einen der folgenden gültigen Adresstypen benutzen: %(validTypesList)s.", "Failed to remove '%(roomName)s' from %(groupId)s": "Entfernen von '%(roomName)s' aus %(groupId)s fehlgeschlagen", "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Bist du sicher, dass du '%(roomName)s' aus '%(groupId)s' entfernen möchtest?", "Invites sent": "Einladungen gesendet", @@ -845,17 +845,17 @@ "Pinned Messages": "Angeheftete Nachrichten", "%(senderName)s changed the pinned messages for the room.": "%(senderName)s hat die angehefteten Nachrichten für diesen Raum geändert.", "Jump to read receipt": "Zur Lesebestätigung springen", - "Message Pinning": "Nachricht-Anheftung", + "Message Pinning": "Anheften von Nachrichten", "Publish this community on your profile": "Diese Community in deinem Profil veröffentlichen", "Long Description (HTML)": "Lange Beschreibung (HTML)", "Jump to message": "Zur Nachricht springen", - "No pinned messages.": "Keine angehefteten Nachrichten.", + "No pinned messages.": "Keine angehefteten Nachrichten vorhanden.", "Loading...": "Lade...", "Unpin Message": "Nachricht nicht mehr anheften", "Unnamed room": "Unbenannter Raum", "World readable": "Lesbar für alle", "Guests can join": "Gäste können beitreten", - "No rooms to show": "Keine anzeigbaren Räume vorhanden", + "No rooms to show": "Keine anzeigbaren Räume", "Community Settings": "Community-Einstellungen", "Community Member Settings": "Community-Mitglieder-Einstellungen", "Who would you like to add to this community?": "Wen möchtest du zu dieser Community hinzufügen?", @@ -896,7 +896,7 @@ "You are an administrator of this community": "Du bist ein Administrator dieser Community", "Community %(groupId)s not found": "Community '%(groupId)s' nicht gefunden", "This Home server does not support communities": "Dieser Heimserver unterstützt keine Communities", - "Failed to load %(groupId)s": "Laden von '%(groupId)s' fehlgeschlagen", + "Failed to load %(groupId)s": "'%(groupId)s' konnte nicht geladen werden", "Error whilst fetching joined communities": "Fehler beim Laden beigetretener Communities", "Create a new community": "Neue Community erstellen", "Create a community to represent your community! Define a set of rooms and your own custom homepage to mark out your space in the Matrix universe.": "Erzeuge eine Community um deine Community zu repräsentieren! Definiere eine Menge von Räumen und deine eigene angepasste Startseite um dein Revier im Matrix-Universum zu markieren.", From 0dac8b0aa2c3e655cdfdf0cd10a672022df440df Mon Sep 17 00:00:00 2001 From: Bamstam Date: Fri, 27 Oct 2017 15:25:36 +0000 Subject: [PATCH 094/365] Translated using Weblate (German) Currently translated at 100.0% (912 of 912 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 49db6f8ded..faf8e6afb7 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -910,8 +910,8 @@ "And %(count)s more...|other": "Und %(count)s weitere...", "Delete Widget": "Widget löschen", "Message removed": "Nachricht entfernt", - "Mention": "Erwähnung", - "Invite": "Einladung", + "Mention": "Erwähnen", + "Invite": "Einladen", "Remove this room from the community": "Entferne diesen Raum von der Community", "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Das Löschen eines Widgets entfernt es für alle Nutzer in diesem Raum. Bist du sicher, dass du dieses Widget löschen willst?", "Mirror local video feed": "Spiegele lokalen Video-Feed" From 0a7273bf1df7ee1c3216288da8003bc43debd9cc Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Fri, 27 Oct 2017 16:38:49 +0100 Subject: [PATCH 095/365] Add URL search paramas polyfill --- package-lock.json | 5 +++++ package.json | 1 + 2 files changed, 6 insertions(+) diff --git a/package-lock.json b/package-lock.json index 6dd02674be..5810e571f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6241,6 +6241,11 @@ } } }, + "url-search-params": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/url-search-params/-/url-search-params-0.10.0.tgz", + "integrity": "sha512-oFPzmbPAbdthStgffGq8alULe47skPDt1X3KW6NOQnKkcLHP4IS1NfdfHG/CBP5lGsr2gDzNp87pfWLx/eIxjw==" + }, "user-home": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", diff --git a/package.json b/package.json index 883fdae8d5..9c3dddf346 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "sanitize-html": "^1.14.1", "text-encoding-utf-8": "^1.0.1", "url": "^0.11.0", + "url-search-params": "^0.10.0", "velocity-vector": "vector-im/velocity#059e3b2", "whatwg-fetch": "^1.0.0" }, From 138a61c9ac490e7492996849299ec1789ba4ed2a Mon Sep 17 00:00:00 2001 From: Krombel Date: Fri, 27 Oct 2017 15:26:56 +0000 Subject: [PATCH 096/365] Translated using Weblate (German) Currently translated at 100.0% (912 of 912 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index faf8e6afb7..747361eb9e 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -798,7 +798,7 @@ "Unignore": "Entignorieren", "User Options": "Benutzer-Optionen", "Unignored user": "Benutzer nicht mehr ignoriert", - "Ignored user": "Ignorierte Benutzer", + "Ignored user": "Benutzer ignoriert", "Stops ignoring a user, showing their messages going forward": "Beendet das Ignorieren eines Benutzers, nachfolgende Nachrichten werden wieder angezeigt", "Ignores a user, hiding their messages from you": "Ignoriert einen Benutzer und verbirgt dessen Nachrichten", "Disable Emoji suggestions while typing": "Emoji-Vorschläge während des Schreibens deaktivieren", From 3756ce606d4809d0dcc9dfe5dc458487cd3a1f8e Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Fri, 27 Oct 2017 17:49:14 +0100 Subject: [PATCH 097/365] Check URL prop updates and ensure that widgets are refreshed. --- src/components/views/elements/AppTile.js | 75 ++++++++++++++---------- 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 7da0407c00..c2f252ce3a 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -17,6 +17,7 @@ limitations under the License. 'use strict'; import url from 'url'; +import URLSearchParams from 'url-search-params'; import React from 'react'; import MatrixClientPeg from '../../../MatrixClientPeg'; import PlatformPeg from '../../../PlatformPeg'; @@ -51,28 +52,32 @@ export default React.createClass({ creatorUserId: React.PropTypes.string, }, - getDefaultProps: function() { + getDefaultProps() { return { url: "", }; }, - getInitialState: function() { - const widgetPermissionId = [this.props.room.roomId, encodeURIComponent(this.props.url)].join('_'); - const hasPermissionToLoad = localStorage.getItem(widgetPermissionId); - return { - loading: false, - widgetUrl: this.props.url, - widgetPermissionId: widgetPermissionId, - // Assume that widget has permission to load if we are the user who added it to the room, or if explicitly granted by the user - hasPermissionToLoad: hasPermissionToLoad === 'true' || this.props.userId === this.props.creatorUserId, - error: null, - deleting: false, - }; + _getInitialState() { + const widgetPermissionId = [this.props.room.roomId, encodeURIComponent(this.props.url)].join('_'); + const hasPermissionToLoad = localStorage.getItem(widgetPermissionId); + return { + loading: true, + widgetUrl: this.props.url, + widgetPermissionId: widgetPermissionId, + // Assume that widget has permission to load if we are the user who added it to the room, or if explicitly granted by the user + hasPermissionToLoad: hasPermissionToLoad === 'true' || this.props.userId === this.props.creatorUserId, + error: null, + deleting: false, + }; + }, + + getInitialState() { + return this._getInitialState(); }, // Returns true if props.url is a scalar URL, typically https://scalar.vector.im/api - isScalarUrl: function() { + isScalarUrl() { let scalarUrls = SdkConfig.get().integrations_widgets_urls; if (!scalarUrls || scalarUrls.length == 0) { scalarUrls = [SdkConfig.get().integrations_rest_url]; @@ -86,7 +91,7 @@ export default React.createClass({ return false; }, - isMixedContent: function() { + isMixedContent() { const parentContentProtocol = window.location.protocol; const u = url.parse(this.props.url); const childContentProtocol = u.protocol; @@ -98,17 +103,16 @@ export default React.createClass({ return false; }, - componentWillMount: function() { + componentWillMount() { window.addEventListener('message', this._onMessage, false); this.updateWidgetContent(); }, // Update widget content updateWidgetContent() { + this.setState(this._getInitialState()); + // Set scalar token on the wUrl, if needed this.setScalarToken(); - this.setState({ - loading: true, - }); }, // Adds a scalar token to the widget URL, if required @@ -121,13 +125,13 @@ export default React.createClass({ this._scalarClient = new ScalarAuthClient(); } this._scalarClient.getScalarToken().done((token) => { - // Append scalar_token as a query param + // Append scalar_token as a query param if not already present this._scalarClient.scalarToken = token; const u = url.parse(this.props.url); - if (!u.search) { - u.search = "?scalar_token=" + encodeURIComponent(token); - } else { - u.search += "&scalar_token=" + encodeURIComponent(token); + const params = new URLSearchParams(u.search); + if (!params.get('scalar_token')) { + params.set('scalar_token', encodeURIComponent(token)); + u.search = params.toString(); } this.setState({ @@ -147,8 +151,10 @@ export default React.createClass({ window.removeEventListener('message', this._onMessage); }, - componentWillReceiveProps(nextProps) { - console.warn("Apptile", this.id, "got new props", this.url, nextProps.url); + componentDidUpdate(prevProps) { + if (prevProps.url !== this.props.url) { + this.updateWidgetContent(); + } }, _onMessage(event) { @@ -170,11 +176,11 @@ export default React.createClass({ } }, - _canUserModify: function() { + _canUserModify() { return WidgetUtils.canUserModifyWidgets(this.props.room.roomId); }, - _onEditClick: function(e) { + _onEditClick(e) { console.log("Edit widget ID ", this.props.id); const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); const src = this._scalarClient.getScalarInterfaceUrlForRoom( @@ -186,7 +192,7 @@ export default React.createClass({ /* If user has permission to modify widgets, delete the widget, otherwise revoke access for the widget to load in the user's browser */ - _onDeleteClick: function() { + _onDeleteClick() { if (this._canUserModify()) { // Show delete confirmation dialog const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); @@ -218,6 +224,10 @@ export default React.createClass({ } }, + _onLoaded() { + this.setState({loading: false}); + }, + // Widget labels to render, depending upon user permissions // These strings are translated at the point that they are inserted in to the DOM, in the render method _deleteWidgetLabel() { @@ -240,7 +250,7 @@ export default React.createClass({ this.setState({hasPermissionToLoad: false}); }, - formatAppTileName: function() { + formatAppTileName() { let appTileName = "No name"; if(this.props.name && this.props.name.trim()) { appTileName = this.props.name.trim(); @@ -248,7 +258,7 @@ export default React.createClass({ return appTileName; }, - onClickMenuBar: function(ev) { + onClickMenuBar(ev) { ev.preventDefault(); // Ignore clicks on menu bar children @@ -263,7 +273,7 @@ export default React.createClass({ }); }, - render: function() { + render() { let appTileBody; // Don't render widget if it is in the process of being deleted @@ -349,6 +359,7 @@ export default React.createClass({ alt={_t('Edit')} title={_t('Edit')} onClick={this._onEditClick} + onLoad={this._onLoaded} /> } { /* Delete widget */ } From 41257020c085b5d1f5bde78c7b963df2df917eaf Mon Sep 17 00:00:00 2001 From: Walter Date: Fri, 27 Oct 2017 17:01:20 +0000 Subject: [PATCH 098/365] Translated using Weblate (Ukrainian) Currently translated at 11.0% (101 of 912 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/uk/ --- src/i18n/strings/uk.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/uk.json b/src/i18n/strings/uk.json index 1ef3ee1edd..7e1a7352b5 100644 --- a/src/i18n/strings/uk.json +++ b/src/i18n/strings/uk.json @@ -96,5 +96,9 @@ "Email address": "Адреса е-почти", "Email address (optional)": "Адреса е-почти (не обов'язково)", "Email, name or matrix ID": "Е-почта, ім'я або matrix ID", - "Failed to send email": "Помилка відправки е-почти" + "Failed to send email": "Помилка відправки е-почти", + "Edit": "Редактувати", + "Unpin Message": "Відкріпити повідомлення", + "Register": "Зарегіструватись", + "Rooms": "Кімнати" } From 5312a869e4d6a48564c3cf50adf2e7b6f044a1c0 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 27 Oct 2017 18:36:59 +0100 Subject: [PATCH 099/365] Try lowercase username on login Fixes https://github.com/vector-im/riot-web/issues/5446 --- src/Login.js | 74 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 19 deletions(-) diff --git a/src/Login.js b/src/Login.js index 0eff94ce60..f1fb9a5b61 100644 --- a/src/Login.js +++ b/src/Login.js @@ -143,6 +143,48 @@ export default class Login { Object.assign(loginParams, legacyParams); const client = this._createTemporaryClient(); + + const tryFallbackHs = (originalError) => { + const fbClient = Matrix.createClient({ + baseUrl: self._fallbackHsUrl, + idBaseUrl: this._isUrl, + }); + + return fbClient.login('m.login.password', loginParams).then(function(data) { + return Promise.resolve({ + homeserverUrl: self._fallbackHsUrl, + identityServerUrl: self._isUrl, + userId: data.user_id, + deviceId: data.device_id, + accessToken: data.access_token, + }); + }, function(fallback_error) { + // throw the original error + throw originalError; + }); + }; + const tryLowercaseUsername = (originalError) => { + const loginParamsLowercase = Object.assign({}, loginParams, { + user: username.toLowerCase(), + identifier: { + user: username.toLowerCase(), + }, + }); + return client.login('m.login.password', loginParamsLowercase).then(function(data) { + return Promise.resolve({ + homeserverUrl: self._fallbackHsUrl, + identityServerUrl: self._isUrl, + userId: data.user_id, + deviceId: data.device_id, + accessToken: data.access_token, + }); + }, function(fallback_error) { + // throw the original error + throw originalError; + }); + }; + + let originalLoginError = null; return client.login('m.login.password', loginParams).then(function(data) { return Promise.resolve({ homeserverUrl: self._hsUrl, @@ -151,29 +193,23 @@ export default class Login { deviceId: data.device_id, accessToken: data.access_token, }); - }, function(error) { + }).catch((error) => { + originalLoginError = error; if (error.httpStatus === 403) { if (self._fallbackHsUrl) { - const fbClient = Matrix.createClient({ - baseUrl: self._fallbackHsUrl, - idBaseUrl: this._isUrl, - }); - - return fbClient.login('m.login.password', loginParams).then(function(data) { - return Promise.resolve({ - homeserverUrl: self._fallbackHsUrl, - identityServerUrl: self._isUrl, - userId: data.user_id, - deviceId: data.device_id, - accessToken: data.access_token, - }); - }, function(fallback_error) { - // throw the original error - throw error; - }); + return tryFallbackHs(originalLoginError); } } - throw error; + throw originalLoginError; + }).catch((error) => { + if ( + error.httpStatus === 403 && + loginParams.identifier.type === 'm.id.user' && + username.search(/[A-Z]/) > -1 + ) { + return tryLowercaseUsername(originalLoginError); + } + throw originalLoginError; }); } From d72f70dcfd7ee9b0f4f254528dbeacd9bd2a1b12 Mon Sep 17 00:00:00 2001 From: Walter Date: Fri, 27 Oct 2017 17:05:49 +0000 Subject: [PATCH 100/365] Translated using Weblate (Ukrainian) Currently translated at 11.1% (102 of 912 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/uk/ --- src/i18n/strings/uk.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/uk.json b/src/i18n/strings/uk.json index 7e1a7352b5..07bed1fd62 100644 --- a/src/i18n/strings/uk.json +++ b/src/i18n/strings/uk.json @@ -6,7 +6,7 @@ "Dismiss": "Відхилити", "Drop here %(toAction)s": "Кидайте сюди %(toAction)s", "Error": "Помилка", - "Failed to forget room %(errCode)s": "Не вдалось забути кімнату %(errCode)s", + "Failed to forget room %(errCode)s": "Не вдалось видалити кімнату %(errCode)s", "Favourite": "Вибране", "Mute": "Стишити", "Notifications": "Сповіщення", @@ -100,5 +100,6 @@ "Edit": "Редактувати", "Unpin Message": "Відкріпити повідомлення", "Register": "Зарегіструватись", - "Rooms": "Кімнати" + "Rooms": "Кімнати", + "Add rooms to this community": "Добавити кімнати в це суспільство" } From b437a2559dd62207272bda52221c46e7c4543230 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 27 Oct 2017 18:59:13 +0100 Subject: [PATCH 101/365] PR feedback --- src/Login.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Login.js b/src/Login.js index f1fb9a5b61..9039c9f511 100644 --- a/src/Login.js +++ b/src/Login.js @@ -158,7 +158,7 @@ export default class Login { deviceId: data.device_id, accessToken: data.access_token, }); - }, function(fallback_error) { + }).catch((fallback_error) => { // throw the original error throw originalError; }); @@ -172,13 +172,13 @@ export default class Login { }); return client.login('m.login.password', loginParamsLowercase).then(function(data) { return Promise.resolve({ - homeserverUrl: self._fallbackHsUrl, + homeserverUrl: self._hsUrl, identityServerUrl: self._isUrl, userId: data.user_id, deviceId: data.device_id, accessToken: data.access_token, }); - }, function(fallback_error) { + }).catch((fallback_error) => { // throw the original error throw originalError; }); From 8a2155822fb09242fb9caf6acf5c5003c511a4b8 Mon Sep 17 00:00:00 2001 From: grrgui Date: Fri, 27 Oct 2017 22:01:06 +0000 Subject: [PATCH 102/365] Translated using Weblate (French) Currently translated at 89.6% (818 of 912 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/fr/ --- src/i18n/strings/fr.json | 48 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index 815960c4cc..19d3b527e3 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -370,7 +370,7 @@ "Warning!": "Attention !", "Who can access this room?": "Qui peut accéder au salon ?", "Who can read history?": "Qui peut lire l'historique ?", - "Who would you like to add to this room?": "Qui voulez-vous inviter dans ce salon ?", + "Who would you like to add to this room?": "Qui voulez-vous ajouter à ce salon ?", "Who would you like to communicate with?": "Avec qui voulez-vous communiquer ?", "%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s a annulé l’invitation de %(targetName)s.", "You are already in a call.": "Vous avez déjà un appel en cours.", @@ -774,5 +774,49 @@ "Failed to copy": "Échec de la copie", "Verifies a user, device, and pubkey tuple": "Vérifie un utilisateur, un appareil et une clé publique", "%(widgetName)s widget modified by %(senderName)s": "Widget %(widgetName)s modifié par %(senderName)s", - "Robot check is currently unavailable on desktop - please use a web browser": "La vérification robot n'est pas encore disponible pour le bureau - veuillez utiliser un navigateur" + "Robot check is currently unavailable on desktop - please use a web browser": "La vérification robot n'est pas encore disponible pour le bureau - veuillez utiliser un navigateur", + "Who would you like to add to this community?": "Qui souhaitez-vous ajouter à cette communauté ?", + "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Attention : toute personne ajoutée à une communauté sera visible par tous ceux connaissant l'identifiant de la communauté", + "Invite new community members": "Inviter de nouveaux membres dans cette communauté", + "Name or matrix ID": "Nom ou identifiant matrix", + "Which rooms would you like to add to this community?": "Quels salons souhaitez-vous ajouter à cette communauté ?", + "Warning: any room you add to a community will be publicly visible to anyone who knows the community ID": "Attention : tout salon ajouté à une communauté est visible par quiconque connaissant l'identifiant de communauté", + "Add rooms to the community": "Ajouter des salons à la communauté", + "Room name or alias": "Nom du salon ou alias", + "Add to community": "Ajouter à la communauté", + "Failed to invite the following users to %(groupId)s:": "Échec de l'invitation des utilisateurs à %(groupId)s :", + "Failed to invite users to community": "Échec de l'invitation d'utilisateurs à la communauté", + "Failed to invite users to %(groupId)s": "Échec de l'invitation d'utilisateurs à %(groupId)s", + "Failed to add the following rooms to %(groupId)s:": "Échec de l'ajout des salons suivants à %(groupId)s :", + "Ignored user": "Utilisateur ignoré", + "You are now ignoring %(userId)s": "Dorénavant vous ignorez %(userId)s", + "Unignored user": "Utilisateur n'étant plus ignoré", + "You are no longer ignoring %(userId)s": "Vous n'ignorez plus %(userId)s", + "Invite to Community": "Inviter dans la Communauté", + "Communities": "Communautés", + "Message Pinning": "Épingler un message", + "Mention": "Mentionner", + "Unignore": "Cessez d'ignorer", + "Ignore": "Ignorer", + "Invite": "Inviter", + "User Options": "Options d'utilisateur", + "Admin Tools": "Outils d'administration", + "Unpin Message": "Dépingler le message", + "Jump to message": "Aller au message", + "No pinned messages.": "Aucun message épinglé.", + "Loading...": "Chargement...", + "Pinned Messages": "Messages épinglés", + "Unknown": "Inconnu", + "Unnamed room": "Salon sans nom", + "No rooms to show": "Aucun salon à afficher", + "Remove avatar": "Supprimer l'avatar", + "To change the room's avatar, you must be a": "Pour modifier l'avatar du salon, vous devez être un", + "To change the room's name, you must be a": "Pour changer le nom du salon, vous devez être un", + "To change the room's main address, you must be a": "Pour changer l'adresse principale du salon, vous devez être un", + "To change the room's history visibility, you must be a": "Pour changer la visibilité de l'historique d'un salon, vous devez être un", + "To change the permissions in the room, you must be a": "Pour changer les autorisations du salon, vous devez être un", + "To change the topic, you must be a": "Pour changer le sujet, vous devez être un", + "To modify widgets in the room, you must be a": "Pour modifier les widgets vous devez être un", + "Banned by %(displayName)s": "Banni par %(displayName)s", + "To send messages, you must be a": "Pour envoyer des messages, vous devez être un" } From 8c3e5ebbad4ff59ce7f28f08b8942e0cf6355d89 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sun, 22 Oct 2017 20:15:20 -0600 Subject: [PATCH 103/365] Create GranularSettingStore GranularSettingStore is a class to manage settings of varying granularity, such as URL previews at the device, room, and account levels. Signed-off-by: Travis Ralston --- src/GranularSettingStore.js | 426 ++++++++++++++++++++++++++++++++++++ 1 file changed, 426 insertions(+) create mode 100644 src/GranularSettingStore.js diff --git a/src/GranularSettingStore.js b/src/GranularSettingStore.js new file mode 100644 index 0000000000..9e8bbf093e --- /dev/null +++ b/src/GranularSettingStore.js @@ -0,0 +1,426 @@ +/* +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 Promise from 'bluebird'; +import MatrixClientPeg from './MatrixClientPeg'; + +const SETTINGS = [ + /* + // EXAMPLE SETTING + { + name: "my-setting", + type: "room", // or "account" + ignoreLevels: [], // options: "device", "room-account", "account", "room" + // "room-account" and "room" don't apply for `type: account`. + defaults: { + your: "defaults", + }, + }, + */ + + // TODO: Populate this +]; + +// This controls the priority of particular handlers. Handler order should match the +// documentation throughout this file, as should the `types`. The priority is directly +// related to the index in the map, where index 0 is highest preference. +const PRIORITY_MAP = [ + {level: 'device', settingClass: DeviceSetting, types: ['room', 'account']}, + {level: 'room-account', settingClass: RoomAccountSetting, types: ['room']}, + {level: 'account', settingClass: AccountSetting, types: ['room', 'account']}, + {level: 'room', settingClass: RoomSetting, types: ['room']}, + {level: 'default', settingClass: DefaultSetting, types: ['room', 'account']}, + + // TODO: Add support for 'legacy' settings (old events, etc) + // TODO: Labs handler? (or make UserSettingsStore use this as a backend) +]; + +/** + * Controls and manages Granular Settings through use of localStorage, account data, + * and room state. Granular Settings are user settings that can have overrides at + * particular levels, notably the device, account, and room level. With the topmost + * level being the preferred setting, the override procedure is: + * - localstorage (per-device) + * - room account data (per-account in room) + * - account data (per-account) + * - room state event (per-room) + * - default (defined by Riot) + * + * There are two types of settings: Account and Room. + * + * Account Settings use the same override procedure described above, but drop the room + * account data and room state event checks. Account Settings are best used for things + * like which theme the user would prefer. + * + * Room Settings use the exact override procedure described above. Room Settings are + * best suited for settings which room administrators may want to define a default + * for members of the room, such as the case is with URL previews. Room Settings may + * also elect to not allow the room state event check, allowing for per-room settings + * that are not defaulted by the room administrator. + */ +export default class GranularSettingStore { + /** + * Gets the content for an account setting. + * @param {string} name The name of the setting to lookup + * @returns {Promise<*>} Resolves to the content for the setting, or null if the + * value cannot be found. + */ + static getAccountSetting(name) { + const handlers = GranularSettingStore._getHandlers('account'); + const initFn = settingClass => new settingClass('account', name); + return GranularSettingStore._iterateHandlers(handlers, initFn); + } + + /** + * Gets the content for a room setting. + * @param {string} name The name of the setting to lookup + * @param {string} roomId The room ID to lookup the setting for + * @returns {Promise<*>} Resolves to the content for the setting, or null if the + * value cannot be found. + */ + static getRoomSetting(name, roomId) { + const handlers = GranularSettingStore._getHandlers('room'); + const initFn = settingClass => new settingClass('room', name, roomId); + return GranularSettingStore._iterateHandlers(handlers, initFn); + } + + static _iterateHandlers(handlers, initFn) { + let index = 0; + const wrapperFn = () => { + // If we hit the end with no result, return 'not found' + if (handlers.length >= index) return null; + + // Get the handler, increment the index, and create a setting object + const handler = handlers[index++]; + const setting = initFn(handler.settingClass); + + // Try to read the value of the setting. If we get nothing for a value, + // then try the next handler. Otherwise, return the value early. + return Promise.resolve(setting.getValue()).then(value => { + if (!value) return wrapperFn(); + return value; + }); + }; + return wrapperFn(); + } + + /** + * Sets the content for a particular account setting at a given level in the hierarchy. + * If the setting does not exist at the given level, this will attempt to create it. The + * default level may not be modified. + * @param {string} name The name of the setting. + * @param {string} level The level to set the value of. Either 'device' or 'account'. + * @param {Object} content The value for the setting, or null to clear the level's value. + * @returns {Promise} Resolves when completed + */ + static setAccountSetting(name, level, content) { + const handler = GranularSettingStore._getHandler('account', level); + if (!handler) throw new Error("Missing account setting handler for " + name + " at " + level); + + const setting = new handler.settingClass('account', name); + return Promise.resolve(setting.setValue(content)); + } + + /** + * Sets the content for a particular room setting at a given level in the hierarchy. If + * the setting does not exist at the given level, this will attempt to create it. The + * default level may not be modified. + * @param {string} name The name of the setting. + * @param {string} level The level to set the value of. One of 'device', 'room-account', + * 'account', or 'room'. + * @param {string} roomId The room ID to set the value of. + * @param {Object} content The value for the setting, or null to clear the level's value. + * @returns {Promise} Resolves when completed + */ + static setRoomSetting(name, level, roomId, content) { + const handler = GranularSettingStore._getHandler('room', level); + if (!handler) throw new Error("Missing room setting handler for " + name + " at " + level); + + const setting = new handler.settingClass('room', name, roomId); + return Promise.resolve(setting.setValue(content)); + } + + /** + * Checks to ensure the current user may set the given account setting. + * @param {string} name The name of the setting. + * @param {string} level The level to check at. Either 'device' or 'account'. + * @returns {boolean} Whether or not the current user may set the account setting value. + */ + static canSetAccountSetting(name, level) { + const handler = GranularSettingStore._getHandler('account', level); + if (!handler) return false; + + const setting = new handler.settingClass('account', name); + return setting.canSetValue(); + } + + /** + * Checks to ensure the current user may set the given room setting. + * @param {string} name The name of the setting. + * @param {string} level The level to check at. One of 'device', 'room-account', 'account', + * or 'room'. + * @param {string} roomId The room ID to check in. + * @returns {boolean} Whether or not the current user may set the room setting value. + */ + static canSetRoomSetting(name, level, roomId) { + const handler = GranularSettingStore._getHandler('room', level); + if (!handler) return false; + + const setting = new handler.settingClass('room', name, roomId); + return setting.canSetValue(); + } + + /** + * Removes an account setting at a given level, forcing the level to inherit from an + * earlier stage in the hierarchy. + * @param {string} name The name of the setting. + * @param {string} level The level to clear. Either 'device' or 'account'. + */ + static removeAccountSetting(name, level) { + // This is just a convenience method. + GranularSettingStore.setAccountSetting(name, level, null); + } + + /** + * Removes a room setting at a given level, forcing the level to inherit from an earlier + * stage in the hierarchy. + * @param {string} name The name of the setting. + * @param {string} level The level to clear. One of 'device', 'room-account', 'account', + * or 'room'. + * @param {string} roomId The room ID to clear the setting on. + */ + static removeRoomSetting(name, level, roomId) { + // This is just a convenience method. + GranularSettingStore.setRoomSetting(name, level, roomId, null); + } + + /** + * Determines whether or not a particular level is supported on the current platform. + * @param {string} level The level to check. One of 'device', 'room-account', 'account', + * 'room', or 'default'. + * @returns {boolean} Whether or not the level is supported. + */ + static isLevelSupported(level) { + return GranularSettingStore._getHandlersAtLevel(level).length > 0; + } + + static _getHandlersAtLevel(level) { + return PRIORITY_MAP.filter(h => h.level === level && h.settingClass.isSupported()); + } + + static _getHandlers(type) { + return PRIORITY_MAP.filter(h => { + if (!h.types.includes(type)) return false; + if (!h.settingClass.isSupported()) return false; + + return true; + }); + } + + static _getHandler(type, level) { + const handlers = GranularSettingStore._getHandlers(type); + return handlers.filter(h => h.level === level)[0]; + } +} + +// Validate of properties is assumed to be done well prior to instantiation of these classes, +// therefore these classes don't do any sanity checking. The following interface is assumed: +// constructor(type, name, roomId) - roomId may be null for type=='account' +// getValue() - returns a promise for the value. Falsey resolves are treated as 'not found'. +// setValue(content) - sets the new value for the setting. Falsey should remove the value. +// canSetValue() - returns true if the current user can set this setting. +// static isSupported() - returns true if the setting type is supported + +class DefaultSetting { + constructor(type, name, roomId = null) { + this.type = type; + this.name = name; + this.roomId = roomId; + } + + getValue() { + for (let setting of SETTINGS) { + if (setting.type === this.type && setting.name === this.name) { + return setting.defaults; + } + } + + return null; + } + + setValue() { + throw new Error("Operation not permitted: Cannot set value of a default setting."); + } + + canSetValue() { + // It's a default, so no, you can't. + return false; + } + + static isSupported() { + return true; // defaults are always accepted + } +} + +class DeviceSetting { + constructor(type, name, roomId = null) { + this.type = type; + this.name = name; + this.roomId = roomId; + } + + _getKey() { + return "mx_setting_" + this.name + "_" + this.type; + } + + getValue() { + if (!localStorage) return null; + const value = localStorage.getItem(this._getKey()); + if (!value) return null; + return JSON.parse(value); + } + + setValue(content) { + if (!localStorage) throw new Error("Operation not possible: No device storage available."); + if (!content) localStorage.removeItem(this._getKey()); + else localStorage.setItem(this._getKey(), JSON.stringify(content)); + } + + canSetValue() { + // The user likely has control over their own localstorage. + return true; + } + + static isSupported() { + // We can only do something if we have localstorage + return !!localStorage; + } +} + +class RoomAccountSetting { + constructor(type, name, roomId = null) { + this.type = type; + this.name = name; + this.roomId = roomId; + } + + _getEventType() { + return "im.vector.setting." + this.type + "." + this.name; + } + + getValue() { + if (!MatrixClientPeg.get()) return null; + + const room = MatrixClientPeg.getRoom(this.roomId); + if (!room) return null; + + const event = room.getAccountData(this._getEventType()); + if (!event || !event.getContent()) return null; + + return event.getContent(); + } + + setValue(content) { + if (!MatrixClientPeg.get()) throw new Error("Operation not possible: No client peg"); + return MatrixClientPeg.get().setRoomAccountData(this.roomId, this._getEventType(), content); + } + + canSetValue() { + // It's their own room account data, so they should be able to set it. + return true; + } + + static isSupported() { + // We can only do something if we have a client + return !!MatrixClientPeg.get(); + } +} + +class AccountSetting { + constructor(type, name, roomId = null) { + this.type = type; + this.name = name; + this.roomId = roomId; + } + + _getEventType() { + return "im.vector.setting." + this.type + "." + this.name; + } + + getValue() { + if (!MatrixClientPeg.get()) return null; + return MatrixClientPeg.getAccountData(this._getEventType()); + } + + setValue(content) { + if (!MatrixClientPeg.get()) throw new Error("Operation not possible: No client peg"); + return MatrixClientPeg.setAccountData(this._getEventType(), content); + } + + canSetValue() { + // It's their own account data, so they should be able to set it + return true; + } + + static isSupported() { + // We can only do something if we have a client + return !!MatrixClientPeg.get(); + } +} + +class RoomSetting { + constructor(type, name, roomId = null) { + this.type = type; + this.name = name; + this.roomId = roomId; + } + + _getEventType() { + return "im.vector.setting." + this.type + "." + this.name; + } + + getValue() { + if (!MatrixClientPeg.get()) return null; + + const room = MatrixClientPeg.get().getRoom(this.roomId); + if (!room) return null; + + const stateEvent = room.currentState.getStateEvents(this._getEventType(), ""); + if (!stateEvent || !stateEvent.getContent()) return null; + + return stateEvent.getContent(); + } + + setValue(content) { + if (!MatrixClientPeg.get()) throw new Error("Operation not possible: No client peg"); + + return MatrixClientPeg.get().sendStateEvent(this.roomId, this._getEventType(), content, ""); + } + + canSetValue() { + const cli = MatrixClientPeg.get(); + + const room = cli.getRoom(this.roomId); + if (!room) return false; // They're not in the room, likely. + + return room.currentState.maySendStateEvent(this._getEventType(), cli.getUserId()); + } + + static isSupported() { + // We can only do something if we have a client + return !!MatrixClientPeg.get(); + } +} From e02dcae3b65aec1d3bb8502ef9b815254d449ebd Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sun, 22 Oct 2017 21:00:35 -0600 Subject: [PATCH 104/365] Change wording to better describe the class Signed-off-by: Travis Ralston --- src/GranularSettingStore.js | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/GranularSettingStore.js b/src/GranularSettingStore.js index 9e8bbf093e..c6e7de2b1e 100644 --- a/src/GranularSettingStore.js +++ b/src/GranularSettingStore.js @@ -49,27 +49,28 @@ const PRIORITY_MAP = [ ]; /** - * Controls and manages Granular Settings through use of localStorage, account data, - * and room state. Granular Settings are user settings that can have overrides at - * particular levels, notably the device, account, and room level. With the topmost - * level being the preferred setting, the override procedure is: - * - localstorage (per-device) - * - room account data (per-account in room) - * - account data (per-account) - * - room state event (per-room) - * - default (defined by Riot) + * Controls and manages application settings at different levels through a variety of + * backends. Settings may be overridden at each level to provide the user with more + * options for customization and tailoring of their experience. These levels are most + * notably at the device, room, and account levels. The preferred order of levels is: + * - per-device + * - per-account in a particular room + * - per-account + * - per-room + * - defaults (as defined here) * * There are two types of settings: Account and Room. * - * Account Settings use the same override procedure described above, but drop the room - * account data and room state event checks. Account Settings are best used for things - * like which theme the user would prefer. + * Account Settings use the same preferences described above, but do not look at the + * per-account in a particular room or the per-room levels. Account Settings are best + * used for things like which theme the user would prefer. * - * Room Settings use the exact override procedure described above. Room Settings are - * best suited for settings which room administrators may want to define a default - * for members of the room, such as the case is with URL previews. Room Settings may - * also elect to not allow the room state event check, allowing for per-room settings - * that are not defaulted by the room administrator. + * Room settings use the exact preferences described above. Room Settings are best + * suited for settings which room administrators may want to define a default for the + * room members, or where users may want an individual room to be different. Using the + * setting definitions, particular preferences may be excluded to prevent, for example, + * room administrators from defining that all messages should have timestamps when the + * user may not want that. An example of a Room Setting would be URL previews. */ export default class GranularSettingStore { /** From c43bf336a932654003612e5e73075223be8e1dca Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sun, 22 Oct 2017 21:07:01 -0600 Subject: [PATCH 105/365] Appease the linter Signed-off-by: Travis Ralston --- src/GranularSettingStore.js | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/GranularSettingStore.js b/src/GranularSettingStore.js index c6e7de2b1e..96b06fc66c 100644 --- a/src/GranularSettingStore.js +++ b/src/GranularSettingStore.js @@ -81,7 +81,7 @@ export default class GranularSettingStore { */ static getAccountSetting(name) { const handlers = GranularSettingStore._getHandlers('account'); - const initFn = settingClass => new settingClass('account', name); + const initFn = (SettingClass) => new SettingClass('account', name); return GranularSettingStore._iterateHandlers(handlers, initFn); } @@ -94,7 +94,7 @@ export default class GranularSettingStore { */ static getRoomSetting(name, roomId) { const handlers = GranularSettingStore._getHandlers('room'); - const initFn = settingClass => new settingClass('room', name, roomId); + const initFn = (SettingClass) => new SettingClass('room', name, roomId); return GranularSettingStore._iterateHandlers(handlers, initFn); } @@ -110,7 +110,7 @@ export default class GranularSettingStore { // Try to read the value of the setting. If we get nothing for a value, // then try the next handler. Otherwise, return the value early. - return Promise.resolve(setting.getValue()).then(value => { + return Promise.resolve(setting.getValue()).then((value) => { if (!value) return wrapperFn(); return value; }); @@ -131,7 +131,8 @@ export default class GranularSettingStore { const handler = GranularSettingStore._getHandler('account', level); if (!handler) throw new Error("Missing account setting handler for " + name + " at " + level); - const setting = new handler.settingClass('account', name); + const SettingClass = handler.settingClass; + const setting = new SettingClass('account', name); return Promise.resolve(setting.setValue(content)); } @@ -150,7 +151,8 @@ export default class GranularSettingStore { const handler = GranularSettingStore._getHandler('room', level); if (!handler) throw new Error("Missing room setting handler for " + name + " at " + level); - const setting = new handler.settingClass('room', name, roomId); + const SettingClass = handler.settingClass; + const setting = new SettingClass('room', name, roomId); return Promise.resolve(setting.setValue(content)); } @@ -164,7 +166,8 @@ export default class GranularSettingStore { const handler = GranularSettingStore._getHandler('account', level); if (!handler) return false; - const setting = new handler.settingClass('account', name); + const SettingClass = handler.settingClass; + const setting = new SettingClass('account', name); return setting.canSetValue(); } @@ -180,7 +183,8 @@ export default class GranularSettingStore { const handler = GranularSettingStore._getHandler('room', level); if (!handler) return false; - const setting = new handler.settingClass('room', name, roomId); + const SettingClass = handler.settingClass; + const setting = new SettingClass('room', name, roomId); return setting.canSetValue(); } @@ -219,11 +223,11 @@ export default class GranularSettingStore { } static _getHandlersAtLevel(level) { - return PRIORITY_MAP.filter(h => h.level === level && h.settingClass.isSupported()); + return PRIORITY_MAP.filter((h) => h.level === level && h.settingClass.isSupported()); } static _getHandlers(type) { - return PRIORITY_MAP.filter(h => { + return PRIORITY_MAP.filter((h) => { if (!h.types.includes(type)) return false; if (!h.settingClass.isSupported()) return false; @@ -233,7 +237,7 @@ export default class GranularSettingStore { static _getHandler(type, level) { const handlers = GranularSettingStore._getHandlers(type); - return handlers.filter(h => h.level === level)[0]; + return handlers.filter((h) => h.level === level)[0]; } } @@ -253,7 +257,7 @@ class DefaultSetting { } getValue() { - for (let setting of SETTINGS) { + for (const setting of SETTINGS) { if (setting.type === this.type && setting.name === this.name) { return setting.defaults; } From 672fbb287316641bdcccf0b6314a40bd165f23a9 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sat, 28 Oct 2017 18:33:38 +0100 Subject: [PATCH 106/365] hopefully fix NPE on toLowerCase --- src/components/structures/login/Registration.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/structures/login/Registration.js b/src/components/structures/login/Registration.js index 17204ee4e8..2403610416 100644 --- a/src/components/structures/login/Registration.js +++ b/src/components/structures/login/Registration.js @@ -303,7 +303,9 @@ module.exports = React.createClass({ } : {}; return this._matrixClient.register( - this.state.formVals.username.toLowerCase(), + (this.state.formVals.username ? + this.state.formVals.username.toLowerCase() : + this.state.formVals.username), this.state.formVals.password, undefined, // session id: included in the auth dict already auth, From 989bdcf5fbe800f4e64fa952608aeef6cfe3b2a3 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 28 Oct 2017 19:13:06 -0600 Subject: [PATCH 107/365] Rebuild SettingsStore to be better supported This does away with the room- and account-style settings, and just replaces them with `supportedLevels`. The handlers have also been moved out to be in better support of the other options, like SdkConfig and per-room-per-device. Signed-off-by: Travis Ralston --- src/GranularSettingStore.js | 431 --------------------- src/settings/AccountSettingsHandler.js | 47 +++ src/settings/ConfigSettingsHandler.js | 43 ++ src/settings/DefaultSettingsHandler.js | 51 +++ src/settings/DeviceSettingsHandler.js | 90 +++++ src/settings/RoomAccountSettingsHandler.js | 52 +++ src/settings/RoomDeviceSettingsHandler.js | 52 +++ src/settings/RoomSettingsHandler.js | 56 +++ src/settings/SettingsHandler.js | 70 ++++ src/settings/SettingsStore.js | 275 +++++++++++++ 10 files changed, 736 insertions(+), 431 deletions(-) delete mode 100644 src/GranularSettingStore.js create mode 100644 src/settings/AccountSettingsHandler.js create mode 100644 src/settings/ConfigSettingsHandler.js create mode 100644 src/settings/DefaultSettingsHandler.js create mode 100644 src/settings/DeviceSettingsHandler.js create mode 100644 src/settings/RoomAccountSettingsHandler.js create mode 100644 src/settings/RoomDeviceSettingsHandler.js create mode 100644 src/settings/RoomSettingsHandler.js create mode 100644 src/settings/SettingsHandler.js create mode 100644 src/settings/SettingsStore.js diff --git a/src/GranularSettingStore.js b/src/GranularSettingStore.js deleted file mode 100644 index 96b06fc66c..0000000000 --- a/src/GranularSettingStore.js +++ /dev/null @@ -1,431 +0,0 @@ -/* -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 Promise from 'bluebird'; -import MatrixClientPeg from './MatrixClientPeg'; - -const SETTINGS = [ - /* - // EXAMPLE SETTING - { - name: "my-setting", - type: "room", // or "account" - ignoreLevels: [], // options: "device", "room-account", "account", "room" - // "room-account" and "room" don't apply for `type: account`. - defaults: { - your: "defaults", - }, - }, - */ - - // TODO: Populate this -]; - -// This controls the priority of particular handlers. Handler order should match the -// documentation throughout this file, as should the `types`. The priority is directly -// related to the index in the map, where index 0 is highest preference. -const PRIORITY_MAP = [ - {level: 'device', settingClass: DeviceSetting, types: ['room', 'account']}, - {level: 'room-account', settingClass: RoomAccountSetting, types: ['room']}, - {level: 'account', settingClass: AccountSetting, types: ['room', 'account']}, - {level: 'room', settingClass: RoomSetting, types: ['room']}, - {level: 'default', settingClass: DefaultSetting, types: ['room', 'account']}, - - // TODO: Add support for 'legacy' settings (old events, etc) - // TODO: Labs handler? (or make UserSettingsStore use this as a backend) -]; - -/** - * Controls and manages application settings at different levels through a variety of - * backends. Settings may be overridden at each level to provide the user with more - * options for customization and tailoring of their experience. These levels are most - * notably at the device, room, and account levels. The preferred order of levels is: - * - per-device - * - per-account in a particular room - * - per-account - * - per-room - * - defaults (as defined here) - * - * There are two types of settings: Account and Room. - * - * Account Settings use the same preferences described above, but do not look at the - * per-account in a particular room or the per-room levels. Account Settings are best - * used for things like which theme the user would prefer. - * - * Room settings use the exact preferences described above. Room Settings are best - * suited for settings which room administrators may want to define a default for the - * room members, or where users may want an individual room to be different. Using the - * setting definitions, particular preferences may be excluded to prevent, for example, - * room administrators from defining that all messages should have timestamps when the - * user may not want that. An example of a Room Setting would be URL previews. - */ -export default class GranularSettingStore { - /** - * Gets the content for an account setting. - * @param {string} name The name of the setting to lookup - * @returns {Promise<*>} Resolves to the content for the setting, or null if the - * value cannot be found. - */ - static getAccountSetting(name) { - const handlers = GranularSettingStore._getHandlers('account'); - const initFn = (SettingClass) => new SettingClass('account', name); - return GranularSettingStore._iterateHandlers(handlers, initFn); - } - - /** - * Gets the content for a room setting. - * @param {string} name The name of the setting to lookup - * @param {string} roomId The room ID to lookup the setting for - * @returns {Promise<*>} Resolves to the content for the setting, or null if the - * value cannot be found. - */ - static getRoomSetting(name, roomId) { - const handlers = GranularSettingStore._getHandlers('room'); - const initFn = (SettingClass) => new SettingClass('room', name, roomId); - return GranularSettingStore._iterateHandlers(handlers, initFn); - } - - static _iterateHandlers(handlers, initFn) { - let index = 0; - const wrapperFn = () => { - // If we hit the end with no result, return 'not found' - if (handlers.length >= index) return null; - - // Get the handler, increment the index, and create a setting object - const handler = handlers[index++]; - const setting = initFn(handler.settingClass); - - // Try to read the value of the setting. If we get nothing for a value, - // then try the next handler. Otherwise, return the value early. - return Promise.resolve(setting.getValue()).then((value) => { - if (!value) return wrapperFn(); - return value; - }); - }; - return wrapperFn(); - } - - /** - * Sets the content for a particular account setting at a given level in the hierarchy. - * If the setting does not exist at the given level, this will attempt to create it. The - * default level may not be modified. - * @param {string} name The name of the setting. - * @param {string} level The level to set the value of. Either 'device' or 'account'. - * @param {Object} content The value for the setting, or null to clear the level's value. - * @returns {Promise} Resolves when completed - */ - static setAccountSetting(name, level, content) { - const handler = GranularSettingStore._getHandler('account', level); - if (!handler) throw new Error("Missing account setting handler for " + name + " at " + level); - - const SettingClass = handler.settingClass; - const setting = new SettingClass('account', name); - return Promise.resolve(setting.setValue(content)); - } - - /** - * Sets the content for a particular room setting at a given level in the hierarchy. If - * the setting does not exist at the given level, this will attempt to create it. The - * default level may not be modified. - * @param {string} name The name of the setting. - * @param {string} level The level to set the value of. One of 'device', 'room-account', - * 'account', or 'room'. - * @param {string} roomId The room ID to set the value of. - * @param {Object} content The value for the setting, or null to clear the level's value. - * @returns {Promise} Resolves when completed - */ - static setRoomSetting(name, level, roomId, content) { - const handler = GranularSettingStore._getHandler('room', level); - if (!handler) throw new Error("Missing room setting handler for " + name + " at " + level); - - const SettingClass = handler.settingClass; - const setting = new SettingClass('room', name, roomId); - return Promise.resolve(setting.setValue(content)); - } - - /** - * Checks to ensure the current user may set the given account setting. - * @param {string} name The name of the setting. - * @param {string} level The level to check at. Either 'device' or 'account'. - * @returns {boolean} Whether or not the current user may set the account setting value. - */ - static canSetAccountSetting(name, level) { - const handler = GranularSettingStore._getHandler('account', level); - if (!handler) return false; - - const SettingClass = handler.settingClass; - const setting = new SettingClass('account', name); - return setting.canSetValue(); - } - - /** - * Checks to ensure the current user may set the given room setting. - * @param {string} name The name of the setting. - * @param {string} level The level to check at. One of 'device', 'room-account', 'account', - * or 'room'. - * @param {string} roomId The room ID to check in. - * @returns {boolean} Whether or not the current user may set the room setting value. - */ - static canSetRoomSetting(name, level, roomId) { - const handler = GranularSettingStore._getHandler('room', level); - if (!handler) return false; - - const SettingClass = handler.settingClass; - const setting = new SettingClass('room', name, roomId); - return setting.canSetValue(); - } - - /** - * Removes an account setting at a given level, forcing the level to inherit from an - * earlier stage in the hierarchy. - * @param {string} name The name of the setting. - * @param {string} level The level to clear. Either 'device' or 'account'. - */ - static removeAccountSetting(name, level) { - // This is just a convenience method. - GranularSettingStore.setAccountSetting(name, level, null); - } - - /** - * Removes a room setting at a given level, forcing the level to inherit from an earlier - * stage in the hierarchy. - * @param {string} name The name of the setting. - * @param {string} level The level to clear. One of 'device', 'room-account', 'account', - * or 'room'. - * @param {string} roomId The room ID to clear the setting on. - */ - static removeRoomSetting(name, level, roomId) { - // This is just a convenience method. - GranularSettingStore.setRoomSetting(name, level, roomId, null); - } - - /** - * Determines whether or not a particular level is supported on the current platform. - * @param {string} level The level to check. One of 'device', 'room-account', 'account', - * 'room', or 'default'. - * @returns {boolean} Whether or not the level is supported. - */ - static isLevelSupported(level) { - return GranularSettingStore._getHandlersAtLevel(level).length > 0; - } - - static _getHandlersAtLevel(level) { - return PRIORITY_MAP.filter((h) => h.level === level && h.settingClass.isSupported()); - } - - static _getHandlers(type) { - return PRIORITY_MAP.filter((h) => { - if (!h.types.includes(type)) return false; - if (!h.settingClass.isSupported()) return false; - - return true; - }); - } - - static _getHandler(type, level) { - const handlers = GranularSettingStore._getHandlers(type); - return handlers.filter((h) => h.level === level)[0]; - } -} - -// Validate of properties is assumed to be done well prior to instantiation of these classes, -// therefore these classes don't do any sanity checking. The following interface is assumed: -// constructor(type, name, roomId) - roomId may be null for type=='account' -// getValue() - returns a promise for the value. Falsey resolves are treated as 'not found'. -// setValue(content) - sets the new value for the setting. Falsey should remove the value. -// canSetValue() - returns true if the current user can set this setting. -// static isSupported() - returns true if the setting type is supported - -class DefaultSetting { - constructor(type, name, roomId = null) { - this.type = type; - this.name = name; - this.roomId = roomId; - } - - getValue() { - for (const setting of SETTINGS) { - if (setting.type === this.type && setting.name === this.name) { - return setting.defaults; - } - } - - return null; - } - - setValue() { - throw new Error("Operation not permitted: Cannot set value of a default setting."); - } - - canSetValue() { - // It's a default, so no, you can't. - return false; - } - - static isSupported() { - return true; // defaults are always accepted - } -} - -class DeviceSetting { - constructor(type, name, roomId = null) { - this.type = type; - this.name = name; - this.roomId = roomId; - } - - _getKey() { - return "mx_setting_" + this.name + "_" + this.type; - } - - getValue() { - if (!localStorage) return null; - const value = localStorage.getItem(this._getKey()); - if (!value) return null; - return JSON.parse(value); - } - - setValue(content) { - if (!localStorage) throw new Error("Operation not possible: No device storage available."); - if (!content) localStorage.removeItem(this._getKey()); - else localStorage.setItem(this._getKey(), JSON.stringify(content)); - } - - canSetValue() { - // The user likely has control over their own localstorage. - return true; - } - - static isSupported() { - // We can only do something if we have localstorage - return !!localStorage; - } -} - -class RoomAccountSetting { - constructor(type, name, roomId = null) { - this.type = type; - this.name = name; - this.roomId = roomId; - } - - _getEventType() { - return "im.vector.setting." + this.type + "." + this.name; - } - - getValue() { - if (!MatrixClientPeg.get()) return null; - - const room = MatrixClientPeg.getRoom(this.roomId); - if (!room) return null; - - const event = room.getAccountData(this._getEventType()); - if (!event || !event.getContent()) return null; - - return event.getContent(); - } - - setValue(content) { - if (!MatrixClientPeg.get()) throw new Error("Operation not possible: No client peg"); - return MatrixClientPeg.get().setRoomAccountData(this.roomId, this._getEventType(), content); - } - - canSetValue() { - // It's their own room account data, so they should be able to set it. - return true; - } - - static isSupported() { - // We can only do something if we have a client - return !!MatrixClientPeg.get(); - } -} - -class AccountSetting { - constructor(type, name, roomId = null) { - this.type = type; - this.name = name; - this.roomId = roomId; - } - - _getEventType() { - return "im.vector.setting." + this.type + "." + this.name; - } - - getValue() { - if (!MatrixClientPeg.get()) return null; - return MatrixClientPeg.getAccountData(this._getEventType()); - } - - setValue(content) { - if (!MatrixClientPeg.get()) throw new Error("Operation not possible: No client peg"); - return MatrixClientPeg.setAccountData(this._getEventType(), content); - } - - canSetValue() { - // It's their own account data, so they should be able to set it - return true; - } - - static isSupported() { - // We can only do something if we have a client - return !!MatrixClientPeg.get(); - } -} - -class RoomSetting { - constructor(type, name, roomId = null) { - this.type = type; - this.name = name; - this.roomId = roomId; - } - - _getEventType() { - return "im.vector.setting." + this.type + "." + this.name; - } - - getValue() { - if (!MatrixClientPeg.get()) return null; - - const room = MatrixClientPeg.get().getRoom(this.roomId); - if (!room) return null; - - const stateEvent = room.currentState.getStateEvents(this._getEventType(), ""); - if (!stateEvent || !stateEvent.getContent()) return null; - - return stateEvent.getContent(); - } - - setValue(content) { - if (!MatrixClientPeg.get()) throw new Error("Operation not possible: No client peg"); - - return MatrixClientPeg.get().sendStateEvent(this.roomId, this._getEventType(), content, ""); - } - - canSetValue() { - const cli = MatrixClientPeg.get(); - - const room = cli.getRoom(this.roomId); - if (!room) return false; // They're not in the room, likely. - - return room.currentState.maySendStateEvent(this._getEventType(), cli.getUserId()); - } - - static isSupported() { - // We can only do something if we have a client - return !!MatrixClientPeg.get(); - } -} diff --git a/src/settings/AccountSettingsHandler.js b/src/settings/AccountSettingsHandler.js new file mode 100644 index 0000000000..6352da5ccc --- /dev/null +++ b/src/settings/AccountSettingsHandler.js @@ -0,0 +1,47 @@ +/* +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 Promise from 'bluebird'; +import SettingsHandler from "./SettingsHandler"; +import MatrixClientPeg from '../MatrixClientPeg'; + +/** + * Gets and sets settings at the "account" level for the current user. + * This handler does not make use of the roomId parameter. + */ +export default class AccountSettingHandler extends SettingsHandler { + getValue(settingName, roomId) { + const value = MatrixClientPeg.get().getAccountData(this._getEventType(settingName)); + if (!value) return Promise.reject(); + return Promise.resolve(value); + } + + setValue(settingName, roomId, newValue) { + return MatrixClientPeg.get().setAccountData(this._getEventType(settingName), newValue); + } + + canSetValue(settingName, roomId) { + return true; // It's their account, so they should be able to + } + + isSupported() { + return !!MatrixClientPeg.get(); + } + + _getEventType(settingName) { + return "im.vector.setting." + settingName; + } +} \ No newline at end of file diff --git a/src/settings/ConfigSettingsHandler.js b/src/settings/ConfigSettingsHandler.js new file mode 100644 index 0000000000..8f0cc9041b --- /dev/null +++ b/src/settings/ConfigSettingsHandler.js @@ -0,0 +1,43 @@ +/* +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 Promise from 'bluebird'; +import SettingsHandler from "./SettingsHandler"; +import SdkConfig from "../SdkConfig"; + +/** + * Gets and sets settings at the "config" level. This handler does not make use of the + * roomId parameter. + */ +export default class ConfigSettingsHandler extends SettingsHandler { + getValue(settingName, roomId) { + const settingsConfig = SdkConfig.get()["settingDefaults"]; + if (!settingsConfig || !settingsConfig[settingName]) return Promise.reject(); + return Promise.resolve(settingsConfig[settingName]); + } + + setValue(settingName, roomId, newValue) { + throw new Error("Cannot change settings at the config level"); + } + + canSetValue(settingName, roomId) { + return false; + } + + isSupported() { + return true; // SdkConfig is always there + } +} \ No newline at end of file diff --git a/src/settings/DefaultSettingsHandler.js b/src/settings/DefaultSettingsHandler.js new file mode 100644 index 0000000000..06937fd957 --- /dev/null +++ b/src/settings/DefaultSettingsHandler.js @@ -0,0 +1,51 @@ +/* +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 Promise from 'bluebird'; +import SettingsHandler from "./SettingsHandler"; + +/** + * Gets settings at the "default" level. This handler does not support setting values. + * This handler does not make use of the roomId parameter. + */ +export default class DefaultSettingsHandler extends SettingsHandler { + /** + * Creates a new default settings handler with the given defaults + * @param {object} defaults The default setting values, keyed by setting name. + */ + constructor(defaults) { + super(); + this._defaults = defaults; + } + + getValue(settingName, roomId) { + const value = this._defaults[settingName]; + if (!value) return Promise.reject(); + return Promise.resolve(value); + } + + setValue(settingName, roomId, newValue) { + throw new Error("Cannot set values on the default level handler"); + } + + canSetValue(settingName, roomId) { + return false; + } + + isSupported() { + return true; + } +} \ No newline at end of file diff --git a/src/settings/DeviceSettingsHandler.js b/src/settings/DeviceSettingsHandler.js new file mode 100644 index 0000000000..83cf88bcba --- /dev/null +++ b/src/settings/DeviceSettingsHandler.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 Promise from 'bluebird'; +import SettingsHandler from "./SettingsHandler"; +import MatrixClientPeg from "../MatrixClientPeg"; + +/** + * Gets and sets settings at the "device" level for the current device. + * This handler does not make use of the roomId parameter. This handler + * will special-case features to support legacy settings. + */ +export default class DeviceSettingsHandler extends SettingsHandler { + /** + * Creates a new device settings handler + * @param {string[]} featureNames The names of known features. + */ + constructor(featureNames) { + super(); + this._featureNames = featureNames; + } + + getValue(settingName, roomId) { + if (this._featureNames.includes(settingName)) { + return Promise.resolve(this._readFeature(settingName)); + } + + const value = localStorage.getItem(this._getKey(settingName)); + if (!value) return Promise.reject(); + return Promise.resolve(value); + } + + setValue(settingName, roomId, newValue) { + if (this._featureNames.includes(settingName)) { + return Promise.resolve(this._writeFeature(settingName)); + } + + if (newValue === null) { + localStorage.removeItem(this._getKey(settingName)); + } else { + localStorage.setItem(this._getKey(settingName), newValue); + } + + return Promise.resolve(); + } + + canSetValue(settingName, roomId) { + return true; // It's their device, so they should be able to + } + + isSupported() { + return !!localStorage; + } + + _getKey(settingName) { + return "mx_setting_" + settingName; + } + + // Note: features intentionally don't use the same key as settings to avoid conflicts + // and to be backwards compatible. + + _readFeature(featureName) { + if (MatrixClientPeg.get() && MatrixClientPeg.get().isGuest()) { + // Guests should not have any labs features enabled. + return {enabled: false}; + } + + const value = localStorage.getItem("mx_labs_feature_" + featureName); + const enabled = value === "true"; + + return {enabled}; + } + + _writeFeature(featureName, enabled) { + localStorage.setItem("mx_labs_feature_" + featureName, enabled); + } +} \ No newline at end of file diff --git a/src/settings/RoomAccountSettingsHandler.js b/src/settings/RoomAccountSettingsHandler.js new file mode 100644 index 0000000000..7157d86c34 --- /dev/null +++ b/src/settings/RoomAccountSettingsHandler.js @@ -0,0 +1,52 @@ +/* +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 Promise from 'bluebird'; +import SettingsHandler from "./SettingsHandler"; +import MatrixClientPeg from '../MatrixClientPeg'; + +/** + * Gets and sets settings at the "room-account" level for the current user. + */ +export default class RoomAccountSettingsHandler extends SettingsHandler { + getValue(settingName, roomId) { + const room = MatrixClientPeg.get().getRoom(roomId); + if (!room) return Promise.reject(); + + const value = room.getAccountData(this._getEventType(settingName)); + if (!value) return Promise.reject(); + return Promise.resolve(value); + } + + setValue(settingName, roomId, newValue) { + return MatrixClientPeg.get().setRoomAccountData( + roomId, this._getEventType(settingName), newValue + ); + } + + canSetValue(settingName, roomId) { + const room = MatrixClientPeg.get().getRoom(roomId); + return !!room; // If they have the room, they can set their own account data + } + + isSupported() { + return !!MatrixClientPeg.get(); + } + + _getEventType(settingName) { + return "im.vector.setting." + settingName; + } +} \ No newline at end of file diff --git a/src/settings/RoomDeviceSettingsHandler.js b/src/settings/RoomDeviceSettingsHandler.js new file mode 100644 index 0000000000..fe477564f6 --- /dev/null +++ b/src/settings/RoomDeviceSettingsHandler.js @@ -0,0 +1,52 @@ +/* +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 Promise from 'bluebird'; +import SettingsHandler from "./SettingsHandler"; + +/** + * Gets and sets settings at the "room-device" level for the current device in a particular + * room. + */ +export default class RoomDeviceSettingsHandler extends SettingsHandler { + getValue(settingName, roomId) { + const value = localStorage.getItem(this._getKey(settingName, roomId)); + if (!value) return Promise.reject(); + return Promise.resolve(value); + } + + setValue(settingName, roomId, newValue) { + if (newValue === null) { + localStorage.removeItem(this._getKey(settingName, roomId)); + } else { + localStorage.setItem(this._getKey(settingName, roomId), newValue); + } + + return Promise.resolve(); + } + + canSetValue(settingName, roomId) { + return true; // It's their device, so they should be able to + } + + isSupported() { + return !!localStorage; + } + + _getKey(settingName, roomId) { + return "mx_setting_" + settingName + "_" + roomId; + } +} \ No newline at end of file diff --git a/src/settings/RoomSettingsHandler.js b/src/settings/RoomSettingsHandler.js new file mode 100644 index 0000000000..dcd7a76e87 --- /dev/null +++ b/src/settings/RoomSettingsHandler.js @@ -0,0 +1,56 @@ +/* +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 Promise from 'bluebird'; +import SettingsHandler from "./SettingsHandler"; +import MatrixClientPeg from '../MatrixClientPeg'; + +/** + * Gets and sets settings at the "room" level. + */ +export default class RoomSettingsHandler extends SettingsHandler { + getValue(settingName, roomId) { + const room = MatrixClientPeg.get().getRoom(roomId); + if (!room) return Promise.reject(); + + const event = room.currentState.getStateEvents(this._getEventType(settingName), ""); + if (!event || !event.getContent()) return Promise.reject(); + return Promise.resolve(event.getContent()); + } + + setValue(settingName, roomId, newValue) { + return MatrixClientPeg.get().sendStateEvent( + roomId, this._getEventType(settingName), newValue, "" + ); + } + + canSetValue(settingName, roomId) { + const cli = MatrixClientPeg.get(); + const room = cli.getRoom(roomId); + const eventType = this._getEventType(settingName); + + if (!room) return false; + return room.currentState.maySendStateEvent(eventType, cli.getUserId()); + } + + isSupported() { + return !!MatrixClientPeg.get(); + } + + _getEventType(settingName) { + return "im.vector.setting." + settingName; + } +} \ No newline at end of file diff --git a/src/settings/SettingsHandler.js b/src/settings/SettingsHandler.js new file mode 100644 index 0000000000..7387712367 --- /dev/null +++ b/src/settings/SettingsHandler.js @@ -0,0 +1,70 @@ +/* +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 Promise from 'bluebird'; + +/** + * Represents the base class for all level handlers. This class performs no logic + * and should be overridden. + */ +export default class SettingsHandler { + /** + * Gets the value for a particular setting at this level for a particular room. + * If no room is applicable, the roomId may be null. The roomId may not be + * applicable to this level and may be ignored by the handler. + * @param {string} settingName The name of the setting. + * @param {String} roomId The room ID to read from, may be null. + * @return {Promise} Resolves to the setting value. Rejected if the value + * could not be found. + */ + getValue(settingName, roomId) { + throw new Error("Operation not possible: getValue was not overridden"); + } + + /** + * Sets the value for a particular setting at this level for a particular room. + * If no room is applicable, the roomId may be null. The roomId may not be + * applicable to this level and may be ignored by the handler. Setting a value + * to null will cause the level to remove the value. The current user should be + * able to set the value prior to calling this. + * @param {string} settingName The name of the setting to change. + * @param {String} roomId The room ID to set the value in, may be null. + * @param {Object} newValue The new value for the setting, may be null. + * @return {Promise} Resolves when the setting has been saved. + */ + setValue(settingName, roomId, newValue) { + throw new Error("Operation not possible: setValue was not overridden"); + } + + /** + * Determines if the current user is able to set the value of the given setting + * in the given room at this level. + * @param {string} settingName The name of the setting to check. + * @param {String} roomId The room ID to check in, may be null + * @returns {boolean} True if the setting can be set by the user, false otherwise. + */ + canSetValue(settingName, roomId) { + return false; + } + + /** + * Determines if this level is supported on this device. + * @returns {boolean} True if this level is supported on the current device. + */ + isSupported() { + return false; + } +} \ No newline at end of file diff --git a/src/settings/SettingsStore.js b/src/settings/SettingsStore.js new file mode 100644 index 0000000000..eea91345d8 --- /dev/null +++ b/src/settings/SettingsStore.js @@ -0,0 +1,275 @@ +/* +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 Promise from 'bluebird'; +import DeviceSettingsHandler from "./DeviceSettingsHandler"; +import RoomDeviceSettingsHandler from "./RoomDeviceSettingsHandler"; +import DefaultSettingsHandler from "./DefaultSettingsHandler"; +import RoomAccountSettingsHandler from "./RoomAccountSettingsHandler"; +import AccountSettingsHandler from "./AccountSettingsHandler"; +import RoomSettingsHandler from "./RoomSettingsHandler"; +import ConfigSettingsHandler from "./ConfigSettingsHandler"; +import {_t, _td} from '../languageHandler'; +import SdkConfig from "../SdkConfig"; + +// Preset levels for room-based settings (eg: URL previews). +// Doesn't include 'room' because most settings don't need it. Use .concat('room') to add. +const LEVELS_PRESET_ROOM = ['device', 'room-device', 'room-account', 'account']; + +// Preset levels for account-based settings (eg: interface language). +const LEVELS_PRESET_ACCOUNT = ['device', 'account']; + +// Preset levels for features (labs) settings. +const LEVELS_PRESET_FEATURE = ['device']; + +const SETTINGS = { + "my-setting": { + isFeature: false, // optional + displayName: _td("Cool Name"), + supportedLevels: [ + // The order does not matter. + + "device", // Affects the current device only + "room-device", // Affects the current room on the current device + "room-account", // Affects the current room for the current account + "account", // Affects the current account + "room", // Affects the current room (controlled by room admins) + + // "default" and "config" are always supported and do not get listed here. + ], + defaults: { + your: "value", + }, + }, + + // TODO: Populate this +}; + +// Convert the above into simpler formats for the handlers +let defaultSettings = {}; +let featureNames = []; +for (let key of Object.keys(SETTINGS)) { + defaultSettings[key] = SETTINGS[key].defaults; + if (SETTINGS[key].isFeature) featureNames.push(key); +} + +const LEVEL_HANDLERS = { + "device": new DeviceSettingsHandler(featureNames), + "room-device": new RoomDeviceSettingsHandler(), + "room-account": new RoomAccountSettingsHandler(), + "account": new AccountSettingsHandler(), + "room": new RoomSettingsHandler(), + "config": new ConfigSettingsHandler(), + "default": new DefaultSettingsHandler(defaultSettings), +}; + +/** + * Controls and manages application settings by providing varying levels at which the + * setting value may be specified. The levels are then used to determine what the setting + * value should be given a set of circumstances. The levels, in priority order, are: + * - "device" - Values are determined by the current device + * - "room-device" - Values are determined by the current device for a particular room + * - "room-account" - Values are determined by the current account for a particular room + * - "account" - Values are determined by the current account + * - "room" - Values are determined by a particular room (by the room admins) + * - "config" - Values are determined by the config.json + * - "default" - Values are determined by the hardcoded defaults + * + * Each level has a different method to storing the setting value. For implementation + * specific details, please see the handlers. The "config" and "default" levels are + * both always supported on all platforms. All other settings should be guarded by + * isLevelSupported() prior to attempting to set the value. + * + * Settings can also represent features. Features are significant portions of the + * application that warrant a dedicated setting to toggle them on or off. Features are + * special-cased to ensure that their values respect the configuration (for example, a + * feature may be reported as disabled even though a user has specifically requested it + * be enabled). + */ +export default class SettingsStore { + /** + * Gets the translated display name for a given setting + * @param {string} settingName The setting to look up. + * @return {String} The display name for the setting, or null if not found. + */ + static getDisplayName(settingName) { + if (!SETTINGS[settingName] || !SETTINGS[settingName].displayName) return null; + return _t(SETTINGS[settingName].displayName); + } + + /** + * Determines if a setting is also a feature. + * @param {string} settingName The setting to look up. + * @return {boolean} True if the setting is a feature. + */ + static isFeature(settingName) { + if (!SETTINGS[settingName]) return false; + return SETTINGS[settingName].isFeature; + } + + /** + * Determines if a given feature is enabled. The feature given must be a known + * feature. + * @param {string} settingName The name of the setting that is a feature. + * @param {String} roomId The optional room ID to validate in, may be null. + * @return {boolean} True if the feature is enabled, false otherwise + */ + static isFeatureEnabled(settingName, roomId = null) { + if (!SettingsStore.isFeature(settingName)) { + throw new Error("Setting " + settingName + " is not a feature"); + } + + // Synchronously get the setting value (which should be {enabled: true/false}) + const value = Promise.coroutine(function* () { + return yield SettingsStore.getValue(settingName, roomId); + })(); + + return value.enabled; + } + + /** + * Gets the value of a setting. The room ID is optional if the setting is not to + * be applied to any particular room, otherwise it should be supplied. + * @param {string} settingName The name of the setting to read the value of. + * @param {String} roomId The room ID to read the setting value in, may be null. + * @return {Promise<*>} Resolves to the value for the setting. May result in null. + */ + static getValue(settingName, roomId) { + const levelOrder = [ + 'device', 'room-device', 'room-account', 'account', 'room', 'config', 'default' + ]; + + if (SettingsStore.isFeature(settingName)) { + const configValue = SettingsStore._getFeatureState(settingName); + if (configValue === "enable") return Promise.resolve({enabled: true}); + if (configValue === "disable") return Promise.resolve({enabled: false}); + // else let it fall through the default process + } + + const handlers = SettingsStore._getHandlers(settingName); + + // This wrapper function allows for iterating over the levelOrder to find a suitable + // handler that is supported by the setting. It does this by building the promise chain + // on the fly, wrapping the rejection from handler.getValue() to try the next handler. + // If the last handler also rejects the getValue() call, then this wrapper will convert + // the reply to `null` as per our contract to the caller. + let index = 0; + const wrapperFn = () => { + // Find the next handler that we can use + let handler = null; + while (!handler && index < levelOrder.length) { + handler = handlers[levelOrder[index++]]; + } + + // No handler == no reply (happens when the last available handler rejects) + if (!handler) return null; + + // Get the value and see if the handler will reject us (meaning it doesn't have + // a value for us). + const value = handler.getValue(settingName, roomId); + return value.then(null, () => wrapperFn()); // pass success through + }; + + return wrapperFn(); + } + + /** + * Sets the value for a setting. The room ID is optional if the setting is not being + * set for a particular room, otherwise it should be supplied. The value may be null + * to indicate that the level should no longer have an override. + * @param {string} settingName The name of the setting to change. + * @param {String} roomId The room ID to change the value in, may be null. + * @param {"device"|"room-device"|"room-account"|"account"|"room"} level The level + * to change the value at. + * @param {Object} value The new value of the setting, may be null. + * @return {Promise} Resolves when the setting has been changed. + */ + static setValue(settingName, roomId, level, value) { + const handler = SettingsStore._getHandler(settingName, level); + if (!handler) { + throw new Error("Setting " + settingName + " does not have a handler for " + level); + } + + if (!handler.canSetValue(settingName, roomId)) { + throw new Error("User cannot set " + settingName + " at level " + level); + } + + return handler.setValue(settingName, roomId, value); + } + + /** + * Determines if the current user is permitted to set the given setting at the given + * level for a particular room. The room ID is optional if the setting is not being + * set for a particular room, otherwise it should be supplied. + * @param {string} settingName The name of the setting to check. + * @param {String} roomId The room ID to check in, may be null. + * @param {"device"|"room-device"|"room-account"|"account"|"room"} level The level to + * check at. + * @return {boolean} True if the user may set the setting, false otherwise. + */ + static canSetValue(settingName, roomId, level) { + const handler = SettingsStore._getHandler(settingName, level); + if (!handler) return false; + return handler.canSetValue(settingName, roomId); + } + + /** + * Determines if the given level is supported on this device. + * @param {"device"|"room-device"|"room-account"|"account"|"room"} level The level + * to check the feasibility of. + * @return {boolean} True if the level is supported, false otherwise. + */ + static isLevelSupported(level) { + if (!LEVEL_HANDLERS[level]) return false; + return LEVEL_HANDLERS[level].isSupported(); + } + + static _getHandler(settingName, level) { + const handlers = SettingsStore._getHandlers(settingName); + if (!handlers[level]) return null; + return handlers[level]; + } + + static _getHandlers(settingName) { + if (!SETTINGS[settingName]) return {}; + + const handlers = {}; + for (let level of SETTINGS[settingName].supportedLevels) { + if (!LEVEL_HANDLERS[level]) throw new Error("Unexpected level " + level); + handlers[level] = LEVEL_HANDLERS[level]; + } + + return handlers; + } + + static _getFeatureState(settingName) { + const featuresConfig = SdkConfig.get()['features']; + const enableLabs = SdkConfig.get()['enableLabs']; // we'll honour the old flag + + let featureState = enableLabs ? "labs" : "disable"; + if (featuresConfig && featuresConfig[settingName] !== undefined) { + featureState = featuresConfig[settingName]; + } + + const allowedStates = ['enable', 'disable', 'labs']; + if (!allowedStates.contains(featureState)) { + console.warn("Feature state '" + featureState + "' is invalid for " + settingName); + featureState = "disable"; // to prevent accidental features. + } + + return featureState; + } +} From 23d159e21cf2f2c623fe63dcb88468ed3b6a6ff7 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 28 Oct 2017 19:45:48 -0600 Subject: [PATCH 108/365] Make reading settings synchronous Signed-off-by: Travis Ralston --- src/settings/AccountSettingsHandler.js | 14 ++++---- src/settings/ConfigSettingsHandler.js | 4 +-- src/settings/DefaultSettingsHandler.js | 4 +-- src/settings/DeviceSettingsHandler.js | 12 +++---- src/settings/RoomAccountSettingsHandler.js | 22 ++++++------- src/settings/RoomDeviceSettingsHandler.js | 5 +-- src/settings/RoomSettingsHandler.js | 26 +++++++-------- src/settings/SettingsHandler.js | 2 +- src/settings/SettingsStore.js | 38 +++++++--------------- 9 files changed, 56 insertions(+), 71 deletions(-) diff --git a/src/settings/AccountSettingsHandler.js b/src/settings/AccountSettingsHandler.js index 6352da5ccc..c505afe31d 100644 --- a/src/settings/AccountSettingsHandler.js +++ b/src/settings/AccountSettingsHandler.js @@ -24,13 +24,13 @@ import MatrixClientPeg from '../MatrixClientPeg'; */ export default class AccountSettingHandler extends SettingsHandler { getValue(settingName, roomId) { - const value = MatrixClientPeg.get().getAccountData(this._getEventType(settingName)); - if (!value) return Promise.reject(); - return Promise.resolve(value); + return this._getSettings()[settingName]; } setValue(settingName, roomId, newValue) { - return MatrixClientPeg.get().setAccountData(this._getEventType(settingName), newValue); + const content = this._getSettings(); + content[settingName] = newValue; + return MatrixClientPeg.get().setAccountData("im.vector.web.settings", content); } canSetValue(settingName, roomId) { @@ -41,7 +41,9 @@ export default class AccountSettingHandler extends SettingsHandler { return !!MatrixClientPeg.get(); } - _getEventType(settingName) { - return "im.vector.setting." + settingName; + _getSettings() { + const event = MatrixClientPeg.get().getAccountData("im.vector.web.settings"); + if (!event || !event.getContent()) return {}; + return event.getContent(); } } \ No newline at end of file diff --git a/src/settings/ConfigSettingsHandler.js b/src/settings/ConfigSettingsHandler.js index 8f0cc9041b..5307a1dac1 100644 --- a/src/settings/ConfigSettingsHandler.js +++ b/src/settings/ConfigSettingsHandler.js @@ -25,8 +25,8 @@ import SdkConfig from "../SdkConfig"; export default class ConfigSettingsHandler extends SettingsHandler { getValue(settingName, roomId) { const settingsConfig = SdkConfig.get()["settingDefaults"]; - if (!settingsConfig || !settingsConfig[settingName]) return Promise.reject(); - return Promise.resolve(settingsConfig[settingName]); + if (!settingsConfig || !settingsConfig[settingName]) return null; + return settingsConfig[settingName]; } setValue(settingName, roomId, newValue) { diff --git a/src/settings/DefaultSettingsHandler.js b/src/settings/DefaultSettingsHandler.js index 06937fd957..0a4b8d91d3 100644 --- a/src/settings/DefaultSettingsHandler.js +++ b/src/settings/DefaultSettingsHandler.js @@ -32,9 +32,7 @@ export default class DefaultSettingsHandler extends SettingsHandler { } getValue(settingName, roomId) { - const value = this._defaults[settingName]; - if (!value) return Promise.reject(); - return Promise.resolve(value); + return this._defaults[settingName]; } setValue(settingName, roomId, newValue) { diff --git a/src/settings/DeviceSettingsHandler.js b/src/settings/DeviceSettingsHandler.js index 83cf88bcba..dbb833c570 100644 --- a/src/settings/DeviceSettingsHandler.js +++ b/src/settings/DeviceSettingsHandler.js @@ -35,12 +35,13 @@ export default class DeviceSettingsHandler extends SettingsHandler { getValue(settingName, roomId) { if (this._featureNames.includes(settingName)) { - return Promise.resolve(this._readFeature(settingName)); + return this._readFeature(settingName); } const value = localStorage.getItem(this._getKey(settingName)); - if (!value) return Promise.reject(); - return Promise.resolve(value); + if (!value) return null; + + return JSON.parse(value).value; } setValue(settingName, roomId, newValue) { @@ -51,6 +52,7 @@ export default class DeviceSettingsHandler extends SettingsHandler { if (newValue === null) { localStorage.removeItem(this._getKey(settingName)); } else { + newValue = JSON.stringify({value: newValue}); localStorage.setItem(this._getKey(settingName), newValue); } @@ -79,9 +81,7 @@ export default class DeviceSettingsHandler extends SettingsHandler { } const value = localStorage.getItem("mx_labs_feature_" + featureName); - const enabled = value === "true"; - - return {enabled}; + return value === "true"; } _writeFeature(featureName, enabled) { diff --git a/src/settings/RoomAccountSettingsHandler.js b/src/settings/RoomAccountSettingsHandler.js index 7157d86c34..3c775f3ff0 100644 --- a/src/settings/RoomAccountSettingsHandler.js +++ b/src/settings/RoomAccountSettingsHandler.js @@ -23,18 +23,13 @@ import MatrixClientPeg from '../MatrixClientPeg'; */ export default class RoomAccountSettingsHandler extends SettingsHandler { getValue(settingName, roomId) { - const room = MatrixClientPeg.get().getRoom(roomId); - if (!room) return Promise.reject(); - - const value = room.getAccountData(this._getEventType(settingName)); - if (!value) return Promise.reject(); - return Promise.resolve(value); + return this._getSettings(roomId)[settingName]; } setValue(settingName, roomId, newValue) { - return MatrixClientPeg.get().setRoomAccountData( - roomId, this._getEventType(settingName), newValue - ); + const content = this._getSettings(roomId); + content[settingName] = newValue; + return MatrixClientPeg.get().setRoomAccountData(roomId, "im.vector.web.settings", content); } canSetValue(settingName, roomId) { @@ -46,7 +41,12 @@ export default class RoomAccountSettingsHandler extends SettingsHandler { return !!MatrixClientPeg.get(); } - _getEventType(settingName) { - return "im.vector.setting." + settingName; + _getSettings(roomId) { + const room = MatrixClientPeg.get().getRoom(roomId); + if (!room) return {}; + + const event = room.getAccountData("im.vector.settings"); + if (!event || !event.getContent()) return {}; + return event.getContent(); } } \ No newline at end of file diff --git a/src/settings/RoomDeviceSettingsHandler.js b/src/settings/RoomDeviceSettingsHandler.js index fe477564f6..3ee83f2362 100644 --- a/src/settings/RoomDeviceSettingsHandler.js +++ b/src/settings/RoomDeviceSettingsHandler.js @@ -24,14 +24,15 @@ import SettingsHandler from "./SettingsHandler"; export default class RoomDeviceSettingsHandler extends SettingsHandler { getValue(settingName, roomId) { const value = localStorage.getItem(this._getKey(settingName, roomId)); - if (!value) return Promise.reject(); - return Promise.resolve(value); + if (!value) return null; + return JSON.parse(value).value; } setValue(settingName, roomId, newValue) { if (newValue === null) { localStorage.removeItem(this._getKey(settingName, roomId)); } else { + newValue = JSON.stringify({value: newValue}); localStorage.setItem(this._getKey(settingName, roomId), newValue); } diff --git a/src/settings/RoomSettingsHandler.js b/src/settings/RoomSettingsHandler.js index dcd7a76e87..c41a510646 100644 --- a/src/settings/RoomSettingsHandler.js +++ b/src/settings/RoomSettingsHandler.js @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import Promise from 'bluebird'; import SettingsHandler from "./SettingsHandler"; import MatrixClientPeg from '../MatrixClientPeg'; @@ -23,34 +22,33 @@ import MatrixClientPeg from '../MatrixClientPeg'; */ export default class RoomSettingsHandler extends SettingsHandler { getValue(settingName, roomId) { - const room = MatrixClientPeg.get().getRoom(roomId); - if (!room) return Promise.reject(); - - const event = room.currentState.getStateEvents(this._getEventType(settingName), ""); - if (!event || !event.getContent()) return Promise.reject(); - return Promise.resolve(event.getContent()); + return this._getSettings(roomId)[settingName]; } setValue(settingName, roomId, newValue) { - return MatrixClientPeg.get().sendStateEvent( - roomId, this._getEventType(settingName), newValue, "" - ); + const content = this._getSettings(roomId); + content[settingName] = newValue; + return MatrixClientPeg.get().sendStateEvent(roomId, "im.vector.web.settings", content, ""); } canSetValue(settingName, roomId) { const cli = MatrixClientPeg.get(); const room = cli.getRoom(roomId); - const eventType = this._getEventType(settingName); if (!room) return false; - return room.currentState.maySendStateEvent(eventType, cli.getUserId()); + return room.currentState.maySendStateEvent("im.vector.web.settings", cli.getUserId()); } isSupported() { return !!MatrixClientPeg.get(); } - _getEventType(settingName) { - return "im.vector.setting." + settingName; + _getSettings(roomId) { + const room = MatrixClientPeg.get().getRoom(roomId); + if (!room) return {}; + + const event = room.currentState.getStateEvents("im.vector.web.settings"); + if (!event || !event.getContent()) return {}; + return event.getContent(); } } \ No newline at end of file diff --git a/src/settings/SettingsHandler.js b/src/settings/SettingsHandler.js index 7387712367..20f09cb1f2 100644 --- a/src/settings/SettingsHandler.js +++ b/src/settings/SettingsHandler.js @@ -42,7 +42,7 @@ export default class SettingsHandler { * able to set the value prior to calling this. * @param {string} settingName The name of the setting to change. * @param {String} roomId The room ID to set the value in, may be null. - * @param {Object} newValue The new value for the setting, may be null. + * @param {*} newValue The new value for the setting, may be null. * @return {Promise} Resolves when the setting has been saved. */ setValue(settingName, roomId, newValue) { diff --git a/src/settings/SettingsStore.js b/src/settings/SettingsStore.js index eea91345d8..330c2def05 100644 --- a/src/settings/SettingsStore.js +++ b/src/settings/SettingsStore.js @@ -137,7 +137,7 @@ export default class SettingsStore { return yield SettingsStore.getValue(settingName, roomId); })(); - return value.enabled; + return value; } /** @@ -145,7 +145,7 @@ export default class SettingsStore { * be applied to any particular room, otherwise it should be supplied. * @param {string} settingName The name of the setting to read the value of. * @param {String} roomId The room ID to read the setting value in, may be null. - * @return {Promise<*>} Resolves to the value for the setting. May result in null. + * @return {*} The value, or null if not found */ static getValue(settingName, roomId) { const levelOrder = [ @@ -154,36 +154,22 @@ export default class SettingsStore { if (SettingsStore.isFeature(settingName)) { const configValue = SettingsStore._getFeatureState(settingName); - if (configValue === "enable") return Promise.resolve({enabled: true}); - if (configValue === "disable") return Promise.resolve({enabled: false}); + if (configValue === "enable") return true; + if (configValue === "disable") return false; // else let it fall through the default process } const handlers = SettingsStore._getHandlers(settingName); - // This wrapper function allows for iterating over the levelOrder to find a suitable - // handler that is supported by the setting. It does this by building the promise chain - // on the fly, wrapping the rejection from handler.getValue() to try the next handler. - // If the last handler also rejects the getValue() call, then this wrapper will convert - // the reply to `null` as per our contract to the caller. - let index = 0; - const wrapperFn = () => { - // Find the next handler that we can use - let handler = null; - while (!handler && index < levelOrder.length) { - handler = handlers[levelOrder[index++]]; - } + for (let level of levelOrder) { + let handler = handlers[level]; + if (!handler) continue; - // No handler == no reply (happens when the last available handler rejects) - if (!handler) return null; - - // Get the value and see if the handler will reject us (meaning it doesn't have - // a value for us). const value = handler.getValue(settingName, roomId); - return value.then(null, () => wrapperFn()); // pass success through - }; - - return wrapperFn(); + if (!value) continue; + return value; + } + return null; } /** @@ -194,7 +180,7 @@ export default class SettingsStore { * @param {String} roomId The room ID to change the value in, may be null. * @param {"device"|"room-device"|"room-account"|"account"|"room"} level The level * to change the value at. - * @param {Object} value The new value of the setting, may be null. + * @param {*} value The new value of the setting, may be null. * @return {Promise} Resolves when the setting has been changed. */ static setValue(settingName, roomId, level, value) { From 7dda5e9196710d82bd64284a9f895a1d29c46d69 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 28 Oct 2017 19:53:12 -0600 Subject: [PATCH 109/365] Appease the linter round 1 Signed-off-by: Travis Ralston --- src/settings/AccountSettingsHandler.js | 2 +- src/settings/ConfigSettingsHandler.js | 2 +- src/settings/DefaultSettingsHandler.js | 2 +- src/settings/DeviceSettingsHandler.js | 2 +- src/settings/RoomAccountSettingsHandler.js | 3 +-- src/settings/RoomDeviceSettingsHandler.js | 2 +- src/settings/RoomSettingsHandler.js | 2 +- src/settings/SettingsHandler.js | 9 +++------ src/settings/SettingsStore.js | 17 ++++++----------- 9 files changed, 16 insertions(+), 25 deletions(-) diff --git a/src/settings/AccountSettingsHandler.js b/src/settings/AccountSettingsHandler.js index c505afe31d..db25cd0737 100644 --- a/src/settings/AccountSettingsHandler.js +++ b/src/settings/AccountSettingsHandler.js @@ -46,4 +46,4 @@ export default class AccountSettingHandler extends SettingsHandler { if (!event || !event.getContent()) return {}; return event.getContent(); } -} \ No newline at end of file +} diff --git a/src/settings/ConfigSettingsHandler.js b/src/settings/ConfigSettingsHandler.js index 5307a1dac1..5cd6d22411 100644 --- a/src/settings/ConfigSettingsHandler.js +++ b/src/settings/ConfigSettingsHandler.js @@ -40,4 +40,4 @@ export default class ConfigSettingsHandler extends SettingsHandler { isSupported() { return true; // SdkConfig is always there } -} \ No newline at end of file +} diff --git a/src/settings/DefaultSettingsHandler.js b/src/settings/DefaultSettingsHandler.js index 0a4b8d91d3..2c3a05a18a 100644 --- a/src/settings/DefaultSettingsHandler.js +++ b/src/settings/DefaultSettingsHandler.js @@ -46,4 +46,4 @@ export default class DefaultSettingsHandler extends SettingsHandler { isSupported() { return true; } -} \ No newline at end of file +} diff --git a/src/settings/DeviceSettingsHandler.js b/src/settings/DeviceSettingsHandler.js index dbb833c570..329634a810 100644 --- a/src/settings/DeviceSettingsHandler.js +++ b/src/settings/DeviceSettingsHandler.js @@ -87,4 +87,4 @@ export default class DeviceSettingsHandler extends SettingsHandler { _writeFeature(featureName, enabled) { localStorage.setItem("mx_labs_feature_" + featureName, enabled); } -} \ No newline at end of file +} diff --git a/src/settings/RoomAccountSettingsHandler.js b/src/settings/RoomAccountSettingsHandler.js index 3c775f3ff0..e1edaffb90 100644 --- a/src/settings/RoomAccountSettingsHandler.js +++ b/src/settings/RoomAccountSettingsHandler.js @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import Promise from 'bluebird'; import SettingsHandler from "./SettingsHandler"; import MatrixClientPeg from '../MatrixClientPeg'; @@ -49,4 +48,4 @@ export default class RoomAccountSettingsHandler extends SettingsHandler { if (!event || !event.getContent()) return {}; return event.getContent(); } -} \ No newline at end of file +} diff --git a/src/settings/RoomDeviceSettingsHandler.js b/src/settings/RoomDeviceSettingsHandler.js index 3ee83f2362..b61e266a4a 100644 --- a/src/settings/RoomDeviceSettingsHandler.js +++ b/src/settings/RoomDeviceSettingsHandler.js @@ -50,4 +50,4 @@ export default class RoomDeviceSettingsHandler extends SettingsHandler { _getKey(settingName, roomId) { return "mx_setting_" + settingName + "_" + roomId; } -} \ No newline at end of file +} diff --git a/src/settings/RoomSettingsHandler.js b/src/settings/RoomSettingsHandler.js index c41a510646..91bcff384a 100644 --- a/src/settings/RoomSettingsHandler.js +++ b/src/settings/RoomSettingsHandler.js @@ -51,4 +51,4 @@ export default class RoomSettingsHandler extends SettingsHandler { if (!event || !event.getContent()) return {}; return event.getContent(); } -} \ No newline at end of file +} diff --git a/src/settings/SettingsHandler.js b/src/settings/SettingsHandler.js index 20f09cb1f2..49265feb9a 100644 --- a/src/settings/SettingsHandler.js +++ b/src/settings/SettingsHandler.js @@ -14,8 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import Promise from 'bluebird'; - /** * Represents the base class for all level handlers. This class performs no logic * and should be overridden. @@ -27,8 +25,7 @@ export default class SettingsHandler { * applicable to this level and may be ignored by the handler. * @param {string} settingName The name of the setting. * @param {String} roomId The room ID to read from, may be null. - * @return {Promise} Resolves to the setting value. Rejected if the value - * could not be found. + * @returns {*} The setting value, or null if not found. */ getValue(settingName, roomId) { throw new Error("Operation not possible: getValue was not overridden"); @@ -43,7 +40,7 @@ export default class SettingsHandler { * @param {string} settingName The name of the setting to change. * @param {String} roomId The room ID to set the value in, may be null. * @param {*} newValue The new value for the setting, may be null. - * @return {Promise} Resolves when the setting has been saved. + * @returns {Promise} Resolves when the setting has been saved. */ setValue(settingName, roomId, newValue) { throw new Error("Operation not possible: setValue was not overridden"); @@ -67,4 +64,4 @@ export default class SettingsHandler { isSupported() { return false; } -} \ No newline at end of file +} diff --git a/src/settings/SettingsStore.js b/src/settings/SettingsStore.js index 330c2def05..e73be42887 100644 --- a/src/settings/SettingsStore.js +++ b/src/settings/SettingsStore.js @@ -59,9 +59,9 @@ const SETTINGS = { }; // Convert the above into simpler formats for the handlers -let defaultSettings = {}; -let featureNames = []; -for (let key of Object.keys(SETTINGS)) { +const defaultSettings = {}; +const featureNames = []; +for (const key of Object.keys(SETTINGS)) { defaultSettings[key] = SETTINGS[key].defaults; if (SETTINGS[key].isFeature) featureNames.push(key); } @@ -132,12 +132,7 @@ export default class SettingsStore { throw new Error("Setting " + settingName + " is not a feature"); } - // Synchronously get the setting value (which should be {enabled: true/false}) - const value = Promise.coroutine(function* () { - return yield SettingsStore.getValue(settingName, roomId); - })(); - - return value; + return SettingsStore.getValue(settingName, roomId); } /** @@ -149,7 +144,7 @@ export default class SettingsStore { */ static getValue(settingName, roomId) { const levelOrder = [ - 'device', 'room-device', 'room-account', 'account', 'room', 'config', 'default' + 'device', 'room-device', 'room-account', 'account', 'room', 'config', 'default', ]; if (SettingsStore.isFeature(settingName)) { @@ -161,7 +156,7 @@ export default class SettingsStore { const handlers = SettingsStore._getHandlers(settingName); - for (let level of levelOrder) { + for (const level of levelOrder) { let handler = handlers[level]; if (!handler) continue; From bf815f4be961ba09a80fde1cac1e72a6735918b8 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 28 Oct 2017 20:21:34 -0600 Subject: [PATCH 110/365] Support labs features Signed-off-by: Travis Ralston --- src/UserSettingsStore.js | 76 ---------------------- src/components/structures/UserSettings.js | 10 +-- src/components/views/elements/Flair.js | 4 +- src/components/views/rooms/RoomHeader.js | 4 +- src/components/views/rooms/RoomSettings.js | 3 +- src/settings/DeviceSettingsHandler.js | 3 +- src/settings/SettingsStore.js | 74 +++++++++++++++------ 7 files changed, 68 insertions(+), 106 deletions(-) diff --git a/src/UserSettingsStore.js b/src/UserSettingsStore.js index ce39939bc0..2d2045d15b 100644 --- a/src/UserSettingsStore.js +++ b/src/UserSettingsStore.js @@ -25,50 +25,7 @@ import SdkConfig from './SdkConfig'; * TODO: Make things use this. This is all WIP - see UserSettings.js for usage. */ -const FEATURES = [ - { - id: 'feature_groups', - name: _td("Communities"), - }, - { - id: 'feature_pinning', - name: _td("Message Pinning"), - }, -]; - export default { - getLabsFeatures() { - const featuresConfig = SdkConfig.get()['features'] || {}; - - // The old flag: honoured for backwards compatibility - const enableLabs = SdkConfig.get()['enableLabs']; - - let labsFeatures; - if (enableLabs) { - labsFeatures = FEATURES; - } else { - labsFeatures = FEATURES.filter((f) => { - const sdkConfigValue = featuresConfig[f.id]; - if (sdkConfigValue === 'labs') { - return true; - } - }); - } - return labsFeatures.map((f) => { - return f.id; - }); - }, - - translatedNameForFeature(featureId) { - const feature = FEATURES.filter((f) => { - return f.id === featureId; - })[0]; - - if (feature === undefined) return null; - - return _t(feature.name); - }, - loadProfileInfo: function() { const cli = MatrixClientPeg.get(); return cli.getProfileInfo(cli.credentials.userId); @@ -213,37 +170,4 @@ export default { // FIXME: handle errors localStorage.setItem('mx_local_settings', JSON.stringify(settings)); }, - - isFeatureEnabled: function(featureId: string): boolean { - const featuresConfig = SdkConfig.get()['features']; - - // The old flag: honoured for backwards compatibility - const enableLabs = SdkConfig.get()['enableLabs']; - - let sdkConfigValue = enableLabs ? 'labs' : 'disable'; - if (featuresConfig && featuresConfig[featureId] !== undefined) { - sdkConfigValue = featuresConfig[featureId]; - } - - if (sdkConfigValue === 'enable') { - return true; - } else if (sdkConfigValue === 'disable') { - return false; - } else if (sdkConfigValue === 'labs') { - if (!MatrixClientPeg.get().isGuest()) { - // Make it explicit that guests get the defaults (although they shouldn't - // have been able to ever toggle the flags anyway) - const userValue = localStorage.getItem(`mx_labs_feature_${featureId}`); - return userValue === 'true'; - } - return false; - } else { - console.warn(`Unknown features config for ${featureId}: ${sdkConfigValue}`); - return false; - } - }, - - setFeatureEnabled: function(featureId: string, enabled: boolean) { - localStorage.setItem(`mx_labs_feature_${featureId}`, enabled); - }, }; diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index 68ea932f93..9c28f7a869 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -15,6 +15,8 @@ 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 SettingsStore from "../../settings/SettingsStore"; + const React = require('react'); const ReactDOM = require('react-dom'); const sdk = require('../../index'); @@ -934,11 +936,11 @@ module.exports = React.createClass({ _renderLabs: function() { const features = []; - UserSettingsStore.getLabsFeatures().forEach((featureId) => { + SettingsStore.getLabsFeatures().forEach((featureId) => { // TODO: this ought to be a separate component so that we don't need // to rebind the onChange each time we render const onChange = (e) => { - UserSettingsStore.setFeatureEnabled(featureId, e.target.checked); + SettingsStore.setFeatureEnabled(featureId, e.target.checked); this.forceUpdate(); }; @@ -948,10 +950,10 @@ module.exports = React.createClass({ type="checkbox" id={featureId} name={featureId} - defaultChecked={UserSettingsStore.isFeatureEnabled(featureId)} + defaultChecked={SettingsStore.isFeatureEnabled(featureId)} onChange={onChange} /> - + ); }); diff --git a/src/components/views/elements/Flair.js b/src/components/views/elements/Flair.js index 69d9aa35b7..2e2c7f2595 100644 --- a/src/components/views/elements/Flair.js +++ b/src/components/views/elements/Flair.js @@ -19,9 +19,9 @@ import React from 'react'; import PropTypes from 'prop-types'; import {MatrixClient} from 'matrix-js-sdk'; -import UserSettingsStore from '../../../UserSettingsStore'; import FlairStore from '../../../stores/FlairStore'; import dis from '../../../dispatcher'; +import SettingsStore from "../../../settings/SettingsStore"; class FlairAvatar extends React.Component { @@ -79,7 +79,7 @@ export default class Flair extends React.Component { componentWillMount() { this._unmounted = false; - if (UserSettingsStore.isFeatureEnabled('feature_groups') && FlairStore.groupSupport()) { + if (SettingsStore.isFeatureEnabled('feature_groups') && FlairStore.groupSupport()) { this._generateAvatars(); } this.context.matrixClient.on('RoomState.events', this.onRoomStateEvents); diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 4dfbdb3644..fbfe7ebe18 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -31,7 +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"; +import SettingsStore from "../../../settings/SettingsStore"; linkifyMatrix(linkify); @@ -304,7 +304,7 @@ module.exports = React.createClass({ ; } - if (this.props.onPinnedClick && UserSettingsStore.isFeatureEnabled('feature_pinning')) { + if (this.props.onPinnedClick && SettingsStore.isFeatureEnabled('feature_pinning')) { pinnedEventsButton = diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index dbdcdf596a..f582cc29ef 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -25,6 +25,7 @@ import ObjectUtils from '../../../ObjectUtils'; import dis from '../../../dispatcher'; import UserSettingsStore from '../../../UserSettingsStore'; import AccessibleButton from '../elements/AccessibleButton'; +import SettingsStore from "../../../settings/SettingsStore"; // parse a string as an integer; if the input is undefined, or cannot be parsed @@ -671,7 +672,7 @@ module.exports = React.createClass({ const self = this; let relatedGroupsSection; - if (UserSettingsStore.isFeatureEnabled('feature_groups')) { + if (SettingsStore.isFeatureEnabled('feature_groups')) { relatedGroupsSection = SettingsStore.isFeature(s)); + + const enableLabs = SdkConfig.get()["enableLabs"]; + if (enableLabs) return possibleFeatures; + + return possibleFeatures.filter((s) => SettingsStore._getFeatureState(s) === "labs"); + } + /** * Determines if a setting is also a feature. * @param {string} settingName The setting to look up. @@ -135,6 +159,16 @@ export default class SettingsStore { return SettingsStore.getValue(settingName, roomId); } + /** + * Sets a feature as enabled or disabled on the current device. + * @param {string} settingName The name of the setting. + * @param {boolean} value True to enable the feature, false otherwise. + * @returns {Promise} Resolves when the setting has been set. + */ + static setFeatureEnabled(settingName, value) { + return SettingsStore.setValue(settingName, null, "device", value); + } + /** * Gets the value of a setting. The room ID is optional if the setting is not to * be applied to any particular room, otherwise it should be supplied. @@ -228,7 +262,7 @@ export default class SettingsStore { if (!SETTINGS[settingName]) return {}; const handlers = {}; - for (let level of SETTINGS[settingName].supportedLevels) { + for (const level of SETTINGS[settingName].supportedLevels) { if (!LEVEL_HANDLERS[level]) throw new Error("Unexpected level " + level); handlers[level] = LEVEL_HANDLERS[level]; } @@ -246,7 +280,7 @@ export default class SettingsStore { } const allowedStates = ['enable', 'disable', 'labs']; - if (!allowedStates.contains(featureState)) { + if (!allowedStates.includes(featureState)) { console.warn("Feature state '" + featureState + "' is invalid for " + settingName); featureState = "disable"; // to prevent accidental features. } From ae10a11ac4c1b3c96be16f630800c389117a6e50 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sun, 29 Oct 2017 01:43:52 -0600 Subject: [PATCH 111/365] Convert synced settings to granular settings Signed-off-by: Travis Ralston --- src/Unread.js | 4 +- src/UserSettingsStore.js | 17 --- src/autocomplete/EmojiProvider.js | 4 +- src/components/structures/LoggedInView.js | 4 +- src/components/structures/MessagePanel.js | 5 +- src/components/structures/RoomView.js | 8 +- src/components/structures/TimelinePanel.js | 9 +- src/components/structures/UserSettings.js | 116 +++++------------- src/components/views/messages/MImageBody.js | 8 +- src/components/views/messages/MVideoBody.js | 4 +- src/components/views/messages/TextualBody.js | 8 +- src/components/views/rooms/AuxPanel.js | 1 - src/components/views/rooms/MessageComposer.js | 8 +- .../views/rooms/MessageComposerInput.js | 12 +- src/components/views/rooms/RoomTile.js | 1 - src/components/views/voip/VideoView.js | 4 +- src/settings/RoomSettingsHandler.js | 3 +- src/settings/SettingsStore.js | 100 ++++++++++++++- src/shouldHideEvent.js | 13 +- .../structures/MessagePanel-test.js | 8 +- .../views/rooms/MessageComposerInput-test.js | 1 - 21 files changed, 177 insertions(+), 161 deletions(-) diff --git a/src/Unread.js b/src/Unread.js index 20e876ad88..383b5c2e5a 100644 --- a/src/Unread.js +++ b/src/Unread.js @@ -15,7 +15,6 @@ limitations under the License. */ const MatrixClientPeg = require('./MatrixClientPeg'); -import UserSettingsStore from './UserSettingsStore'; import shouldHideEvent from './shouldHideEvent'; const sdk = require('./index'); @@ -64,7 +63,6 @@ module.exports = { // we have and the read receipt. We could fetch more history to try & find out, // but currently we just guess. - const syncedSettings = UserSettingsStore.getSyncedSettings(); // Loop through messages, starting with the most recent... for (let i = room.timeline.length - 1; i >= 0; --i) { const ev = room.timeline[i]; @@ -74,7 +72,7 @@ module.exports = { // that counts and we can stop looking because the user's read // this and everything before. return false; - } else if (!shouldHideEvent(ev, syncedSettings) && this.eventTriggersUnreadCount(ev)) { + } else if (!shouldHideEvent(ev) && this.eventTriggersUnreadCount(ev)) { // We've found a message that counts before we hit // the read marker, so this room is definitely unread. return true; diff --git a/src/UserSettingsStore.js b/src/UserSettingsStore.js index 2d2045d15b..591eaa1f0f 100644 --- a/src/UserSettingsStore.js +++ b/src/UserSettingsStore.js @@ -137,23 +137,6 @@ export default { }); }, - getSyncedSettings: function() { - const event = MatrixClientPeg.get().getAccountData('im.vector.web.settings'); - return event ? event.getContent() : {}; - }, - - getSyncedSetting: function(type, defaultValue = null) { - const settings = this.getSyncedSettings(); - return settings.hasOwnProperty(type) ? settings[type] : defaultValue; - }, - - setSyncedSetting: function(type, value) { - const settings = this.getSyncedSettings(); - settings[type] = value; - // FIXME: handle errors - return MatrixClientPeg.get().setAccountData('im.vector.web.settings', settings); - }, - getLocalSettings: function() { const localSettingsString = localStorage.getItem('mx_local_settings') || '{}'; return JSON.parse(localSettingsString); diff --git a/src/autocomplete/EmojiProvider.js b/src/autocomplete/EmojiProvider.js index a5b80e3b0e..b2ec73faca 100644 --- a/src/autocomplete/EmojiProvider.js +++ b/src/autocomplete/EmojiProvider.js @@ -25,7 +25,7 @@ import {PillCompletion} from './Components'; import type {SelectionRange, Completion} from './Autocompleter'; import _uniq from 'lodash/uniq'; import _sortBy from 'lodash/sortBy'; -import UserSettingsStore from '../UserSettingsStore'; +import SettingsStore from "../settings/SettingsStore"; import EmojiData from '../stripped-emoji.json'; @@ -97,7 +97,7 @@ export default class EmojiProvider extends AutocompleteProvider { } async getCompletions(query: string, selection: SelectionRange) { - if (UserSettingsStore.getSyncedSetting("MessageComposerInput.dontSuggestEmoji")) { + if (SettingsStore.getValue("MessageComposerInput.dontSuggestEmoji")) { return []; // don't give any suggestions if the user doesn't want them } diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index 5d1d47c5b2..31f59e4849 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -19,7 +19,6 @@ limitations under the License. import * as Matrix from 'matrix-js-sdk'; import React from 'react'; -import UserSettingsStore from '../../UserSettingsStore'; import KeyCode from '../../KeyCode'; import Notifier from '../../Notifier'; import PageTypes from '../../PageTypes'; @@ -28,6 +27,7 @@ import sdk from '../../index'; import dis from '../../dispatcher'; import sessionStore from '../../stores/SessionStore'; import MatrixClientPeg from '../../MatrixClientPeg'; +import SettingsStore from "../../settings/SettingsStore"; /** * This is what our MatrixChat shows when we are logged in. The precise view is @@ -74,7 +74,7 @@ export default React.createClass({ getInitialState: function() { return { // use compact timeline view - useCompactLayout: UserSettingsStore.getSyncedSetting('useCompactLayout'), + useCompactLayout: SettingsStore.getValue('useCompactLayout'), }; }, diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 2331e096c0..53cc660a9b 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -17,7 +17,6 @@ limitations under the License. import React from 'react'; import ReactDOM from 'react-dom'; import classNames from 'classnames'; -import UserSettingsStore from '../../UserSettingsStore'; import shouldHideEvent from '../../shouldHideEvent'; import dis from "../../dispatcher"; import sdk from '../../index'; @@ -110,8 +109,6 @@ module.exports = React.createClass({ // Velocity requires this._readMarkerGhostNode = null; - this._syncedSettings = UserSettingsStore.getSyncedSettings(); - this._isMounted = true; }, @@ -251,7 +248,7 @@ module.exports = React.createClass({ // Always show highlighted event if (this.props.highlightedEventId === mxEv.getId()) return true; - return !shouldHideEvent(mxEv, this._syncedSettings); + return !shouldHideEvent(mxEv); }, _getEventTiles: function() { diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 9b6dbb4c27..4256c19f4d 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -29,7 +29,6 @@ const classNames = require("classnames"); const Matrix = require("matrix-js-sdk"); import { _t } from '../../languageHandler'; -const UserSettingsStore = require('../../UserSettingsStore'); const MatrixClientPeg = require("../../MatrixClientPeg"); const ContentMessages = require("../../ContentMessages"); const Modal = require("../../Modal"); @@ -48,6 +47,7 @@ import UserProvider from '../../autocomplete/UserProvider'; import RoomViewStore from '../../stores/RoomViewStore'; import RoomScrollStateStore from '../../stores/RoomScrollStateStore'; +import SettingsStore from "../../settings/SettingsStore"; const DEBUG = false; let debuglog = function() {}; @@ -151,8 +151,6 @@ module.exports = React.createClass({ MatrixClientPeg.get().on("RoomMember.membership", this.onRoomMemberMembership); MatrixClientPeg.get().on("accountData", this.onAccountData); - this._syncedSettings = UserSettingsStore.getSyncedSettings(); - // Start listening for RoomViewStore updates this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); this._onRoomViewStoreUpdate(true); @@ -535,7 +533,7 @@ module.exports = React.createClass({ // update unread count when scrolled up if (!this.state.searchResults && this.state.atEndOfLiveTimeline) { // no change - } else if (!shouldHideEvent(ev, this._syncedSettings)) { + } else if (!shouldHideEvent(ev)) { this.setState((state, props) => { return {numUnreadMessages: state.numUnreadMessages + 1}; }); @@ -1778,7 +1776,7 @@ module.exports = React.createClass({ const messagePanel = (