diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 30e897e5dc..2b5b3d5353 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -31,7 +31,6 @@ import GroupStoreCache from '../../stores/GroupStoreCache'; import GroupStore from '../../stores/GroupStore'; import FlairStore from '../../stores/FlairStore'; import { showGroupAddRoomDialog } from '../../GroupAddressPicker'; -import GeminiScrollbar from 'react-gemini-scrollbar'; import {makeGroupPermalink, makeUserPermalink} from "../../matrix-to"; const LONG_DESC_PLACEHOLDER = _td( @@ -969,6 +968,7 @@ export default React.createClass({ const GroupAvatar = sdk.getComponent("avatars.GroupAvatar"); const Spinner = sdk.getComponent("elements.Spinner"); const TintableSvg = sdk.getComponent("elements.TintableSvg"); + const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); if (this.state.summaryLoading && this.state.error === null || this.state.saving) { return ; @@ -1119,9 +1119,9 @@ export default React.createClass({ { rightButtons } - + { bodyNodes } - + ); } else if (this.state.error) { diff --git a/src/components/structures/MyGroups.js b/src/components/structures/MyGroups.js index da7bebd16a..7a93cfb886 100644 --- a/src/components/structures/MyGroups.js +++ b/src/components/structures/MyGroups.js @@ -16,7 +16,6 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import GeminiScrollbar from 'react-gemini-scrollbar'; import sdk from '../../index'; import { _t } from '../../languageHandler'; import dis from '../../dispatcher'; @@ -63,6 +62,8 @@ export default withMatrixClient(React.createClass({ const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader'); const TintableSvg = sdk.getComponent("elements.TintableSvg"); const GroupTile = sdk.getComponent("groups.GroupTile"); + const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); + let content; let contentHeader; @@ -73,7 +74,7 @@ export default withMatrixClient(React.createClass({ }); contentHeader = groupNodes.length > 0 ?

{ _t('Your Communities') }

:
; content = groupNodes.length > 0 ? - +

{ _t( @@ -92,7 +93,7 @@ export default withMatrixClient(React.createClass({

{ groupNodes }
- : + :
{ _t( "You're not currently a member of any communities.", diff --git a/src/components/structures/ScrollPanel.js b/src/components/structures/ScrollPanel.js index cbb6001d5f..0fdbc9a349 100644 --- a/src/components/structures/ScrollPanel.js +++ b/src/components/structures/ScrollPanel.js @@ -17,9 +17,9 @@ limitations under the License. const React = require("react"); const ReactDOM = require("react-dom"); import PropTypes from 'prop-types'; -const GeminiScrollbar = require('react-gemini-scrollbar'); import Promise from 'bluebird'; import { KeyCode } from '../../Keyboard'; +import sdk from '../../index.js'; const DEBUG_SCROLL = false; // var DEBUG_SCROLL = true; @@ -224,7 +224,7 @@ module.exports = React.createClass({ onResize: function() { this.props.onResize(); this.checkScroll(); - this.refs.geminiPanel.forceUpdate(); + if (this._gemScroll) this._gemScroll.forceUpdate(); }, // after an update to the contents of the panel, check that the scroll is @@ -665,14 +665,25 @@ module.exports = React.createClass({ throw new Error("ScrollPanel._getScrollNode called when unmounted"); } - return this.refs.geminiPanel.scrollbar.getViewElement(); + if (!this._gemScroll) { + // Likewise, we should have the ref by this point, but if not + // turn the NPE into something meaningful. + throw new Error("ScrollPanel._getScrollNode called before gemini ref collected"); + } + + return this._gemScroll.scrollbar.getViewElement(); + }, + + _collectGeminiScroll: function(gemScroll) { + this._gemScroll = gemScroll; }, render: function() { + const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); // TODO: the classnames on the div and ol could do with being updated to // reflect the fact that we don't necessarily contain a list of messages. // it's not obvious why we have a separate div and ol anyway. - return (
@@ -680,7 +691,7 @@ module.exports = React.createClass({ { this.props.children }
-
+ ); }, }); diff --git a/src/components/structures/TagPanel.js b/src/components/structures/TagPanel.js index 7a187e4298..790c497a67 100644 --- a/src/components/structures/TagPanel.js +++ b/src/components/structures/TagPanel.js @@ -17,7 +17,6 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; import { MatrixClient } from 'matrix-js-sdk'; -import GeminiScrollbar from 'react-gemini-scrollbar'; import TagOrderStore from '../../stores/TagOrderStore'; import GroupActions from '../../actions/GroupActions'; @@ -102,6 +101,8 @@ const TagPanel = React.createClass({ const DNDTagTile = sdk.getComponent('elements.DNDTagTile'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const TintableSvg = sdk.getComponent('elements.TintableSvg'); + const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); + const tags = this.state.orderedTags.map((tag, index) => { return
- ) } - +
diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index f6629b4b09..85223c4eef 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -30,7 +30,6 @@ import Promise from 'bluebird'; const packageJson = require('../../../package.json'); const UserSettingsStore = require('../../UserSettingsStore'); const CallMediaHandler = require('../../CallMediaHandler'); -const GeminiScrollbar = require('react-gemini-scrollbar'); const Email = require('../../email'); const AddThreepid = require('../../AddThreepid'); const SdkConfig = require('../../SdkConfig'); @@ -1118,6 +1117,7 @@ module.exports = React.createClass({ const ChangeAvatar = sdk.getComponent('settings.ChangeAvatar'); const Notifications = sdk.getComponent("settings.Notifications"); const EditableText = sdk.getComponent('elements.EditableText'); + const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); const avatarUrl = ( this.state.avatarUrl ? MatrixClientPeg.get().mxcUrlToHttp(this.state.avatarUrl) : null @@ -1213,8 +1213,9 @@ module.exports = React.createClass({ onCancelClick={this.props.onClose} /> - +

{ _t("Profile") }

@@ -1327,7 +1328,7 @@ module.exports = React.createClass({ { this._renderDeactivateAccount() } -
+
); }, diff --git a/src/components/views/dialogs/UnknownDeviceDialog.js b/src/components/views/dialogs/UnknownDeviceDialog.js index 26aa5d3ecb..07f8b6187f 100644 --- a/src/components/views/dialogs/UnknownDeviceDialog.js +++ b/src/components/views/dialogs/UnknownDeviceDialog.js @@ -146,6 +146,7 @@ export default React.createClass({ }, render: function() { + const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); if (this.props.devices === null) { const Spinner = sdk.getComponent("elements.Spinner"); return ; @@ -191,7 +192,7 @@ export default React.createClass({ title={_t('Room contains unknown devices')} contentId='mx_Dialog_content' > - +

{ _t('"%(RoomName)s" contains devices that you haven\'t seen before.', {RoomName: this.props.room.name}) }

@@ -199,7 +200,7 @@ export default React.createClass({ { _t("Unknown devices") }: -
+ diff --git a/src/components/views/elements/GeminiScrollbarWrapper.js b/src/components/views/elements/GeminiScrollbarWrapper.js new file mode 100644 index 0000000000..af801c0113 --- /dev/null +++ b/src/components/views/elements/GeminiScrollbarWrapper.js @@ -0,0 +1,33 @@ +/* +Copyright 2018 New Vector Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import GeminiScrollbar from 'react-gemini-scrollbar'; + +function GeminiScrollbarWrapper(props) { + // Enable forceGemini so that gemini is always enabled. This is + // to avoid future issues where a feature is implemented without + // doing QA on every OS/browser combination. + // + // By default GeminiScrollbar allows native scrollbars to be used + // on macOS. Use forceGemini to enable Gemini's non-native + // scrollbars on all OSs. + return + { props.children } + ; +} +export default GeminiScrollbarWrapper; + diff --git a/src/components/views/groups/GroupMemberInfo.js b/src/components/views/groups/GroupMemberInfo.js index 097fb1f7db..4970a26e5b 100644 --- a/src/components/views/groups/GroupMemberInfo.js +++ b/src/components/views/groups/GroupMemberInfo.js @@ -25,7 +25,6 @@ import { _t } from '../../../languageHandler'; import { GroupMemberType } from '../../../groups'; import GroupStoreCache from '../../../stores/GroupStoreCache'; import AccessibleButton from '../elements/AccessibleButton'; -import GeminiScrollbar from 'react-gemini-scrollbar'; module.exports = React.createClass({ displayName: 'GroupMemberInfo', @@ -180,9 +179,10 @@ module.exports = React.createClass({ ); const EmojiText = sdk.getComponent('elements.EmojiText'); + const GeminiScrollbarWrapper = sdk.getComponent('elements.GeminiScrollbarWrapper'); return (
- + @@ -199,7 +199,7 @@ module.exports = React.createClass({
{ adminTools } - +
); }, diff --git a/src/components/views/groups/GroupMemberList.js b/src/components/views/groups/GroupMemberList.js index 71c168f5e6..17a91d83fa 100644 --- a/src/components/views/groups/GroupMemberList.js +++ b/src/components/views/groups/GroupMemberList.js @@ -18,7 +18,6 @@ import React from 'react'; import { _t } from '../../../languageHandler'; import sdk from '../../../index'; import GroupStoreCache from '../../../stores/GroupStoreCache'; -import GeminiScrollbar from 'react-gemini-scrollbar'; import PropTypes from 'prop-types'; const INITIAL_LOAD_NUM_MEMBERS = 30; @@ -134,6 +133,7 @@ export default React.createClass({ }, render: function() { + const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); if (this.state.fetching || this.state.fetchingInvitedMembers) { const Spinner = sdk.getComponent("elements.Spinner"); return (
@@ -162,10 +162,10 @@ export default React.createClass({ return (
{ inputBox } - + { joined } { invited } - +
); }, diff --git a/src/components/views/groups/GroupRoomInfo.js b/src/components/views/groups/GroupRoomInfo.js index fa4ed89ae0..2d2b4e655c 100644 --- a/src/components/views/groups/GroupRoomInfo.js +++ b/src/components/views/groups/GroupRoomInfo.js @@ -22,7 +22,6 @@ import Modal from '../../../Modal'; import sdk from '../../../index'; import { _t } from '../../../languageHandler'; import GroupStoreCache from '../../../stores/GroupStoreCache'; -import GeminiScrollbar from 'react-gemini-scrollbar'; module.exports = React.createClass({ displayName: 'GroupRoomInfo', @@ -157,6 +156,7 @@ module.exports = React.createClass({ const EmojiText = sdk.getComponent('elements.EmojiText'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const InlineSpinner = sdk.getComponent('elements.InlineSpinner'); + const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); if (this.state.groupRoomRemoveLoading || !this.state.groupRoom) { const Spinner = sdk.getComponent("elements.Spinner"); return
@@ -216,7 +216,7 @@ module.exports = React.createClass({ const avatar = ; return (
- + @@ -233,7 +233,7 @@ module.exports = React.createClass({
{ adminTools } - +
); }, diff --git a/src/components/views/groups/GroupRoomList.js b/src/components/views/groups/GroupRoomList.js index 189fa944e2..0515865c6b 100644 --- a/src/components/views/groups/GroupRoomList.js +++ b/src/components/views/groups/GroupRoomList.js @@ -17,7 +17,6 @@ import React from 'react'; import { _t } from '../../../languageHandler'; import sdk from '../../../index'; import GroupStoreCache from '../../../stores/GroupStoreCache'; -import GeminiScrollbar from 'react-gemini-scrollbar'; import PropTypes from 'prop-types'; const INITIAL_LOAD_NUM_ROOMS = 30; @@ -120,16 +119,17 @@ export default React.createClass({ ); + const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); const TruncatedList = sdk.getComponent("elements.TruncatedList"); return (
{ inputBox } - + { this.makeGroupRoomTiles(this.state.searchQuery) } - +
); }, diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index c449ad2683..2789c0e4cd 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -39,7 +39,6 @@ import Unread from '../../../Unread'; import { findReadReceiptFromUserId } from '../../../utils/Receipt'; import withMatrixClient from '../../../wrappers/withMatrixClient'; import AccessibleButton from '../elements/AccessibleButton'; -import GeminiScrollbar from 'react-gemini-scrollbar'; import RoomViewStore from '../../../stores/RoomViewStore'; import SdkConfig from '../../../SdkConfig'; @@ -897,11 +896,12 @@ module.exports = withMatrixClient(React.createClass({
; } + const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); const MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); const EmojiText = sdk.getComponent('elements.EmojiText'); return (
- +
@@ -925,7 +925,7 @@ module.exports = withMatrixClient(React.createClass({ { this._renderDevices() } { spinner } - +
); }, diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index 620cfe33fc..3dfcd38cbd 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -21,7 +21,6 @@ import { _t } from '../../../languageHandler'; import SdkConfig from '../../../SdkConfig'; const MatrixClientPeg = require("../../../MatrixClientPeg"); const sdk = require('../../../index'); -const GeminiScrollbar = require('react-gemini-scrollbar'); const rate_limited_func = require('../../../ratelimitedfunc'); const CallHandler = require("../../../CallHandler"); @@ -395,6 +394,7 @@ module.exports = React.createClass({ render: function() { const TruncatedList = sdk.getComponent("elements.TruncatedList"); + const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); let invitedSection = null; if (this._getChildCountInvited() > 0) { @@ -423,14 +423,14 @@ module.exports = React.createClass({ return (
{ inputBox } - + { invitedSection } - +
); }, diff --git a/src/components/views/rooms/ReadReceiptMarker.js b/src/components/views/rooms/ReadReceiptMarker.js index 5dd62ff6b7..0029395d3d 100644 --- a/src/components/views/rooms/ReadReceiptMarker.js +++ b/src/components/views/rooms/ReadReceiptMarker.js @@ -185,10 +185,21 @@ module.exports = React.createClass({ let title; if (this.props.timestamp) { - title = _t( - "Seen by %(userName)s at %(dateTime)s", - {userName: this.props.member.userId, dateTime: formatDate(new Date(this.props.timestamp), this.props.showTwelveHour)}, - ); + const dateString = formatDate(new Date(this.props.timestamp), this.props.showTwelveHour); + if (this.props.member.userId === this.props.member.rawDisplayName) { + title = _t( + "Seen by %(userName)s at %(dateTime)s", + {userName: this.props.member.userId, + dateTime: dateString}, + ); + } else { + title = _t( + "Seen by %(displayName)s (%(userName)s) at %(dateTime)s", + {displayName: this.props.member.rawDisplayName, + userName: this.props.member.userId, + dateTime: dateString}, + ); + } } return ( diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index 6ccf04c5d8..acf04831e8 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -20,7 +20,6 @@ const React = require("react"); const ReactDOM = require("react-dom"); import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; -const GeminiScrollbar = require('react-gemini-scrollbar'); const MatrixClientPeg = require("../../../MatrixClientPeg"); const CallHandler = require('../../../CallHandler'); const dis = require("../../../dispatcher"); @@ -351,7 +350,7 @@ module.exports = React.createClass({ return Boolean(isRoomVisible[taggedRoom.roomId]); }); - + if (filteredRooms.length > 0 || tagName.match(STANDARD_TAGS_REGEX)) { filteredLists[tagName] = filteredRooms; } @@ -508,7 +507,8 @@ module.exports = React.createClass({ onShowMoreRooms: function() { // kick gemini in the balls to get it to wake up // XXX: uuuuuuugh. - this.refs.gemscroll.forceUpdate(); + if (!this._gemScroll) return; + this._gemScroll.forceUpdate(); }, _getEmptyContent: function(section) { @@ -599,13 +599,18 @@ module.exports = React.createClass({ return ret; }, + _collectGemini(gemScroll) { + this._gemScroll = gemScroll; + }, + render: function() { const RoomSubList = sdk.getComponent('structures.RoomSubList'); + const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); const self = this; return ( - +
-
+
); }, }); diff --git a/src/components/views/rooms/SearchableEntityList.js b/src/components/views/rooms/SearchableEntityList.js index ff75524a5d..321f365da7 100644 --- a/src/components/views/rooms/SearchableEntityList.js +++ b/src/components/views/rooms/SearchableEntityList.js @@ -19,7 +19,6 @@ const MatrixClientPeg = require("../../../MatrixClientPeg"); const Modal = require("../../../Modal"); const sdk = require("../../../index"); import { _t } from '../../../languageHandler'; -const GeminiScrollbar = require('react-gemini-scrollbar'); // A list capable of displaying entities which conform to the SearchableEntity // interface which is an object containing getJsx(): Jsx and matches(query: string): boolean @@ -164,11 +163,12 @@ const SearchableEntityList = React.createClass({
); } + const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); list = ( - { list } - + ); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 1dc9669b4a..f7ce28b139 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -392,6 +392,7 @@ "Unknown": "Unknown", "Seen by %(userName)s at %(dateTime)s": "Seen by %(userName)s at %(dateTime)s", "Replying": "Replying", + "Seen by %(displayName)s (%(userName)s) at %(dateTime)s": "Seen by %(displayName)s (%(userName)s) at %(dateTime)s", "No rooms to show": "No rooms to show", "Unnamed room": "Unnamed room", "World readable": "World readable",