From c2034d53359abb67123177438304f49f06072f76 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 25 Jul 2017 15:56:16 +0100 Subject: [PATCH 001/425] Add user list to groups --- src/components/structures/LoggedInView.js | 2 + src/components/views/avatars/BaseAvatar.js | 6 +- .../views/groups/GroupMemberList.js | 146 ++++++++++++++++++ .../views/groups/GroupMemberTile.js | 68 ++++++++ src/components/views/rooms/MemberList.js | 9 +- .../views/rooms/SearchableEntityList.js | 4 +- src/i18n/strings/de_DE.json | 6 +- src/i18n/strings/el.json | 6 +- src/i18n/strings/en_EN.json | 6 +- src/i18n/strings/en_US.json | 6 +- src/i18n/strings/es.json | 6 +- src/i18n/strings/fr.json | 6 +- src/i18n/strings/hu.json | 6 +- src/i18n/strings/ko.json | 6 +- src/i18n/strings/nl.json | 6 +- src/i18n/strings/pt.json | 6 +- src/i18n/strings/pt_BR.json | 6 +- src/i18n/strings/ru.json | 6 +- src/i18n/strings/sv.json | 6 +- src/i18n/strings/th.json | 6 +- src/i18n/strings/tr.json | 6 +- src/i18n/strings/zh_Hans.json | 6 +- 22 files changed, 287 insertions(+), 44 deletions(-) create mode 100644 src/components/views/groups/GroupMemberList.js create mode 100644 src/components/views/groups/GroupMemberTile.js diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index a051af5d08..edc663547b 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -227,6 +227,7 @@ export default React.createClass({ const HomePage = sdk.getComponent('structures.HomePage'); const GroupView = sdk.getComponent('structures.GroupView'); const MyGroups = sdk.getComponent('structures.MyGroups'); + const GroupMemberList = sdk.getComponent('groups.GroupMemberList'); const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar'); const NewVersionBar = sdk.getComponent('globals.NewVersionBar'); const UpdateCheckBar = sdk.getComponent('globals.UpdateCheckBar'); @@ -307,6 +308,7 @@ export default React.createClass({ page_element = ; + right_panel = ; break; } diff --git a/src/components/views/avatars/BaseAvatar.js b/src/components/views/avatars/BaseAvatar.js index a4443430f4..e88fc869fa 100644 --- a/src/components/views/avatars/BaseAvatar.js +++ b/src/components/views/avatars/BaseAvatar.js @@ -14,10 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -'use strict'; - -var React = require('react'); -var AvatarLogic = require("../../../Avatar"); +import React from 'react'; +import AvatarLogic from '../../../Avatar'; import sdk from '../../../index'; import AccessibleButton from '../elements/AccessibleButton'; diff --git a/src/components/views/groups/GroupMemberList.js b/src/components/views/groups/GroupMemberList.js new file mode 100644 index 0000000000..48d49dc04b --- /dev/null +++ b/src/components/views/groups/GroupMemberList.js @@ -0,0 +1,146 @@ +/* +Copyright 2017 Vector Creations Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +import React from 'react'; +import { _t } from '../../../languageHandler'; +import Promise from 'bluebird'; +import sdk from '../../../index'; +import GeminiScrollbar from 'react-gemini-scrollbar'; +import PropTypes from 'prop-types'; +import withMatrixClient from '../../../wrappers/withMatrixClient'; + +const INITIAL_LOAD_NUM_MEMBERS = 30; + +export default withMatrixClient(React.createClass({ + displayName: 'GroupMemberList', + + propTypes: { + matrixClient: PropTypes.object.isRequired, + groupId: PropTypes.string.isRequired, + }, + + getInitialState: function() { + return { + fetching: false, + members: null, + } + }, + + componentWillMount: function() { + this._unmounted = false; + this._fetchMembers(); + }, + + _fetchMembers: function() { + this.setState({fetching: true}); + this.props.matrixClient.getGroupUsers(this.props.groupId).then((result) => { + this.setState({ + members: result.chunk, + fetching: false, + }); + }).catch((e) => { + this.setState({fetching: false}); + console.error("Failed to get group member list: " + e); + }); + }, + + _createOverflowTile: function(overflowCount, totalCount) { + // For now we'll pretend this is any entity. It should probably be a separate tile. + const EntityTile = sdk.getComponent("rooms.EntityTile"); + const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); + const text = _t("and %(count)s others...", { count: overflowCount }); + return ( + + } name={text} presenceState="online" suppressOnHover={true} + onClick={this._showFullMemberList} /> + ); + }, + + _showFullMemberList: function() { + this.setState({ + truncateAt: -1 + }); + }, + + onSearchQueryChanged: function(ev) { + this.setState({ searchQuery: ev.target.value }); + }, + + makeGroupMemberTiles: function(query) { + const GroupMemberTile = sdk.getComponent("groups.GroupMemberTile"); + query = (query || "").toLowerCase(); + + const memberList = this.state.members.filter((m) => { + if (query) { + //const matchesName = m.name.toLowerCase().indexOf(query) !== -1; + const matchesId = m.user_id.toLowerCase().indexOf(query) !== -1; + + if (/*!matchesName &&*/ !matchesId) { + return false; + } + } + + return true; + }).map(function(m) { + return ( + + ); + }); + + memberList.sort((a, b) => { + // should put admins at the top: we don't yet have that info + if (a < b) { + return -1; + } else if (a > b) { + return 1; + } else { + return 0; + } + }); + + return memberList; + }, + + render: function() { + if (this.state.fetching) { + const Spinner = sdk.getComponent("elements.Spinner"); + return ; + } else if (this.state.members === null) { + return null; + } + + const inputBox = ( +
+ +
+ ); + + const TruncatedList = sdk.getComponent("elements.TruncatedList"); + return ( +
+ { inputBox } + + + {this.makeGroupMemberTiles(this.state.searchQuery)} + + +
+ ); + } +})); diff --git a/src/components/views/groups/GroupMemberTile.js b/src/components/views/groups/GroupMemberTile.js new file mode 100644 index 0000000000..1ef59fb1a6 --- /dev/null +++ b/src/components/views/groups/GroupMemberTile.js @@ -0,0 +1,68 @@ +/* +Copyright 2017 Vector Creations Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; +import sdk from '../../../index'; +import dis from '../../../dispatcher'; +import { _t } from '../../../languageHandler'; +import withMatrixClient from '../../../wrappers/withMatrixClient'; +import Matrix from "matrix-js-sdk"; + +export default withMatrixClient(React.createClass({ + displayName: 'GroupMemberTile', + + propTypes: { + matrixClient: PropTypes.object, + member: PropTypes.shape({ + user_id: PropTypes.string.isRequired, + }).isRequired, + }, + + getInitialState: function() { + return {}; + }, + + onClick: function(e) { + const member = new Matrix.RoomMember(null, this.props.member.user_id); + dis.dispatch({ + action: 'view_user', + member: member, + }); + }, + + getPowerLabel: function() { + return _t("%(userName)s (power %(powerLevelNumber)s)", {userName: this.props.member.userId, powerLevelNumber: this.props.member.powerLevel}); + }, + + render: function() { + const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); + const EntityTile = sdk.getComponent('rooms.EntityTile'); + + const name = this.props.member.user_id; + + const av = ( + + ); + + return ( + + ); + } +})); diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index 37a9bcd0cd..e5f4f08572 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -1,5 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd +Copyright 2017 Vector Creations Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -200,11 +201,9 @@ module.exports = React.createClass({ _createOverflowTile: function(overflowCount, totalCount) { // For now we'll pretend this is any entity. It should probably be a separate tile. - var EntityTile = sdk.getComponent("rooms.EntityTile"); - var BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); - var text = (overflowCount > 1) - ? _t("and %(overflowCount)s others...", { overflowCount: overflowCount }) - : _t("and one other..."); + const EntityTile = sdk.getComponent("rooms.EntityTile"); + const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); + const text = _t("and %(count)s others...", { count: overflowCount }); return ( diff --git a/src/components/views/rooms/SearchableEntityList.js b/src/components/views/rooms/SearchableEntityList.js index 4412fc539f..45ad2c6b9e 100644 --- a/src/components/views/rooms/SearchableEntityList.js +++ b/src/components/views/rooms/SearchableEntityList.js @@ -117,9 +117,7 @@ var SearchableEntityList = React.createClass({ _createOverflowEntity: function(overflowCount, totalCount) { var EntityTile = sdk.getComponent("rooms.EntityTile"); var BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); - var text = (overflowCount > 1) - ? _t("and %(overflowCount)s others...", { overflowCount: overflowCount }) - : _t("and one other..."); + const text = _t("and %(count)s others...", { count: overflowCount }); return ( diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 68cfec2cd9..e855abffc0 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -552,8 +552,10 @@ "Failed to forget room %(errCode)s": "Das Entfernen des Raums ist fehlgeschlagen %(errCode)s", "Failed to join the room": "Fehler beim Betreten des Raumes", "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "Eine Textnachricht wurde an +%(msisdn)s gesendet. Bitte gebe den Verifikationscode ein, den er beinhaltet", - "and %(overflowCount)s others...": "und %(overflowCount)s weitere...", - "and one other...": "und ein(e) weitere(r)...", + "and %(count)s others...": { + "other": "und %(count)s weitere...", + "one": "und ein(e) weitere(r)..." + }, "Are you sure?": "Bist du sicher?", "Attachment": "Anhang", "Ban": "Dauerhaft aus dem Raum ausschließen", diff --git a/src/i18n/strings/el.json b/src/i18n/strings/el.json index d339508488..bf96278fbd 100644 --- a/src/i18n/strings/el.json +++ b/src/i18n/strings/el.json @@ -175,8 +175,10 @@ "an address": "μία διεύθηνση", "%(items)s and %(remaining)s others": "%(items)s και %(remaining)s ακόμα", "%(items)s and one other": "%(items)s και ένας ακόμα", - "and %(overflowCount)s others...": "και %(overflowCount)s άλλοι...", - "and one other...": "και ένας ακόμα...", + "and %(count)s others...": { + "other": "και %(count)s άλλοι...", + "one": "και ένας ακόμα..." + }, "%(names)s and %(lastPerson)s are typing": "%(names)s και %(lastPerson)s γράφουν", "%(names)s and one other are typing": "%(names)s και ένας ακόμα γράφουν", "%(names)s and %(count)s others are typing": "%(names)s και %(count)s άλλοι γράφουν", diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4841f2f0de..da3d1545ae 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -157,8 +157,10 @@ "%(items)s and %(remaining)s others": "%(items)s and %(remaining)s others", "%(items)s and one other": "%(items)s and one other", "%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s", - "and %(overflowCount)s others...": "and %(overflowCount)s others...", - "and one other...": "and one other...", + "and %(count)s others...": { + "other": "and %(count)s others...", + "one": "and one other..." + }, "%(names)s and %(lastPerson)s are typing": "%(names)s and %(lastPerson)s are typing", "%(names)s and one other are typing": "%(names)s and one other are typing", "%(names)s and %(count)s others are typing": "%(names)s and %(count)s others are typing", diff --git a/src/i18n/strings/en_US.json b/src/i18n/strings/en_US.json index 6a2a326870..47bb96ed14 100644 --- a/src/i18n/strings/en_US.json +++ b/src/i18n/strings/en_US.json @@ -154,8 +154,10 @@ "%(items)s and %(remaining)s others": "%(items)s and %(remaining)s others", "%(items)s and one other": "%(items)s and one other", "%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s", - "and %(overflowCount)s others...": "and %(overflowCount)s others...", - "and one other...": "and one other...", + "and %(count)s others...": { + "other": "and %(count)s others...", + "one": "and one other..." + }, "%(names)s and %(lastPerson)s are typing": "%(names)s and %(lastPerson)s are typing", "%(names)s and one other are typing": "%(names)s and one other are typing", "%(names)s and %(count)s others are typing": "%(names)s and %(count)s others are typing", diff --git a/src/i18n/strings/es.json b/src/i18n/strings/es.json index f7506b946d..05b1abffd7 100644 --- a/src/i18n/strings/es.json +++ b/src/i18n/strings/es.json @@ -140,8 +140,10 @@ "%(items)s and %(remaining)s others": "%(items)s y %(remaining)s otros", "%(items)s and one other": "%(items)s y otro", "%(items)s and %(lastItem)s": "%(items)s y %(lastItem)s", - "and %(overflowCount)s others...": "y %(overflowCount)s otros...", - "and one other...": "y otro...", + "and %(count)s others...": { + "other": "y %(count)s otros...", + "one": "y otro..." + }, "%(names)s and %(lastPerson)s are typing": "%(names)s y %(lastPerson)s están escribiendo", "%(names)s and one other are typing": "%(names)s y otro están escribiendo", "%(names)s and %(count)s others are typing": "%(names)s y %(count)s otros están escribiendo", diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index 9f6fdf3391..9ae015eaff 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -188,8 +188,10 @@ "%(items)s and %(remaining)s others": "%(items)s et %(remaining)s autres", "%(items)s and one other": "%(items)s et un autre", "%(items)s and %(lastItem)s": "%(items)s et %(lastItem)s", - "and %(overflowCount)s others...": "et %(overflowCount)s autres...", - "and one other...": "et un autre...", + "and %(count)s others...": { + "other": "et %(count)s autres...", + "one": "et un autre..." + }, "%(names)s and %(lastPerson)s are typing": "%(names)s et %(lastPerson)s sont en train de taper", "%(names)s and one other are typing": "%(names)s et un autre sont en train de taper", "%(names)s and %(count)s others are typing": "%(names)s et %(count)s d'autres sont en train de taper", diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 8335976a06..2d1f07c34e 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -190,8 +190,10 @@ "%(items)s and %(remaining)s others": "%(items)s és még: %(remaining)s", "%(items)s and one other": "%(items)s és még egy", "%(items)s and %(lastItem)s": "%(items)s és %(lastItem)s", - "and %(overflowCount)s others...": "és még: %(overflowCount)s ...", - "and one other...": "és még egy...", + "and %(count)s others...": { + "other": "és még: %(count)s ...", + "one": "és még egy..." + }, "%(names)s and %(lastPerson)s are typing": "%(names)s és %(lastPerson)s írnak", "%(names)s and one other are typing": "%(names)s és még valaki ír", "%(names)s and %(count)s others are typing": "%(names)s és %(count)s ember ír", diff --git a/src/i18n/strings/ko.json b/src/i18n/strings/ko.json index 601f7d1ce0..52970db2ed 100644 --- a/src/i18n/strings/ko.json +++ b/src/i18n/strings/ko.json @@ -225,8 +225,10 @@ "%(items)s and %(remaining)s others": "%(items)s과 %(remaining)s", "%(items)s and one other": "%(items)s과 다른 하나", "%(items)s and %(lastItem)s": "%(items)s과 %(lastItem)s", - "and %(overflowCount)s others...": "그리고 %(overflowCount)s...", - "and one other...": "그리고 다른 하나...", + "and %(count)s others...": { + "other": "그리고 %(count)s...", + "one": "그리고 다른 하나..." + }, "%(names)s and %(lastPerson)s are typing": "%(names)s님과 %(lastPerson)s님이 입력중", "%(names)s and one other are typing": "%(names)s님과 다른 분이 입력중", "%(names)s and %(count)s others are typing": "%(names)s님과 %(count)s 분들이 입력중", diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json index 95c5e0ee78..ae2cf977ea 100644 --- a/src/i18n/strings/nl.json +++ b/src/i18n/strings/nl.json @@ -141,8 +141,10 @@ "%(items)s and %(remaining)s others": "%(items)s en %(remaining)s andere", "%(items)s and one other": "%(items)s en één andere", "%(items)s and %(lastItem)s": "%(items)s en %(lastItem)s", - "and %(overflowCount)s others...": "en %(overflowCount)s andere...", - "and one other...": "en één andere...", + "and %(count)s others...": { + "other": "en %(count)s andere...", + "one": "en één andere..." + }, "%(names)s and %(lastPerson)s are typing": "%(names)s en %(lastPerson)s zijn aan het typen", "%(names)s and one other are typing": "%(names)s en één andere zijn aan het typen", "%(names)s and %(count)s others are typing": "%(names)s en %(count)s andere zijn aan het typen", diff --git a/src/i18n/strings/pt.json b/src/i18n/strings/pt.json index 0405d000af..16ffc1f107 100644 --- a/src/i18n/strings/pt.json +++ b/src/i18n/strings/pt.json @@ -556,8 +556,10 @@ "%(items)s and %(remaining)s others": "%(items)s e %(remaining)s outros", "%(items)s and one other": "%(items)s e um outro", "%(items)s and %(lastItem)s": "%(items)s e %(lastItem)s", - "and %(overflowCount)s others...": "e %(overflowCount)s outros...", - "and one other...": "e um outro...", + "and %(count)s others...": { + "other": "e %(count)s outros...", + "one": "e um outro..." + }, "Are you sure?": "Você tem certeza?", "Attachment": "Anexo", "Autoplay GIFs and videos": "Reproduzir automaticamente GIFs e videos", diff --git a/src/i18n/strings/pt_BR.json b/src/i18n/strings/pt_BR.json index 4820104aa1..330dc8f143 100644 --- a/src/i18n/strings/pt_BR.json +++ b/src/i18n/strings/pt_BR.json @@ -557,8 +557,10 @@ "%(items)s and %(remaining)s others": "%(items)s e %(remaining)s outros", "%(items)s and one other": "%(items)s e um outro", "%(items)s and %(lastItem)s": "%(items)s e %(lastItem)s", - "and %(overflowCount)s others...": "e %(overflowCount)s outros...", - "and one other...": "e um outro...", + "and %(count)s others...": { + "other": "e %(count)s outros...", + "one": "e um outro..." + }, "Are you sure?": "Você tem certeza?", "Attachment": "Anexo", "Autoplay GIFs and videos": "Reproduzir automaticamente GIFs e videos", diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 76a047d079..cad0617ae2 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -448,7 +448,10 @@ "sx": "Суту", "zh-hk": "Китайский (Гонконг)", "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "На +%(msisdn)s было отправлено текстовое сообщение. Пожалуйста, введите проверочный код из него", - "and %(overflowCount)s others...": "и %(overflowCount)s других...", + "and %(count)s others...": { + "other": "и %(count)s других...", + "one": "и ещё один..." + }, "Are you sure?": "Вы уверены?", "Autoplay GIFs and videos": "Проигрывать GIF и видео автоматически", "Can't connect to homeserver - please check your connectivity and ensure your homeserver's SSL certificate is trusted.": "Невозможно соединиться с домашним сервером - проверьте своё соединение и убедитесь, что SSL-сертификат вашего домашнего сервера включён в доверяемые.", @@ -479,7 +482,6 @@ "%(items)s and %(remaining)s others": "%(items)s и другие %(remaining)s", "%(items)s and one other": "%(items)s и ещё один", "%(items)s and %(lastItem)s": "%(items)s и %(lastItem)s", - "and one other...": "и ещё один...", "An error has occurred.": "Произошла ошибка.", "Attachment": "Вложение", "Ban": "Запретить", diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 21bda3b741..549e06c991 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -150,8 +150,10 @@ "%(items)s and %(remaining)s others": "%(items)s och %(remaining)s andra", "%(items)s and one other": "%(items)s och en annan", "%(items)s and %(lastItem)s": "%(items)s och %(lastItem)s", - "and %(overflowCount)s others...": "och %(overflowCount)s andra...", - "and one other...": "och en annan...", + "and %(count)s others...": { + "other": "och %(count)s andra...", + "one": "och en annan..." + }, "%(names)s and %(lastPerson)s are typing": "%(names)s och %(lastPerson)s skriver", "%(names)s and one other are typing": "%(names)s och en annan skriver", "%(names)s and %(count)s others are typing": "%(names)s och %(count)s andra skriver", diff --git a/src/i18n/strings/th.json b/src/i18n/strings/th.json index 5dff4816ee..265c3e1b58 100644 --- a/src/i18n/strings/th.json +++ b/src/i18n/strings/th.json @@ -98,8 +98,10 @@ "%(items)s and %(remaining)s others": "%(items)s และอีก %(remaining)s ผู้ใช้", "%(items)s and one other": "%(items)s และอีกหนึ่งผู้ใช้", "%(items)s and %(lastItem)s": "%(items)s และ %(lastItem)s", - "and %(overflowCount)s others...": "และอีก %(overflowCount)s ผู้ใช้...", - "and one other...": "และอีกหนึ่งผู้ใช้...", + "and %(count)s others...": { + "other": "และอีก %(count)s ผู้ใช้...", + "one": "และอีกหนึ่งผู้ใช้..." + }, "%(names)s and %(lastPerson)s are typing": "%(names)s และ %(lastPerson)s กำลังพิมพ์", "%(names)s and one other are typing": "%(names)s และอีกหนึ่งคนกำลังพิมพ์", "%(names)s and %(count)s others are typing": "%(names)s และอีก %(count)s คนกำลังพิมพ์", diff --git a/src/i18n/strings/tr.json b/src/i18n/strings/tr.json index 3d0d774926..effc426464 100644 --- a/src/i18n/strings/tr.json +++ b/src/i18n/strings/tr.json @@ -156,8 +156,10 @@ "%(items)s and %(remaining)s others": "%(items)s ve %(remaining)s diğerleri", "%(items)s and one other": "%(items)s ve bir başkası", "%(items)s and %(lastItem)s": "%(items)s ve %(lastItem)s", - "and %(overflowCount)s others...": "ve %(overflowCount)s diğerleri...", - "and one other...": "ve bir diğeri...", + "and %(count)s others...": { + "other": "ve %(count)s diğerleri...", + "one": "ve bir diğeri..." + }, "%(names)s and %(lastPerson)s are typing": "%(names)s ve %(lastPerson)s yazıyorlar", "%(names)s and one other are typing": "%(names)s ve birisi yazıyor", "%(names)s and %(count)s others are typing": "%(names)s ve %(count)s diğeri yazıyor", diff --git a/src/i18n/strings/zh_Hans.json b/src/i18n/strings/zh_Hans.json index 9fdc7a0b42..ad56ab45b8 100644 --- a/src/i18n/strings/zh_Hans.json +++ b/src/i18n/strings/zh_Hans.json @@ -239,8 +239,10 @@ "%(items)s and %(remaining)s others": "%(items)s 和其它 %(remaining)s 个", "%(items)s and one other": "%(items)s 和其它一个", "%(items)s and %(lastItem)s": "%(items)s 和 %(lastItem)s", - "and %(overflowCount)s others...": "和其它 %(overflowCount)s 个...", - "and one other...": "和其它一个...", + "and %(count)s others...": { + "other": "和其它 %(count)s 个...", + "one": "和其它一个..." + }, "%(names)s and one other are typing": "%(names)s 和另一个人正在打字", "anyone": "任何人", "Anyone": "任何人", From 598411dd03892189a04fcf35937541abe80173d5 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 25 Jul 2017 17:22:05 +0100 Subject: [PATCH 002/425] Add translation --- src/i18n/strings/en_EN.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 86cf8727bb..a12d277a92 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -961,5 +961,7 @@ "Edit Group": "Edit Group", "Automatically replace plain text Emoji": "Automatically replace plain text Emoji", "Failed to upload image": "Failed to upload image", - "Failed to update group": "Failed to update group" + "Failed to update group": "Failed to update group", + "Description": "Description", + "Filter group members": "Filter group members" } From 79b83e60ab4032b64e0df218cc14f6f81fbddca3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 31 Jul 2017 12:08:28 +0100 Subject: [PATCH 003/425] add /devtools command --- src/SlashCommands.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/SlashCommands.js b/src/SlashCommands.js index dea3d27751..81fd52f9c9 100644 --- a/src/SlashCommands.js +++ b/src/SlashCommands.js @@ -292,6 +292,13 @@ const commands = { return reject(this.getUsage()); }), + // Open developer tools + devtools: new Command("devtools", "", function(roomId) { + const DevtoolsDialog = sdk.getComponent("dialogs.DevtoolsDialog"); + Modal.createDialog(DevtoolsDialog, { roomId }); + return success(); + }), + // Verify a user, device, and pubkey tuple verify: new Command("verify", " ", function(roomId, args) { if (args) { From fe19dbee02fef5bc69e8c0051b9e1075d3fe30e9 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 3 Aug 2017 09:26:41 +0100 Subject: [PATCH 004/425] Remove unused component --- src/components/structures/LoggedInView.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index edc663547b..2dcb72deb9 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -227,7 +227,6 @@ export default React.createClass({ const HomePage = sdk.getComponent('structures.HomePage'); const GroupView = sdk.getComponent('structures.GroupView'); const MyGroups = sdk.getComponent('structures.MyGroups'); - const GroupMemberList = sdk.getComponent('groups.GroupMemberList'); const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar'); const NewVersionBar = sdk.getComponent('globals.NewVersionBar'); const UpdateCheckBar = sdk.getComponent('globals.UpdateCheckBar'); From 234a88c0988f40b801e288332ff75c130da57d7d Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 4 Aug 2017 15:00:34 +0100 Subject: [PATCH 005/425] WIP group member list/tiles --- .../views/dialogs/ConfirmUserActionDialog.js | 27 ++- .../views/groups/GroupMemberInfo.js | 158 ++++++++++++++++++ .../views/groups/GroupMemberList.js | 11 +- .../views/groups/GroupMemberTile.js | 16 +- src/i18n/strings/en_EN.json | 3 +- 5 files changed, 198 insertions(+), 17 deletions(-) create mode 100644 src/components/views/groups/GroupMemberInfo.js diff --git a/src/components/views/dialogs/ConfirmUserActionDialog.js b/src/components/views/dialogs/ConfirmUserActionDialog.js index b10df3ccef..c4e4f18ee0 100644 --- a/src/components/views/dialogs/ConfirmUserActionDialog.js +++ b/src/components/views/dialogs/ConfirmUserActionDialog.js @@ -18,6 +18,7 @@ import React from 'react'; import sdk from '../../../index'; import { _t } from '../../../languageHandler'; import classnames from 'classnames'; +import { GroupMemberType } from '../../../groups'; /* * A dialog for confirming an operation on another user. @@ -30,7 +31,10 @@ import classnames from 'classnames'; export default React.createClass({ displayName: 'ConfirmUserActionDialog', propTypes: { - member: React.PropTypes.object.isRequired, // matrix-js-sdk member object + // matrix-js-sdk (room) member object. Supply either this or 'groupMember' + member: React.PropTypes.object, + // group member object. Supply either this or 'member' + groupMember: GroupMemberType, action: React.PropTypes.string.isRequired, // eg. 'Ban' // Whether to display a text field for a reason @@ -69,6 +73,7 @@ export default React.createClass({ render: function() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); 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({ @@ -91,6 +96,20 @@ export default React.createClass({ ); } + let avatar; + let name; + let userId; + if (this.props.member) { + avatar = ; + name = this.props.member.name; + userId = this.props.member.userId; + } else { + // we don't get this info from the API yet + avatar = + name = this.props.groupMember.userId; + userId = this.props.groupMember.userId; + } + return (
- + {avatar}
-
{this.props.member.name}
-
{this.props.member.userId}
+
{name}
+
{userId}
{reasonBox}
diff --git a/src/components/views/groups/GroupMemberInfo.js b/src/components/views/groups/GroupMemberInfo.js new file mode 100644 index 0000000000..70aa7d4504 --- /dev/null +++ b/src/components/views/groups/GroupMemberInfo.js @@ -0,0 +1,158 @@ +/* +Copyright 2017 Vector Creations Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import PropTypes from 'prop-types'; +import React from 'react'; +import classNames from 'classnames'; +import dis from '../../../dispatcher'; +import Modal from '../../../Modal'; +import sdk from '../../../index'; +import { _t } from '../../../languageHandler'; +import createRoom from '../../../createRoom'; +import DMRoomMap from '../../../utils/DMRoomMap'; +import Unread from '../../../Unread'; +import { GroupMemberType } from '../../../groups'; +import { findReadReceiptFromUserId } from '../../../utils/Receipt'; +import withMatrixClient from '../../../wrappers/withMatrixClient'; +import AccessibleButton from '../elements/AccessibleButton'; +import GeminiScrollbar from 'react-gemini-scrollbar'; + + +module.exports = withMatrixClient(React.createClass({ + displayName: 'GroupMemberInfo', + + propTypes: { + matrixClient: PropTypes.object.isRequired, + groupId: PropTypes.string, + member: GroupMemberType, + }, + + componentWillMount: function() { + this._fetchMembers(); + }, + + _fetchMembers: function() { + this.setState({fetching: true}); + this.props.matrixClient.getGroupUsers(this.props.groupId).then((result) => { + this.setState({ + members: result.chunk, + fetching: false, + }); + }).catch((e) => { + this.setState({fetching: false}); + console.error("Failed to get group member list: " + e); + }); + }, + + _onKick: function() { + const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog"); + Modal.createDialog(ConfirmUserActionDialog, { + groupMember: this.props.member, + action: _t('Remove from group'), + danger: true, + onFinished: (proceed) => { + }, + }); + }, + + onCancel: function(e) { + dis.dispatch({ + action: "view_user", + member: null + }); + }, + + onRoomTileClick(roomId) { + dis.dispatch({ + action: 'view_room', + room_id: roomId, + }); + }, + + render: function() { + if (this.state.fetching) { + const Loader = sdk.getComponent("elements.Spinner"); + return ; + } + if (!this.state.members) return null; + + let targetIsInGroup = false; + for (const m of this.state.members) { + if (m.user_id == this.props.member.userId) { + targetIsInGroup = true; + } + } + + let kickButton, adminButton; + + if (targetIsInGroup) { + kickButton = ( + + {_t('Remove from group')} + + ); + + // No make/revoke admin API yet + /*const opLabel = this.state.isTargetMod ? _t("Revoke Moderator") : _t("Make Moderator"); + giveModButton = + {giveOpLabel} + ;*/ + } + + let adminTools; + if (kickButton || adminButton) { + adminTools = +
+

{_t("Admin tools")}

+ +
+ {kickButton} + {adminButton} +
+
; + } + + const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); + const avatar = ( + + ); + + const memberName = this.props.member.userId; + + const EmojiText = sdk.getComponent('elements.EmojiText'); + return ( +
+ + +
+ {avatar} +
+ + {memberName} + +
+
+ { this.props.member.userId } +
+
+ + { adminTools } +
+
+ ); + } +})); diff --git a/src/components/views/groups/GroupMemberList.js b/src/components/views/groups/GroupMemberList.js index 48d49dc04b..0f74ff5eed 100644 --- a/src/components/views/groups/GroupMemberList.js +++ b/src/components/views/groups/GroupMemberList.js @@ -17,6 +17,7 @@ import React from 'react'; import { _t } from '../../../languageHandler'; import Promise from 'bluebird'; import sdk from '../../../index'; +import { groupMemberFromApiObject } from '../../../groups'; import GeminiScrollbar from 'react-gemini-scrollbar'; import PropTypes from 'prop-types'; import withMatrixClient from '../../../wrappers/withMatrixClient'; @@ -47,7 +48,9 @@ export default withMatrixClient(React.createClass({ this.setState({fetching: true}); this.props.matrixClient.getGroupUsers(this.props.groupId).then((result) => { this.setState({ - members: result.chunk, + members: result.chunk.map((apiMember) => { + return groupMemberFromApiObject(apiMember); + }), fetching: false, }); }).catch((e) => { @@ -86,7 +89,7 @@ export default withMatrixClient(React.createClass({ const memberList = this.state.members.filter((m) => { if (query) { //const matchesName = m.name.toLowerCase().indexOf(query) !== -1; - const matchesId = m.user_id.toLowerCase().indexOf(query) !== -1; + const matchesId = m.userId.toLowerCase().indexOf(query) !== -1; if (/*!matchesName &&*/ !matchesId) { return false; @@ -94,9 +97,9 @@ export default withMatrixClient(React.createClass({ } return true; - }).map(function(m) { + }).map((m) => { return ( - + ); }); diff --git a/src/components/views/groups/GroupMemberTile.js b/src/components/views/groups/GroupMemberTile.js index 1ef59fb1a6..d129fb9440 100644 --- a/src/components/views/groups/GroupMemberTile.js +++ b/src/components/views/groups/GroupMemberTile.js @@ -19,6 +19,7 @@ import PropTypes from 'prop-types'; import sdk from '../../../index'; import dis from '../../../dispatcher'; import { _t } from '../../../languageHandler'; +import { GroupMemberType } from '../../../groups'; import withMatrixClient from '../../../wrappers/withMatrixClient'; import Matrix from "matrix-js-sdk"; @@ -27,9 +28,8 @@ export default withMatrixClient(React.createClass({ propTypes: { matrixClient: PropTypes.object, - member: PropTypes.shape({ - user_id: PropTypes.string.isRequired, - }).isRequired, + groupId: PropTypes.string.isRequired, + member: GroupMemberType.isRequired, }, getInitialState: function() { @@ -37,10 +37,10 @@ export default withMatrixClient(React.createClass({ }, onClick: function(e) { - const member = new Matrix.RoomMember(null, this.props.member.user_id); dis.dispatch({ - action: 'view_user', - member: member, + action: 'view_group_user', + member: this.props.member, + groupId: this.props.groupId, }); }, @@ -52,10 +52,10 @@ export default withMatrixClient(React.createClass({ const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); const EntityTile = sdk.getComponent('rooms.EntityTile'); - const name = this.props.member.user_id; + const name = this.props.member.userId; const av = ( - + ); return ( diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index a12d277a92..82e10bf3d1 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -963,5 +963,6 @@ "Failed to upload image": "Failed to upload image", "Failed to update group": "Failed to update group", "Description": "Description", - "Filter group members": "Filter group members" + "Filter group members": "Filter group members", + "Remove from group": "Remove from group" } From 1973b2bbe78ced0c53d2c1975683f74603a3020e Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Sat, 5 Aug 2017 00:00:19 +0100 Subject: [PATCH 006/425] Switch app drawer icons --- src/components/views/rooms/MessageComposer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 14f52706ec..7c8723e197 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -289,12 +289,12 @@ export default class MessageComposer extends React.Component { if (this.props.showApps) { hideAppsButton =
- +
; } else { showAppsButton =
- +
; } } From c4803c6d89a364bb3f1338341daa7dc4d781ecf9 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sat, 5 Aug 2017 13:01:19 +0100 Subject: [PATCH 007/425] suppressOnHover for member entity tiles which have no onClick Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/MemberList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index e5f4f08572..692502b855 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -333,7 +333,7 @@ module.exports = React.createClass({ return; } memberList.push( - + ); }); } From a22e76834336a823a739e4f69c7f2a47a8abd1f6 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Sun, 6 Aug 2017 10:01:48 +0100 Subject: [PATCH 008/425] Move room settings button to RoomHeader --- src/components/views/rooms/RoomHeader.js | 83 ++++++++++++++++++++++ src/components/views/rooms/RoomSettings.js | 78 -------------------- 2 files changed, 83 insertions(+), 78 deletions(-) diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 85aedadf64..d4e390f750 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -30,6 +30,9 @@ import linkifyElement from 'linkifyjs/element'; import linkifyMatrix from '../../../linkify-matrix'; import AccessibleButton from '../elements/AccessibleButton'; import {CancelButton} from './SimpleRoomHeader'; +import SdkConfig from '../../../SdkConfig'; +import ScalarAuthClient from '../../../ScalarAuthClient'; +import ScalarMessaging from '../../../ScalarMessaging'; linkifyMatrix(linkify); @@ -57,6 +60,13 @@ module.exports = React.createClass({ }; }, + getInitialState: function() { + return { + scalar_error: null, + showIntegrationsError: false, + }; + }, + componentDidMount: function() { const cli = MatrixClientPeg.get(); cli.on("RoomState.events", this._onRoomStateEvents); @@ -75,7 +85,23 @@ module.exports = React.createClass({ } }, + componentWillMount: function() { + ScalarMessaging.startListening(); + this.scalarClient = null; + if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) { + this.scalarClient = new ScalarAuthClient(); + this.scalarClient.connect().done(() => { + this.forceUpdate(); + }, (err) => { + this.setState({ + scalar_error: err, + }); + }); + } + }, + componentWillUnmount: function() { + ScalarMessaging.stopListening(); if (this.props.room) { this.props.room.removeListener("Room.name", this._onRoomNameChange); } @@ -85,6 +111,28 @@ module.exports = React.createClass({ } }, + onManageIntegrations(ev) { + ev.preventDefault(); + const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); + Modal.createDialog(IntegrationsManager, { + src: (this.scalarClient !== null && this.scalarClient.hasCredentials()) ? + this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId) : + null, + onFinished: ()=>{ + if (this._calcSavePromises().length === 0) { + this.props.onCancelClick(ev); + } + }, + }, "mx_IntegrationsManager"); + }, + + onShowIntegrationsError(ev) { + ev.preventDefault(); + this.setState({ + showIntegrationsError: !this.state.showIntegrationsError, + }); + }, + _onRoomStateEvents: function(event, state) { if (!this.props.room || event.getRoomId() !== this.props.room.roomId) { return; @@ -320,10 +368,45 @@ module.exports = React.createClass({ } let rightRow; + let integrationsButton; + let integrationsError; + if (this.scalarClient !== null) { + if (this.state.showIntegrationsError && this.state.scalar_error) { + console.error(this.state.scalar_error); + integrationsError = ( + + { _t('Could not connect to the integration server') } + + ); + } + + if (this.scalarClient.hasCredentials()) { + integrationsButton = ( + + + + ); + } else if (this.state.scalar_error) { + integrationsButton = ( +
+ + { integrationsError } +
+ ); + } else { + integrationsButton = ( + + + + ); + } + } + if (!this.props.editing) { rightRow =
{ settingsButton } + { integrationsButton } { forgetButton } { searchButton } { rightPanelButtons } diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index d6a973f648..1baaf3aaa9 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -24,8 +24,6 @@ import sdk from '../../../index'; import Modal from '../../../Modal'; import ObjectUtils from '../../../ObjectUtils'; import dis from '../../../dispatcher'; -import ScalarAuthClient from '../../../ScalarAuthClient'; -import ScalarMessaging from '../../../ScalarMessaging'; import UserSettingsStore from '../../../UserSettingsStore'; import AccessibleButton from '../elements/AccessibleButton'; @@ -118,14 +116,10 @@ module.exports = React.createClass({ // Default to false if it's undefined, otherwise react complains about changing // components from uncontrolled to controlled isRoomPublished: this._originalIsRoomPublished || false, - scalar_error: null, - showIntegrationsError: false, }; }, componentWillMount: function() { - ScalarMessaging.startListening(); - MatrixClientPeg.get().on("RoomMember.membership", this._onRoomMemberMembership); MatrixClientPeg.get().getRoomDirectoryVisibility( @@ -137,18 +131,6 @@ module.exports = React.createClass({ console.error("Failed to get room visibility: " + err); }); - this.scalarClient = null; - if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) { - this.scalarClient = new ScalarAuthClient(); - this.scalarClient.connect().done(() => { - this.forceUpdate(); - }, (err) => { - this.setState({ - scalar_error: err - }); - }); - } - dis.dispatch({ action: 'ui_opacity', sideOpacity: 0.3, @@ -157,8 +139,6 @@ module.exports = React.createClass({ }, componentWillUnmount: function() { - ScalarMessaging.stopListening(); - const cli = MatrixClientPeg.get(); if (cli) { cli.removeListener("RoomMember.membership", this._onRoomMemberMembership); @@ -513,28 +493,6 @@ module.exports = React.createClass({ roomState.mayClientSendStateEvent("m.room.guest_access", cli)); }, - onManageIntegrations(ev) { - ev.preventDefault(); - var IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); - Modal.createDialog(IntegrationsManager, { - src: (this.scalarClient !== null && this.scalarClient.hasCredentials()) ? - this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId) : - null, - onFinished: ()=>{ - if (this._calcSavePromises().length === 0) { - this.props.onCancelClick(ev); - } - }, - }, "mx_IntegrationsManager"); - }, - - onShowIntegrationsError(ev) { - ev.preventDefault(); - this.setState({ - showIntegrationsError: !this.state.showIntegrationsError, - }); - }, - onLeaveClick() { dis.dispatch({ action: 'leave_room', @@ -796,46 +754,10 @@ module.exports = React.createClass({
; } - let integrationsButton; - let integrationsError; - - if (this.scalarClient !== null) { - if (this.state.showIntegrationsError && this.state.scalar_error) { - console.error(this.state.scalar_error); - integrationsError = ( - - { _t('Could not connect to the integration server') } - - ); - } - - if (this.scalarClient.hasCredentials()) { - integrationsButton = ( -
- { _t('Manage Integrations') } -
- ); - } else if (this.state.scalar_error) { - integrationsButton = ( -
- Integrations Error - { integrationsError } -
- ); - } else { - integrationsButton = ( -
- { _t('Manage Integrations') } -
- ); - } - } - return (
{ leaveButton } - { integrationsButton } { tagsSection } From 308d932b2f02c6ed2120828af1f22e4401b4bd41 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Sun, 6 Aug 2017 10:29:43 +0100 Subject: [PATCH 009/425] CancelClick prop. --- src/components/views/rooms/RoomHeader.js | 6 +++--- src/components/views/rooms/RoomSettings.js | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index d4e390f750..66b0eab3e7 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -50,6 +50,7 @@ module.exports = React.createClass({ onSaveClick: React.PropTypes.func, onSearchClick: React.PropTypes.func, onLeaveClick: React.PropTypes.func, + onCancelClick: React.PropTypes.func, }, getDefaultProps: function() { @@ -57,6 +58,7 @@ module.exports = React.createClass({ editing: false, inRoom: false, onSaveClick: function() {}, + onCancelClick: function() {}, }; }, @@ -119,9 +121,7 @@ module.exports = React.createClass({ this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId) : null, onFinished: ()=>{ - if (this._calcSavePromises().length === 0) { - this.props.onCancelClick(ev); - } + this.props.onCancelClick(ev); }, }, "mx_IntegrationsManager"); }, diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index 1baaf3aaa9..0c1372f54e 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -90,7 +90,6 @@ module.exports = React.createClass({ propTypes: { room: React.PropTypes.object.isRequired, onSaveClick: React.PropTypes.func, - onCancelClick: React.PropTypes.func, }, getInitialState: function() { From 18ae5fd129ae49bfddeab4be050db73031c2fac7 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Sun, 6 Aug 2017 11:01:14 +0100 Subject: [PATCH 010/425] Send messages on widget addition and deletion --- src/ScalarMessaging.js | 6 ++++++ src/components/views/rooms/RoomHeader.js | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js index d14d439d66..cb6e79f5e6 100644 --- a/src/ScalarMessaging.js +++ b/src/ScalarMessaging.js @@ -338,6 +338,12 @@ function setWidget(event, roomId) { sendResponse(event, { success: true, }); + + if (widgetUrl !== null) { + client.sendTextMessage(roomId, `Added ${widgetType} widget - ${widgetUrl}`); + } else { + client.sendTextMessage(roomId, `Removed ${widgetType} widget`); + } }, (err) => { sendError(event, _t('Failed to send request.'), err); }); diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 66b0eab3e7..f26aa29d31 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -121,7 +121,9 @@ module.exports = React.createClass({ this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId) : null, onFinished: ()=>{ - this.props.onCancelClick(ev); + if (this.props.onCancelClick) { + this.props.onCancelClick(ev); + } }, }, "mx_IntegrationsManager"); }, From 4aff43afdad9c6215cace8b0481215572e2227dd Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 7 Aug 2017 10:24:51 +0100 Subject: [PATCH 011/425] Forgot to commit this file --- src/groups.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/groups.js diff --git a/src/groups.js b/src/groups.js new file mode 100644 index 0000000000..9627fcc1cd --- /dev/null +++ b/src/groups.js @@ -0,0 +1,27 @@ +/* +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. +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 PropTypes from 'prop-types'; + +export const GroupMemberType = PropTypes.shape({ + userId: PropTypes.string.isRequired, +}); + +export function groupMemberFromApiObject(apiObject) { + return { + userId: apiObject.user_id, + } +} From 9f8e8ae1fd58ccaf75505f5c0350e8b6585d87f9 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Tue, 8 Aug 2017 17:34:54 +0100 Subject: [PATCH 012/425] Split timeline updates in to different PR. --- src/ScalarMessaging.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js index cb6e79f5e6..d14d439d66 100644 --- a/src/ScalarMessaging.js +++ b/src/ScalarMessaging.js @@ -338,12 +338,6 @@ function setWidget(event, roomId) { sendResponse(event, { success: true, }); - - if (widgetUrl !== null) { - client.sendTextMessage(roomId, `Added ${widgetType} widget - ${widgetUrl}`); - } else { - client.sendTextMessage(roomId, `Removed ${widgetType} widget`); - } }, (err) => { sendError(event, _t('Failed to send request.'), err); }); From 4bc25f12cb792fccd79d1598346c5c9d57ba2d10 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Wed, 9 Aug 2017 11:44:24 +0100 Subject: [PATCH 013/425] Move manage integrations button in to stand-alone component --- .../views/elements/ManageIntegsButton.js | 127 ++++++++++++++++++ src/components/views/rooms/RoomHeader.js | 87 +----------- 2 files changed, 132 insertions(+), 82 deletions(-) create mode 100644 src/components/views/elements/ManageIntegsButton.js diff --git a/src/components/views/elements/ManageIntegsButton.js b/src/components/views/elements/ManageIntegsButton.js new file mode 100644 index 0000000000..8d4c9d8b85 --- /dev/null +++ b/src/components/views/elements/ManageIntegsButton.js @@ -0,0 +1,127 @@ +/* +Copyright 2017 Vector Creations Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; +import sdk from '../../../index'; +import SdkConfig from '../../../SdkConfig'; +import ScalarAuthClient from '../../../ScalarAuthClient'; +import ScalarMessaging from '../../../ScalarMessaging'; +import Modal from "../../../Modal"; +import { _t } from '../../../languageHandler'; +import AccessibleButton from './AccessibleButton'; +import TintableSvg from './TintableSvg'; + +export default class ManageIntegsButton extends React.Component { + constructor(props) { + super(props); + + this.state = { + scalar_error: null, + showIntegrationsError: false, + }; + + this.onManageIntegrations = this.onManageIntegrations.bind(this); + this.onShowIntegrationsError = this.onShowIntegrationsError.bind(this); + } + + componentWillMount() { + ScalarMessaging.startListening(); + this.scalarClient = null; + + if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) { + this.scalarClient = new ScalarAuthClient(); + this.scalarClient.connect().done(() => { + this.forceUpdate(); + }, (err) => { + this.setState({ scalar_error: err}); + }); + } + } + + componentWillUnmount() { + ScalarMessaging.stopListening(); + } + + onManageIntegrations(ev) { + ev.preventDefault(); + const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); + Modal.createDialog(IntegrationsManager, { + src: (this.scalarClient !== null && this.scalarClient.hasCredentials()) ? + this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId) : + null, + onFinished: ()=>{ + if (this.props.onCancelClick) { + this.props.onCancelClick(ev); + } + }, + }, "mx_IntegrationsManager"); + } + + onShowIntegrationsError(ev) { + ev.preventDefault(); + this.setState({ + showIntegrationsError: !this.state.showIntegrationsError, + }); + } + + render() { + let integrationsButton; + let integrationsError; + if (this.scalarClient !== null) { + if (this.state.showIntegrationsError && this.state.scalar_error) { + console.error(this.state.scalar_error); + integrationsError = ( + + { _t('Could not connect to the integration server') } + + ); + } + + if (this.scalarClient.hasCredentials()) { + integrationsButton = ( + + + + ); + } else if (this.state.scalar_error) { + integrationsButton = ( +
+ + { integrationsError } +
+ ); + } else { + integrationsButton = ( + + + + ); + } + } + + return integrationsButton; + } +} + +ManageIntegsButton.propTypes = { + room: PropTypes.object.isRequired, + onCancelClick: PropTypes.func, +}; + +ManageIntegsButton.defaultProps = { + onCancelClick: function() {}, +}; diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index f26aa29d31..ed354017b2 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -29,10 +29,8 @@ import * as linkify from 'linkifyjs'; import linkifyElement from 'linkifyjs/element'; import linkifyMatrix from '../../../linkify-matrix'; import AccessibleButton from '../elements/AccessibleButton'; +import ManageIntegsButton from '../elements/ManageIntegsButton'; import {CancelButton} from './SimpleRoomHeader'; -import SdkConfig from '../../../SdkConfig'; -import ScalarAuthClient from '../../../ScalarAuthClient'; -import ScalarMessaging from '../../../ScalarMessaging'; linkifyMatrix(linkify); @@ -62,13 +60,6 @@ module.exports = React.createClass({ }; }, - getInitialState: function() { - return { - scalar_error: null, - showIntegrationsError: false, - }; - }, - componentDidMount: function() { const cli = MatrixClientPeg.get(); cli.on("RoomState.events", this._onRoomStateEvents); @@ -87,23 +78,7 @@ module.exports = React.createClass({ } }, - componentWillMount: function() { - ScalarMessaging.startListening(); - this.scalarClient = null; - if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) { - this.scalarClient = new ScalarAuthClient(); - this.scalarClient.connect().done(() => { - this.forceUpdate(); - }, (err) => { - this.setState({ - scalar_error: err, - }); - }); - } - }, - componentWillUnmount: function() { - ScalarMessaging.stopListening(); if (this.props.room) { this.props.room.removeListener("Room.name", this._onRoomNameChange); } @@ -113,28 +88,6 @@ module.exports = React.createClass({ } }, - onManageIntegrations(ev) { - ev.preventDefault(); - const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); - Modal.createDialog(IntegrationsManager, { - src: (this.scalarClient !== null && this.scalarClient.hasCredentials()) ? - this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId) : - null, - onFinished: ()=>{ - if (this.props.onCancelClick) { - this.props.onCancelClick(ev); - } - }, - }, "mx_IntegrationsManager"); - }, - - onShowIntegrationsError(ev) { - ev.preventDefault(); - this.setState({ - showIntegrationsError: !this.state.showIntegrationsError, - }); - }, - _onRoomStateEvents: function(event, state) { if (!this.props.room || event.getRoomId() !== this.props.room.roomId) { return; @@ -370,45 +323,15 @@ module.exports = React.createClass({ } let rightRow; - let integrationsButton; - let integrationsError; - if (this.scalarClient !== null) { - if (this.state.showIntegrationsError && this.state.scalar_error) { - console.error(this.state.scalar_error); - integrationsError = ( - - { _t('Could not connect to the integration server') } - - ); - } - - if (this.scalarClient.hasCredentials()) { - integrationsButton = ( - - - - ); - } else if (this.state.scalar_error) { - integrationsButton = ( -
- - { integrationsError } -
- ); - } else { - integrationsButton = ( - - - - ); - } - } if (!this.props.editing) { rightRow =
{ settingsButton } - { integrationsButton } + { forgetButton } { searchButton } { rightPanelButtons } From 4daf9064d9ffa44545b5ac14f572e9a7227f1380 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 9 Aug 2017 18:15:31 +0100 Subject: [PATCH 014/425] Convert to API objects & remove redundant power label --- src/components/views/groups/GroupMemberInfo.js | 7 +++++-- src/components/views/groups/GroupMemberTile.js | 6 +----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/views/groups/GroupMemberInfo.js b/src/components/views/groups/GroupMemberInfo.js index 70aa7d4504..d37afb70a9 100644 --- a/src/components/views/groups/GroupMemberInfo.js +++ b/src/components/views/groups/GroupMemberInfo.js @@ -26,6 +26,7 @@ import DMRoomMap from '../../../utils/DMRoomMap'; import Unread from '../../../Unread'; import { GroupMemberType } from '../../../groups'; import { findReadReceiptFromUserId } from '../../../utils/Receipt'; +import { groupMemberFromApiObject } from '../../../groups'; import withMatrixClient from '../../../wrappers/withMatrixClient'; import AccessibleButton from '../elements/AccessibleButton'; import GeminiScrollbar from 'react-gemini-scrollbar'; @@ -48,7 +49,9 @@ module.exports = withMatrixClient(React.createClass({ this.setState({fetching: true}); this.props.matrixClient.getGroupUsers(this.props.groupId).then((result) => { this.setState({ - members: result.chunk, + members: result.chunk.map((apiMember) => { + return groupMemberFromApiObject(apiMember); + }), fetching: false, }); }).catch((e) => { @@ -91,7 +94,7 @@ module.exports = withMatrixClient(React.createClass({ let targetIsInGroup = false; for (const m of this.state.members) { - if (m.user_id == this.props.member.userId) { + if (m.userId == this.props.member.userId) { targetIsInGroup = true; } } diff --git a/src/components/views/groups/GroupMemberTile.js b/src/components/views/groups/GroupMemberTile.js index d129fb9440..35dbd8b531 100644 --- a/src/components/views/groups/GroupMemberTile.js +++ b/src/components/views/groups/GroupMemberTile.js @@ -44,10 +44,6 @@ export default withMatrixClient(React.createClass({ }); }, - getPowerLabel: function() { - return _t("%(userName)s (power %(powerLevelNumber)s)", {userName: this.props.member.userId, powerLevelNumber: this.props.member.powerLevel}); - }, - render: function() { const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); const EntityTile = sdk.getComponent('rooms.EntityTile'); @@ -60,7 +56,7 @@ export default withMatrixClient(React.createClass({ return ( ); From 0323151bee584434be0dd7a699c1ffc465aa0c3a Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Thu, 10 Aug 2017 23:53:43 +0100 Subject: [PATCH 015/425] Show a dialog if the maximum number of widgets allowed has been reached. --- src/components/views/rooms/AppsDrawer.js | 33 +++++++++++++++++------- src/i18n/strings/en_EN.json | 2 ++ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 5427d4ec6d..9071a5bcab 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -28,6 +28,8 @@ import ScalarMessaging from '../../../ScalarMessaging'; import { _t } from '../../../languageHandler'; import WidgetUtils from '../../../WidgetUtils'; +// The maximum number of widgets that can be added in a room +const MAX_WIDGETS = 2; module.exports = React.createClass({ displayName: 'AppsDrawer', @@ -162,6 +164,18 @@ module.exports = React.createClass({ e.preventDefault(); } + // Display a warning dialog if the max number of widgets have already been added to the room + if (this.state.apps && this.state.apps.length >= MAX_WIDGETS) { + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + const errorMsg = `The maximum number of ${MAX_WIDGETS} widgets have already been added to this room.`; + console.error(errorMsg); + Modal.createDialog(ErrorDialog, { + title: _t("Cannot add any more widgets"), + description: _t("The maximum permitted number of widgets have already been added to this room."), + }); + return; + } + const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); const src = (this.scalarClient !== null && this.scalarClient.hasCredentials()) ? this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId, 'add_integ') : @@ -186,21 +200,22 @@ module.exports = React.createClass({ />); }); - const addWidget = this.state.apps && this.state.apps.length < 2 && this._canUserModify() && - (
- [+] {_t('Add a widget')} -
); + const addWidget = ( +
+ [+] {_t('Add a widget')} +
+ ); return (
{apps}
- {addWidget} + {this._canUserModify() && addWidget}
); }, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 0d5b7d9d96..784ca54bb7 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -190,6 +190,7 @@ "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.", "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.", "Can't load user settings": "Can't load user settings", + "Cannot add any more widgets": "Cannot add any more widgets", "Change Password": "Change Password", "%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.": "%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.", "%(senderName)s changed their profile picture.": "%(senderName)s changed their profile picture.", @@ -546,6 +547,7 @@ "Tagged as: ": "Tagged as: ", "The default role for new room members is": "The default role for new room members is", "The main address for this room is": "The main address for this room is", + "The maximum permitted number of widgets have already been added to this room.": "The maximum permitted number of widgets have already been added to this room.", "The phone number entered looks invalid": "The phone number entered looks invalid", "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.", "This action cannot be performed by a guest user. Please register to be able to do this.": "This action cannot be performed by a guest user. Please register to be able to do this.", From 4a1ba01f44df7bbe7a7d7812b9c359701edb0e6c Mon Sep 17 00:00:00 2001 From: MTRNord Date: Sun, 13 Aug 2017 00:56:37 +0000 Subject: [PATCH 016/425] fix deprecation warning --- src/languageHandler.js | 47 +++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/src/languageHandler.js b/src/languageHandler.js index e956d4f8bd..54a8596d5d 100644 --- a/src/languageHandler.js +++ b/src/languageHandler.js @@ -231,35 +231,30 @@ export function getCurrentLanguage() { } function getLangsJson() { - const deferred = Promise.defer(); - - request( - { method: "GET", url: i18nFolder + 'languages.json' }, - (err, response, body) => { - if (err || response.status < 200 || response.status >= 300) { - deferred.reject({err: err, response: response}); - return; + return new Promise((resolve, reject) => { + request( + { method: "GET", url: i18nFolder + 'languages.json' }, + (err, response, body) => { + if (err || response.status < 200 || response.status >= 300) { + reject({err: err, response: response}); + } + resolve(JSON.parse(body)); } - deferred.resolve(JSON.parse(body)); - } - ); - return deferred.promise; + ); + }); } function getLanguage(langPath) { - const deferred = Promise.defer(); - - let response_return = {}; - request( - { method: "GET", url: langPath }, - (err, response, body) => { - if (err || response.status < 200 || response.status >= 300) { - deferred.reject({err: err, response: response}); - return; + return new Promise((resolve, reject) => { + let response_return = {}; + request( + { method: "GET", url: langPath }, + (err, response, body) => { + if (err || response.status < 200 || response.status >= 300) { + reject({err: err, response: response}); + } + resolve(JSON.parse(body)); } - - deferred.resolve(JSON.parse(body)); - } - ); - return deferred.promise; + ); + }); } From a15c2100d1de9d9cf8f3cfc7e9f6fbed01c4ffbb Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 15 Aug 2017 13:12:39 +0100 Subject: [PATCH 017/425] Sort out right panel callapsing on GroupView --- src/components/structures/GroupView.js | 40 ++++++++++++++++------- src/components/structures/LoggedInView.js | 3 +- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 20fc4841ba..14d5d24bb5 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -216,6 +216,10 @@ export default React.createClass({ }); }, + _onShowRhsClick: function(ev) { + dis.dispatch({ action: 'show_right_panel' }); + }, + _onEditClick: function() { this.setState({ editing: true, @@ -384,8 +388,8 @@ export default React.createClass({ let avatarNode; let nameNode; let shortDescNode; - let rightButtons; let roomBody; + let rightButtons = []; const headerClasses = { mx_GroupView_header: true, }; @@ -428,15 +432,19 @@ export default React.createClass({ placeholder={_t('Description')} tabIndex="2" />; - rightButtons = - + rightButtons.push( + {_t('Save')} - + ); + rightButtons.push( + {_t("Cancel")}/ - ; + ); roomBody =