소스 검색

feat(chat): convert to use React

- Change "features/chat" to support listening for new chat messages
  and storing them, removing that logic from conference.js.
- Combine chat.scss and side_toolbar_container.css, and remove unused
  scss files. Chat is the only side panel so the two concepts have
  been merged.
- Remove direct access to the chat feature from non-react and non-redux
  flows.
- Modify the i18n translate function to take in an options object.
  By default the option "wait" is set to true, but that causes
  components to mount after the parent has been notified of
  an update, which means autoscrolling down to the latest rendered
  messages does not work. With "wait" set to false, the children
  will mount and then the parent will trigger componentDidUpdate.
- Create react components for chat. Chat is the side panel
  plus the entiren chat feature. ChatInput is a child of Chat and
  is used for composing messages. ChatMessage displays one message
  and extends PureComponent to limit re-renders.
- Fix a bug where the toolbar was not showing automatically when
  chat is closed and a new message is received.
- Import react-transition-group to time the animation of the
  side panel showing/hiding and unmounting the Chat component.
  This gets around the issue of having to control autofocus if the
  component were always mounted and visibility toggled, but
  introduces not being able to store previous scroll state
  (without additional work or re-work).
master
Pratik Shah 6 년 전
부모
커밋
b7b43e8d9c
44개의 변경된 파일1203개의 추가작업 그리고 1209개의 파일을 삭제
  1. 9
    31
      conference.js
  2. 0
    19
      css/_animations.scss
  3. 127
    75
      css/_chat.scss
  4. 0
    105
      css/_side_toolbar_container.scss
  5. 0
    21
      css/_toolbars.scss
  6. 0
    5
      css/main.scss
  7. 1
    0
      lang/main.json
  8. 10
    67
      modules/UI/UI.js
  9. 0
    168
      modules/UI/side_pannels/SideContainerToggler.js
  10. 0
    13
      modules/UI/side_pannels/SidePanels.js
  11. 0
    404
      modules/UI/side_pannels/chat/Chat.js
  12. 0
    4
      modules/keyboardshortcut/keyboardshortcut.js
  13. 32
    10
      package-lock.json
  14. 1
    0
      package.json
  15. 4
    2
      react/features/base/i18n/functions.js
  16. 17
    6
      react/features/chat/actionTypes.js
  17. 29
    31
      react/features/chat/actions.js
  18. 0
    0
      react/features/chat/components/Chat.native.js
  19. 312
    0
      react/features/chat/components/Chat.web.js
  20. 11
    10
      react/features/chat/components/ChatCounter.web.js
  21. 0
    0
      react/features/chat/components/ChatInput.native.js
  22. 229
    0
      react/features/chat/components/ChatInput.web.js
  23. 0
    0
      react/features/chat/components/ChatMessage.native.js
  24. 114
    0
      react/features/chat/components/ChatMessage.web.js
  25. 0
    0
      react/features/chat/components/DisplayNameForm.native.js
  26. 142
    0
      react/features/chat/components/DisplayNameForm.web.js
  27. 0
    0
      react/features/chat/components/SmileysPanel.native.js
  28. 69
    0
      react/features/chat/components/SmileysPanel.web.js
  29. 1
    0
      react/features/chat/components/index.js
  30. 64
    10
      react/features/chat/middleware.js
  31. 12
    11
      react/features/chat/reducer.js
  32. 14
    5
      react/features/chat/replacement.js
  33. 0
    0
      react/features/chat/smileys.js
  34. 2
    2
      react/features/conference/components/Conference.web.js
  35. 0
    29
      react/features/side-panel/actionTypes.js
  36. 0
    49
      react/features/side-panel/actions.js
  37. 0
    62
      react/features/side-panel/components/SidePanel.web.js
  38. 0
    1
      react/features/side-panel/components/index.js
  39. 0
    6
      react/features/side-panel/index.js
  40. 0
    32
      react/features/side-panel/middleware.js
  41. 0
    18
      react/features/side-panel/reducer.js
  42. 1
    3
      react/features/toolbox/actions.web.js
  43. 2
    4
      react/features/toolbox/components/web/Toolbox.js
  44. 0
    6
      service/UI/UIEvents.js

+ 9
- 31
conference.js 파일 보기

97
     getLocationContextRoot,
97
     getLocationContextRoot,
98
     getJitsiMeetGlobalNS
98
     getJitsiMeetGlobalNS
99
 } from './react/features/base/util';
99
 } from './react/features/base/util';
100
+import { addMessage } from './react/features/chat';
100
 import { showDesktopPicker } from './react/features/desktop-picker';
101
 import { showDesktopPicker } from './react/features/desktop-picker';
101
 import { appendSuffix } from './react/features/display-name';
102
 import { appendSuffix } from './react/features/display-name';
102
 import {
103
 import {
424
         switch (err) {
425
         switch (err) {
425
         case JitsiConferenceErrors.CHAT_ERROR:
426
         case JitsiConferenceErrors.CHAT_ERROR:
426
             logger.error('Chat error.', err);
427
             logger.error('Chat error.', err);
427
-            if (isButtonEnabled('chat')) {
428
+            if (isButtonEnabled('chat') && !interfaceConfig.filmStripOnly) {
428
                 const [ code, msg ] = params;
429
                 const [ code, msg ] = params;
429
 
430
 
430
-                APP.UI.showChatError(code, msg);
431
+                APP.store.dispatch(addMessage({
432
+                    hasRead: true,
433
+                    error: code,
434
+                    message: msg,
435
+                    timestamp: Date.now(),
436
+                    type: 'error'
437
+                }));
431
             }
438
             }
432
             break;
439
             break;
433
         default:
440
         default:
1819
             JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
1826
             JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
1820
             id => APP.store.dispatch(dominantSpeakerChanged(id, room)));
1827
             id => APP.store.dispatch(dominantSpeakerChanged(id, room)));
1821
 
1828
 
1822
-        if (!interfaceConfig.filmStripOnly) {
1823
-            if (isButtonEnabled('chat')) {
1824
-                room.on(
1825
-                    JitsiConferenceEvents.MESSAGE_RECEIVED,
1826
-                    (id, body, ts) => {
1827
-                        let nick = getDisplayName(id);
1828
-
1829
-                        if (!nick) {
1830
-                            nick = `${
1831
-                                interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME} (${
1832
-                                id})`;
1833
-                        }
1834
-
1835
-                        APP.API.notifyReceivedChatMessage({
1836
-                            id,
1837
-                            nick,
1838
-                            body,
1839
-                            ts
1840
-                        });
1841
-                        APP.UI.addMessage(id, nick, body, ts);
1842
-                    }
1843
-                );
1844
-                APP.UI.addListener(UIEvents.MESSAGE_CREATED, message => {
1845
-                    APP.API.notifySendingChatMessage(message);
1846
-                    room.sendTextMessage(message);
1847
-                });
1848
-            }
1849
-        }
1850
-
1851
         room.on(JitsiConferenceEvents.CONNECTION_INTERRUPTED, () => {
1829
         room.on(JitsiConferenceEvents.CONNECTION_INTERRUPTED, () => {
1852
             APP.store.dispatch(localParticipantConnectionStatusChanged(
1830
             APP.store.dispatch(localParticipantConnectionStatusChanged(
1853
                 JitsiParticipantConnectionStatus.INTERRUPTED));
1831
                 JitsiParticipantConnectionStatus.INTERRUPTED));

+ 0
- 19
css/_animations.scss 파일 보기

1
-/**
2
-* Project animations
3
-**/
4
-
5
-/**
6
- *   Slide in animation for extended toolbar (inner) panel.
7
- */
8
-
9
-// FIX: Can't use percentage because of breaking animation when width is changed
10
-// (100% of 0 is also zero) Extracted this to config variable.
11
-@include keyframes(slideInExt) {
12
-    from { left: -$sidebarWidth; }
13
-    to   { left: 0; }
14
-}
15
-
16
-@include keyframes(slideOutExt) {
17
-    from { left: 0; }
18
-    to   { left: -$sidebarWidth; }
19
-}

+ 127
- 75
css/_chat.scss 파일 보기

1
+#sideToolbarContainer {
2
+    background-color: $newToolbarBackgroundColor;
3
+    display: flex;
4
+    /**
5
+     * Make the sidebar flush with the top of the toolbar. Take the size of
6
+     * the toolbar and subtract from 100%.
7
+     */
8
+    height: calc(100% - #{$newToolbarSizeWithPadding});
9
+    left: -$sidebarWidth;
10
+    overflow: hidden;
11
+    position: absolute;
12
+    top: 0;
13
+    transition: left 0.5s;
14
+    width: $sidebarWidth;
15
+    z-index: $sideToolbarContainerZ;
16
+
17
+    /**
18
+     * The sidebar (chat) is off-screen when hidden. Move it flush to the left
19
+     * side of the window when it should be visible.
20
+     */
21
+    &.slideInExt {
22
+        left: 0;
23
+    }
24
+
25
+    .sideToolbarContainer__inner {
26
+        box-sizing: border-box;
27
+        color: #FFF;
28
+        display: flex;
29
+        flex-direction: column;
30
+        height: 100%;
31
+        width: $sidebarWidth;
32
+    }
33
+}
34
+
1
 #chat_container * {
35
 #chat_container * {
2
     -webkit-user-select: text;
36
     -webkit-user-select: text;
3
     user-select: text;
37
     user-select: text;
4
 }
38
 }
5
 
39
 
6
 #chatconversation {
40
 #chatconversation {
7
-    visibility: hidden;
8
-    position: relative;
9
-    top: 15px;
10
-    padding: 5px;
11
-    text-align: left;
12
-    line-height: 20px;
41
+    box-sizing: border-box;
42
+    flex: 1;
13
     font-size: 10pt;
43
     font-size: 10pt;
14
-    width: 100%;
15
-    height: 90%;
44
+    line-height: 20px;
45
+    margin-top: 15px;
16
     overflow: auto;
46
     overflow: auto;
47
+    padding: 5px;
48
+    text-align: left;
49
+    width: $sidebarWidth;
17
     word-wrap: break-word;
50
     word-wrap: break-word;
18
 
51
 
52
+    a {
53
+        display: block;
54
+    }
55
+
19
     a:link {
56
     a:link {
20
         color: rgb(184, 184, 184);
57
         color: rgb(184, 184, 184);
21
     }
58
     }
55
     }
92
     }
56
 }
93
 }
57
 
94
 
58
-#chat_container.is-conversation-mode #chatconversation {
59
-    visibility: visible;
60
-}
61
-
62
-.localuser {
63
-    color: #4C9AFF
95
+.chat-close {
96
+    background: gray;
97
+    border: 3px solid rgba(255, 255, 255, 0.1);
98
+    border-radius: 100%;
99
+    color: white;
100
+    cursor:pointer;
101
+    height: 10px;
102
+    line-height: 10px;
103
+    padding: 4px;
104
+    position: absolute;
105
+    right: 5px;
106
+    text-align: center;
107
+    top: 5px;
108
+    width: 10px;
109
+    z-index: 1;
64
 }
110
 }
65
 
111
 
66
-.errorMessage {
67
-    color: red;
112
+#chat-input {
113
+    background-color: $newToolbarBackgroundColor;
114
+    display: flex;
68
 }
115
 }
69
 
116
 
70
 .remoteuser {
117
 .remoteuser {
71
     color: #B8C7E0;
118
     color: #B8C7E0;
72
 }
119
 }
73
 
120
 
121
+.usrmsg-form {
122
+    flex: 1;
123
+    margin-left: 5px;
124
+}
125
+
74
 #usermsg {
126
 #usermsg {
75
     background-color: $newToolbarBackgroundColor;
127
     background-color: $newToolbarBackgroundColor;
76
-    visibility:hidden;
77
-    position: absolute;
78
-    bottom: 0px;
79
-    right: 0px;
80
-    width: 83%;
81
-    height: 30px;
82
-    padding: 5px 5px 5px 0px;
83
-    max-height:150px;
84
-    min-height:35px;
85
     border: 0px none;
128
     border: 0px none;
86
-    color: white;
87
-    box-shadow: none;
88
     border-radius:0;
129
     border-radius:0;
130
+    box-shadow: none;
131
+    color: white;
89
     font-size: 10pt;
132
     font-size: 10pt;
90
     line-height: 30px;
133
     line-height: 30px;
91
-    overflow: hidden;
134
+    padding: 5px 5px 5px 0px;
135
+    max-height:150px;
136
+    min-height:35px;
137
+    overflow-y: auto;
92
     resize: none;
138
     resize: none;
139
+    width: 100%;
140
+    word-break: break-word;
93
 }
141
 }
94
 
142
 
95
 #usermsg:hover {
143
 #usermsg:hover {
97
     box-shadow: none;
145
     box-shadow: none;
98
 }
146
 }
99
 
147
 
100
-#chat_container.is-conversation-mode #usermsg {
101
-    visibility: visible;
102
-}
103
-
104
 #nickname {
148
 #nickname {
105
     position: absolute;
149
     position: absolute;
106
     text-align: center;
150
     text-align: center;
112
     width: 95%;
156
     width: 95%;
113
 }
157
 }
114
 
158
 
115
-#chat_container.is-conversation-mode #nickname {
116
-    visibility: hidden;
117
-}
118
-
119
-#nickinput {
120
-    margin-top: 20px;
121
-    font-size: 14px;
122
-    background: #3a3a3a;
123
-    box-shadow: inset 0 0 3px 2px #a7a7a7;
124
-    border: 1px solid #a7a7a7;
125
-    color: #a7a7a7;
126
-}
127
-
128
-#chat_container .username {
159
+#chat_container .display-name {
129
     float: left;
160
     float: left;
130
     padding-left: 5px;
161
     padding-left: 5px;
131
     font-weight: bold;
162
     font-weight: bold;
141
     font-size: 11px;
172
     font-size: 11px;
142
 }
173
 }
143
 
174
 
144
-#chat_container .usermessage {
175
+.usermessage {
145
     padding-top: 20px;
176
     padding-top: 20px;
146
     padding-left: 5px;
177
     padding-left: 5px;
147
 }
