Преглед на файлове

feat(chat): on web, group messages by sender

master
Leonard Kim преди 6 години
родител
ревизия
fb5a45f714

+ 40
- 15
css/_chat.scss Целия файл

130
     border-radius:0;
130
     border-radius:0;
131
     box-shadow: none;
131
     box-shadow: none;
132
     color: white;
132
     color: white;
133
-    font-size: 10pt;
133
+    font-size: 15px;
134
     line-height: 30px;
134
     line-height: 30px;
135
     padding: 5px;
135
     padding: 5px;
136
     max-height:150px;
136
     max-height:150px;
162
     }
162
     }
163
 
163
 
164
     .display-name {
164
     .display-name {
165
-        float: left;
166
-        padding-left: 5px;
165
+        font-size: 13px;
167
         font-weight: bold;
166
         font-weight: bold;
168
         white-space: nowrap;
167
         white-space: nowrap;
169
         text-overflow: ellipsis;
168
         text-overflow: ellipsis;
170
-        width: 95%;
171
         overflow: hidden;
169
         overflow: hidden;
172
     }
170
     }
173
-
174
-    .timestamp {
175
-        float: right;
176
-        padding-right: 5px;
177
-        font-size: 11px;
178
-    }
179
-}
180
-
181
-.usermessage {
182
-    padding-top: 20px;
183
-    padding-left: 5px;
184
 }
171
 }
185
 
172
 
186
 .chatmessage {
173
 .chatmessage {
290
 #usermsg::-webkit-scrollbar-track-piece {
277
 #usermsg::-webkit-scrollbar-track-piece {
291
     background: #3a3a3a;
278
     background: #3a3a3a;
292
 }
279
 }
280
+
281
+.chat-message-group {
282
+    display: flex;
283
+    flex-direction: column;
284
+
285
+    &.local {
286
+        align-items: flex-end;
287
+
288
+        .display-name {
289
+            display: none;
290
+        }
291
+
292
+        .chatmessage {
293
+            background-color: $chatLocalMessageBackgroundColor;
294
+            border-radius: 6px 0px 6px 6px;
295
+        }
296
+    }
297
+
298
+    &.error {
299
+        .chatmessage {
300
+            border-radius: 0px;
301
+            color: red;
302
+        }
303
+
304
+        .display-name {
305
+            display: none;
306
+        }
307
+    }
308
+
309
+    .chatmessage {
310
+        background-color: $chatRemoteMessageBackgroundColor;
311
+        border-radius: 0px 6px 6px 6px;
312
+        display: inline-block;
313
+        margin-top: 3px;
314
+        color: white;
315
+        padding: 8px;
316
+    }
317
+}

+ 11
- 1
react/features/chat/components/AbstractChatMessage.js Целия файл

19
      */
19
      */
20
     message: Object,
20
     message: Object,
21
 
21
 
22
+    /**
23
+     * Whether or not the name of the participant which sent the message should
24
+     * be displayed.
25
+     */
26
+    showDisplayName: boolean,
27
+
22
     /**
28
     /**
23
      * Invoked to receive translated strings.
29
      * Invoked to receive translated strings.
24
      */
30
      */
28
 /**
34
 /**
29
  * Abstract component to display a chat message.
35
  * Abstract component to display a chat message.
30
  */
36
  */
31
-export default class AbstractChatMessage<P: Props> extends PureComponent<P> {}
37
+export default class AbstractChatMessage<P: Props> extends PureComponent<P> {
38
+    static defaultProps = {
39
+        showDisplayName: true
40
+    };
41
+}
32
 
42
 
