1
1
/* @flow strict-local */
2
2
import { PixelRatio } from 'react-native' ;
3
+ import invariant from 'invariant' ;
3
4
import distanceInWordsToNow from 'date-fns/distance_in_words_to_now' ;
5
+ // $FlowFixMe[untyped-import]
6
+ import { PollData } from '@zulip/shared/js/poll_data' ;
7
+
4
8
import template from './template' ;
5
9
import type {
6
10
AggregatedReaction ,
@@ -10,14 +14,17 @@ import type {
10
14
MessageLike ,
11
15
Outbox ,
12
16
Reaction ,
17
+ SubmessageData ,
13
18
ImageEmojiType ,
14
19
UserId ,
20
+ WidgetData ,
15
21
} from '../../types' ;
16
22
import type { BackgroundData } from '../MessageList' ;
17
23
import { shortTime } from '../../utils/date' ;
18
24
import aggregateReactions from '../../reactions/aggregateReactions' ;
19
25
import { codeToEmojiMap } from '../../emoji/data' ;
20
26
import processAlertWords from './processAlertWords' ;
27
+ import * as logging from '../../utils/logging' ;
21
28
22
29
const messageTagsAsHtml = ( isStarred : boolean , timeEdited : number | void ) : string => {
23
30
const pieces = [ ] ;
@@ -72,14 +79,106 @@ $!${messageReactionListAsHtml(reactions, ownUser.user_id, allImageEmojiById)}
72
79
` ;
73
80
} ;
74
81
75
- const widgetBody = ( message : Message | Outbox ) => template `
82
+ /**
83
+ * Render the body of a message that has submessages.
84
+ *
85
+ * Must not be called on a message without any submessages.
86
+ */
87
+ const widgetBody = ( message : Message , ownUserId : UserId ) => {
88
+ invariant (
89
+ message . submessages !== undefined && message . submessages . length > 0 ,
90
+ 'should have submessages' ,
91
+ ) ;
92
+
93
+ const widgetSubmessages : Array < {
94
+ sender_id : number ,
95
+ content : SubmessageData ,
96
+ ...
97
+ } > = message . submessages
98
+ . filter ( submessage => submessage . msg_type === 'widget' )
99
+ . sort ( ( m1 , m2 ) => m1 . id - m2 . id )
100
+ . map ( submessage => ( {
101
+ sender_id : submessage . sender_id ,
102
+ content : JSON . parse ( submessage . content ) ,
103
+ } ) ) ;
104
+
105
+ const errorMessage = template `
76
106
$!${ message . content }
77
107
<div class="special-message"
78
108
><p>Interactive message</p
79
109
><p>To use, open on web or desktop</p
80
110
></div>
81
111
` ;
82
112
113
+ const pollWidget = widgetSubmessages . shift ( ) ;
114
+ if ( ! pollWidget || ! pollWidget . content ) {
115
+ return errorMessage ;
116
+ }
117
+
118
+ /* $FlowFixMe[incompatible-type]: The first widget submessage should be
119
+ a `WidgetData`; see jsdoc on `SubmessageData`. */
120
+ const pollWidgetContent : WidgetData = pollWidget . content ;
121
+
122
+ if ( pollWidgetContent . widget_type !== 'poll' ) {
123
+ return errorMessage ;
124
+ }
125
+
126
+ if ( pollWidgetContent . extra_data == null ) {
127
+ // We don't expect this to happen in general, but there are some malformed
128
+ // messages lying around that will trigger this [1]. The code here is slightly
129
+ // different the webapp code, but mostly because the current webapp
130
+ // behaviour seems accidental: an error is printed to the console, and the
131
+ // code that is written to handle the situation is never reached. Instead
132
+ // of doing that, we've opted to catch this case here, and print out the
133
+ // message (which matches the behaviour of the webapp, minus the console
134
+ // error, although it gets to that behaviour in a different way). The bug
135
+ // tracking fixing this on the webapp side is zulip/zulip#19145.
136
+ // [1]: https://chat.zulip.org/#narrow/streams/public/near/582872
137
+ return template `$ ! ${message . content } `;
138
+ }
139
+
140
+ const pollData = new PollData({
141
+ message_sender_id: message.sender_id,
142
+ current_user_id: ownUserId,
143
+ is_my_poll: message.sender_id === ownUserId,
144
+ question: pollWidgetContent.extra_data.question ?? '',
145
+ options: pollWidgetContent.extra_data.options ?? [],
146
+ // TODO: Implement this.
147
+ comma_separated_names: () => '',
148
+ report_error_function: (msg: string) => {
149
+ logging.error(msg);
150
+ },
151
+ });
152
+
153
+ for (const pollEvent of widgetSubmessages) {
154
+ pollData.handle_event(pollEvent.sender_id, pollEvent.content);
155
+ }
156
+
157
+ const parsedPollData = pollData.get_widget_data();
158
+
159
+ return template`
160
+ < div class = "poll-widget" >
161
+ < p class = "poll-question" > ${ parsedPollData . question } </ p >
162
+ < ul >
163
+ $ ! ${parsedPollData . options
164
+ . map (
165
+ option =>
166
+ template `
167
+ <li>
168
+ <button
169
+ class="poll-vote"
170
+ data-voted="${ option . current_user_vote } "
171
+ data-key="${ option . key } "
172
+ >${ option . count } </button>
173
+ <span class="poll-option">${ option . option } </span>
174
+ </li>` ,
175
+ )
176
+ . join ( '' ) }
177
+ </ul >
178
+ < / div >
179
+ `;
180
+ } ;
181
+
83
182
export const flagsStateToStringList = ( flags : FlagsState , id : number ) : string [ ] =>
84
183
Object . keys ( flags ) . filter ( key => flags [ key ] [ id ] ) ;
85
184
@@ -112,7 +211,7 @@ export default (
112
211
` ;
113
212
const bodyHtml =
114
213
message . submessages && message . submessages . length > 0
115
- ? widgetBody ( message )
214
+ ? widgetBody ( message , backgroundData . ownUser . user_id )
116
215
: messageBody ( backgroundData , message ) ;
117
216
118
217
if ( isBrief ) {
0 commit comments