178
 }
148
 
179
 
149
 .chatArrow {
180
 .chatArrow {
150
-    position: absolute;
151
     height: 15px;
181
     height: 15px;
152
-    left: 5px;
182
+    left: -10px;
183
+    position: absolute;
153
 }
184
 }
154
 
185
 
155
 .chatmessage {
186
 .chatmessage {
156
-    background-color: $newToolbarBackgroundColor;;
187
+    background-color: $newToolbarBackgroundColor;
157
     width: 93%;
188
     width: 93%;
158
     margin-left: 9px;
189
     margin-left: 9px;
159
     margin-right: auto;
190
     margin-right: auto;
160
     border-radius: 5px;
191
     border-radius: 5px;
161
     border-top-left-radius: 0px;
192
     border-top-left-radius: 0px;
162
     margin-top: 3px;
193
     margin-top: 3px;
163
-    left: 5px;
164
     color: white;
194
     color: white;
165
-    overflow: hidden;
166
     padding-bottom: 3px;
195
     padding-bottom: 3px;
196
+    position: relative;
197
+
198
+    &.localuser .display-name {
199
+        color: #4C9AFF
200
+    }
201
+
202
+    &.error {
203
+        .chatArrow,
204
+        .timestamp,
205
+        .display-name {
206
+            display: none;
207
+        }
208
+
209
+        .usermessage {
210
+            color: red;
211
+            padding: 0;
212
+        }
213
+    }
167
 }
214
 }
168
 
215
 
169
 .smiley {
216
 .smiley {
171
 }
218
 }
172
 
219
 
173
 #smileys {
220
 #smileys {
174
-    position: absolute;
175
-    bottom: 7px;
176
-    right: 5px;
177
     background: white;
221
     background: white;
178
-    border-radius: 50px;
222
+    border-radius: 50%;
223
+    display: inline-block;
179
     height: 26px;
224
     height: 26px;
180
     margin: auto;
225
     margin: auto;
181
     cursor: pointer;
226
     cursor: pointer;
187
 }
232
 }
188
 
233
 
189
 #smileysarea {
234
 #smileysarea {
190
-    position: absolute;
191
-    bottom: 0px;
192
-    left: 0px;
193
-    width: 17%;
235
+    background-color: $newToolbarBackgroundColor;
236
+    border: 0px none;
237
+    display: flex;
238
+    height: 70px;
239
+    max-height: 150px;
240
+    min-height: 35px;
194
     min-width: 31px;
241
     min-width: 31px;
195
-    height: 40px;
196
     padding: 0px;
242
     padding: 0px;
197
-    max-height:150px;
198
-    min-height:35px;
199
-    border: 0px none;
200
-    background-color: $newToolbarBackgroundColor;
201
     overflow: hidden;
243
     overflow: hidden;
202
-    visibility: hidden;
244
+    width: 17%;
203
 }
245
 }
204
 
246
 
205
-#chat_container.is-conversation-mode #smileysarea {
206
-    visibility: visible;
247
+.smiley-input {
248
+    position: relative;
207
 }
249
 }
208
 
250
 
209
-#smileysContainer {
210
-    display: none;
251
+.smileys-panel {
252
+    bottom: 100%;
253
+    box-sizing: border-box;
254
+    height: 0;
255
+    overflow: hidden;
211
     position: absolute;
256
     position: absolute;
212
-    background-color: $newToolbarBackgroundColor;
213
-    border-bottom: 1px solid;
214
-    border-top: 1px solid;
215
-    width: 100%;
216
-    bottom: 10%;
257
+    transition: height 0.3s;
258
+    width: $sidebarWidth;
259
+
260
+    &.show-smileys {
261
+        height: 202px;
262
+    }
263
+
264
+    #smileysContainer {
265
+        background-color: $newToolbarBackgroundColor;
266
+        border-bottom: 1px solid;
267
+        border-top: 1px solid;
268
+    }
217
 }
269
 }
218
 
270
 
219
 #smileysContainer .smiley {
271
 #smileysContainer .smiley {

+ 0
- 105
css/_side_toolbar_container.scss 파일 보기

1
-/**
2
- * Toolbar side panel main container element.
3
- */
4
-#sideToolbarContainer {
5
-    background-color: $newToolbarBackgroundColor;
6
-    /**
7
-     * Make the sidebar flush with the top of the toolbar. Take the size of
8
-     * the toolbar and subtract from 100%.
9
-     */
10
-    height: calc(100% - #{$newToolbarSizeWithPadding});
11
-    left: 0;
12
-    max-width: $sidebarWidth;
13
-    overflow: hidden;
14
-    position: absolute;
15
-    top: 0;
16
-    width: 0;
17
-    z-index: $sideToolbarContainerZ;
18
-
19
-    /**
20
-     * Labels inside the side panel.
21
-     */
22
-    label {
23
-        color: $baseLight;
24
-    }
25
-
26
-    /**
27
-     * Form elements and blocks.
28
-     */
29
-    input,
30
-    a,
31
-    .sideToolbarBlock,
32
-    .form-control {
33
-        display: block;
34
-        margin-top: 15px;
35
-        margin-left: 10%;
36
-        width: 80%;
37
-    }
38
-
39
-    /**
40
-     * Specify styling of elements inside a block.
41
-     */
42
-    .sideToolbarBlock {
43
-        input, a {
44
-            margin-left: 0;
45
-            margin-top: 5px;
46
-            width: 100%;
47
-        }
48
-    }
49
-
50
-    /**
51
-     * Inner container, for example settings or profile.
52
-     */
53
-    .sideToolbarContainer__inner {
54
-        display: none;
55
-        height: 100%;
56
-        width: $sidebarWidth;
57
-        position: absolute;
58
-        box-sizing: border-box;
59
-        color: #FFF;
60
-
61
-        .input-control {
62
-            border: 0;
63
-        }
64
-
65
-        /**
66
-         * Titles and subtitles of inner containers.
67
-         */
68
-        div.title {
69
-            margin: 24px 0 11px;
70
-        }
71
-
72
-        /**
73
-         * Main title size.
74
-         */
75
-        div.title {
76
-            color: $toolbarTitleColor;
77
-            text-align: center;
78
-            font-size: $toolbarTitleFontSize;
79
-        }
80
-
81
-        /**
82
-         * First element after a title.
83
-         */
84
-        .first {
85
-            margin-top: 0 !important;
86
-        }
87
-    }
88
-
89
-    .side-toolbar-close {
90
-        background: gray;
91
-        border: 3px solid rgba(255, 255, 255, 0.1);
92
-        border-radius: 100%;
93
-        color: white;
94
-        cursor:pointer;
95
-        height: 10px;
96
-        line-height: 10px;
97
-        padding: 4px;
98
-        position: absolute;
99
-        right: 5px;
100
-        text-align: center;
101
-        top: 5px;
102
-        width: 10px;
103
-        z-index: 1;
104
-    }
105
-}

+ 0
- 21
css/_toolbars.scss 파일 보기

303
     }
303
     }
304
 }
304
 }
305
 
305
 
306
-/**
307
- * START of slide in animation for extended toolbar panel.
308
- */
309
-@include keyframes(slideInExt) {
310
-    from { width: 0px; }
311
-    to   { width: $sidebarWidth; } // TO FIX: Make this value a percentage.
312
-}
313
-
314
-.slideInExt {
315
-    @include animation("slideInExt .5s forwards");
316
-}
317
-
318
-@include keyframes(slideOutExt) {
319
-  from { width: $sidebarWidth; } // TO FIX: Make this value a percentage.
320
-  to   { width: 0px; }
321
-}
322
-
323
-.slideOutExt {
324
-  @include animation("slideOutExt .5s forwards");
325
-}
326
-
327
 /**
306
 /**
328
  * START of fade in animation for main toolbar
307
  * START of fade in animation for main toolbar
329
  */
308
  */

+ 0
- 5
css/main.scss 파일 보기

16
 
16
 
17
 /* Mixins END */
17
 /* Mixins END */
18
 
18
 
19
-/* Animations BEGIN */
20
-
21
-@import "animations";
22
-
23
 /* Animations END */
19
 /* Animations END */
24
 
20
 
25
 /* Fonts BEGIN */
21
 /* Fonts BEGIN */
56
 @import 'welcome_page';
52
 @import 'welcome_page';
57
 @import 'welcome_page_content';
53
 @import 'welcome_page_content';
58
 @import 'toolbars';
54
 @import 'toolbars';
59
-@import 'side_toolbar_container';
60
 @import 'jquery.contextMenu';
55
 @import 'jquery.contextMenu';
61
 @import 'keyboard-shortcuts';
56
 @import 'keyboard-shortcuts';
62
 @import 'redirect_page';
57
 @import 'redirect_page';

+ 1
- 0
lang/main.json 파일 보기

158
             "title": "Enter a nickname in the box below",
158
             "title": "Enter a nickname in the box below",
159
             "popover": "Choose a nickname"
159
             "popover": "Choose a nickname"
160
         },
160
         },
161
+        "error": "Error: your message \"__originalText__\" was not sent. Reason: __error__",
161
         "messagebox": "Enter text..."
162
         "messagebox": "Enter text..."
162
     },
163
     },
163
     "settings": {
164
     "settings": {

+ 10
- 67
modules/UI/UI.js 파일 보기

4
 
4
 
5
 const UI = {};
5
 const UI = {};
6
 
6
 
7
-import Chat from './side_pannels/chat/Chat';
8
-import SidePanels from './side_pannels/SidePanels';
9
-import SideContainerToggler from './side_pannels/SideContainerToggler';
10
 import messageHandler from './util/MessageHandler';
7
 import messageHandler from './util/MessageHandler';
11
 import UIUtil from './util/UIUtil';
8
 import UIUtil from './util/UIUtil';
12
 import UIEvents from '../../service/UI/UIEvents';
9
 import UIEvents from '../../service/UI/UIEvents';
22
     showParticipantJoinedNotification
19
     showParticipantJoinedNotification
23
 } from '../../react/features/base/participants';
20
 } from '../../react/features/base/participants';
24
 import { destroyLocalTracks } from '../../react/features/base/tracks';
21
 import { destroyLocalTracks } from '../../react/features/base/tracks';
22
+import { toggleChat } from '../../react/features/chat';
25
 import { openDisplayNamePrompt } from '../../react/features/display-name';
23
 import { openDisplayNamePrompt } from '../../react/features/display-name';
26
 import { setEtherpadHasInitialzied } from '../../react/features/etherpad';
24
 import { setEtherpadHasInitialzied } from '../../react/features/etherpad';
27
 import { setFilmstripVisible } from '../../react/features/filmstrip';
25
 import { setFilmstripVisible } from '../../react/features/filmstrip';
89
     ], [
87
     ], [
90
         UIEvents.SHARED_VIDEO_CLICKED,
88
         UIEvents.SHARED_VIDEO_CLICKED,
91
         () => sharedVideoManager && sharedVideoManager.toggleSharedVideo()
89
         () => sharedVideoManager && sharedVideoManager.toggleSharedVideo()
92
-    ], [
93
-        UIEvents.TOGGLE_CHAT,
94
-        () => UI.toggleChat()
95
     ], [
90
     ], [
96
         UIEvents.TOGGLE_FILMSTRIP,
91
         UIEvents.TOGGLE_FILMSTRIP,
97
         () => UI.toggleFilmstrip()
92
         () => UI.toggleFilmstrip()
175
     });
170
     });
176
 };
171
 };
177
 
172
 
178
-/**
179
- * Show chat error.
180
- * @param err the Error
181
- * @param msg
182
- */
183
-UI.showChatError = function(err, msg) {
184
-    if (!interfaceConfig.filmStripOnly) {
185
-        Chat.chatAddError(err, msg);
186
-    }
187
-};
188
-
189
 /**
173
 /**
190
  * Change nickname for the user.
174
  * Change nickname for the user.
191
  * @param {string} id user id
175
  * @param {string} id user id
193
  */
177
  */
194
 UI.changeDisplayName = function(id, displayName) {
178
 UI.changeDisplayName = function(id, displayName) {
195
     VideoLayout.onDisplayNameChanged(id, displayName);
179
     VideoLayout.onDisplayNameChanged(id, displayName);
196
-
197
-    if (APP.conference.isLocalId(id) || id === 'localVideoContainer') {
198
-        Chat.setChatConversationMode(Boolean(displayName));
199
-    }
200
 };
180
 };
201
 
181
 
202
 /**
182
 /**
276
     // Set the defaults for prompt dialogs.
256
     // Set the defaults for prompt dialogs.
277
     $.prompt.setDefaults({ persistent: false });
257
     $.prompt.setDefaults({ persistent: false });
278
 
258
 
279
-    SideContainerToggler.init(eventEmitter);
280
     Filmstrip.init(eventEmitter);
259
     Filmstrip.init(eventEmitter);
281
 
260
 
282
     VideoLayout.init(eventEmitter);
261
     VideoLayout.init(eventEmitter);
295
     if (interfaceConfig.filmStripOnly) {
274
     if (interfaceConfig.filmStripOnly) {
296
         $('body').addClass('filmstrip-only');
275
         $('body').addClass('filmstrip-only');
297
         APP.store.dispatch(setNotificationsEnabled(false));
276
         APP.store.dispatch(setNotificationsEnabled(false));
298
-    } else {
299
-        // Initialize recording mode UI.
300
-        if (config.iAmRecorder) {
301
-            // in case of iAmSipGateway keep local video visible
302
-            if (!config.iAmSipGateway) {
303
-                VideoLayout.setLocalVideoVisible(false);
304
-            }
305
-
306
-            APP.store.dispatch(setToolboxEnabled(false));
307
-            APP.store.dispatch(setNotificationsEnabled(false));
308
-            UI.messageHandler.enablePopups(false);
277
+    } else if (config.iAmRecorder) {
278
+        // in case of iAmSipGateway keep local video visible
279
+        if (!config.iAmSipGateway) {
280
+            VideoLayout.setLocalVideoVisible(false);
309
         }
281
         }
310
 
282
 
311
-        // Initialize side panels
312
-        SidePanels.init(eventEmitter);
283
+        APP.store.dispatch(setToolboxEnabled(false));
284
+        APP.store.dispatch(setNotificationsEnabled(false));
285
+        UI.messageHandler.enablePopups(false);
313
     }
286
     }
314
 
287
 
315
     document.title = interfaceConfig.APP_NAME;
288
     document.title = interfaceConfig.APP_NAME;
335
      *
308
      *
336
      */