33
 /**
43
 /**
34
  * Maps part of the Redux state to the props of this component.
44
  * Maps part of the Redux state to the props of this component.

+ 51
- 20
react/features/chat/components/web/Chat.js Целия файл

12
     type Props
12
     type Props
13
 } from '../AbstractChat';
13
 } from '../AbstractChat';
14
 import ChatInput from './ChatInput';
14
 import ChatInput from './ChatInput';
15
-import ChatMessage from './ChatMessage';
15
+import ChatMessageGroup from './ChatMessageGroup';
16
 import DisplayNameForm from './DisplayNameForm';
16
 import DisplayNameForm from './DisplayNameForm';
17
 
17
 
18
 /**
18
 /**
46
         this._messagesListEnd = null;
46
         this._messagesListEnd = null;
47
 
47
 
48
         // Bind event handlers so they are only bound once for every instance.
48
         // Bind event handlers so they are only bound once for every instance.
49
-        this._renderMessage = this._renderMessage.bind(this);
50
         this._renderPanelContent = this._renderPanelContent.bind(this);
49
         this._renderPanelContent = this._renderPanelContent.bind(this);
51
         this._setMessageListEndRef = this._setMessageListEndRef.bind(this);
50
         this._setMessageListEndRef = this._setMessageListEndRef.bind(this);
52
     }
51
     }
88
         );
87
         );
89
     }
88
     }
90
 
89
 
90
+    /**
91
+     * Iterates over all the messages and creates nested arrays which hold
92
+     * consecutive messages sent be the same participant.
93
+     *
94
+     * @private
95
+     * @returns {Array<Array<Object>>}
96
+     */
97
+    _getMessagesGroupedBySender() {
98
+        const messagesCount = this.props._messages.length;
99
+        const groups = [];
100
+        let currentGrouping = [];
101
+        let currentGroupParticipantId;
102
+
103
+        for (let i = 0; i < messagesCount; i++) {
104
+            const message = this.props._messages[i];
105
+
106
+            if (message.id === currentGroupParticipantId) {
107
+                currentGrouping.push(message);
108
+            } else {
109
+                groups.push(currentGrouping);
110
+
111
+                currentGrouping = [ message ];
112
+                currentGroupParticipantId = message.id;
113
+            }
114
+        }
115
+
116
+        groups.push(currentGrouping);
117
+
118
+        return groups;
119
+    }
120
+
91
     /**
121
     /**
92
      * Returns a React Element for showing chat messages and a form to send new
122
      * Returns a React Element for showing chat messages and a form to send new
93
      * chat messages.
123
      * chat messages.
96
      * @returns {ReactElement}
126
      * @returns {ReactElement}
97
      */
127
      */
98
     _renderChat() {
128
     _renderChat() {
99
-        const messages = this.props._messages.map(this._renderMessage);
129
+        const groupedMessages = this._getMessagesGroupedBySender();
130
+
131
+        const messages = groupedMessages.map((group, index) => {
132
+            const messageType = group[0] && group[0].messageType;
133
+            let className = 'remote';
134
+
135
+            if (messageType === 'local') {
136
+                className = 'local';
137
+            } else if (messageType === 'error') {
138
+                className = 'error';
139
+            }
140
+
141
+            return (
142
+                <ChatMessageGroup
143
+                    className = { className }
144
+                    key = { index }
145
+                    messages = { group } />
146
+            );
147
+        });
100
 
148
 
101
         messages.push(<div
149
         messages.push(<div
102
             key = 'end-marker'
150
             key = 'end-marker'
129
         );
177
         );
130
     }
178
     }
131
 
179
 
132
-    _renderMessage: (Object) => void;
133
-
134
-    /**
135
-     * Called by {@code _onSubmitMessage} to create the chat div.
136
-     *
137
-     * @param {string} message - The chat message to display.
138
-     * @param {string} id - The chat message ID to use as a unique key.
139
-     * @returns {Array<ReactElement>}
140
-     */
141
-    _renderMessage(message: Object, id: string) {
142
-        return (
143
-            <ChatMessage
144
-                key = { id }
145
-                message = { message } />
146
-        );
147
-    }
148
-
149
     _renderPanelContent: (string) => React$Node | null;
180
     _renderPanelContent: (string) => React$Node | null;
150
 
181
 
151
     /**
182
     /**

+ 8
- 51
react/features/chat/components/web/ChatMessage.js Целия файл

23
      */
23
      */
24
     render() {
24
     render() {
25
         const { message } = this.props;
25
         const { message } = this.props;
26
-        let messageTypeClassname = '';
27
-        let messageToDisplay = message.message;
28
-
29
-        switch (message.messageType) {
30
-        case 'local':
31
-            messageTypeClassname = 'localuser';
32
-
33
-            break;
34
-        case 'error':
35
-            messageTypeClassname = 'error';
36
-            messageToDisplay = this.props.t('chat.error', {
26
+        const messageToDisplay = message.messageType === 'error'
27
+            ? this.props.t('chat.error', {
37
                 error: message.error,
28
                 error: message.error,
38
-                originalText: messageToDisplay
39
-            });
40
-            break;
41
-        default:
42
-            messageTypeClassname = 'remoteuser';
43
-        }
29
+                originalText: message.message
30
+            })
31
+            : message.message;
44
 
32
 
45
         // replace links and smileys
33
         // replace links and smileys
46
         // Strophe already escapes special symbols on sending,
34
         // Strophe already escapes special symbols on sending,
68
         });
