This repository has been archived by the owner on Jun 12, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 41
/
message-list.coffee
630 lines (566 loc) · 25.1 KB
/
message-list.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
{div, ul, li, a, span, i, p, button, input, img} = React.DOM
classer = React.addons.classSet
RouterMixin = require '../mixins/router_mixin'
MessageUtils = require '../utils/message_utils'
SocketUtils = require '../utils/socketio_utils'
{MessageFlags, MessageFilter, FlagsConstants} = require '../constants/app_constants'
LayoutActionCreator = require '../actions/layout_action_creator'
ContactActionCreator = require '../actions/contact_action_creator'
ConversationActionCreator = require '../actions/conversation_action_creator'
MessageActionCreator = require '../actions/message_action_creator'
MessageStore = require '../stores/message_store'
MailboxList = require './mailbox-list'
Participants = require './participant'
ToolboxActions = require './toolbox_actions'
ToolboxMove = require './toolbox_move'
alertError = LayoutActionCreator.alertError
alertSuccess = LayoutActionCreator.alertSuccess
MessageList = React.createClass
displayName: 'MessageList'
mixins: [RouterMixin]
shouldComponentUpdate: (nextProps, nextState) ->
return not(_.isEqual(nextState, @state)) or not (_.isEqual(nextProps, @props))
getInitialState: ->
return {
edited: false
filterFlag: false
filterUnsead: false
selected: {}
allSelected: false
}
componentWillReceiveProps: (props) ->
if props.mailboxID isnt @props.mailboxID
@setState allSelected: false, edited: false, selected: {}
else
selected = @state.selected
Object.keys(selected).forEach (id) ->
if not props.messages.get(id)
delete selected[id]
@setState selected: selected
if Object.keys(selected).length is 0
@setState allSelected: false, edited: false
render: ->
compact = @props.settings.get('listStyle') is 'compact'
messages = @props.messages.map (message, key) =>
id = message.get('id')
isActive = @props.messageID is id
MessageItem
message: message,
key: key,
isActive: isActive,
edited: @state.edited,
settings: @props.settings,
selected: @state.selected[id]?,
onSelect: (val) =>
selected = _.clone @state.selected
if val
selected[id] = val
else
delete selected[id]
if Object.keys(selected).length > 0
@setState edited: true, selected: selected
else
@setState allSelected: false, edited: false, selected: {}
.toJS()
nbMessages = parseInt @props.counterMessage, 10
filterParams =
accountID: @props.accountID
mailboxID: @props.mailboxID
query: @props.query
nextPage = =>
LayoutActionCreator.showMessageList parameters: @props.query
getMailboxUrl = (mailbox) =>
@buildUrl
direction: 'first'
action: 'account.mailbox.messages'
parameters: [@props.accountID, mailbox.get('id')]
configMailboxUrl = @buildUrl
direction: 'first'
action: 'account.config'
parameters: [@props.accountID, 'account']
fullWidth: true
advanced = @props.settings.get('advanced')
nbSelected = if Object.keys(@state.selected).length > 0 then null else true
toggleFilterFlag = =>
filter = if @state.filterFlag then MessageFilter.ALL else MessageFilter.FLAGGED
LayoutActionCreator.filterMessages filter
params = MessageStore.getParams()
params.accountID = @props.accountID
params.mailboxID = @props.mailboxID
LayoutActionCreator.showMessageList parameters: params
@setState filterFlag: not @state.filterFlag, filterUnseen: false
toggleFilterUnseen = =>
filter = if @state.filterUnseen then MessageFilter.ALL else MessageFilter.UNSEEN
LayoutActionCreator.filterMessages filter
params = MessageStore.getParams()
params.accountID = @props.accountID
params.mailboxID = @props.mailboxID
LayoutActionCreator.showMessageList parameters: params
@setState filterUnseen: not @state.filterUnseen, filterFlag: false
classList = classer
compact: compact
edited: @state.edited
classCompact = classer
active: compact
classEdited = classer
active: @state.edited
div className: 'message-list ' + classList, ref: 'list',
div className: 'message-list-actions',
#MessagesQuickFilter {}
div className: 'btn-toolbar', role: 'toolbar',
div className: 'btn-group',
# Toggle edit
if advanced
div className: 'btn-group btn-group-sm message-list-option',
button
type: "button"
className: "btn btn-default " + classEdited
onClick: @toggleEdited,
i className: 'fa fa-square-o'
# mailbox-list
if advanced and not @state.edited
div className: 'btn-group btn-group-sm message-list-option',
MailboxList
getUrl: getMailboxUrl
mailboxes: @props.mailboxes
selectedMailbox: @props.mailboxID
# filters
if not advanced and not @state.edited
div className: 'btn-group btn-group-sm message-list-option ',
button
onClick: toggleFilterUnseen
title: t 'list filter unseen title'
className: 'btn btn-default ' + if @state.filterUnseen then ' shown ' else '',
span className: 'fa fa-envelope'
if not advanced and not @state.edited
div className: 'btn-group btn-group-sm message-list-option ',
button
onClick: toggleFilterFlag
title: t 'list filter flagged title'
className: 'btn btn-default ' + if @state.filterFlag then ' shown ' else '',
span className: 'fa fa-flag'
if advanced and not @state.edited
div className: 'btn-group btn-group-sm message-list-option',
MessagesFilter filterParams
## sort
if advanced and not @state.edited
div className: 'btn-group btn-group-sm message-list-option',
MessagesSort filterParams
# refresh
if not @state.edited
div className: 'btn-group btn-group-sm message-list-option',
button
className: 'btn btn-default trash',
type: 'button',
disabled: null,
onClick: @refresh,
span
className: 'fa fa-refresh'
# config
if not @state.edited
div className: 'btn-group btn-group-sm message-list-option',
a
href: configMailboxUrl
className: 'btn btn-default',
i className: 'fa fa-cog'
if @state.edited
div className: 'btn-group btn-group-sm message-list-option',
button
type: "button"
className: "btn btn-default " + classEdited
onClick: @toggleAll,
i className: 'fa fa-square-o'
if @state.edited
div className: 'btn-group btn-group-sm message-list-option',
button
className: 'btn btn-default trash',
type: 'button',
disabled: nbSelected
onClick: @onDelete,
span
className: 'fa fa-trash-o'
if @state.edited
ToolboxMove
mailboxes: @props.mailboxes
onMove: @onMove
direction: 'left'
if @state.edited
ToolboxActions
mailboxes: @props.mailboxes
onMark: @onMark
onConversation: @onConversation
onHeaders: @onHeaders
direction: 'left'
if @props.messages.count() is 0
if @props.fetching
p null, t 'list fetching'
else
p null, @props.emptyListMessage
else
div null,
#p null, @props.counterMessage
ul className: 'list-unstyled',
messages
if @props.messages.count() < nbMessages
p className: 'text-center',
if @props.fetching
i className: "fa fa-refresh fa-spin"
else
a
#href: @props.paginationUrl
className: 'more-messages'
onClick: nextPage,
ref: 'nextPage',
t 'list next page'
else
p ref: 'listEnd', t 'list end'
refresh: (event) ->
event.preventDefault()
LayoutActionCreator.refreshMessages()
toggleEdited: ->
if @state.edited
@setState allSelected: false, edited: false, selected: {}
else
@setState edited: true
toggleAll: ->
if @state.allSelected
@setState allSelected: false, edited: false, selected: {}
else
selected = {}
@props.messages.map (message, key) ->
selected[key] = true
.toJS()
@setState allSelected: true, edited: true, selected: selected
onDelete: ->
selected = Object.keys @state.selected
if selected.length is 0
alertError t 'list mass no message'
else
if window.confirm(t 'list delete confirm', nb: selected.length)
selected.forEach (id) ->
MessageActionCreator.delete id, (error) ->
if error?
alertError "#{t("message action delete ko")} #{error}"
else
window.cozyMails.messageNavigate()
onMove: (args) ->
selected = Object.keys @state.selected
if selected.length is 0
alertError t 'list mass no message'
else
newbox = args.target.dataset.value
if args.target.dataset.conversation?
selected.forEach (id) =>
message = @props.messages.get id
conversationID = message.get('conversationID')
ConversationActionCreator.move conversationID, newbox, (error) ->
if error?
alertError "#{t("conversation move ko")} #{error}"
else
window.cozyMails.messageNavigate()
else
selected.forEach (id) =>
message = @props.messages.get id
MessageActionCreator.move message, @props.mailboxID, newbox, (error) ->
if error?
alertError "#{t("message action move ko")} #{error}"
else
window.cozyMails.messageNavigate()
onMark: (args) ->
selected = Object.keys @state.selected
if selected.length is 0
alertError t 'list mass no message'
else
flag = args.target.dataset.value
selected.forEach (id) =>
message = @props.messages.get id
flags = message.get('flags').slice()
switch flag
when FlagsConstants.SEEN
flags.push MessageFlags.SEEN
when FlagsConstants.UNSEEN
flags = flags.filter (e) -> return e isnt FlagsConstants.SEEN
when FlagsConstants.FLAGGED
flags.push MessageFlags.FLAGGED
when FlagsConstants.NOFLAG
flags = flags.filter (e) -> return e isnt FlagsConstants.FLAGGED
MessageActionCreator.updateFlag message, flags, (error) ->
if error?
alertError "#{t("message action mark ko")} #{error}"
onConversation: (args) ->
selected = Object.keys @state.selected
if selected.length is 0
alertError t 'list mass no message'
else
selected.forEach (id) =>
message = @props.messages.get id
conversationID = message.get 'conversationID'
action = args.target.dataset.action
switch action
when 'delete'
ConversationActionCreator.delete conversationID, (error) ->
if error?
alertError "#{t("conversation delete ko")} #{error}"
when 'seen'
ConversationActionCreator.seen conversationID, (error) ->
if error?
alertError "#{t("conversation seen ok ")} #{error}"
when 'unseen'
ConversationActionCreator.unseen conversationID, (error) ->
if error?
alertError "#{t("conversation unseen ok")} #{error}"
_isVisible: (node, before) ->
margin = if before then 40 else 0
rect = node.getBoundingClientRect()
height = window.innerHeight or document.documentElement.clientHeight
width = window.innerWidth or document.documentElement.clientWidth
return rect.bottom <= ( height + 0 ) and rect.top >= 0
_loadNext: ->
if @refs.nextPage? and @_isVisible(@refs.nextPage.getDOMNode(), true)
LayoutActionCreator.showMessageList parameters: @props.query
_handleRealtimeGrowth: ->
nbMessages = parseInt @props.counterMessage, 10
if nbMessages < @props.messages.count() and @refs.listEnd? and
not @_isVisible(@refs.listEnd.getDOMNode(), true)
lastdate = @props.messages.last().get('date')
SocketUtils.changeRealtimeScope @props.mailboxID, lastdate
_initScroll: ->
if not @refs.nextPage?
return
# scroll current message into view
if @state.messageID isnt @props.messageID
active = document.querySelector("[data-message-id='#{@props.messageID}']")
if active? and not @_isVisible(active)
active.scrollIntoView()
@setState messageID: @props.messageID
# listen to scroll events
scrollable = @refs.list.getDOMNode().parentNode
setTimeout =>
scrollable.removeEventListener 'scroll', @_loadNext
scrollable.addEventListener 'scroll', @_loadNext
, 0
componentDidMount: ->
@_initScroll()
componentDidUpdate: ->
@_initScroll()
@_handleRealtimeGrowth()
componentWillUnmount: ->
scrollable = @refs.list.getDOMNode().parentNode
scrollable.removeEventListener 'scroll', @_loadNext
module.exports = MessageList
MessageItem = React.createClass
displayName: 'MessagesItem'
mixins: [RouterMixin]
render: ->
message = @props.message
flags = message.get('flags')
classes = classer
message: true
read: message.get 'isRead'
active: @props.isActive
edited: @props.edited
'unseen': flags.indexOf(MessageFlags.SEEN) is -1
'has-attachments': message.get 'hasAttachments'
'is-fav': flags.indexOf(MessageFlags.FLAGGED) isnt -1
isDraft = message.get('flags').indexOf(MessageFlags.DRAFT) isnt -1
if isDraft
action = 'edit'
id = message.get 'id'
else
conversationID = message.get 'conversationID'
if conversationID and @props.settings.get('displayConversation')
action = 'conversation'
id = message.get 'id'
else
action = 'message'
id = message.get 'id'
if not @props.edited
url = @buildUrl
direction: 'second'
action: action
parameters: id
tag = a
else
tag = span
compact = @props.settings.get('listStyle') is 'compact'
date = MessageUtils.formatDate message.get('createdAt'), compact
avatar = MessageUtils.getAvatar message
li
className: classes
key: @props.key
'data-message-id': message.get('id')
draggable: not @props.edited
onClick: @onMessageClick
onDragStart: @onDragStart
,
tag
href: url,
className: 'wrapper'
'data-message-id': message.get('id'),
onClick: @onMessageClick,
onDoubleClick: @onMessageDblClick,
div
className: 'avatar-wrapper',
input
ref: 'select'
className: 'select',
type: 'checkbox',
checked: @props.selected,
onChange: @onSelect
if avatar?
img className: 'avatar', src: avatar
else
i className: 'fa fa-user'
span className: 'participants', @getParticipants message
div className: 'preview',
span className: 'title', message.get 'subject'
p null, message.get('text')?.substr(0, 100) + "…"
span className: 'hour', date
span className: "flags",
i className: 'attach fa fa-paperclip'
i className: 'fav fa fa-star'
_doCheck: ->
# please don't ask me why this **** react needs this
if @props.selected
setTimeout =>
@refs.select.getDOMNode().checked = true
, 50
else
setTimeout =>
@refs.select.getDOMNode().checked = false
, 50
componentDidMount: ->
@_doCheck()
componentDidUpdate: ->
@_doCheck()
onSelect: (e) ->
@props.onSelect(not @props.selected)
e.preventDefault()
e.stopPropagation()
onMessageClick: (event) ->
if @props.edited
@props.onSelect(not @props.selected)
event.preventDefault()
event.stopPropagation()
else
if not @props.settings.get('displayPreview')
event.preventDefault()
MessageActionCreator.setCurrent event.currentTarget.dataset.messageId
onMessageDblClick: (event) ->
if not @props.edited
url = event.currentTarget.href.split('#')[1]
window.router.navigate url, {trigger: true}
onDragStart: (event) ->
event.stopPropagation()
data =
messageID: event.currentTarget.dataset.messageId
mailboxID: @props.mailboxID
event.dataTransfer.setData 'text', JSON.stringify(data)
event.dataTransfer.effectAllowed = 'move'
event.dataTransfer.dropEffect = 'move'
getParticipants: (message) ->
from = message.get 'from'
to = message.get('to').concat(message.get('cc'))
span null,
Participants participants: from, onAdd: @addAddress
span null, ', '
Participants participants: to, onAdd: @addAddress
addAddress: (address) ->
ContactActionCreator.createContact address
MessagesQuickFilter = React.createClass
displayName: 'MessagesQuickFilter'
render: ->
div
className: "form-group message-list-action",
input
className: "form-control"
type: "text"
onBlur: @onQuick
onQuick: (ev) ->
LayoutActionCreator.quickFilterMessages ev.target.value.trim()
MessagesFilter = React.createClass
displayName: 'MessagesFilter'
mixins: [RouterMixin]
render: ->
filter = @props.query.flag
if not filter? or filter is '-'
title = i className: 'fa fa-filter'
else
title = t 'list filter ' + filter
div className: 'btn-group btn-group-sm dropdown filter-dropdown',
button
className: 'btn btn-default dropdown-toggle message-list-action'
type: 'button'
'data-toggle': 'dropdown'
title
span className: 'caret'
ul
className: 'dropdown-menu',
role: 'menu',
li role: 'presentation',
a
onClick: @onFilter,
'data-filter': MessageFilter.ALL,
t 'list filter all'
li role: 'presentation',
a
onClick: @onFilter,
'data-filter': MessageFilter.UNSEEN,
t 'list filter unseen'
li role: 'presentation',
a
onClick: @onFilter,
'data-filter': MessageFilter.FLAGGED,
t 'list filter flagged'
onFilter: (ev) ->
LayoutActionCreator.filterMessages ev.target.dataset.filter
params = MessageStore.getParams()
params.accountID = @props.accountID
params.mailboxID = @props.mailboxID
LayoutActionCreator.showMessageList parameters: params
#@redirect @buildUrl
# direction: 'first'
# action: 'account.mailbox.messages.full'
# parameters: params
MessagesSort = React.createClass
displayName: 'MessagesSort'
mixins: [RouterMixin]
render: ->
sort = @props.query.sort
if not sort? or sort is '-'
title = t 'list sort'
else
sort = sort.substr 1
title = t 'list sort ' + sort
div className: 'btn-group btn-group-sm dropdown sort-dropdown',
button
className: 'btn btn-default dropdown-toggle message-list-action'
type: 'button'
'data-toggle': 'dropdown'
title
span className: 'caret'
ul
className: 'dropdown-menu',
role: 'menu',
li role: 'presentation',
a
onClick: @onSort,
'data-sort': 'date',
t 'list sort date'
li role: 'presentation',
a
onClick: @onSort,
'data-sort': 'subject',
t 'list sort subject'
onSort: (ev) ->
field = ev.target.dataset.sort
LayoutActionCreator.sortMessages
field: field
params = MessageStore.getParams()
params.accountID = @props.accountID
params.mailboxID = @props.mailboxID
LayoutActionCreator.showMessageList parameters: params
#@redirect @buildUrl
# direction: 'first'
# action: 'account.mailbox.messages.full'
# parameters: params