309
      */
337
     function onResize() {
310
     function onResize() {
338
-        SideContainerToggler.resize();
339
         VideoLayout.resizeVideoArea();
311
         VideoLayout.resizeVideoArea();
340
     }
312
     }
341
 
313
 
495
         { status: UIUtil.escapeHtml(status) });
467
         { status: UIUtil.escapeHtml(status) });
496
 };
468
 };
497
 
469
 
498
-/**
499
- * Toggles smileys in the chat.
500
- */
501
-UI.toggleSmileys = () => Chat.toggleSmileys();
502
-
503
 /**
470
 /**
504
  * Toggles filmstrip.
471
  * Toggles filmstrip.
505
  */
472
  */
516
 UI.isFilmstripVisible = () => Filmstrip.isFilmstripVisible();
483
 UI.isFilmstripVisible = () => Filmstrip.isFilmstripVisible();
517
 
484
 
518
 /**
485
 /**
519
- * @returns {true} if the chat panel is currently visible, and false otherwise.
486
+ * Toggles the visibility of the chat panel.
520
  */
487
  */
521
-UI.isChatVisible = () => Chat.isVisible();
522
-
523
-/**
524
- * Toggles chat panel.
525
- */
526
-UI.toggleChat = () => UI.toggleSidePanel('chat_container');
527
-
528
-/**
529
- * Toggles the given side panel.
530
- *
531
- * @param {String} sidePanelId the identifier of the side panel to toggle
532
- */
533
-UI.toggleSidePanel = sidePanelId => SideContainerToggler.toggle(sidePanelId);
534
-
488
+UI.toggleChat = () => APP.store.dispatch(toggleChat());
535
 
489
 
536
 /**
490
 /**
537
  * Handle new user display name.
491
  * Handle new user display name.
741
     VideoLayout.hideStats();
695
     VideoLayout.hideStats();
742
 };
696
 };
743
 
697
 
744
-/**
745
- * Add chat message.
746
- * @param {string} from user id
747
- * @param {string} displayName user nickname
748
- * @param {string} message message text
749
- * @param {number} stamp timestamp when message was created
750
- */
751
-// eslint-disable-next-line max-params
752
-UI.addMessage = function(from, displayName, message, stamp) {
753
-    Chat.updateChatConversation(from, displayName, message, stamp);
754
-};
755
 
698
 
756
 UI.notifyTokenAuthFailed = function() {
699
 UI.notifyTokenAuthFailed = function() {
757
     messageHandler.showError({
700
     messageHandler.showError({

+ 0
- 168
modules/UI/side_pannels/SideContainerToggler.js 파일 보기

1
-/* global $, APP */
2
-import UIEvents from '../../../service/UI/UIEvents';
3
-import { setVisiblePanel } from '../../../react/features/side-panel';
4
-
5
-/**
6
- * Handles open and close of the extended toolbar side panel
7
- * (chat, settings, etc.).
8
- *
9
- * @type {{init, toggle, isVisible, hide, show, resize}}
10
- */
11
-const SideContainerToggler = {
12
-    /**
13
-     * Initialises this toggler by registering the listeners.
14
-     *
15
-     * @param eventEmitter
16
-     */
17
-    init(eventEmitter) {
18
-        this.eventEmitter = eventEmitter;
19
-
20
-        // We may not have a side toolbar container, for example, in
21
-        // filmstrip-only mode.
22
-        const sideToolbarContainer
23
-            = document.getElementById('sideToolbarContainer');
24
-
25
-        if (!sideToolbarContainer) {
26
-            return;
27
-        }
28
-
29
-        // Adds a listener for the animationend event that would take care of
30
-        // hiding all internal containers when the extendedToolbarPanel is
31
-        // closed.
32
-        sideToolbarContainer.addEventListener(
33
-            'animationend',
34
-            e => {
35
-                if (e.animationName === 'slideOutExt') {
36
-                    $('#sideToolbarContainer').children()
37
-                    .each(function() {
38
-                        /* eslint-disable no-invalid-this */
39
-                        if ($(this).hasClass('show')) {
40
-                            SideContainerToggler.hideInnerContainer($(this));
41
-                        }
42
-                        /* eslint-enable no-invalid-this */
43
-                    });
44
-                }
45
-            },
46
-            false);
47
-    },
48
-
49
-    /**
50
-     * Toggles the container with the given element id.
51
-     *
52
-     * @param {String} elementId the identifier of the container element to
53
-     * toggle
54
-     */
55
-    toggle(elementId) {
56
-        const elementSelector = $(`#${elementId}`);
57
-        const isSelectorVisible = elementSelector.hasClass('show');
58
-
59
-        if (isSelectorVisible) {
60
-            this.hide();
61
-            APP.store.dispatch(setVisiblePanel(null));
62
-        } else {
63
-            if (this.isVisible()) {
64
-                $('#sideToolbarContainer').children()
65
-                .each(function() {
66
-                    /* eslint-disable no-invalid-this */
67
-                    if ($(this).id !== elementId && $(this).hasClass('show')) {
68
-                        SideContainerToggler.hideInnerContainer($(this));
69
-                    }
70
-                    /* eslint-enable no-invalid-this */
71
-                });
72
-            }
73
-
74
-            if (!this.isVisible()) {
75
-                this.show();
76
-            }
77
-
78
-            this.showInnerContainer(elementSelector);
79
-            APP.store.dispatch(setVisiblePanel(elementId));
80
-        }
81
-    },
82
-
83
-    /**
84
-     * Returns {true} if the side toolbar panel is currently visible,
85
-     * otherwise returns {false}.
86
-     */
87
-    isVisible() {
88
-        return $('#sideToolbarContainer').hasClass('slideInExt');
89
-    },
90
-
91
-    /**
92
-     * Returns {true} if the side toolbar panel is currently hovered and
93
-     * {false} otherwise.
94
-     */
95
-    isHovered() {
96
-        return $('#sideToolbarContainer:hover').length > 0;
97
-    },
98
-
99
-    /**
100
-     * Hides the side toolbar panel with a slide out animation.
101
-     */
102
-    hide() {
103
-        $('#sideToolbarContainer')
104
-            .removeClass('slideInExt')
105
-.addClass('slideOutExt');
106
-    },
107
-
108
-    /**
109
-     * Shows the side toolbar panel with a slide in animation.
110
-     */
111
-    show() {
112
-        if (!this.isVisible()) {
113
-            $('#sideToolbarContainer')
114
-                .removeClass('slideOutExt')
115
-.addClass('slideInExt');
116
-        }
117
-    },
118
-
119
-    /**
120
-     * Hides the inner container given by the selector.
121
-     *
122
-     * @param {Object} containerSelector the jquery selector for the
123
-     * element to hide
124
-     */
125
-    hideInnerContainer(containerSelector) {
126
-        containerSelector.removeClass('show').addClass('hide');
127
-
128
-        this.eventEmitter.emit(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
129
-            containerSelector.attr('id'), false);
130
-    },
131
-
132
-    /**
133
-     * Shows the inner container given by the selector.
134
-     *
135
-     * @param {Object} containerSelector the jquery selector for the
136
-     * element to show
137
-     */
138
-    showInnerContainer(containerSelector) {
139
-
140
-        // Before showing the container, make sure there is no other visible.
141
-        // If we quickly show a container, while another one is animating
142
-        // and animation never ends, so we do not really hide the first one and
143
-        // we end up with to shown panels
144
-        $('#sideToolbarContainer').children()
145
-        .each(function() {
146
-            /* eslint-disable no-invalid-this */
147
-            if ($(this).hasClass('show')) {
148
-                SideContainerToggler.hideInnerContainer($(this));
149
-            }
150
-            /* eslint-enable no-invalid-this */
151
-        });
152
-
153
-        containerSelector.removeClass('hide').addClass('show');
154
-
155
-        this.eventEmitter.emit(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
156
-            containerSelector.attr('id'), true);
157
-    },
158
-
159
-    /**
160
-     * TO FIX: do we need to resize the chat?
161
-     */
162
-    resize() {
163
-        // let [width, height] = UIUtil.getSidePanelSize();
164
-        // Chat.resizeChat(width, height);
165
-    }
166
-};
167
-
168
-export default SideContainerToggler;

+ 0
- 13
modules/UI/side_pannels/SidePanels.js 파일 보기

1
-import Chat from './chat/Chat';
2
-import { isButtonEnabled } from '../../../react/features/toolbox';
3
-
4
-const SidePanels = {
5
-    init(eventEmitter) {
6
-        // Initialize chat
7
-        if (isButtonEnabled('chat')) {
8
-            Chat.init(eventEmitter);
9
-        }
10
-    }
11
-};
12
-
13
-export default SidePanels;

+ 0
- 404
modules/UI/side_pannels/chat/Chat.js 파일 보기

1
-/* global APP, $ */
2
-
3
-import { processReplacements } from './Replacement';
4
-import VideoLayout from '../../videolayout/VideoLayout';
5
-
6
-import UIUtil from '../../util/UIUtil';
7
-import UIEvents from '../../../../service/UI/UIEvents';
8
-
9
-import { smileys } from './smileys';
10
-
11
-import { addMessage, markAllRead } from '../../../../react/features/chat';
12
-import {
13
-    dockToolbox,
14
-    getToolboxHeight
15
-} from '../../../../react/features/toolbox';
16
-
17
-let unreadMessages = 0;
18
-const sidePanelsContainerId = 'sideToolbarContainer';
19
-const htmlStr = `
20
-    <div id="chat_container" class="sideToolbarContainer__inner">
21
-        <div id="nickname">
22
-            <span data-i18n="chat.nickname.title"></span>
23
-            <form>
24
-                <input type='text'
25
-                       class="input-control" id="nickinput" autofocus
26
-                    data-i18n="[placeholder]chat.nickname.popover">
27
-            </form>
28
-        </div>
29
-
30
-        <div id="chatconversation"></div>
31
-        <textarea id="usermsg" autofocus
32
-            data-i18n="[placeholder]chat.messagebox"></textarea>
33
-        <div id="smileysarea">
34
-            <div id="smileys">
35
-                <img src="images/smile.svg"/>
36
-            </div>
37
-        </div>
38
-    </div>`;
39
-
40
-/**
41
- *
42
- */
43
-function initHTML() {
44
-    $(`#${sidePanelsContainerId}`)
45
-        .append(htmlStr);
46
-
47
-    // make sure we translate the panel, as adding it can be after i18n
48
-    // library had initialized and translated already present html
49
-    APP.translation.translateElement($(`#${sidePanelsContainerId}`));
50
-}
51
-
52
-/**
53
- * The container id, which is and the element id.
54
- */
55
-const CHAT_CONTAINER_ID = 'chat_container';
56
-
57
-/**
58
- *  Updates visual notification, indicating that a message has arrived.
59
- */
60
-function updateVisualNotification() {
61
-    // XXX The rewrite of the toolbar in React delayed the availability of the
62
-    // element unreadMessages. In order to work around the delay, I introduced
63
-    // and utilized unreadMsgSelector in addition to unreadMsgElement.
64
-    const unreadMsgSelector = $('#unreadMessages');
65
-    const unreadMsgElement
66
-        = unreadMsgSelector.length > 0 ? unreadMsgSelector[0] : undefined;
67
-
68
-    if (unreadMessages && unreadMsgElement) {
69
-        unreadMsgElement.innerHTML = unreadMessages.toString();
70
-
71
-        APP.store.dispatch(dockToolbox(true));
72
-
73
-        const chatButtonElement
74
-            = document.getElementById('toolbar_button_chat');
75
-        const leftIndent
76
-            = (UIUtil.getTextWidth(chatButtonElement)
77
-                - UIUtil.getTextWidth(unreadMsgElement)) / 2;
78
-        const topIndent
79
-            = ((UIUtil.getTextHeight(chatButtonElement)
80
-                - UIUtil.getTextHeight(unreadMsgElement)) / 2) - 5;
81
-
82
-        unreadMsgElement.setAttribute(
83
-                'style',
84
-                `top:${topIndent}; left:${leftIndent};`);
85
-    } else {
86
-        unreadMsgSelector.html('');
87
-    }
88
-
89
-    if (unreadMsgElement) {
90
-        unreadMsgSelector.parent()[unreadMessages > 0 ? 'show' : 'hide']();
91
-    }
92
-}
93
-
94
-
95
-/**
96
- * Returns the current time in the format it is shown to the user
97
- * @returns {string}
98
- */
99
-function getCurrentTime(stamp) {
100
-    const now = stamp ? new Date(stamp) : new Date();
101
-    let hour = now.getHours();
102
-    let minute = now.getMinutes();
103
-    let second = now.getSeconds();
104
-
105
-    if (hour.toString().length === 1) {
106
-        hour = `0${hour}`;
107
-    }
108
-    if (minute.toString().length === 1) {
109
-        minute = `0${minute}`;
110
-    }
111
-    if (second.toString().length === 1) {
112
-        second = `0${second}`;
113
-    }
114
-
115
-    return `${hour}:${minute}:${second}`;
116
-}
117
-
118
-/**
119
- *
120
- */
121
-function toggleSmileys() {
122
-    const smileys = $('#smileysContainer'); // eslint-disable-line no-shadow
123
-
124
-    smileys.slideToggle();
125
-
126
-    $('#usermsg').focus();
127
-}
128
-
129
-/**
130
- *
131
- */
132
-function addClickFunction(smiley, number) {
133
-    smiley.onclick = function addSmileyToMessage() {
134
-        const usermsg = $('#usermsg');
135
-        let message = usermsg.val();
136
-
137
-        message += smileys[`smiley${number}`];
138
-        usermsg.val(message);
139
-        usermsg.get(0).setSelectionRange(message.length, message.length);
140
-        toggleSmileys();
141
-        usermsg.focus();
142
-    };
143
-}
144
-
145
-/**
146
- * Adds the smileys container to the chat
147
- */
148
-function addSmileys() {
149
-    const smileysContainer = document.createElement('div');
150
-
151
-    smileysContainer.id = 'smileysContainer';
152
-    for (let i = 1; i <= 21; i++) {
153
-        const smileyContainer = document.createElement('div');
154
-
155
-        smileyContainer.id = `smiley${i}`;
156
-        smileyContainer.className = 'smileyContainer';
157
-        const smiley = document.createElement('img');
158
-
159
-        smiley.src = `images/smileys/smiley${i}.svg`;
160
-        smiley.className = 'smiley';
161
-        addClickFunction(smiley, i);
162
-        smileyContainer.appendChild(smiley);
163
-        smileysContainer.appendChild(smileyContainer);
164
-    }
165
-
166
-    $('#chat_container').append(smileysContainer);
167
-}
168
-
169
-/**
170
- * Resizes the chat conversation.
171
- */
172
-function resizeChatConversation() {
173
-    // FIXME: this function can all be done with CSS. If Chat is ever rewritten,
174
-    // do not copy over this logic.
175
-    const msgareaHeight = $('#usermsg').outerHeight();
176
-    const chatspace = $(`#${CHAT_CONTAINER_ID}`);
177
-    const width = chatspace.width();
178
-    const chat = $('#chatconversation');
179
-    const smileys = $('#smileysarea'); // eslint-disable-line no-shadow
180
-
181
-    smileys.height(msgareaHeight);
182
-    $('#smileys').css('bottom', (msgareaHeight - 26) / 2);
183
-    $('#smileysContainer').css('bottom', msgareaHeight);
184
-    chat.width(width - 10);
185
-
186
-    const maybeAMagicNumberForPaddingAndMargin = 100;
187
-    const offset = maybeAMagicNumberForPaddingAndMargin
188
-        + msgareaHeight + getToolboxHeight();
189
-
190
-    chat.height(window.innerHeight - offset);
191
-}
192
-
193
-/**
194
- * Focus input after 400 ms
195
- * Found input by id
196
- *
197
- * @param id {string} input id
198
- */
199
-function deferredFocus(id) {
200
-    setTimeout(() => $(`#${id}`).focus(), 400);
201
-}
202
-
203
-/**
204
- * Chat related user interface.
205
- */
206
-const Chat = {
207
-    /**
208
-     * Initializes chat related interface.
209
-     */
210
-    init(eventEmitter) {
211
-        initHTML();
212
-        if (APP.conference.getLocalDisplayName()) {
213
-            Chat.setChatConversationMode(true);
214
-        }
215
-
216
-        $('#smileys').click(() => {
217
-            Chat.toggleSmileys();
218
-        });
219
-
220
-        $('#nickinput').keydown(function(event) {
221
-            if (event.keyCode === 13) {
222
-                event.preventDefault();
223
-                const val = this.value; // eslint-disable-line no-invalid-this
224
-
225
-                this.value = '';// eslint-disable-line no-invalid-this
226
-                eventEmitter.emit(UIEvents.NICKNAME_CHANGED, val);
227
-                deferredFocus('usermsg');
228
-            }
229
-        });
230
-
231
-        const usermsg = $('#usermsg');
232
-
233
-        usermsg.keydown(function(event) {
234
-            if (event.keyCode === 13) {
235
-                event.preventDefault();
236
-                const value = this.value; // eslint-disable-line no-invalid-this
237
-
238
-                usermsg.val('').trigger('autosize.resize');
239
-                this.focus();// eslint-disable-line no-invalid-this
240
-
241
-                const message = UIUtil.escapeHtml(value);
242
-
243
-                eventEmitter.emit(UIEvents.MESSAGE_CREATED, message);
244
-            }
245
-        });
246
-
247
-        const onTextAreaResize = function() {
248
-            resizeChatConversation();
249
-            Chat.scrollChatToBottom();
250
-        };
251
-
252
-        usermsg.autosize({ callback: onTextAreaResize });
253
-
254
-        eventEmitter.on(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
255
-            (containerId, isVisible) => {
256
-                if (containerId !== CHAT_CONTAINER_ID || !isVisible) {
257
-                    return;
258
-                }
259
-
260
-                unreadMessages = 0;
261
-                APP.store.dispatch(markAllRead());
262
-                updateVisualNotification();
263
-
264
-                // Undock the toolbar when the chat is shown and if we're in a
265
-                // video mode.
266
-                if (VideoLayout.isLargeVideoVisible()) {
267
-                    APP.store.dispatch(dockToolbox(false));
268
-                }
269
-
270
-                // if we are in conversation mode focus on the text input
271
-                // if we are not, focus on the display name input
272
-                deferredFocus(
273
-                    APP.conference.getLocalDisplayName()
274
-                        ? 'usermsg'
275
-                        : 'nickinput');
276
-            });
277
-
278
-        addSmileys();
279
-        updateVisualNotification();
280
-    },
281
-
282
-    /**
283
-     * Appends the given message to the chat conversation.
284
-     */
285
-    // eslint-disable-next-line max-params
286
-    updateChatConversation(id, displayName, message, stamp) {
287
-        const isFromLocalParticipant = APP.conference.isLocalId(id);
288
-        let divClassName = '';
289
-
290
-        if (isFromLocalParticipant) {
291
-            divClassName = 'localuser';
292
-        } else {
293
-            divClassName = 'remoteuser';
294
-
295
-            if (!Chat.isVisible()) {
296
-                unreadMessages++;
297
-                updateVisualNotification();
298
-            }
299
-        }
300
-
301
-        // replace links and smileys
302
-        // Strophe already escapes special symbols on sending,
303
-        // so we escape here only tags to avoid double &amp;
304
-        const escMessage = message.replace(/</g, '&lt;')
305
-            .replace(/>/g, '&gt;')
306
-.replace(/\n/g, '<br/>');
307
-        const escDisplayName = UIUtil.escapeHtml(displayName);
308
-        const timestamp = getCurrentTime(stamp);
309
-
310
-        // eslint-disable-next-line no-param-reassign
311
-        message = processReplacements(escMessage);
312
-
313
-        const messageContainer
314
-            = `${'<div class="chatmessage">'
315
-                + '<img src="images/chatArrow.svg" class="chatArrow">'
316
-                + '<div class="username '}${divClassName}">${escDisplayName
317
-            }</div><div class="timestamp">${timestamp
318
-            }</div><div class="usermessage">${message}</div>`
319
-            + '</div>';
320
-
321
-        $('#chatconversation').append(messageContainer);
322
-        $('#chatconversation').animate(
323
-                { scrollTop: $('#chatconversation')[0].scrollHeight }, 1000);
324
-
325
-        const markAsRead = Chat.isVisible() || isFromLocalParticipant;
326
-
327
-        APP.store.dispatch(addMessage(
328
-            escDisplayName, message, timestamp, markAsRead));
329
-    },
330
-
331
-    /**
332
-     * Appends error message to the conversation
333
-     * @param errorMessage the received error message.
334
-     * @param originalText the original message.
335
-     */
336
-    chatAddError(errorMessage, originalText) {
337
-        // eslint-disable-next-line no-param-reassign
338
-        errorMessage = UIUtil.escapeHtml(errorMessage);
339
-        // eslint-disable-next-line no-param-reassign
340
-        originalText = UIUtil.escapeHtml(originalText);
341
-
342
-        $('#chatconversation').append(
343
-            `${'<div class="errorMessage"><b>Error: </b>Your message'}${
344
-                originalText ? ` "${originalText}"` : ''
345
-            } was not sent.${
346
-                errorMessage ? ` Reason: ${errorMessage}` : ''}</div>`);
347
-        $('#chatconversation').animate(
348
-            { scrollTop: $('#chatconversation')[0].scrollHeight }, 1000);
349
-    },
350
-
351
-    /**
352
-     * Sets the chat conversation mode.
353
-     * Conversation mode is the normal chat mode, non conversation mode is
354
-     * where we ask user to input its display name.
355
-     * @param {boolean} isConversationMode if chat should be in
356
-     * conversation mode or not.
357
-     */
358
-    setChatConversationMode(isConversationMode) {
359
-        $(`#${CHAT_CONTAINER_ID}`)
360
-            .toggleClass('is-conversation-mode', isConversationMode);
361
-    },
362
-
363
-    /**
364
-     * Resizes the chat area.
365
-     */
366
-    resizeChat(width, height) {
367
-        $(`#${CHAT_CONTAINER_ID}`).width(width)
368
-.height(height);
369
-
370
-        resizeChatConversation();
371
-    },
372
-
373
-    /**
374
-     * Indicates if the chat is currently visible.
375
-     */
376
-    isVisible() {
377
-        return UIUtil.isVisible(
378
-            document.getElementById(CHAT_CONTAINER_ID));
379
-    },
380
-
381
-    /**
382
-     * Shows and hides the window with the smileys
383
-     */
384
-    toggleSmileys,
385
-
386
-    /**
387
-     * Scrolls chat to the bottom.
388
-     */
389
-    scrollChatToBottom() {
390
-        setTimeout(
391
-            () => {
392
-                const chatconversation = $('#chatconversation');
393
-
394
-                // XXX Prevent TypeError: undefined is not an object when the
395
-                // Web browser does not support WebRTC (yet).
396
-                chatconversation.length > 0
397
-                    && chatconversation.scrollTop(
398
-                            chatconversation[0].scrollHeight);
399
-            },
400
-            5);
401
-    }
402
-};
403
-
404
-export default Chat;

+ 0
- 4
modules/keyboardshortcut/keyboardshortcut.js 파일 보기

55
                     APP.UI.clickOnVideo(num);
55
                     APP.UI.clickOnVideo(num);
56
                 }
56
                 }
57
 
57
 
58
-            // esc while the smileys are visible hides them
59
-            } else if (key === 'ESCAPE'
60
-                && $('#smileysContainer').is(':visible')) {
61
-                APP.UI.toggleSmileys();
62
             }
58
             }
63
         };
59
         };
