Completed
Push — master ( e88c19...17669b )
by Rain
03:00
created

dev/View/User/MailBox/MessageView.js   F

Complexity

Total Complexity 261
Complexity/F 2.42

Size

Lines of Code 1220
Function Count 108

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 0
c 2
b 0
f 0
nc 0
dl 0
loc 1220
rs 2.4
wmc 261
mnd 4
bc 194
fnc 108
bpm 1.7962
cpm 2.4166
noi 0

How to fix   Complexity   

Complexity

Complex classes like dev/View/User/MailBox/MessageView.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
2
import window from 'window';
3
import _ from '_';
4
import $ from '$';
5
import ko from 'ko';
6
import key from 'key';
7
8
import {DATA_IMAGE_USER_DOT_PIC, UNUSED_OPTION_VALUE} from 'Common/Consts';
9
10
import {
11
	Capa, ComposeType, ClientSideKeyName, KeyState,
12
	FolderType, Focused, Layout, Magics, MessageSetAction
13
} from 'Common/Enums';
14
15
import {
16
	$html,
17
	leftPanelDisabled,
18
	keyScopeReal,
19
	useKeyboardShortcuts
20
} from 'Common/Globals';
21
22
import {
23
	createCommand, inArray, trim, noop,
24
	isNonEmptyArray, windowResize, windowResizeCallback,
25
	removeSelection, removeInFocus, mailToHelper,
26
	inFocus, isArray
27
} from 'Common/Utils';
28
29
import Audio from 'Common/Audio';
30
import * as Events from 'Common/Events';
31
32
import {i18n} from 'Common/Translator';
33
import {attachmentDownload} from 'Common/Links';
34
35
import {
36
	getUserPic,
37
	storeMessageFlagsToCache
38
} from 'Common/Cache';
39
40
import SocialStore from 'Stores/Social';
41
import AppStore from 'Stores/User/App';
42
import SettingsStore from 'Stores/User/Settings';
43
import AccountStore from 'Stores/User/Account';
44
import FolderStore from 'Stores/User/Folder';
45
import MessageStore from 'Stores/User/Message';
46
47
import * as Local from 'Storage/Client';
48
import * as Settings from 'Storage/Settings';
49
50
import Remote from 'Remote/User/Ajax';
51
import Promises from 'Promises/User/Ajax';
52
53
import {getApp} from 'Helper/Apps/User';
54
55
import {view, ViewType, showScreenPopup} from 'Knoin/Knoin';
56
import {AbstractViewNext} from 'Knoin/AbstractViewNext';
57
58
@view({
59
	name: 'View/User/MailBox/MessageView',
60
	type: ViewType.Right,
61
	templateID: 'MailMessageView'
62
})
63
class MessageViewMailBoxUserView extends AbstractViewNext
64
{
65
	constructor() {
66
		super();
67
68
		let lastEmail = '';
69
		const createCommandHelper = (type) => createCommand(() => {
70
			this.lastReplyAction(type);
71
			this.replyOrforward(type);
72
		}, this.canBeRepliedOrForwarded);
73
74
		this.oDom = null;
75
		this.oHeaderDom = null;
76
		this.oMessageScrollerDom = null;
77
78
		this.bodyBackgroundColor = ko.observable('');
79
80
		this.pswp = null;
81
82
		this.allowComposer = !!Settings.capa(Capa.Composer);
83
		this.allowMessageActions = !!Settings.capa(Capa.MessageActions);
84
		this.allowMessageListActions = !!Settings.capa(Capa.MessageListActions);
85
86
		this.logoImg = trim(Settings.settingsGet('UserLogoMessage'));
87
		this.logoIframe = trim(Settings.settingsGet('UserIframeMessage'));
88
89
		this.mobile = !!Settings.appSettingsGet('mobile');
90
91
		this.attachmentsActions = AppStore.attachmentsActions;
92
93
		this.message = MessageStore.message;
94
		this.messageListChecked = MessageStore.messageListChecked;
95
		this.hasCheckedMessages = MessageStore.hasCheckedMessages;
96
		this.messageListCheckedOrSelectedUidsWithSubMails = MessageStore.messageListCheckedOrSelectedUidsWithSubMails;
97
		this.messageLoadingThrottle = MessageStore.messageLoadingThrottle;
98
		this.messagesBodiesDom = MessageStore.messagesBodiesDom;
99
		this.useThreads = SettingsStore.useThreads;
100
		this.replySameFolder = SettingsStore.replySameFolder;
101
		this.layout = SettingsStore.layout;
102
		this.usePreviewPane = SettingsStore.usePreviewPane;
103
		this.isMessageSelected = MessageStore.isMessageSelected;
104
		this.messageActiveDom = MessageStore.messageActiveDom;
105
		this.messageError = MessageStore.messageError;
106
107
		this.fullScreenMode = MessageStore.messageFullScreenMode;
108
109
		this.messageListOfThreadsLoading = ko.observable(false).extend({rateLimit: 1});
110
		this.highlightUnselectedAttachments = ko.observable(false).extend({falseTimeout: 2000});
111
112
		this.showAttachmnetControls = ko.observable(false);
113
114
		this.allowAttachmnetControls = ko.computed(
115
			() => 0 < this.attachmentsActions().length && Settings.capa(Capa.AttachmentsActions)
116
		);
117
118
		this.downloadAsZipAllowed = ko.computed(
119
			() => -1 < inArray('zip', this.attachmentsActions()) && this.allowAttachmnetControls()
120
		);
121
122
		this.downloadAsZipLoading = ko.observable(false);
123
		this.downloadAsZipError = ko.observable(false).extend({falseTimeout: 7000});
124
125
		this.saveToOwnCloudAllowed = ko.computed(
126
			() => -1 < inArray('owncloud', this.attachmentsActions()) && this.allowAttachmnetControls()
127
		);
128
129
		this.saveToOwnCloudLoading = ko.observable(false);
130
		this.saveToOwnCloudSuccess = ko.observable(false).extend({falseTimeout: 2000});
131
		this.saveToOwnCloudError = ko.observable(false).extend({falseTimeout: 7000});
132
133
		this.saveToOwnCloudSuccess.subscribe((v) => {
134
			if (v)
135
			{
136
				this.saveToOwnCloudError(false);
137
			}
138
		});
139
140
		this.saveToOwnCloudError.subscribe((v) => {
141
			if (v)
142
			{
143
				this.saveToOwnCloudSuccess(false);
144
			}
145
		});
146
147
		this.saveToDropboxAllowed = ko.computed(
148
			() => -1 < inArray('dropbox', this.attachmentsActions()) && this.allowAttachmnetControls()
149
		);
150
151
		this.saveToDropboxLoading = ko.observable(false);
152
		this.saveToDropboxSuccess = ko.observable(false).extend({falseTimeout: 2000});
153
		this.saveToDropboxError = ko.observable(false).extend({falseTimeout: 7000});
154
155
		this.saveToDropboxSuccess.subscribe((v) => {
156
			if (v)
157
			{
158
				this.saveToDropboxError(false);
159
			}
160
		});
161
162
		this.saveToDropboxError.subscribe((v) => {
163
			if (v)
164
			{
165
				this.saveToDropboxSuccess(false);
166
			}
167
		});
168
169
		this.showAttachmnetControls.subscribe((v) => {
170
			if (this.message())
171
			{
172
				_.each(this.message().attachments(), (item) => {
173
					if (item)
174
					{
175
						item.checked(!!v);
176
					}
177
				});
178
			}
179
		});
180
181
		this.lastReplyAction_ = ko.observable('');
182
		this.lastReplyAction = ko.computed({
183
			read: this.lastReplyAction_,
184
			write: (value) => {
185
				this.lastReplyAction_(-1 === inArray(value, [
186
					ComposeType.Reply, ComposeType.ReplyAll, ComposeType.Forward
187
				]) ? ComposeType.Reply : value);
188
			}
189
		});
190
191
		this.lastReplyAction(Local.get(ClientSideKeyName.LastReplyAction) || ComposeType.Reply);
192
193
		this.lastReplyAction_.subscribe((value) => {
194
			Local.set(ClientSideKeyName.LastReplyAction, value);
195
		});
196
197
		this.showFullInfo = ko.observable('1' === Local.get(ClientSideKeyName.MessageHeaderFullInfo));
198
199
		this.moreDropdownTrigger = ko.observable(false);
200
		this.messageDomFocused = ko.observable(false).extend({rateLimit: 0});
201
202
		this.messageVisibility = ko.computed(() => !this.messageLoadingThrottle() && !!this.message());
203
204
		this.message.subscribe((message) => {
205
			if (!message)
206
			{
207
				MessageStore.selectorMessageSelected(null);
208
			}
209
		});
210
211
		this.canBeRepliedOrForwarded = ko.computed(() => {
212
			const v = this.messageVisibility();
213
			return !this.isDraftFolder() && v;
214
		});
215
216
		// commands
217
		this.closeMessage = createCommand(() => {
218
			MessageStore.message(null);
219
		});
220
221
		this.replyCommand = createCommandHelper(ComposeType.Reply);
222
		this.replyAllCommand = createCommandHelper(ComposeType.ReplyAll);
223
		this.forwardCommand = createCommandHelper(ComposeType.Forward);
224
		this.forwardAsAttachmentCommand = createCommandHelper(ComposeType.ForwardAsAttachment);
225
		this.editAsNewCommand = createCommandHelper(ComposeType.EditAsNew);
226
227
		this.messageVisibilityCommand = createCommand(noop, this.messageVisibility);
228
229
		this.messageEditCommand = createCommand(() => {
230
			this.editMessage();
231
		}, this.messageVisibility);
232
233
		this.deleteCommand = createCommand(() => {
234
			const message = this.message();
235
			if (message && this.allowMessageListActions)
236
			{
237
				this.message(null);
238
				getApp().deleteMessagesFromFolder(FolderType.Trash,
239
					message.folderFullNameRaw, [message.uid], true);
240
			}
241
		}, this.messageVisibility);
242
243
		this.deleteWithoutMoveCommand = createCommand(() => {
244
			const message = this.message();
245
			if (message && this.allowMessageListActions)
246
			{
247
				this.message(null);
248
				getApp().deleteMessagesFromFolder(FolderType.Trash,
249
					message.folderFullNameRaw, [message.uid], false);
250
			}
251
		}, this.messageVisibility);
252
253
		this.archiveCommand = createCommand(() => {
254
			const message = this.message();
255
			if (message && this.allowMessageListActions)
256
			{
257
				this.message(null);
258
				getApp().deleteMessagesFromFolder(FolderType.Archive,
259
					message.folderFullNameRaw, [message.uid], true);
260
			}
261
		}, this.messageVisibility);
262
263
		this.spamCommand = createCommand(() => {
264
			const message = this.message();
265
			if (message && this.allowMessageListActions)
266
			{
267
				this.message(null);
268
				getApp().deleteMessagesFromFolder(FolderType.Spam,
269
					message.folderFullNameRaw, [message.uid], true);
270
			}
271
		}, this.messageVisibility);
272
273
		this.notSpamCommand = createCommand(() => {
274
			const message = this.message();
275
			if (message && this.allowMessageListActions)
276
			{
277
				this.message(null);
278
				getApp().deleteMessagesFromFolder(FolderType.NotSpam,
279
					message.folderFullNameRaw, [message.uid], true);
280
			}
281
		}, this.messageVisibility);
282
283
		this.dropboxEnabled = SocialStore.dropbox.enabled;
284
		this.dropboxApiKey = SocialStore.dropbox.apiKey;
285
286
		// viewer
287
288
		this.viewBodyTopValue = ko.observable(0);
289
290
		this.viewFolder = '';
291
		this.viewUid = '';
292
		this.viewHash = '';
293
		this.viewSubject = ko.observable('');
294
		this.viewFromShort = ko.observable('');
295
		this.viewFromDkimData = ko.observable(['none', '']);
296
		this.viewToShort = ko.observable('');
297
		this.viewFrom = ko.observable('');
298
		this.viewTo = ko.observable('');
299
		this.viewCc = ko.observable('');
300
		this.viewBcc = ko.observable('');
301
		this.viewReplyTo = ko.observable('');
302
		this.viewTimeStamp = ko.observable(0);
303
		this.viewSize = ko.observable('');
304
		this.viewLineAsCss = ko.observable('');
305
		this.viewViewLink = ko.observable('');
306
		this.viewUnsubscribeLink = ko.observable('');
307
		this.viewDownloadLink = ko.observable('');
308
		this.viewUserPic = ko.observable(DATA_IMAGE_USER_DOT_PIC);
309
		this.viewUserPicVisible = ko.observable(false);
310
		this.viewIsImportant = ko.observable(false);
311
		this.viewIsFlagged = ko.observable(false);
312
313
		this.viewFromDkimVisibility = ko.computed(() => 'none' !== this.viewFromDkimData()[0]);
314
315
		this.viewFromDkimStatusIconClass = ko.computed(() => {
316
			switch (this.viewFromDkimData()[0])
317
			{
318
				case 'none':
319
					return 'icon-none iconcolor-display-none';
320
				case 'pass':
321
					return 'icon-ok iconcolor-green';
322
				default:
323
					return 'icon-warning-alt iconcolor-red';
324
			}
325
		});
326
327
		this.viewFromDkimStatusTitle = ko.computed(() => {
328
329
			const status = this.viewFromDkimData();
330
			if (isNonEmptyArray(status))
331
			{
332
				if (status[0] && status[1])
333
				{
334
					return status[1];
335
				}
336
				else if (status[0])
337
				{
338
					return 'DKIM: ' + status[0];
339
				}
340
			}
341
342
			return '';
343
		});
344
345
		this.messageActiveDom.subscribe((dom) => {
346
			this.bodyBackgroundColor(dom ? this.detectDomBackgroundColor(dom) : '');
347
		}, this);
348
349
		this.message.subscribe((message) => {
350
351
			this.messageActiveDom(null);
352
353
			if (message)
354
			{
355
				this.showAttachmnetControls(false);
356
357
				if (this.viewHash !== message.hash)
358
				{
359
					this.scrollMessageToTop();
360
				}
361
362
				this.viewFolder = message.folderFullNameRaw;
363
				this.viewUid = message.uid;
364
				this.viewHash = message.hash;
365
				this.viewSubject(message.subject());
366
				this.viewFromShort(message.fromToLine(true, true));
367
				this.viewFromDkimData(message.fromDkimData());
368
				this.viewToShort(message.toToLine(true, true));
369
				this.viewFrom(message.fromToLine(false));
370
				this.viewTo(message.toToLine(false));
371
				this.viewCc(message.ccToLine(false));
372
				this.viewBcc(message.bccToLine(false));
373
				this.viewReplyTo(message.replyToToLine(false));
374
				this.viewTimeStamp(message.dateTimeStampInUTC());
375
				this.viewSize(message.friendlySize());
376
				this.viewLineAsCss(message.lineAsCss());
377
				this.viewViewLink(message.viewLink());
378
				this.viewUnsubscribeLink(message.getFirstUnsubsribeLink());
379
				this.viewDownloadLink(message.downloadLink());
380
				this.viewIsImportant(message.isImportant());
381
				this.viewIsFlagged(message.flagged());
382
383
				lastEmail = message.fromAsSingleEmail();
384
				getUserPic(lastEmail, (pic, email) => {
385
					if (pic !== this.viewUserPic() && lastEmail === email)
386
					{
387
						this.viewUserPicVisible(false);
388
						this.viewUserPic(DATA_IMAGE_USER_DOT_PIC);
389
						if ('' !== pic)
390
						{
391
							this.viewUserPicVisible(true);
392
							this.viewUserPic(pic);
393
						}
394
					}
395
				});
396
			}
397
			else
398
			{
399
				this.viewFolder = '';
400
				this.viewUid = '';
401
				this.viewHash = '';
402
403
				this.scrollMessageToTop();
404
			}
405
406
		});
407
408
		this.message.viewTrigger.subscribe(() => {
409
			const message = this.message();
410
			if (message)
411
			{
412
				this.viewIsFlagged(message.flagged());
413
			}
414
			else
415
			{
416
				this.viewIsFlagged(false);
417
			}
418
		});
419
420
		this.fullScreenMode.subscribe((value) => {
421
			$html.toggleClass('rl-message-fullscreen', value);
422
			windowResize();
423
		});
424
425
		this.messageLoadingThrottle.subscribe(windowResizeCallback);
426
427
		this.messageFocused = ko.computed(() => Focused.MessageView === AppStore.focusedState());
428
429
		this.messageListAndMessageViewLoading = ko.computed(
430
			() => MessageStore.messageListCompleteLoadingThrottle() || MessageStore.messageLoadingThrottle()
431
		);
432
433
		this.goUpCommand = createCommand(() => {
434
			Events.pub('mailbox.message-list.selector.go-up', [
435
				Layout.NoPreview === this.layout() ? !!this.message() : true
436
			]);
437
		}, () => !this.messageListAndMessageViewLoading());
438
439
		this.goDownCommand = createCommand(() => {
440
			Events.pub('mailbox.message-list.selector.go-down', [
441
				Layout.NoPreview === this.layout() ? !!this.message() : true
442
			]);
443
		}, () => !this.messageListAndMessageViewLoading());
444
445
		Events.sub('mailbox.message-view.toggle-full-screen', () => {
446
			this.toggleFullScreen();
447
		});
448
449
		this.attachmentPreview = _.bind(this.attachmentPreview, this);
450
	}
451
452
	detectDomBackgroundColor(dom) {
453
		let
454
			limit = 5,
455
			result = '';
456
457
		const
458
			fFindDom = function(inputDom) {
459
				const children = inputDom ? inputDom.children() : null;
460
				return (children && 1 === children.length && children.is('table,div,center')) ? children : null;
461
			},
462
			fFindColor = function(inputDom) {
463
				let color = '';
464
				if (inputDom)
465
				{
466
					color = inputDom.css('background-color') || '';
467
					if (!inputDom.is('table'))
468
					{
469
						color = 'rgba(0, 0, 0, 0)' === color || 'transparent' === color ? '' : color;
470
					}
471
				}
472
473
				return color;
474
			};
475
476
		if (dom && 1 === dom.length)
477
		{
478
			let aC = dom;
479
			while ('' === result)
480
			{
481
				limit -= 1;
482
				if (0 >= limit)
483
				{
484
					break;
485
				}
486
487
				aC = fFindDom(aC);
488
				if (aC)
489
				{
490
					result = fFindColor(aC);
491
				}
492
				else
493
				{
494
					break;
495
				}
496
			}
497
498
			result = 'rgba(0, 0, 0, 0)' === result || 'transparent' === result ? '' : result;
499
		}
500
501
		return result;
502
	}
503
504
	fullScreen() {
505
		this.fullScreenMode(true);
506
		windowResize();
507
	}
508
509
	unFullScreen() {
510
		this.fullScreenMode(false);
511
		windowResize();
512
	}
513
514
	toggleFullScreen() {
515
		removeSelection();
516
517
		this.fullScreenMode(!this.fullScreenMode());
518
		windowResize();
519
	}
520
521
	/**
522
	 * @param {string} sType
523
	 * @returns {void}
524
	 */
525
	replyOrforward(sType) {
526
		if (Settings.capa(Capa.Composer))
527
		{
528
			showScreenPopup(require('View/Popup/Compose'), [sType, MessageStore.message()]);
529
		}
530
	}
531
532
	checkHeaderHeight() {
533
		if (this.oHeaderDom)
534
		{
535
			this.viewBodyTopValue(this.message() ? this.oHeaderDom.height() +
536
				Magics.Size20px /* padding-(top/bottom): 20px */ + Magics.Size1px /* borded-bottom: 1px */ : 0);
537
		}
538
	}
539
540
	//  displayMailToPopup(sMailToUrl) {
541
	//		sMailToUrl = sMailToUrl.replace(/\?.+$/, '');
542
	//
543
	//		var
544
	//			sResult = '',
545
	//			aTo = [],
546
	//			EmailModel = require('Model/Email').default,
547
	//			fParseEmailLine = function(sLine) {
548
	//				return sLine ? _.compact(_.map([window.decodeURIComponent(sLine)], function(sItem) {
549
	//						var oEmailModel = new EmailModel();
550
	//						oEmailModel.mailsoParse(sItem);
551
	//						return '' !== oEmailModel.email ? oEmailModel : null;
552
	//					})) : null;
553
	//			}
554
	//		;
555
	//
556
	//		aTo = fParseEmailLine(sMailToUrl);
557
	//		sResult = aTo && aTo[0] ? aTo[0].email : '';
558
	//
559
	//		return sResult;
560
	//	}
561
562
	/**
563
	 * @param {Object} oAttachment
564
	 * @returns {boolean}
565
	 */
566
	attachmentPreview(attachment) {
567
		if (attachment && attachment.isImage() && !attachment.isLinked && this.message() && this.message().attachments())
568
		{
569
			let
570
				index = 0,
571
				listIndex = 0;
572
573
			const
574
				div = $('<div>'),
575
				dynamicEls = _.compact(_.map(this.message().attachments(), (item) => {
576
					if (item && !item.isLinked && item.isImage())
577
					{
578
						if (item === attachment)
579
						{
580
							index = listIndex;
581
						}
582
583
						listIndex += 1;
584
585
						return {
586
							src: item.linkPreview(),
587
							thumb: item.linkThumbnail(),
588
							subHtml: item.fileName,
589
							downloadUrl: item.linkPreview()
590
						};
591
					}
592
593
					return null;
594
				}));
595
596
			if (0 < dynamicEls.length)
597
			{
598
				div.on('onBeforeOpen.lg', () => {
599
					useKeyboardShortcuts(false);
600
					removeInFocus(true);
601
				});
602
603
				div.on('onCloseAfter.lg', () => {
604
					useKeyboardShortcuts(true);
605
				});
606
607
				div.lightGallery({
608
					dynamic: true,
609
					loadYoutubeThumbnail: false,
610
					loadVimeoThumbnail: false,
611
					thumbWidth: 80,
612
					thumbContHeight: 95,
613
					showThumbByDefault: false,
614
					mode: 'lg-lollipop', // 'lg-slide',
615
					index: index,
616
					dynamicEl: dynamicEls
617
				});
618
			}
619
620
			return false;
621
		}
622
623
		return true;
624
	}
625
626
	onBuild(dom) {
627
628
		const
629
			self = this,
630
			fCheckHeaderHeight = _.bind(this.checkHeaderHeight, this);
631
632
		this.oDom = dom;
633
634
		this.fullScreenMode.subscribe((value) => {
635
			if (value && this.message())
636
			{
637
				AppStore.focusedState(Focused.MessageView);
638
			}
639
		});
640
641
		this.showAttachmnetControls.subscribe(fCheckHeaderHeight);
642
		this.fullScreenMode.subscribe(fCheckHeaderHeight);
643
		this.showFullInfo.subscribe(fCheckHeaderHeight);
644
		this.message.subscribe(fCheckHeaderHeight);
645
646
		Events.sub('window.resize', _.throttle(() => {
647
			_.delay(fCheckHeaderHeight, 1);
648
			_.delay(fCheckHeaderHeight, Magics.Time200ms);
649
			_.delay(fCheckHeaderHeight, Magics.Time500ms);
650
		}, Magics.Time50ms));
651
652
		this.showFullInfo.subscribe((value) => {
653
			windowResize();
654
			windowResize(Magics.Time200ms);
655
			Local.set(ClientSideKeyName.MessageHeaderFullInfo, value ? '1' : '0');
656
		});
657
658
		SocialStore.appendDropbox();
659
660
		this.oHeaderDom = $('.messageItemHeader', dom);
661
		this.oHeaderDom = this.oHeaderDom[0] ? this.oHeaderDom : null;
662
663
		if (this.mobile)
664
		{
665
			dom.on('click', () => {
666
				leftPanelDisabled(true);
667
			});
668
		}
669
670
		dom
671
			.on('click', 'a', function(event) { // eslint-disable-line prefer-arrow-callback
672
				// setup maito protocol
673
				return !(!!event && Magics.EventWhichMouseMiddle !== event.which && mailToHelper(
674
					$(this).attr('href'), Settings.capa(Capa.Composer) ? require('View/Popup/Compose') : null // eslint-disable-line no-invalid-this
675
				));
676
			})
677
			.on('click', '.attachmentsPlace .attachmentIconParent', (event) => {
678
				if (event && event.stopPropagation)
679
				{
680
					event.stopPropagation();
681
				}
682
			})
683
			.on('click', '.attachmentsPlace .showPreplay', function(event) { // eslint-disable-line prefer-arrow-callback
684
				if (event && event.stopPropagation)
685
				{
686
					event.stopPropagation();
687
				}
688
689
				const attachment = ko.dataFor(this); // eslint-disable-line no-invalid-this
690
				if (attachment && Audio.supported)
691
				{
692
					switch (true)
693
					{
694
						case Audio.supportedMp3 && attachment.isMp3():
695
							Audio.playMp3(attachment.linkDownload(), attachment.fileName);
696
							break;
697
						case Audio.supportedOgg && attachment.isOgg():
698
							Audio.playOgg(attachment.linkDownload(), attachment.fileName);
699
							break;
700
						case Audio.supportedWav && attachment.isWav():
701
							Audio.playWav(attachment.linkDownload(), attachment.fileName);
702
							break;
703
						// no default
704
					}
705
				}
706
			})
707
			.on('click', '.attachmentsPlace .attachmentItem .attachmentNameParent', function() { // eslint-disable-line prefer-arrow-callback
708
				const attachment = ko.dataFor(this); // eslint-disable-line no-invalid-this
709
				if (attachment && attachment.download)
710
				{
711
					getApp().download(attachment.linkDownload());
712
				}
713
			})
714
			.on('click', '.messageItemHeader .subjectParent .flagParent', function() { // eslint-disable-line prefer-arrow-callback
715
				const message = self.message();
716
				if (message)
717
				{
718
					getApp().messageListAction(message.folderFullNameRaw, message.uid,
719
						message.flagged() ? MessageSetAction.UnsetFlag : MessageSetAction.SetFlag, [message]);
720
				}
721
			})
722
			.on('click', '.thread-list .flagParent', function() { // eslint-disable-line prefer-arrow-callback
723
				const message = ko.dataFor(this); // eslint-disable-line no-invalid-this
724
				if (message && message.folder && message.uid)
725
				{
726
					getApp().messageListAction(
727
						message.folder, message.uid,
728
						message.flagged() ? MessageSetAction.UnsetFlag : MessageSetAction.SetFlag, [message]);
729
				}
730
731
				self.threadsDropdownTrigger(true);
732
733
				return false;
734
			});
735
736
		AppStore.focusedState.subscribe((value) => {
737
			if (Focused.MessageView !== value)
738
			{
739
				this.scrollMessageToTop();
740
				this.scrollMessageToLeft();
741
			}
742
		});
743
744
		keyScopeReal.subscribe((value) => {
745
			this.messageDomFocused(KeyState.MessageView === value && !inFocus());
746
		});
747
748
		this.oMessageScrollerDom = dom.find('.messageItem .content');
749
		this.oMessageScrollerDom = this.oMessageScrollerDom && this.oMessageScrollerDom[0] ? this.oMessageScrollerDom : null;
750
751
		this.initShortcuts();
752
	}
753
754
	/**
755
	 * @returns {boolean}
756
	 */
757
	escShortcuts() {
758
		if (this.viewModelVisibility() && this.message())
759
		{
760
			if (this.fullScreenMode())
761
			{
762
				this.fullScreenMode(false);
763
764
				if (Layout.NoPreview !== this.layout())
765
				{
766
					AppStore.focusedState(Focused.MessageList);
767
				}
768
			}
769
			else if (Layout.NoPreview === this.layout())
770
			{
771
				this.message(null);
772
			}
773
			else
774
			{
775
				AppStore.focusedState(Focused.MessageList);
776
			}
777
778
			return false;
779
		}
780
781
		return true;
782
	}
783
784
	initShortcuts() {
785
786
		// exit fullscreen, back
787
		key('esc, backspace', KeyState.MessageView, _.bind(this.escShortcuts, this));
788
789
		// fullscreen
790
		key('enter', KeyState.MessageView, () => {
791
			this.toggleFullScreen();
792
			return false;
793
		});
794
795
		// reply
796
		key('r', [KeyState.MessageList, KeyState.MessageView], () => {
797
			if (MessageStore.message())
798
			{
799
				this.replyCommand();
800
				return false;
801
			}
802
803
			return true;
804
		});
805
806
		// replaAll
807
		key('a', [KeyState.MessageList, KeyState.MessageView], () => {
808
			if (MessageStore.message())
809
			{
810
				this.replyAllCommand();
811
				return false;
812
			}
813
814
			return true;
815
		});
816
817
		// forward
818
		key('f', [KeyState.MessageList, KeyState.MessageView], () => {
819
			if (MessageStore.message())
820
			{
821
				this.forwardCommand();
822
				return false;
823
			}
824
825
			return true;
826
		});
827
828
		// message information
829
		key('ctrl+i, command+i', [KeyState.MessageList, KeyState.MessageView], () => {
830
			if (MessageStore.message())
831
			{
832
				this.showFullInfo(!this.showFullInfo());
833
			}
834
			return false;
835
		});
836
837
		// toggle message blockquotes
838
		key('b', [KeyState.MessageList, KeyState.MessageView], () => {
839
			if (MessageStore.message() && MessageStore.message().body)
840
			{
841
				MessageStore.message().body.find('.rlBlockquoteSwitcher').click();
842
				return false;
843
			}
844
845
			return true;
846
		});
847
848
		key('ctrl+up, command+up, ctrl+left, command+left', [KeyState.MessageList, KeyState.MessageView], () => {
849
			this.goUpCommand();
850
			return false;
851
		});
852
853
		key('ctrl+down, command+down, ctrl+right, command+right', [KeyState.MessageList, KeyState.MessageView], () => {
854
			this.goDownCommand();
855
			return false;
856
		});
857
858
		// print
859
		key('ctrl+p, command+p', [KeyState.MessageView, KeyState.MessageList], () => {
860
			if (this.message())
861
			{
862
				this.message().printMessage();
863
			}
864
865
			return false;
866
		});
867
868
		// delete
869
		key('delete, shift+delete', KeyState.MessageView, (event, handler) => {
870
			if (event)
871
			{
872
				if (handler && 'shift+delete' === handler.shortcut)
873
				{
874
					this.deleteWithoutMoveCommand();
875
				}
876
				else
877
				{
878
					this.deleteCommand();
879
				}
880
881
				return false;
882
			}
883
884
			return true;
885
		});
886
887
		// change focused state
888
		key('tab, shift+tab, left', KeyState.MessageView, (event, handler) => {
889
			if (!this.fullScreenMode() && this.message() && Layout.NoPreview !== this.layout())
890
			{
891
				if (event && handler && 'left' === handler.shortcut)
892
				{
893
					if (this.oMessageScrollerDom && 0 < this.oMessageScrollerDom.scrollLeft())
894
					{
895
						return true;
896
					}
897
898
					AppStore.focusedState(Focused.MessageList);
899
				}
900
				else
901
				{
902
					AppStore.focusedState(Focused.MessageList);
903
				}
904
			}
905
			else if (this.message() && Layout.NoPreview === this.layout() && event && handler && 'left' === handler.shortcut)
906
			{
907
				return true;
908
			}
909
910
			return false;
911
		});
912
	}
913
914
	/**
915
	 * @returns {boolean}
916
	 */
917
	isDraftFolder() {
918
		return MessageStore.message() && FolderStore.draftFolder() === MessageStore.message().folderFullNameRaw;
919
	}
920
921
	/**
922
	 * @returns {boolean}
923
	 */
924
	isSentFolder() {
925
		return MessageStore.message() && FolderStore.sentFolder() === MessageStore.message().folderFullNameRaw;
926
	}
927
928
	/**
929
	 * @returns {boolean}
930
	 */
931
	isSpamFolder() {
932
		return MessageStore.message() && FolderStore.spamFolder() === MessageStore.message().folderFullNameRaw;
933
	}
934
935
	/**
936
	 * @returns {boolean}
937
	 */
938
	isSpamDisabled() {
939
		return MessageStore.message() && FolderStore.spamFolder() === UNUSED_OPTION_VALUE;
940
	}
941
942
	/**
943
	 * @returns {boolean}
944
	 */
945
	isArchiveFolder() {
946
		return MessageStore.message() && FolderStore.archiveFolder() === MessageStore.message().folderFullNameRaw;
947
	}
948
949
	/**
950
	 * @returns {boolean}
951
	 */
952
	isArchiveDisabled() {
953
		return MessageStore.message() && FolderStore.archiveFolder() === UNUSED_OPTION_VALUE;
954
	}
955
956
	/**
957
	 * @returns {boolean}
958
	 */
959
	isDraftOrSentFolder() {
960
		return this.isDraftFolder() || this.isSentFolder();
961
	}
962
963
	composeClick() {
964
		if (Settings.capa(Capa.Composer))
965
		{
966
			showScreenPopup(require('View/Popup/Compose'));
967
		}
968
	}
969
970
	editMessage() {
971
		if (Settings.capa(Capa.Composer) && MessageStore.message())
972
		{
973
			showScreenPopup(require('View/Popup/Compose'), [ComposeType.Draft, MessageStore.message()]);
974
		}
975
	}
976
977
	scrollMessageToTop() {
978
		if (this.oMessageScrollerDom)
979
		{
980
			if (Magics.Size50px < this.oMessageScrollerDom.scrollTop())
981
			{
982
				this.oMessageScrollerDom
983
					.scrollTop(Magics.Size50px)
984
					.animate({'scrollTop': 0}, Magics.Time200ms);
985
			}
986
			else
987
			{
988
				this.oMessageScrollerDom.scrollTop(0);
989
			}
990
991
			windowResize();
992
		}
993
	}
994
995
	scrollMessageToLeft() {
996
		if (this.oMessageScrollerDom)
997
		{
998
			this.oMessageScrollerDom.scrollLeft(0);
999
			windowResize();
1000
		}
1001
	}
1002
1003
	getAttachmentsHashes() {
1004
		const atts = this.message() ? this.message().attachments() : [];
1005
		return _.compact(_.map(atts, (item) => (item && !item.isLinked && item.checked() ? item.download : '')));
1006
	}
1007
1008
	downloadAsZip() {
1009
		const hashes = this.getAttachmentsHashes();
1010
		if (0 < hashes.length)
1011
		{
1012
			Promises.attachmentsActions('Zip', hashes, this.downloadAsZipLoading).then((result) => {
1013
				if (result && result.Result && result.Result.Files &&
1014
					result.Result.Files[0] && result.Result.Files[0].Hash)
1015
				{
1016
					getApp().download(attachmentDownload(result.Result.Files[0].Hash));
1017
				}
1018
				else
1019
				{
1020
					this.downloadAsZipError(true);
1021
				}
1022
			}).catch(() => {
1023
				this.downloadAsZipError(true);
1024
			});
1025
		}
1026
		else
1027
		{
1028
			this.highlightUnselectedAttachments(true);
1029
		}
1030
	}
1031
1032
	saveToOwnCloud() {
1033
1034
		const hashes = this.getAttachmentsHashes();
1035
		if (0 < hashes.length)
1036
		{
1037
			Promises.attachmentsActions('OwnCloud', hashes, this.saveToOwnCloudLoading).then((result) => {
1038
				if (result && result.Result)
1039
				{
1040
					this.saveToOwnCloudSuccess(true);
1041
				}
1042
				else
1043
				{
1044
					this.saveToOwnCloudError(true);
1045
				}
1046
			}).catch(() => {
1047
				this.saveToOwnCloudError(true);
1048
			});
1049
		}
1050
		else
1051
		{
1052
			this.highlightUnselectedAttachments(true);
1053
		}
1054
	}
1055
1056
	saveToDropbox() {
1057
1058
		const
1059
			files = [],
1060
			hashes = this.getAttachmentsHashes();
1061
1062
		if (0 < hashes.length)
1063
		{
1064
			if (window.Dropbox)
1065
			{
1066
				Promises.attachmentsActions('Dropbox', hashes, this.saveToDropboxLoading).then((result) => {
1067
					if (result && result.Result && result.Result.Url && result.Result.ShortLife && result.Result.Files)
1068
					{
1069
						if (window.Dropbox && isArray(result.Result.Files))
1070
						{
1071
							_.each(result.Result.Files, (item) => {
1072
								files.push({
1073
									url: result.Result.Url + attachmentDownload(item.Hash, result.Result.ShortLife),
1074
									filename: item.FileName
1075
								});
1076
							});
1077
1078
							window.Dropbox.save({
1079
								files: files,
1080
								progress: () => {
1081
									this.saveToDropboxLoading(true);
1082
									this.saveToDropboxError(false);
1083
									this.saveToDropboxSuccess(false);
1084
								},
1085
								cancel: () => {
1086
									this.saveToDropboxSuccess(false);
1087
									this.saveToDropboxError(false);
1088
									this.saveToDropboxLoading(false);
1089
								},
1090
								success: () => {
1091
									this.saveToDropboxSuccess(true);
1092
									this.saveToDropboxLoading(false);
1093
								},
1094
								error: () => {
1095
									this.saveToDropboxError(true);
1096
									this.saveToDropboxLoading(false);
1097
								}
1098
							});
1099
						}
1100
						else
1101
						{
1102
							this.saveToDropboxError(true);
1103
						}
1104
					}
1105
				}).catch(() => {
1106
					this.saveToDropboxError(true);
1107
				});
1108
			}
1109
		}
1110
		else
1111
		{
1112
			this.highlightUnselectedAttachments(true);
1113
		}
1114
	}
1115
1116
	/**
1117
	 * @param {MessageModel} oMessage
1118
	 * @returns {void}
1119
	 */
1120
	showImages(message) {
1121
		if (message && message.showExternalImages)
1122
		{
1123
			message.showExternalImages(true);
1124
		}
1125
1126
		this.checkHeaderHeight();
1127
	}
1128
1129
	/**
1130
	 * @returns {string}
1131
	 */
1132
	printableCheckedMessageCount() {
1133
		const cnt = this.messageListCheckedOrSelectedUidsWithSubMails().length;
1134
		return 0 < cnt ? (100 > cnt ? cnt : '99+') : ''; // eslint-disable-line no-magic-numbers
1135
	}
1136
1137
	/**
1138
	 * @param {MessageModel} oMessage
1139
	 * @returns {void}
1140
	 */
1141
	readReceipt(oMessage) {
1142
		if (oMessage && '' !== oMessage.readReceipt())
1143
		{
1144
			Remote.sendReadReceiptMessage(noop, oMessage.folderFullNameRaw, oMessage.uid,
1145
				oMessage.readReceipt(),
1146
				i18n('READ_RECEIPT/SUBJECT', {'SUBJECT': oMessage.subject()}),
1147
				i18n('READ_RECEIPT/BODY', {'READ-RECEIPT': AccountStore.email()}));
1148
1149
			oMessage.isReadReceipt(true);
1150
1151
			storeMessageFlagsToCache(oMessage);
1152
1153
			getApp().reloadFlagsCurrentMessageListAndMessageFromCache();
1154
		}
1155
1156
		this.checkHeaderHeight();
1157
	}
1158
}
1159
1160
export {MessageViewMailBoxUserView, MessageViewMailBoxUserView as default};
1161