56
         });
69
 
57
 
70
         return (
58
         return (
71
-            <div className = { `chatmessage ${messageTypeClassname}` }>
72
-                <div className = 'display-name'>
59
+            <div className = 'chatmessage'>
60
+                { this.props.showDisplayName && <div className = 'display-name'>
73
                     { message.displayName }
61
                     { message.displayName }
74
-                </div>
75
-                <div className = { 'timestamp' }>
76
-                    { ChatMessage.formatTimestamp(message.timestamp) }
77
-                </div>
62
+                </div> }
78
                 <div className = 'usermessage'>
63
                 <div className = 'usermessage'>
79
                     { processedMessage }
64
                     { processedMessage }
80
                 </div>
65
                 </div>
81
             </div>
66
             </div>
82
         );
67
         );
83
     }
68
     }
84
-
85
-    /**
86
-     * Returns a timestamp formatted for display.
87
-     *
88
-     * @param {number} timestamp - The timestamp for the chat message.
89
-     * @private
90
-     * @returns {string}
91
-     */
92
-    static formatTimestamp(timestamp) {
93
-        const now = new Date(timestamp);
94
-        let hour = now.getHours();
95
-        let minute = now.getMinutes();
96
-        let second = now.getSeconds();
97
-
98
-        if (hour.toString().length === 1) {
99
-            hour = `0${hour}`;
100
-        }
101
-
102
-        if (minute.toString().length === 1) {
103
-            minute = `0${minute}`;
104
-        }
105
-
106
-        if (second.toString().length === 1) {
107
-            second = `0${second}`;
108
-        }
109
-
110
-        return `${hour}:${minute}:${second}`;
111
-    }
112
 }
69
 }
113
 
70
 
114
 export default translate(ChatMessage, { wait: false });
71
 export default translate(ChatMessage, { wait: false });

+ 68
- 0
react/features/chat/components/web/ChatMessageGroup.js Целия файл

1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+import ChatMessage from './ChatMessage';
5
+
6
+import { getLocalizedDateFormatter } from '../../../base/i18n';
7
+
8
+type Props = {
9
+
10
+    /**
11
+     * Additional CSS classes to apply to the root element.
12
+     */
13
+    className: string,
14
+
15
+    /**
16
+     * The messages to display as a group.
17
+     */
18
+    messages: Array<Object>,
19
+};
20
+
21
+/**
22
+ * Displays a list of chat messages. Will show only the display name for the
23
+ * first chat message and the timestamp for the last chat message.
24
+ *
25
+ * @extends React.Component
26
+ */
27
+class ChatMessageGroup extends Component<Props> {
28
+    static defaultProps = {
29
+        className: ''
30
+    };
31
+
32
+    /**
33
+     * Implements React's {@link Component#render()}.
34
+     *
35
+     * @inheritdoc
36
+     */
37
+    render() {
38
+        const { className, messages } = this.props;
39
+
40
+        const messagesLength = messages.length;
41
+
42
+        if (!messagesLength) {
43
+            return null;
44
+        }
45
+
46
+        const { timestamp } = messages[messagesLength - 1];
47
+
48
+        return (
49
+            <div className = { `chat-message-group ${className}` }>
50
+                {
51
+                    messages.map((message, i) => (
52
+                        <div key = { i }>
53
+                            <ChatMessage
54
+                                key = { i }
55
+                                message = { message }
56
+                                showDisplayName = { i === 0 } />
57
+                        </div>))
58
+                }
59
+                <div className = 'chat-message-group-footer'>
60
+                    { getLocalizedDateFormatter(
61
+                        new Date(timestamp)).format('H:mm') }
62
+                </div>
63
+            </div>
64
+        );
65
+    }
66
+}
67
+
68
+export default ChatMessageGroup;

Loading…
Отказ
Запис