64
 
60
 

+ 32
- 10
package-lock.json 파일 보기

180
             "prop-types": "^15.5.10",
180
             "prop-types": "^15.5.10",
181
             "styled-components": "1.4.6 - 3"
181
             "styled-components": "1.4.6 - 3"
182
           }
182
           }
183
+        },
184
+        "react-transition-group": {
185
+          "version": "1.2.1",
186
+          "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-1.2.1.tgz",
187
+          "integrity": "sha512-CWaL3laCmgAFdxdKbhhps+c0HRGF4c+hdM4H23+FI1QBNUyx/AMeIJGWorehPNSaKnQNOAxL7PQmqMu78CDj3Q==",
188
+          "requires": {
189
+            "chain-function": "^1.0.0",
190
+            "dom-helpers": "^3.2.0",
191
+            "loose-envify": "^1.3.1",
192
+            "prop-types": "^15.5.6",
193
+            "warning": "^3.0.0"
194
+          }
183
         }
195
         }
184
       }
196
       }
185
     },
197
     },
4384
       }
4396
       }
4385
     },
4397
     },
4386
     "chain-function": {
4398
     "chain-function": {
4387
-      "version": "1.0.0",
4388
-      "resolved": "https://registry.npmjs.org/chain-function/-/chain-function-1.0.0.tgz",
4389
-      "integrity": "sha1-DUqzfn4Y6tC9xHuSB2QRjOWHM9w="
4399
+      "version": "1.0.1",
4400
+      "resolved": "https://registry.npmjs.org/chain-function/-/chain-function-1.0.1.tgz",
4401
+      "integrity": "sha512-SxltgMwL9uCko5/ZCLiyG2B7R9fY4pDZUw7hJ4MhirdjBLosoDqkWABi3XMucddHdLiFJMb7PD2MZifZriuMTg=="
4390
     },
4402
     },
4391
     "chalk": {
4403
     "chalk": {
4392
       "version": "1.1.3",
4404
       "version": "1.1.3",
12489
       }
12501
       }
12490
     },
12502
     },
12491
     "react-transition-group": {
12503
     "react-transition-group": {
12492
-      "version": "1.2.1",
12493
-      "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-1.2.1.tgz",
12494
-      "integrity": "sha512-CWaL3laCmgAFdxdKbhhps+c0HRGF4c+hdM4H23+FI1QBNUyx/AMeIJGWorehPNSaKnQNOAxL7PQmqMu78CDj3Q==",
12504
+      "version": "2.4.0",
12505
+      "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.4.0.tgz",
12506
+      "integrity": "sha512-Xv5d55NkJUxUzLCImGSanK8Cl/30sgpOEMGc5m86t8+kZwrPxPCPcFqyx83kkr+5Lz5gs6djuvE5By+gce+VjA==",
12495
       "requires": {
12507
       "requires": {
12496
-        "chain-function": "^1.0.0",
12497
-        "dom-helpers": "^3.2.0",
12508
+        "dom-helpers": "^3.3.1",
12498
         "loose-envify": "^1.3.1",
12509
         "loose-envify": "^1.3.1",
12499
-        "prop-types": "^15.5.6",
12500
-        "warning": "^3.0.0"
12510
+        "prop-types": "^15.6.2",
12511
+        "react-lifecycles-compat": "^3.0.4"
12512
+      },
12513
+      "dependencies": {
12514
+        "prop-types": {
12515
+          "version": "15.6.2",
12516
+          "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz",
12517
+          "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==",
12518
+          "requires": {
12519
+            "loose-envify": "^1.3.1",
12520
+            "object-assign": "^4.1.1"
12521
+          }
12522
+        }
12501
       }
12523
       }
12502
     },
12524
     },
