diff --git a/res/css/_components.scss b/res/css/_components.scss index 860b0f126e..62b5031a31 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -184,6 +184,7 @@ @import "./views/rooms/_MemberList.scss"; @import "./views/rooms/_MessageComposer.scss"; @import "./views/rooms/_MessageComposerFormatBar.scss"; +@import "./views/rooms/_NewRoomIntro.scss"; @import "./views/rooms/_NotificationBadge.scss"; @import "./views/rooms/_PinnedEventTile.scss"; @import "./views/rooms/_PinnedEventsPanel.scss"; diff --git a/res/css/views/messages/_CreateEvent.scss b/res/css/views/messages/_CreateEvent.scss index 1181a80d10..a61a261ff1 100644 --- a/res/css/views/messages/_CreateEvent.scss +++ b/res/css/views/messages/_CreateEvent.scss @@ -16,7 +16,7 @@ limitations under the License. .mx_CreateEvent { &::before { - background-color: $primary-fg-color; + background-color: $composer-e2e-icon-color; mask-image: url('$(res)/img/element-icons/chat-bubbles.svg'); } } diff --git a/res/css/views/rooms/_NewRoomIntro.scss b/res/css/views/rooms/_NewRoomIntro.scss new file mode 100644 index 0000000000..af72a0dd69 --- /dev/null +++ b/res/css/views/rooms/_NewRoomIntro.scss @@ -0,0 +1,67 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +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. +*/ + +.mx_NewRoomIntro { + margin: 80px 0 48px 64px; + + .mx_MiniAvatarUploader_hasAvatar:not(.mx_MiniAvatarUploader_busy):not(:hover) { + &::before, &::after { + content: unset; + } + } + + .mx_AccessibleButton_kind_link { + padding: 0; + font-size: inherit; + } + + .mx_NewRoomIntro_buttons { + margin-top: 28px; + + .mx_AccessibleButton { + line-height: $font-24px; + + &::before { + content: ''; + display: inline-block; + background-color: $button-fg-color; + mask-position: center; + mask-repeat: no-repeat; + mask-size: 20px; + width: 20px; + height: 20px; + margin-right: 5px; + vertical-align: text-bottom; + } + } + + .mx_NewRoomIntro_inviteButton::before { + mask-image: url('$(res)/img/element-icons/room/invite.svg'); + } + } + + > h2 { + margin-top: 24px; + font-size: $font-24px; + font-weight: 600; + } + + > p { + margin: 0; + font-size: $font-15px; + color: $secondary-fg-color; + } +} diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 9cf3eaaa9d..512ffb6c07 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -31,6 +31,7 @@ import {haveTileForEvent} from "../views/rooms/EventTile"; import {textForEvent} from "../../TextForEvent"; import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer"; import DMRoomMap from "../../utils/DMRoomMap"; +import NewRoomIntro from "../views/rooms/NewRoomIntro"; const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes const continuedTypes = ['m.sticker', 'm.room.message']; @@ -955,13 +956,16 @@ class CreationGrouper { const ev = this.events[this.events.length - 1]; let summaryText; + const roomId = ev.getRoomId(); const creator = ev.sender ? ev.sender.name : ev.getSender(); - if (DMRoomMap.shared().getUserIdForRoomId(ev.getRoomId())) { + if (DMRoomMap.shared().getUserIdForRoomId(roomId)) { summaryText = _t("%(creator)s created this DM.", { creator }); } else { summaryText = _t("%(creator)s created and configured the room.", { creator }); } + ret.push(); + ret.push( { }; public render() { - const {room, oobData, viewAvatarOnClick, ...otherProps} = this.props; + const {room, oobData, viewAvatarOnClick, onClick, ...otherProps} = this.props; const roomName = room ? room.name : oobData.name; @@ -139,7 +140,7 @@ export default class RoomAvatar extends React.Component { name={roomName} idName={room ? room.roomId : null} urls={this.state.urls} - onClick={viewAvatarOnClick && this.state.urls[0] ? this.onRoomAvatarClick : null} + onClick={viewAvatarOnClick && this.state.urls[0] ? this.onRoomAvatarClick : onClick} /> ); } diff --git a/src/components/views/rooms/NewRoomIntro.tsx b/src/components/views/rooms/NewRoomIntro.tsx new file mode 100644 index 0000000000..27404eef12 --- /dev/null +++ b/src/components/views/rooms/NewRoomIntro.tsx @@ -0,0 +1,131 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +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, {useContext} from "react"; +import {EventType} from "matrix-js-sdk/src/@types/event"; + +import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import RoomContext from "../../../contexts/RoomContext"; +import DMRoomMap from "../../../utils/DMRoomMap"; +import {_t} from "../../../languageHandler"; +import AccessibleButton from "../elements/AccessibleButton"; +import MiniAvatarUploader, {AVATAR_SIZE} from "../elements/MiniAvatarUploader"; +import RoomAvatar from "../avatars/RoomAvatar"; +import defaultDispatcher from "../../../dispatcher/dispatcher"; +import {ViewUserPayload} from "../../../dispatcher/payloads/ViewUserPayload"; +import {Action} from "../../../dispatcher/actions"; +import dis from "../../../dispatcher/dispatcher"; + +const NewRoomIntro = () => { + const cli = useContext(MatrixClientContext); + const {room, roomId} = useContext(RoomContext); + + const dmPartner = DMRoomMap.shared().getUserIdForRoomId(roomId); + let body; + if (dmPartner) { + let caption; + if ((room.getJoinedMemberCount() + room.getInvitedMemberCount()) === 2) { + caption = _t("Only the two of you are in this conversation, unless either of you invites anyone to join."); + } + + const member = room?.getMember(dmPartner); + const displayName = member?.rawDisplayName || dmPartner; + body = + { + defaultDispatcher.dispatch({ + action: Action.ViewUser, + // XXX: We should be using a real member object and not assuming what the receiver wants. + member: member || {userId: dmPartner}, + }); + }} /> + +