12503
     "read-pkg": {
12525
     "read-pkg": {

+ 1
- 0
package.json 파일 보기

74
     "react-native-vector-icons": "4.4.2",
74
     "react-native-vector-icons": "4.4.2",
75
     "react-native-webrtc": "github:jitsi/react-native-webrtc#be3de15bb988cfabbb62cd4b3b06f4c920ee5ba0",
75
     "react-native-webrtc": "github:jitsi/react-native-webrtc#be3de15bb988cfabbb62cd4b3b06f4c920ee5ba0",
76
     "react-redux": "5.0.7",
76
     "react-redux": "5.0.7",
77
+    "react-transition-group": "2.4.0",
77
     "redux": "4.0.0",
78
     "redux": "4.0.0",
78
     "redux-thunk": "2.2.0",
79
     "redux-thunk": "2.2.0",
79
     "styled-components": "1.4.6",
80
     "styled-components": "1.4.6",

+ 4
- 2
react/features/base/i18n/functions.js 파일 보기

5
  * Wraps a specific React Component in order to enable translations in it.
5
  * Wraps a specific React Component in order to enable translations in it.
6
  *
6
  *
7
  * @param {Component} component - The React Component to wrap.
7
  * @param {Component} component - The React Component to wrap.
8
+ * @param {Object} options - Additional options to pass into react-i18next's
9
+ * initialization.
8
  * @returns {Component} The React Component which wraps {@link component} and
10
  * @returns {Component} The React Component which wraps {@link component} and
9
  * enables translations in it.
11
  * enables translations in it.
10
  */
12
  */
11
-export function translate(component) {
13
+export function translate(component, options = { wait: true }) {
12
     // Use the default list of namespaces.
14
     // Use the default list of namespaces.
13
     return (
15
     return (
14
-        reactI18nextTranslate([ 'main', 'languages' ], { wait: true })(
16
+        reactI18nextTranslate([ 'main', 'languages' ], options)(
15
             component));
17
             component));
16
 }
18
 }
17
 
19
 

+ 17
- 6
react/features/chat/actionTypes.js 파일 보기

3
  *
3
  *
4
  * {
4
  * {
5
  *     type: ADD_MESSAGE,
5
  *     type: ADD_MESSAGE,
6
+ *     displayName: string
6
  *     hasRead: boolean,
7
  *     hasRead: boolean,
8
+ *     id: string,
9
+ *     messageType: string,
7
  *     message: string,
10
  *     message: string,
8
  *     timestamp: string,
11
  *     timestamp: string,
9
- *     userName: string
10
  * }
12
  * }
11
  */
13
  */
12
 export const ADD_MESSAGE = Symbol('ADD_MESSAGE');
14
 export const ADD_MESSAGE = Symbol('ADD_MESSAGE');
13
 
15
 
14
 /**
16
 /**
15
- * The type of the action which updates which is the most recent message that
16
- * has been seen by the local participant.
17
+ * The type of the action which signals a send a chat message to everyone in the
18
+ * conference.
17
  *
19
  *
18
  * {
20
  * {
19
- *     type: SET_LAST_READ_MESSAGE,
20
- *     message: Object
21
+ *     type: SEND_MESSAGE,
22
+ *     message: string
21
  * }
23
  * }
22
  */
24
  */
23
-export const SET_LAST_READ_MESSAGE = Symbol('SET_LAST_READ_MESSAGE');
25
+export const SEND_MESSAGE = Symbol('SEND_MESSAGE');
26
+
27
+/**
28
+ * The type of the action which signals to toggle the display of the chat panel.
29
+ *
30
+ * {
31
+ *     type: TOGGLE_CHAT
32
+ * }
33
+ */
34
+export const TOGGLE_CHAT = Symbol('TOGGLE_CHAT');

+ 29
- 31
react/features/chat/actions.js 파일 보기

1
-import { ADD_MESSAGE, SET_LAST_READ_MESSAGE } from './actionTypes';
1
+import { ADD_MESSAGE, SEND_MESSAGE, TOGGLE_CHAT } from './actionTypes';
2
 
2
 
3
 /* eslint-disable max-params */
3
 /* eslint-disable max-params */
4
 
4
 
5
 /**
5
 /**
6
  * Adds a chat message to the collection of messages.
6
  * Adds a chat message to the collection of messages.
7
  *
7
  *
8
- * @param {string} userName - The username to display of the participant that
9
- * authored the message.
10
- * @param {string} message - The received message to display.
11
- * @param {string} timestamp - A timestamp to display for when the message was
12
- * received.
13
- * @param {boolean} hasRead - Whether or not to immediately mark the message as
14
- * read.
8
+ * @param {Object} messageDetails - The chat message to save.
9
+ * @param {string} messageDetails.displayName - The displayName of the
10
+ * participant that authored the message.
11
+ * @param {boolean} messageDetails.hasRead - Whether or not to immediately mark
12
+ * the message as read.
13
+ * @param {string} messageDetails.message - The received message to display.
14
+ * @param {string} messageDetails.messageType - The kind of message, such as
15
+ * "error" or "local" or "remote".
16
+ * @param {string} messageDetails.timestamp - A timestamp to display for when
17
+ * the message was received.
15
  * @returns {{
18
  * @returns {{
16
  *     type: ADD_MESSAGE,
19
  *     type: ADD_MESSAGE,
20
+ *     displayName: string,
17
  *     hasRead: boolean,
21
  *     hasRead: boolean,
18
  *     message: string,
22
  *     message: string,
23
+ *     messageType: string,
19
  *     timestamp: string,
24
  *     timestamp: string,
20
- *     userName: string
21
  * }}
25
  * }}
22
  */
26
  */
23
-export function addMessage(userName, message, timestamp, hasRead) {
27
+export function addMessage(messageDetails) {
24
     return {
28
     return {
25
         type: ADD_MESSAGE,
29
         type: ADD_MESSAGE,
26
-        hasRead,
27
-        message,
28
-        timestamp,
29
-        userName
30
+        ...messageDetails
30
     };
31
     };
31
 }
32
 }
32
 
33
 
33
-/* eslint-enable max-params */
34
-
35
 /**
34
 /**
36
- * Sets the last read message cursor to the latest message.
35
+ * Sends a chat message to everyone in the conference.
37
  *
36
  *
38
- * @returns {Function}
37
+ * @param {string} message - The chat message to send out.
38
+ * @returns {{
39
+ *     type: SEND_MESSAGE,
40
+ *     message: string
41
+ * }}
39
  */
42
  */
40
-export function markAllRead() {
41
-    return (dispatch, getState) => {
42
-        const { messages } = getState()['features/chat'];
43
-
44
-        dispatch(setLastReadMessage(messages[messages.length - 1]));
43
+export function sendMessage(message) {
44
+    return {
45
+        type: SEND_MESSAGE,
46
+        message
45
     };
47
     };
46
 }
48
 }
47
 
49
 
48
 /**
50
 /**
49
- * Updates the last read message cursor to be set at the passed in message. The
50
- * assumption is that messages will be ordered chronologically.
51
+ * Toggles display of the chat side panel.
51
  *
52
  *
52
- * @param {Object} message - The message from the redux state.
53
  * @returns {{
53
  * @returns {{
54
- *     type: SET_LAST_READ_MESSAGE,
55
- *     message: Object
54
+ *     type: TOGGLE_CHAT
56
  * }}
55
  * }}
57
  */
56
  */
58
-export function setLastReadMessage(message) {
57
+export function toggleChat() {
59
     return {
58
     return {
60
-        type: SET_LAST_READ_MESSAGE,
61
-        message
59
+        type: TOGGLE_CHAT
62
     };
60
     };
63
 }
61
 }

react/features/side-panel/components/SidePanel.native.js → react/features/chat/components/Chat.native.js 파일 보기


+ 312
- 0
react/features/chat/components/Chat.web.js 파일 보기

1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+import { connect } from 'react-redux';
5
+import Transition from 'react-transition-group/Transition';
6
+
7
+import { translate } from '../../base/i18n';
8
+import { getLocalParticipant } from '../../base/participants';
9
+
10
+import { toggleChat } from '../actions';
11
+
12
+import ChatInput from './ChatInput';
13
+import ChatMessage from './ChatMessage';
14
+import DisplayNameForm from './DisplayNameForm';
15
+
16
+/**
17
+ * The type of the React {@code Component} props of {@link Chat}.
18
+ */
19
+type Props = {
20
+
21
+    /**
22
+     * The JitsiConference instance to send messages to.
23
+     */
24
+    _conference: Object,
25
+
26
+    /**
27
+     * Whether or not chat is displayed.
28
+     */
29
+    _isOpen: Boolean,
30
+
31
+    /**
32
+     * The local participant's ID.
33
+     */
34
+    _localUserId: String,
35
+
36
+    /**
37
+     * All the chat messages in the conference.
38
+     */
39
+    _messages: Array<Object>,
40
+
41
+    /**
42
+     * Whether or not to block chat access with a nickname input form.
43
+     */
44
+    _showNamePrompt: boolean,
45
+
46
+    /**
47
+     * Invoked to change the chat panel status.
48
+     */
49
+    dispatch: Dispatch<*>
50
+};
51
+
52
+/**
53
+ * The type of the React {@code Component} state of {@Chat}.
54
+ */
55
+type State = {
56
+
57
+    /**
58
+     * User provided nickname when the input text is provided in the view.
59
+     *
60
+     * @type {String}
61
+     */
62
+    message: string
63
+};
64
+
65
+/**
66
+ * React Component for holding the chat feature in a side panel that slides in
67
+ * and out of view.
68
+ *
69
+ * @extends Component
70
+ */
71
+class Chat extends Component<Props, State> {
72
+
73
+    /**
74
+     * Reference to the HTML element used for typing in a chat message.
75
+     */
76
+    _chatInput: ?HTMLElement;
77
+
78
+    /**
79
+     * Whether or not the {@code Chat} component is off-screen, having finished
80
+     * its hiding animation.
81
+     */
82
+    _isExited: boolean;
83
+
84
+    /**
85
+     * Reference to the HTML element at the end of the list of displayed chat
86
+     * messages. Used for scrolling to the end of the chat messages.
87
+     */
88
+    _messagesListEnd: ?HTMLElement;
89
+
90
+    /**
91
+     * Initializes a new {@code Chat} instance.
92
+     *
93
+     * @param {Object} props - The read-only properties with which the new
94
+     * instance is to be initialized.
95
+     */
96
+    constructor(props: Props) {
97
+        super(props);
98
+
99
+        this._chatInput = null;
100
+        this._isExited = true;
101
+        this._messagesListEnd = null;
102
+
103
+        // Bind event handlers so they are only bound once for every instance.
104
+        this._onCloseClick = this._onCloseClick.bind(this);
105
+        this._renderMessage = this._renderMessage.bind(this);
106
+        this._renderPanelContent = this._renderPanelContent.bind(this);
107
+        this._setChatInputRef = this._setChatInputRef.bind(this);
108
+        this._setMessageListEndRef = this._setMessageListEndRef.bind(this);
109
+    }
110
+
111
+    /**
112
+     * Implements React's {@link Component#componentDidMount()}.
113
+     *
114
+     * @inheritdoc
115
+     */
116
+    componentDidMount() {
117
+        this._scrollMessagesToBottom();
118
+    }
119
+
120
+    /**
121
+     * Updates chat input focus.
122
+     *
123
+     * @inheritdoc
124
+     */
125
+    componentDidUpdate(prevProps) {
126
+        if (this.props._messages !== prevProps._messages) {
127
+            this._scrollMessagesToBottom();
128
+
129
+        }
130
+    }
131
+
132
+    /**
133
+     * Implements React's {@link Component#render()}.
134
+     *
135
+     * @inheritdoc
136
+     * @returns {ReactElement}
137
+     */
138
+    render() {
139
+        return (
140
+            <Transition
141
+                in = { this.props._isOpen }
142
+                timeout = { 500 }>
143
+                { this._renderPanelContent }
144
+            </Transition>
145
+        );
146
+    }
147
+
148
+    _onCloseClick: () => void;
149
+
150
+    /**
151
+     * Callback invoked to hide {@code Chat}.
152
+     *
153
+     * @returns {void}
154
+     */
155
+    _onCloseClick() {
156
+        this.props.dispatch(toggleChat());
157
+    }
158
+
159
+    /**
160
+     * Returns a React Element for showing chat messages and a form to send new
161
+     * chat messages.
162
+     *
163
+     * @private
164
+     * @returns {ReactElement}
165
+     */
166
+    _renderChat() {
167
+        const messages = this.props._messages.map(this._renderMessage);
168
+
169
+        messages.push(<div
170
+            key = 'end-marker'
171
+            ref = { this._setMessageListEndRef } />);
172
+
173
+        return (
174
+            <div
175
+                className = 'sideToolbarContainer__inner'
176
+                id = 'chat_container'>
177
+                <div id = 'chatconversation'>
178
+                    { messages }
179
+                </div>
180
+                <ChatInput getChatInputRef = { this._setChatInputRef } />
181
+            </div>
182
+        );
183
+    }
184
+
185
+    _renderMessage: (Object) => void;
186
+
187
+    /**
188
+     * Called by {@code _onSubmitMessage} to create the chat div.
189
+     *
190
+     * @param {string} message - The chat message to display.
191
+     * @param {string} id - The chat message ID to use as a unique key.
192
+     * @returns {Array<ReactElement>}
193
+     */
194
+    _renderMessage(message: Object, id: string) {
195
+        return (
196
+            <ChatMessage
197
+                key = { id }
198
+                message = { message } />
199
+        );
200
+    }
201
+
202
+    _renderPanelContent: (string) => React$Node | null;
203
+
204
+    /**
205
+     * Renders the contents of the chat panel, depending on the current
206
+     * animation state provided by {@code Transition}.
207
+     *
208
+     * @param {string} state - The current display transition state of the
209
+     * {@code Chat} component, as provided by {@code Transition}.
210
+     * @private
211
+     * @returns {ReactElement | null}
212
+     */
213
+    _renderPanelContent(state) {
214
+        this._isExited = state === 'exited';
215
+
216
+        const { _isOpen, _showNamePrompt } = this.props;
217
+        const ComponentToRender = !_isOpen && state === 'exited'
218
+            ? null
219
+            : (
220
+                <div>
221
+                    <div
222
+                        className = 'chat-close'
223
+                        onClick = { this._onCloseClick }>X</div>
224
+                    { _showNamePrompt
225
+                        ? <DisplayNameForm /> : this._renderChat() }
226
+                </div>
227
+            );
228
+        let className = '';
229
+
230
+        if (_isOpen) {
231
+            className = 'slideInExt';
232
+        } else if (this._isExited) {
233
+            className = 'invisible';
234
+        }
235
+
236
+        return (
237
+            <div
238
+                className = { className }
239
+                id = 'sideToolbarContainer'>
240
+                { ComponentToRender }
241
+            </div>
242
+        );
243
+    }
244
+
245
+    /**
246
+     * Automatically scrolls the displayed chat messages down to the latest.
247
+     *
248
+     * @private
249
+     * @returns {void}
250
+     */
251
+    _scrollMessagesToBottom() {
252
+        if (this._messagesListEnd) {
253
+            this._messagesListEnd.scrollIntoView({
254
+                behavior: this._isExited ? 'auto' : 'smooth'
255
+            });
256
+        }
257
+    }
258
+
259
+    _setChatInputRef: (?HTMLElement) => void;
260
+
261
+    /**
262
+     * Sets a reference to the HTML text input element used for typing in chat
263
+     * messages.
264
+     *
265
+     * @param {Object} chatInput - The input for typing chat messages.
266
+     * @private
267
+     * @returns {void}
268
+     */
269
+    _setChatInputRef(chatInput: ?HTMLElement) {
270
+        this._chatInput = chatInput;
271
+    }
272
+
273
+    _setMessageListEndRef: (?HTMLElement) => void;
274
+
275
+    /**
276
+     * Sets a reference to the HTML element at the bottom of the message list.
277
+     *
278
+     * @param {Object} messageListEnd - The HTML element.
279
+     * @private
280
+     * @returns {void}
281
+     */
282
+    _setMessageListEndRef(messageListEnd: ?HTMLElement) {
283
+        this._messagesListEnd = messageListEnd;
284
+    }
285
+}
286
+
287
+/**
288
+ * Maps (parts of) the redux state to {@link Chat} React {@code Component}
289
+ * props.
290
+ *
291
+ * @param {Object} state - The redux store/state.
292
+ * @private
293
+ * @returns {{
294
+ *     _conference: Object,
295
+ *     _isOpen: boolean,
296
+ *     _messages: Array<Object>,
297
+ *     _showNamePrompt: boolean
298
+ * }}
299
+ */
300
+function _mapStateToProps(state) {
301
+    const { isOpen, messages } = state['features/chat'];
302
+    const localParticipant = getLocalParticipant(state);
303
+
304
+    return {
305
+        _conference: state['features/base/conference'].conference,
306
+        _isOpen: isOpen,
307
+        _messages: messages,
308
+        _showNamePrompt: !localParticipant.name
309
+    };
310
+}
311
+
312
+export default translate(connect(_mapStateToProps)(Chat));

+ 11
- 10
react/features/chat/components/ChatCounter.web.js 파일 보기

1
-import PropTypes from 'prop-types';
1
+// @flow
2
+
2
 import React, { Component } from 'react';
3
 import React, { Component } from 'react';
3
 import { connect } from 'react-redux';
4
 import { connect } from 'react-redux';
4
 
5
 
5
 import { getUnreadCount } from '../functions';
6
 import { getUnreadCount } from '../functions';
6
 
7
 
7
 /**
8
 /**
8
- * FIXME: Move this UI logic to a generic component that can be used for
9
- * {@code ParticipantCounter} as well.
9
+ * The type of the React {@code Component} props of {@link ChatCounter}.
10
  */
10
  */
11
+type Props = {
12
+
13
+    /**
14
+     * The value of to display as a count.
15
+     */
16
+    _count: number
17
+};
11
 
18
 
12
 /**
19
 /**
13
  * Implements a React {@link Component} which displays a count of the number of
20
  * Implements a React {@link Component} which displays a count of the number of
15
  *
22
  *
16
  * @extends Component
23
  * @extends Component
17
  */
24
  */
18
-class ChatCounter extends Component {
19
-    static propTypes = {
20
-        /**
21
-         * The number of unread chat messages in the conference.
22
-         */
23
-        _count: PropTypes.number
24
-    };
25
+class ChatCounter extends Component<Props> {
25
 
26
 
26
     /**
27
     /**
27
      * Implements React's {@link Component#render()}.
28
      * Implements React's {@link Component#render()}.

+ 0
- 0
react/features/chat/components/ChatInput.native.js 파일 보기


+ 229
- 0
react/features/chat/components/ChatInput.web.js 파일 보기

1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+import { connect } from 'react-redux';
5
+
6
+import { sendMessage } from '../actions';
7
+
8
+import SmileysPanel from './SmileysPanel';
9
+
10
+/**
11
+ * The type of the React {@code Component} props of {@link ChatInput}.
12
+ */
13
+type Props = {
14
+
15
+    /**
16
+     * Invoked to send chat messages.
17
+     */
18
+    dispatch: Dispatch<*>,
19
+
20
+    /**
21
+     * Optional callback to get a reference to the chat input element.
22
+     */
23
+    getChatInputRef?: Function
24
+};
25
+
26
+/**
27
+ * The type of the React {@code Component} state of {@link ChatInput}.
28
+ */
29
+type State = {
30
+
31
+    /**
32
+     * User provided nickname when the input text is provided in the view.
33
+     */
34
+    message: string,
35
+
36
+    /**
37
+     * Whether or not the smiley selector is visible.
38
+     */
39
+    showSmileysPanel: boolean
40
+};
41
+
42
+/**
43
+ * Implements a React Component for drafting and submitting a chat message.
44
+ *
45
+ * @extends Component
46
+ */
47
+class ChatInput extends Component<Props, State> {
48
+    _textArea: ?HTMLTextAreaElement;
49
+
50
+    state = {
51
+        message: '',
52
+        showSmileysPanel: false
53
+    };
54
+
55
+    /**
56
+     * Initializes a new {@code ChatInput} instance.
57
+     *
58
+     * @param {Object} props - The read-only properties with which the new
59
+     * instance is to be initialized.
60
+     */
61
+    constructor(props: Props) {
62
+        super(props);
63
+
64
+        this._textArea = null;
65
+
66
+        // Bind event handlers so they are only bound once for every instance.
67
+        this._onDetectSubmit = this._onDetectSubmit.bind(this);
68
+        this._onMessageChange = this._onMessageChange.bind(this);
69
+        this._onSmileySelect = this._onSmileySelect.bind(this);
70
+        this._onToggleSmileysPanel = this._onToggleSmileysPanel.bind(this);
71
+        this._setTextAreaRef = this._setTextAreaRef.bind(this);
72
+    }
73
+
74
+    /**
75
+     * Implements React's {@link Component#componentDidMount()}.
76
+     *
77
+     * @inheritdoc
78
+     */
79
+    componentDidMount() {
80
+        /**
81
+         * HTML Textareas do not support autofocus. Simulate autofocus by
82
+         * manually focusing.
83
+         */
84
+        this.focus();
85
+    }
86
+
87
+    /**
88
+     * Implements React's {@link Component#render()}.
89
+     *
90
+     * @inheritdoc
91
+     * @returns {ReactElement}
92
+     */
93
+    render() {
94
+        const smileysPanelClassName = `${this.state.showSmileysPanel
95
+            ? 'show-smileys' : 'hide-smileys'} smileys-panel`;
96
+
97
+        return (
98
+            <div id = 'chat-input' >
99
+                <div className = 'smiley-input'>
100
+                    <div id = 'smileysarea'>
101
+                        <div id = 'smileys'>
102
+                            <img
103
+                                onClick = { this._onToggleSmileysPanel }
104
+                                src = '../../../../images/smile.svg' />
105
+                        </div>
106
+                    </div>
107
+                    <div className = { smileysPanelClassName }>
108
+                        <SmileysPanel
109
+                            onSmileySelect = { this._onSmileySelect } />
110
+                    </div>
111
+                </div>
112
+                <div className = 'usrmsg-form'>
113
+                    <textarea
114
+                        data-i18n = '[placeholder]chat.messagebox'
115
+                        id = 'usermsg'
116
+                        onChange = { this._onMessageChange }
117
+                        onKeyDown = { this._onDetectSubmit }
118
+                        placeholder = { 'Enter Text...' }
119
+                        ref = { this._setTextAreaRef }
120
+                        value = { this.state.message } />
121
+                </div>
122
+            </div>
123
+        );
124
+    }
125
+
126
+    /**
127
+     * Removes cursor focus on this component's text area.
128
+     *
129
+     * @returns {void}
130
+     */
131
+    blur() {
132
+        this._textArea && this._textArea.blur();
133
+    }
134
+
135
+    /**
136
+     * Place cursor focus on this component's text area.
137
+     *
138
+     * @returns {void}
139
+     */
140
+    focus() {
141
+        this._textArea && this._textArea.focus();
142
+    }
143
+
144
+    _onDetectSubmit: (Object) => void;
145
+
146
+    /**
147
+     * Detects if enter has been pressed. If so, submit the message in the chat
148
+     * window.
149
+     *
150
+     * @param {string} event - Keyboard event.
151
+     * @private
152
+     * @returns {void}
153
+     */
154
+    _onDetectSubmit(event) {
155
+        if (event.keyCode === 13
156
+            && event.shiftKey === false) {
157
+            event.preventDefault();
158
+
159
+            this.props.dispatch(sendMessage(this.state.message));
160
+
161
+            this.setState({ message: '' });
162
+        }
163
+    }
164
+
165
+    _onMessageChange: (Object) => void;
166
+
167
+    /**
168
+     * Updates the known message the user is drafting.
169
+     *
170
+     * @param {string} event - Keyboard event.
171
+     * @private
172
+     * @returns {void}
173
+     */
174
+    _onMessageChange(event) {
175
+        this.setState({ message: event.target.value });
176
+    }
177
+
178
+    _onSmileySelect: (string) => void;
179
+
180
+    /**
181
+     * Appends a selected smileys to the chat message draft.
182
+     *
183
+     * @param {string} smileyText - The value of the smiley to append to the
184
+     * chat message.
185
+     * @private
186
+     * @returns {void}
187
+     */
188
+    _onSmileySelect(smileyText) {
189
+        this.setState({
190
+            message: `${this.state.message} ${smileyText}`,
191
+            showSmileysPanel: false
192
+        });
193
+
194
+        this.focus();
195
+    }
196
+
197
+    _onToggleSmileysPanel: () => void;
198
+
199
+    /**
200
+     * Callback invoked to hide or show the smileys selector.
201
+     *
202
+     * @private
203
+     * @returns {void}
204
+     */
205
+    _onToggleSmileysPanel() {
206
+        this.setState({ showSmileysPanel: !this.state.showSmileysPanel });
207
+
208
+        this.focus();
209
+    }
210
+
211
+    _setTextAreaRef: (?HTMLTextAreaElement) => void;
212
+
213
+    /**
214
+     * Sets the reference to the HTML TextArea.
215
+     *
216
+     * @param {HTMLAudioElement} textAreaElement - The HTML text area element.
217
+     * @private
218
+     * @returns {void}
219
+     */
220
+    _setTextAreaRef(textAreaElement: ?HTMLTextAreaElement) {
221
+        this._textArea = textAreaElement;
222
+
223
+        if (this.props.getChatInputRef) {
224
+            this.props.getChatInputRef(textAreaElement);
225
+        }
226
+    }
227
+}
228
+
229
+export default connect()(ChatInput);

+ 0
- 0
react/features/chat/components/ChatMessage.native.js 파일 보기


+ 114
- 0
react/features/chat/components/ChatMessage.web.js 파일 보기

1
+// @flow
2
+
3
+import React, { PureComponent } from 'react';
4
+
5
+import { translate } from '../../base/i18n';
6
+
7
+import { processReplacements } from '../replacement';
8
+
9
+/**
10
+ * The type of the React {@code Component} props of {@link Chat}.
11
+ */
12
+type Props = {
13
+
14
+    /**
15
+     * The redux representation of a chat message.
16
+     */
17
+    message: Object,
18
+
19
+    /**
20
+     * Invoked to receive translated strings.
21
+     */
22
+    t: Function
23
+};
24
+
25
+/**
26
+ * Displays as passed in chat message.
27
+ *
28
+ * @extends Component
29
+ */
30
+class ChatMessage extends PureComponent<Props> {
31
+    /**
32
+     * Implements React's {@link Component#render()}.
33
+     *
34
+     * @inheritdoc
35
+     * @returns {ReactElement}
36
+     */
37
+    render() {
38
+        const { message } = this.props;
39
+        let messageTypeClassname = '';
40
+        let messagetoDisplay = message.message;
41
+
42
+        switch (message.messageType) {
43
+        case 'local':
44
+            messageTypeClassname = 'localuser';
45
+
46
+            break;
47
+        case 'error':
48
+            messageTypeClassname = 'error';
49
+            messagetoDisplay = this.props.t('chat.error', {
50
+                error: message.error,
51
+                originalText: messagetoDisplay
52
+            });
53
+            break;
54
+        default:
55
+            messageTypeClassname = 'remoteuser';
56
+        }
57
+
58
+        // replace links and smileys
59
+        // Strophe already escapes special symbols on sending,
60
+        // so we escape here only tags to avoid double &amp;
61
+        const escMessage = messagetoDisplay.replace(/</g, '&lt;')
62
+            .replace(/>/g, '&gt;')
63
+            .replace(/\n/g, '<br/>');
64
+        const messageWithHTML = processReplacements(escMessage);
65
+
66
+        return (
67
+            <div className = { `chatmessage ${messageTypeClassname}` }>
68
+                <img
69
+                    className = 'chatArrow'
70
+                    src = '../../../../images/chatArrow.svg' />
71
+                <div className = 'display-name'>
72
+                    { message.displayName }
73
+                </div>
74
+                <div className = { 'timestamp' }>
75
+                    { ChatMessage.formatTimestamp(message.timestamp) }
76
+                </div>
77
+                <div
78
+                    className = 'usermessage'
79
+                    // eslint-disable-next-line react/no-danger
80
+                    dangerouslySetInnerHTML = {{ __html: messageWithHTML }} />
81
+            </div>
82
+        );
83
+    }
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
+}
113
+
114
+export default translate(ChatMessage, { wait: false });

+ 0
- 0
react/features/chat/components/DisplayNameForm.native.js 파일 보기


+ 142
- 0
react/features/chat/components/DisplayNameForm.web.js 파일 보기

1
+// @flow
2
+
3
+import { FieldTextStateless } from '@atlaskit/field-text';
4
+import React, { Component } from 'react';
5
+import { connect } from 'react-redux';
6
+
7
+import { translate } from '../../base/i18n';
8
+import {
9
+    getLocalParticipant,
10
+    participantDisplayNameChanged
11
+} from '../../base/participants';
12
+
13
+/**
14
+ * The type of the React {@code Component} props of {@DisplayNameForm}.
15
+ */
16
+type Props = {
17
+
18
+    /**
19
+     * The ID of the local participant.
20
+     */
21
+    _localParticipantId: string,
22
+
23
+    /**
24
+     * Invoked to set the local participant display name.
25
+     */
26
+    dispatch: Dispatch<*>,
27
+
28
+    /**
29
+     * Invoked to obtain translated strings.
30
+     */
31
+    t: Function
32
+};
33
+
34
+/**
35
+ * The type of the React {@code Component} state of {@DisplayNameForm}.
36
+ */
37
+type State = {
38
+
39
+    /**
40
+     * User provided display name when the input text is provided in the view.
41
+     */
42
+    displayName: string
43
+};
44
+
45
+/**
46
+ * React Component for requesting the local participant to set a display name.
47
+ *
48
+ * @extends Component
49
+ */
50
+class DisplayNameForm extends Component<Props, State> {
51
+    state = {
52
+        displayName: ''
53
+    };
54
+
55
+    /**
56
+     * Initializes a new {@code DisplayNameForm} instance.
57
+     *
58
+     * @param {Object} props - The read-only properties with which the new
59
+     * instance is to be initialized.
60
+     */
61
+    constructor(props: Props) {
62
+        super(props);
63
+
64
+        // Bind event handlers so they are only bound once for every instance.
65
+        this._onDisplayNameChange = this._onDisplayNameChange.bind(this);
66
+        this._onSubmit = this._onSubmit.bind(this);
67
+    }
68
+
69
+    /**
70
+     * Implements React's {@link Component#render()}.
71
+     *
72
+     * @inheritdoc
73
+     * @returns {ReactElement}
74
+     */
75
+    render() {
76
+        const { t } = this.props;
77
+
78
+        return (
79
+            <div id = 'nickname'>
80
+                <span>{ this.props.t('chat.nickname.title') }</span>
81
+                <form onSubmit = { this._onSubmit }>
82
+                    <FieldTextStateless
83
+                        autoFocus = { true }
84
+                        id = 'nickinput'
85
+                        onChange = { this._onDisplayNameChange }
86
+                        placeholder = { t('chat.nickname.popover') }
87
+                        type = 'text'
88
+                        value = { this.state.displayName } />
89
+                </form>
90
+            </div>
91
+        );
92
+    }
93
+
94
+    _onDisplayNameChange: (Object) => void;
95
+
96
+    /**
97
+     * Dispatches an action update the entered display name.
98
+     *
99
+     * @param {event} event - Keyboard event.
100
+     * @private
101
+     * @returns {void}
102
+     */
103
+    _onDisplayNameChange(event: Object) {
104
+        this.setState({ displayName: event.target.value });
105
+    }
106
+
107
+    _onSubmit: (Object) => void;
108
+
109
+    /**
110
+     * Dispatches an action to hit enter to change your display name.
111
+     *
112
+     * @param {event} event - Keyboard event
113
+     * that will check if user has pushed the enter key.
114
+     * @private
115
+     * @returns {void}
116
+     */
117
+    _onSubmit(event: Object) {
118
+        event.preventDefault();
119
+
120
+        this.props.dispatch(participantDisplayNameChanged(
121
+            this.props._localParticipantId,
122
+            this.state.displayName));
123
+    }
124
+}
125
+
126
+/**
127
+ * Maps (parts of) the Redux state to the associated props for the
128
+ * {@code DisplayNameForm} component.
129
+ *
130
+ * @param {Object} state - The Redux state.
131
+ * @private
132
+ * @returns {{
133
+ *     _localParticipantId: string
134
+ * }}
135
+ */
136
+function _mapStateToProps(state) {
137
+    return {
138
+        _localParticipantId: getLocalParticipant(state).id
139
+    };
140
+}
141
+
142
+export default translate(connect(_mapStateToProps)(DisplayNameForm));

+ 0
- 0
react/features/chat/components/SmileysPanel.native.js 파일 보기


+ 69
- 0
react/features/chat/components/SmileysPanel.web.js 파일 보기

1
+// @flow
2
+
3
+import React, { PureComponent } from 'react';
4
+
5
+import { smileys } from '../smileys';
6
+
7
+/**
8
+ * The type of the React {@code Component} props of {@link SmileysPanel}.
9
+ */
10
+type Props = {
11
+
12
+    /**
13
+     * Callback to invoke when a smiley is selected. The smiley will be passed
14
+     * back.
15
+     */
16
+    onSmileySelect: Function
17
+};
18
+
19
+/**
20
+ * Implements a React Component showing smileys that can be be shown in chat.
21
+ *
22
+ * @extends Component
23
+ */
24
+class SmileysPanel extends PureComponent<Props> {
25
+    /**
26
+     * Implements React's {@link Component#render()}.
27
+     *
28
+     * @inheritdoc
29
+     * @returns {ReactElement}
30
+     */
31
+    render() {
32
+        const smileyItems = Object.keys(smileys).map(smileyKey => {
33
+            const onSelectFunction = this._getOnSmileySelectCallback(smileyKey);
34
+
35
+            return (
36
+                <div
37
+                    className = 'smileyContainer'
38
+                    id = { smileyKey }
39
+                    key = { smileyKey }>
40
+                    <img
41
+                        className = 'smiley'
42
+                        id = { smileyKey }
43
+                        onClick = { onSelectFunction }
44
+                        src = { `images/smileys/${smileyKey}.svg` } />
45
+                </div>
46
+            );
47
+        });
48
+
49
+        return (
50
+            <div id = 'smileysContainer'>
51
+                { smileyItems }
52
+            </div>
53
+        );
54
+    }
55
+
56
+    /**
57
+     * Helper method to bind a smiley's click handler.
58
+     *
59
+     * @param {string} smileyKey - The key from the {@link smileys} object
60
+     * that should be added to the chat message.
61
+     * @private
62
+     * @returns {Function}
63
+     */
64
+    _getOnSmileySelectCallback(smileyKey) {
65
+        return () => this.props.onSmileySelect(smileys[smileyKey]);
66
+    }
67
+}
68
+
69
+export default SmileysPanel;

+ 1
- 0
react/features/chat/components/index.js 파일 보기

1
+export Chat from './Chat';
1
 export ChatCounter from './ChatCounter';
2
 export ChatCounter from './ChatCounter';

+ 64
- 10
react/features/chat/middleware.js 파일 보기

1
 // @flow
1
 // @flow
2
 
2
 
3
+import UIUtil from '../../../modules/UI/util/UIUtil';
4
+
3
 import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
5
 import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
4
 import { CONFERENCE_JOINED } from '../base/conference';
6
 import { CONFERENCE_JOINED } from '../base/conference';
5
 import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
7
 import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
8
+import { getParticipantById } from '../base/participants';
6
 import { MiddlewareRegistry } from '../base/redux';
9
 import { MiddlewareRegistry } from '../base/redux';
7
 import { playSound, registerSound, unregisterSound } from '../base/sounds';
10
 import { playSound, registerSound, unregisterSound } from '../base/sounds';
11
+import { isButtonEnabled, showToolbox } from '../toolbox';
8
 
12
 
13
+import { SEND_MESSAGE } from './actionTypes';
14
+import { addMessage } from './actions';
9
 import { INCOMING_MSG_SOUND_ID } from './constants';
15
 import { INCOMING_MSG_SOUND_ID } from './constants';
10
 import { INCOMING_MSG_SOUND_FILE } from './sounds';
16
 import { INCOMING_MSG_SOUND_FILE } from './sounds';
11
 
17
 
12
 declare var APP: Object;
18
 declare var APP: Object;
19
+declare var interfaceConfig : Object;
13
 
20
 
14
 /**
21
 /**
15
  * Implements the middleware of the chat feature.
22
  * Implements the middleware of the chat feature.
38
         typeof APP === 'undefined'
45
         typeof APP === 'undefined'
39
             || _addChatMsgListener(action.conference, store);
46
             || _addChatMsgListener(action.conference, store);
40
         break;
47
         break;
48
+
49
+    case SEND_MESSAGE:
50
+        if (typeof APP !== 'undefined') {
51
+            const { conference } = store.getState()['features/base/conference'];
52
+
53
+            if (conference) {
54
+                const escapedMessage = UIUtil.escapeHtml(action.message);
55
+
56
+                APP.API.notifySendingChatMessage(escapedMessage);
57
+                conference.sendTextMessage(escapedMessage);
58
+            }
59
+        }
60
+        break;
41
     }
61
     }
42
 
62
 
43
     return next(action);
63
     return next(action);
49
  *
69
  *
50
  * @param {JitsiConference} conference - The conference instance on which the
70
  * @param {JitsiConference} conference - The conference instance on which the
51
  * new event listener will be registered.
71
  * new event listener will be registered.
52
- * @param {Dispatch} next - The redux dispatch function to dispatch the
53
- * specified action to the specified store.
72
+ * @param {Object} store - The redux store object.
54
  * @private
73
  * @private
55
  * @returns {void}
74
  * @returns {void}
56
  */
75
  */
57
-function _addChatMsgListener(conference, { dispatch }) {
58
-    // XXX Currently, there's no need to remove the listener, because the
59
-    // JitsiConference instance cannot be reused. Hence, the listener will be
60
-    // gone with the JitsiConference instance.
76
+function _addChatMsgListener(conference, { dispatch, getState }) {
77
+    if ((typeof interfaceConfig === 'object' && interfaceConfig.filmStripOnly)
78
+        || !isButtonEnabled('chat')) {
79
+        return;
80
+    }
81
+
61
     conference.on(
82
     conference.on(
62
         JitsiConferenceEvents.MESSAGE_RECEIVED,
83
         JitsiConferenceEvents.MESSAGE_RECEIVED,
63
-        () => {
64
-            APP.UI.isChatVisible()
65
-                || dispatch(playSound(INCOMING_MSG_SOUND_ID));
66
-        });
84
+        (id, message, timestamp) => {
85
+            const state = getState();
86
+            const { isOpen: isChatOpen } = state['features/chat'];
87
+
88
+            if (!isChatOpen) {
89
+                dispatch(playSound(INCOMING_MSG_SOUND_ID));
90
+                dispatch(showToolbox(4000));
91
+            }
92
+
93
+            // Provide a default for for the case when a message is being
94
+            // backfilled for a participant that has left the conference.
95
+            const participant = getParticipantById(state, id) || {};
96
+            const displayName = participant.name
97
+                || `${interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME} (${id})`;
98
+            const hasRead = participant.local || isChatOpen;
99
+
100
+            APP.API.notifyReceivedChatMessage({
101
+                body: message,
102
+                id,
103
+                nick: displayName,
104
+                ts: timestamp
105
+            });
106
+
107
+            const timestampToDate = timestamp
108
+                ? new Date(timestamp) : new Date();
109
+            const millisecondsTimestamp = timestampToDate.getTime();
110
+
111
+            dispatch(addMessage({
112
+                displayName,
113
+                hasRead,
114
+                id,
115
+                messageType: participant.local ? 'local' : 'remote',
116
+                message,
117
+                timestamp: millisecondsTimestamp
118
+            }));
119
+        }
120
+    );
67
 }
121
 }

+ 12
- 11
react/features/chat/reducer.js 파일 보기

2
 
2
 
3
 import { ReducerRegistry } from '../base/redux';
3
 import { ReducerRegistry } from '../base/redux';
4
 
4
 
5
-import {
6
-    ADD_MESSAGE,
7
-    SET_LAST_READ_MESSAGE
8
-} from './actionTypes';
5
+import { ADD_MESSAGE, TOGGLE_CHAT } from './actionTypes';
9
 
6
 
10
 const DEFAULT_STATE = {
7
 const DEFAULT_STATE = {
11
-    open: false,
12
-    messages: [],
13
-    lastReadMessage: null
8
+    isOpen: false,
9
+    lastReadMessage: undefined,
10
+    messages: []
14
 };
11
 };
15
 
12
 
16
 ReducerRegistry.register('features/chat', (state = DEFAULT_STATE, action) => {
13
 ReducerRegistry.register('features/chat', (state = DEFAULT_STATE, action) => {
17
     switch (action.type) {
14
     switch (action.type) {
18
     case ADD_MESSAGE: {
15
     case ADD_MESSAGE: {
19
         const newMessage = {
16
         const newMessage = {
17
+            displayName: action.displayName,
18
+            error: action.error,
19
+            id: action.id,
20
+            messageType: action.messageType,
20
             message: action.message,
21
             message: action.message,
21
-            timestamp: action.timestamp,
22
-            userName: action.userName
22
+            timestamp: action.timestamp
23
         };
23
         };
24
 
24
 
25
         return {
25
         return {
33
         };
33
         };
34
     }
34
     }
35
 
35
 
36
-    case SET_LAST_READ_MESSAGE:
36
+    case TOGGLE_CHAT:
37
         return {
37
         return {
38
             ...state,
38
             ...state,
39
-            lastReadMessage: action.message
39
+            isOpen: !state.isOpen,
40
+            lastReadMessage: state.messages[state.messages.length - 1]
40
         };
41
         };
41
     }
42
     }
42
 
43
 

modules/UI/side_pannels/chat/Replacement.js → react/features/chat/replacement.js 파일 보기

1
 import { regexes } from './smileys';
1
 import { regexes } from './smileys';
2
 
2
 
3
 /**
3
 /**
4
- * Processes links and smileys in "body"
4
+ * Processes links and smileys in "body".
5
+ *
6
+ * @param  {string} body - The message body.
7
+ * @returns {string} Message body with image tags and href tags.
5
  */
8
  */
6
 export function processReplacements(body) {
9
 export function processReplacements(body) {
7
     // make links clickable + add smileys
10
     // make links clickable + add smileys
8
     return smilify(linkify(body));
11
     return smilify(linkify(body));
9
 }
12
 }
10
 
13
 
14
+
11
 /**
15
 /**
12
- * Finds and replaces all links in the links in "body"
13
- * with their <a href=""></a>
16
+ * Finds and replaces all links in the links in "body" with an href tag.
17
+ *
18
+ * @param  {string} inputText - The message body.
19
+ * @returns {string} The text replaced with HTML tags for links.
14
  */
20
  */
15
-export function linkify(inputText) {
21
+function linkify(inputText) {
16
     let replacedText;
22
     let replacedText;
17
 
23
 
18
     /* eslint-disable no-useless-escape, max-len */
24
     /* eslint-disable no-useless-escape, max-len */
38
 }
44
 }
39
 
45
 
40
 /**
46
 /**
41
- * Replaces common smiley strings with images
47
+ * Replaces common smiley strings with images.
48
+ *
49
+ * @param  {string} body - The message body.
50
+ * @returns {string} Body returned with smiley replaced.
42
  */
51
  */
43
 function smilify(body) {
52
 function smilify(body) {
44
     if (!body) {
53
     if (!body) {

modules/UI/side_pannels/chat/smileys.js → react/features/chat/smileys.js 파일 보기


+ 2
- 2
react/features/conference/components/Conference.web.js 파일 보기

9
 import { obtainConfig } from '../../base/config';
9
 import { obtainConfig } from '../../base/config';
10
 import { connect, disconnect } from '../../base/connection';
10
 import { connect, disconnect } from '../../base/connection';
11
 import { translate } from '../../base/i18n';
11
 import { translate } from '../../base/i18n';
12
+import { Chat } from '../../chat';
12
 import { Filmstrip } from '../../filmstrip';
13
 import { Filmstrip } from '../../filmstrip';
13
 import { CalleeInfoContainer } from '../../invite';
14
 import { CalleeInfoContainer } from '../../invite';
14
 import { LargeVideo } from '../../large-video';
15
 import { LargeVideo } from '../../large-video';
15
 import { NotificationsContainer } from '../../notifications';
16
 import { NotificationsContainer } from '../../notifications';
16
-import { SidePanel } from '../../side-panel';
17
 import {
17
 import {
18
     LAYOUTS,
18
     LAYOUTS,
19
     getCurrentLayout,
19
     getCurrentLayout,
223
                 </div>
223
                 </div>
224
 
224
 
225
                 { filmstripOnly || <Toolbox /> }
225
                 { filmstripOnly || <Toolbox /> }
226
-                { filmstripOnly || <SidePanel /> }
226
+                { filmstripOnly || <Chat /> }
227
 
227
 
228
                 <NotificationsContainer />
228
                 <NotificationsContainer />
229
 
229
 

+ 0
- 29
react/features/side-panel/actionTypes.js 파일 보기

1
-/**
2
- * The type of the action which signals to close the side panel.
3
- *
4
- * {
5
- *     type: CLOSE_PANEL,
6
- * }
7
- */
8
-export const CLOSE_PANEL = Symbol('CLOSE_PANEL');
9
-
10
-/**
11
- * The type of the action which to set the name of the current panel being
12
- * displayed in the side panel.
13
- *
14
- * {
15
- *     type: SET_VISIBLE_PANEL,
16
- *     current: string|null
17
- * }
18
- */
19
-export const SET_VISIBLE_PANEL = Symbol('SET_VISIBLE_PANEL');
20
-
21
-/**
22
- * The type of the action which signals to toggle the display of chat in the
23
- * side panel.
24
- *
25
- * {
26
- *     type: TOGGLE_CHAT
27
- * }
28
- */
29
-export const TOGGLE_CHAT = Symbol('TOGGLE_CHAT');

+ 0
- 49
react/features/side-panel/actions.js 파일 보기

1
-import {
2
-    CLOSE_PANEL,
3
-    SET_VISIBLE_PANEL,
4
-    TOGGLE_CHAT
5
-} from './actionTypes';
6
-
7
-/**
8
- * Dispatches an action to close the currently displayed side panel.
9
- *
10
- * @returns {Function}
11
- */
12
-export function closePanel() {
13
-    return (dispatch, getState) => {
14
-        dispatch({
15
-            type: CLOSE_PANEL,
16
-            current: getState()['features/side-panel'].current
17
-        });
18
-    };
19
-}
20
-
21
-/**
22
- * Updates the redux store with the currently displayed side panel.
23
- *
24
- * @param {string|null} name - The name of the side panel being displayed. Null
25
- * (or falsy) should be set if no side panel is being displayed.
26
- * @returns {{
27
- *     type: SET_VISIBLE_PANEL,
28
- *     current: string
29
- * }}
30
- */
31
-export function setVisiblePanel(name = null) {
32
-    return {
33
-        type: SET_VISIBLE_PANEL,
34
-        current: name
35
-    };
36
-}
37
-
38
-/**
39
- * Toggles display of the chat side panel.
40
- *
41
- * @returns {{
42
- *     type: TOGGLE_CHAT
43
- * }}
44
- */
45
-export function toggleChat() {
46
-    return {
47
-        type: TOGGLE_CHAT
48
-    };
49
-}

+ 0
- 62
react/features/side-panel/components/SidePanel.web.js 파일 보기

1
-import PropTypes from 'prop-types';
2
-import React, { Component } from 'react';
3
-import { connect } from 'react-redux';
4
-
5
-import { closePanel } from '../actions';
6
-
7
-/**
8
- * React Component for holding features in a side panel that slides in and out.
9
- *
10
- * @extends Component
11
- */
12
-class SidePanel extends Component {
13
-    /**
14
-     * {@code SidePanel} component's property types.
15
-     *
16
-     * @static
17
-     */
18
-    static propTypes = {
19
-        dispatch: PropTypes.func
20
-    };
21
-
22
-    /**
23
-     * Initializes a new {@code SidePanel} instance.
24
-     *
25
-     * @param {Object} props - The read-only properties with which the new
26
-     * instance is to be initialized.
27
-     */
28
-    constructor(props) {
29
-        super(props);
30
-
31
-        this._onCloseClick = this._onCloseClick.bind(this);
32
-    }
33
-
34
-    /**
35
-     * Implements React's {@link Component#render()}.
36
-     *
37
-     * @inheritdoc
38
-     * @returns {ReactElement}
39
-     */
40
-    render() {
41
-        return (
42
-            <div id = 'sideToolbarContainer'>
43
-                <div
44
-                    className = 'side-toolbar-close'
45
-                    onClick = { this._onCloseClick }>
46
-                    X
47
-                </div>
48
-            </div>
49
-        );
50
-    }
51
-
52
-    /**
53
-     * Callback invoked to hide {@code SidePanel}.
54
-     *
55
-     * @returns {void}
56
-     */
57
-    _onCloseClick() {
58
-        this.props.dispatch(closePanel());
59
-    }
60
-}
61
-
62
-export default connect()(SidePanel);

+ 0
- 1
react/features/side-panel/components/index.js 파일 보기

1
-export { default as SidePanel } from './SidePanel';

+ 0
- 6
react/features/side-panel/index.js 파일 보기

1
-export * from './actions';
2
-export * from './actionTypes';
3
-export * from './components';
4
-
5
-import './middleware';
6
-import './reducer';

+ 0
- 32
react/features/side-panel/middleware.js 파일 보기

1
-// @flow
2
-
3
-import { MiddlewareRegistry } from '../base/redux';
4
-
5
-import { CLOSE_PANEL, TOGGLE_CHAT } from './actionTypes';
6
-
7
-declare var APP: Object;
8
-
9
-/**
10
- * Middleware that catches actions related to the non-reactified web side panel.
11
- *
12
- * @param {Store} store - Redux store.
13
- * @returns {Function}
14
- */
15
-// eslint-disable-next-line no-unused-vars
16
-MiddlewareRegistry.register(store => next => action => {
17
-    if (typeof APP !== 'object') {
18
-        return next(action);
19
-    }
20
-
21
-    switch (action.type) {
22
-    case CLOSE_PANEL:
23
-        APP.UI.toggleSidePanel(action.current);
24
-        break;
25
-
26
-    case TOGGLE_CHAT:
27
-        APP.UI.toggleChat();
28
-        break;
29
-    }
30
-
31
-    return next(action);
32
-});

+ 0
- 18
react/features/side-panel/reducer.js 파일 보기

1
-import { ReducerRegistry } from '../base/redux';
2
-
3
-import { SET_VISIBLE_PANEL } from './actionTypes';
4
-
5
-/**
6
- * Reduces the Redux actions of the feature features/side-panel.
7
- */
8
-ReducerRegistry.register('features/side-panel', (state = {}, action) => {
9
-    switch (action.type) {
10
-    case SET_VISIBLE_PANEL:
11
-        return {
12
-            ...state,
13
-            current: action.current
14
-        };
15
-    }
16
-
17
-    return state;
18
-});

+ 1
- 3
react/features/toolbox/actions.web.js 파일 보기

1
 /* @flow */
1
 /* @flow */
2
 
2
 
3
-import SideContainerToggler
4
-    from '../../../modules/UI/side_pannels/SideContainerToggler';
5
 
3
 
6
 import {
4
 import {
7
     clearToolboxTimeout,
5
     clearToolboxTimeout,
90
         if (!force
88
         if (!force
91
                 && (hovered
89
                 && (hovered
92
                     || state['features/invite'].calleeInfoVisible
90
                     || state['features/invite'].calleeInfoVisible
93
-                    || SideContainerToggler.isVisible())) {
91
+                    || state['features/chat'].isOpen)) {
94
             dispatch(
92
             dispatch(
95
                 setToolboxTimeout(
93
                 setToolboxTimeout(
96
                     () => dispatch(hideToolbox()),
94
                     () => dispatch(hideToolbox()),

+ 2
- 4
react/features/toolbox/components/web/Toolbox.js 파일 보기

17
     participantUpdated
17
     participantUpdated
18
 } from '../../../base/participants';
18
 } from '../../../base/participants';
19
 import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
19
 import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
20
-import { ChatCounter } from '../../../chat';
20
+import { ChatCounter, toggleChat } from '../../../chat';
21
 import { toggleDocument } from '../../../etherpad';
21
 import { toggleDocument } from '../../../etherpad';
22
 import { openFeedbackDialog } from '../../../feedback';
22
 import { openFeedbackDialog } from '../../../feedback';
23
 import {
23
 import {
41
     openSettingsDialog
41
     openSettingsDialog
42
 } from '../../../settings';
42
 } from '../../../settings';
43
 import { toggleSharedVideo } from '../../../shared-video';
43
 import { toggleSharedVideo } from '../../../shared-video';
44
-import { toggleChat } from '../../../side-panel';
45
 import { SpeakerStats } from '../../../speaker-stats';
44
 import { SpeakerStats } from '../../../speaker-stats';
46
 import { TileViewButton } from '../../../video-layout';
45
 import { TileViewButton } from '../../../video-layout';
47
 import {
46
 import {
1032
         iAmRecorder
1031
         iAmRecorder
1033
     } = state['features/base/config'];
1032
     } = state['features/base/config'];
1034
     const sharedVideoStatus = state['features/shared-video'].status;
1033
     const sharedVideoStatus = state['features/shared-video'].status;
1035
-    const { current } = state['features/side-panel'];
1036
     const {
1034
     const {
1037
         alwaysVisible,
1035
         alwaysVisible,
1038
         fullScreen,
1036
         fullScreen,
1066
     }
1064
     }
1067
 
1065
 
1068
     return {
1066
     return {
1069
-        _chatOpen: current === 'chat_container',
1067
+        _chatOpen: state['features/chat'].isOpen,
1070
         _conference: conference,
1068
         _conference: conference,
1071
         _desktopSharingEnabled: desktopSharingEnabled,
1069
         _desktopSharingEnabled: desktopSharingEnabled,
1072
         _desktopSharingDisabledTooltipKey: desktopSharingDisabledTooltipKey,
1070
         _desktopSharingDisabledTooltipKey: desktopSharingDisabledTooltipKey,

+ 0
- 6
service/UI/UIEvents.js 파일 보기

2
     NICKNAME_CHANGED: 'UI.nickname_changed',
2
     NICKNAME_CHANGED: 'UI.nickname_changed',
3
     PINNED_ENDPOINT: 'UI.pinned_endpoint',
3
     PINNED_ENDPOINT: 'UI.pinned_endpoint',
4
 
4
 
5
-    /**
6
-     * Notifies that local user created text message.
7
-     */
8
-    MESSAGE_CREATED: 'UI.message_created',
9
-
10
     /**
5
     /**
11
      * Notifies that local user changed email.
6
      * Notifies that local user changed email.
12
      */
7
      */
34
      * Notifies that the audio only mode was toggled.
29
      * Notifies that the audio only mode was toggled.
35
      */
30
      */
36
     TOGGLE_AUDIO_ONLY: 'UI.toggle_audioonly',
31
     TOGGLE_AUDIO_ONLY: 'UI.toggle_audioonly',
37
-    TOGGLE_CHAT: 'UI.toggle_chat',
38
 
32
 
39
     /**
33
     /**
40
      * Notifies that a command to toggle the filmstrip has been issued. The
34
      * Notifies that a command to toggle the filmstrip has been issued. The

Loading…
취소
저장