{ room.name }

+ +

{_t("This is the beginning of your direct message history with .", {}, { + displayName: () => { displayName }, + })}

+ { caption &&

{ caption }

} +
; + } else { + const topic = room.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic; + const canAddTopic = room.currentState.maySendStateEvent(EventType.RoomTopic, cli.getUserId()); + + const onTopicClick = () => { + dis.dispatch({ + action: "open_room_settings", + room_id: roomId, + }, true); + // focus the topic field to help the user find it as it'll gain an outline + setImmediate(() => { + window.document.getElementById("profileTopic").focus(); + }); + }; + + let topicText; + if (canAddTopic && topic) { + topicText = _t("Topic: %(topic)s (edit)", { topic }, { + a: sub => { sub }, + }); + } else if (topic) { + topicText = _t("Topic: %(topic)s ", { topic }); + } else if (canAddTopic) { + topicText = _t("Add a topic to help people know what it is about.", {}, { + a: sub => { sub }, + }); + } + + const creator = room.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender(); + const creatorName = room?.getMember(creator)?.rawDisplayName || creator; + + let createdText; + if (creator === cli.getUserId()) { + createdText = _t("You created this room."); + } else { + createdText = _t("%(displayName)s created this room.", { + displayName: creatorName, + }); + } + + const avatarUrl = room.currentState.getStateEvents(EventType.RoomAvatar, "")?.getContent()?.url; + body = + cli.sendStateEvent(roomId, EventType.RoomAvatar, { url }, '')} + > + + + +

{ room.name }

+ +

{createdText} {_t("This is the start of .", {}, { + roomName: () => { room.name }, + })}

+

{topicText}

+
+ + {_t("Invite to this room")} + +
+
; + } + + return
+ { body } +
; +}; + +export default NewRoomIntro;