6d2ac02388b108bdffed81f8e857ed58a84be07f
[tine20] / tine20 / MailFiler / js / NodeGridPanel.js
1 /*
2  * Tine 2.0
3  * 
4  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
5  * @author      Philipp Schüle <p.schuele@metaways.de>
6  * @copyright   Copyright (c) 2007-2017 Metaways Infosystems GmbH (http://www.metaways.de)
7  */
8 Ext.ns('Tine.MailFiler');
9
10 require('./nodeActions');
11
12 /**
13  * File grid panel
14  * 
15  * @namespace   Tine.MailFiler
16  * @class       Tine.MailFiler.NodeGridPanel
17  * @extends     Tine.Filemanager.NodeGridPanel
18  * 
19  * <p>Node Grid Panel</p>
20  * <p><pre>
21  * </pre></p>
22  * 
23  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
24  * @author      Philipp Schüle <p.schuele@metaways.de>
25  * @copyright   Copyright (c) 2007-2017 Metaways Infosystems GmbH (http://www.metaways.de)
26  *
27  * @param       {Object} config
28  * @constructor
29  * Create a new Tine.MailFiler.FileGridPanel
30  */
31 Tine.MailFiler.NodeGridPanel = Ext.extend(Tine.Filemanager.NodeGridPanel, {
32
33     enableDD: false,
34     recordClass: Tine.MailFiler.Model.Node,
35
36     /**
37      * message detail panel
38      *
39      * @type Tine.MailFiler.GridDetailsPanel
40      * @property detailsPanel
41      */
42     detailsPanel: null,
43
44     /**
45      * inits this cmp
46      * @private
47      */
48     initComponent: function() {
49         this.initDetailsPanel();
50         this.recordProxy = Tine.MailFiler.fileRecordBackend;
51
52         Tine.MailFiler.NodeGridPanel.superclass.initComponent.call(this);
53     },
54
55     /**
56      * the details panel (shows message content)
57      *
58      * @private
59      */
60     initDetailsPanel: function() {
61         this.detailsPanel = new Tine.MailFiler.GridDetailsPanel({
62             gridpanel: this,
63             grid: this,
64             app: this.app,
65             i18n: this.app.i18n
66         });
67     },
68
69     /**
70      * returns cm
71      *
72      * @return Ext.grid.ColumnModel
73      * @private
74      *
75      */
76     getColumnModel: function(){
77         var columns = [{
78             id: 'tags',
79             header: this.app.i18n._('Tags'),
80             dataIndex: 'tags',
81             width: 50,
82             renderer: Tine.Tinebase.common.tagsRenderer,
83             sortable: false,
84             hidden: false
85         },{
86             id: 'name',
87             header: this.app.i18n._("Name"),
88             width: 70,
89             sortable: true,
90             dataIndex: 'name',
91             renderer: Ext.ux.PercentRendererWithName
92         },{
93             id: 'flags',
94             header: this.app.i18n._("Flags"),
95             width: 24,
96             sortable: true,
97             dataIndex: 'flags',
98             renderer: this.flagRenderer
99         },{
100             id: 'subject',
101             header: this.app.i18n._("Subject"),
102             width: 300,
103             sortable: true,
104             dataIndex: 'subject',
105             renderer: this.messageRenderer
106         },{
107             id: 'from_email',
108             header: this.app.i18n._("From (Email)"),
109             width: 100,
110             sortable: true,
111             dataIndex: 'from_email',
112             renderer: this.messageRenderer
113         },{
114             id: 'from_name',
115             header: this.app.i18n._("From (Name)"),
116             width: 100,
117             sortable: true,
118             dataIndex: 'from_name',
119             renderer: this.messageRenderer
120         },{
121             id: 'sender',
122             header: this.app.i18n._("Sender"),
123             width: 100,
124             sortable: true,
125             dataIndex: 'sender',
126             hidden: true,
127             renderer: this.messageRenderer
128         },{
129             id: 'to',
130             header: this.app.i18n._("To"),
131             width: 150,
132             sortable: true,
133             dataIndex: 'to',
134             hidden: true,
135             renderer: this.messageRenderer
136         },{
137             id: 'sent',
138             header: this.app.i18n._("Sent"),
139             width: 100,
140             sortable: true,
141             dataIndex: 'sent',
142             hidden: true,
143             renderer: this.messageRenderer
144         },{
145             id: 'received',
146             header: this.app.i18n._("Received"),
147             width: 100,
148             sortable: true,
149             dataIndex: 'received',
150             renderer: this.messageRenderer
151         },{
152             id: 'size',
153             header: this.app.i18n._("Size"),
154             width: 40,
155             sortable: true,
156             dataIndex: 'size',
157             hidden: true,
158             renderer: Tine.Tinebase.common.byteRenderer.createDelegate(this, [2, true], 3)
159         },{
160             id: 'contenttype',
161             header: this.app.i18n._("Contenttype"),
162             width: 50,
163             sortable: true,
164             dataIndex: 'contenttype',
165             hidden: true,
166             renderer: function(value, metadata, record) {
167
168                 var app = Tine.Tinebase.appMgr.get('MailFiler');
169                 if(record.data.type == 'folder') {
170                     return app.i18n._("Folder");
171                 }
172                 else {
173                     return value;
174                 }
175             }
176         },{
177             id: 'creation_time',
178             header: this.app.i18n._("Creation Time"),
179             width: 50,
180             sortable: true,
181             dataIndex: 'creation_time',
182             hidden: true,
183             renderer: Tine.Tinebase.common.dateTimeRenderer
184         },{
185             id: 'created_by',
186             header: this.app.i18n._("Created By"),
187             width: 50,
188             sortable: true,
189             dataIndex: 'created_by',
190             renderer: Tine.Tinebase.common.usernameRenderer
191         },{
192             id: 'last_modified_time',
193             header: this.app.i18n._("Last Modified Time"),
194             width: 80,
195             sortable: true,
196             dataIndex: 'last_modified_time',
197             hidden: true,
198             renderer: Tine.Tinebase.common.dateTimeRenderer
199         },{
200             id: 'last_modified_by',
201             header: this.app.i18n._("Last Modified By"),
202             width: 50,
203             sortable: true,
204             dataIndex: 'last_modified_by',
205             hidden: true,
206             renderer: Tine.Tinebase.common.usernameRenderer
207         }];
208
209         return new Ext.grid.ColumnModel({
210             defaults: {
211                 sortable: true,
212                 resizable: true
213             },
214             columns: columns
215         });
216     },
217
218     /**
219      * get flag icon
220      *
221      * @param {String} flags
222      * @return {String}
223      * @private
224      *
225      * TODO  use spacer if first flag(s) is/are not set?
226      */
227     flagRenderer: function(value, metadata, record) {
228         var icons = [],
229             result = '',
230             record = record.get('message')
231                 ? new Tine.Felamimail.Model.Message(record.get('message'), record.get('message').id)
232                 : null;
233
234         if (! record) {
235             return '';
236         }
237
238         if (record.hasFlag('\\Answered')) {
239             icons.push({src: 'images/oxygen/16x16/actions/mail-reply-sender.png', qtip: Ext.util.Format.htmlEncode(i18n._('Answered'))});
240         }
241         if (record.hasFlag('Passed')) {
242             icons.push({src: 'images/oxygen/16x16/actions/mail-forward.png', qtip: Ext.util.Format.htmlEncode(i18n._('Forwarded'))});
243         }
244         if (record.hasFlag('Tine20')) {
245             icons.push({src: 'images/favicon.png', qtip: Ext.util.Format.htmlEncode(i18n._('Tine20'))});
246         }
247
248         Ext.each(icons, function(icon) {
249             result += '<img class="FelamimailFlagIcon" src="' + icon.src + '" ext:qtip="' + Tine.Tinebase.common.doubleEncode(icon.qtip) + '">';
250         }, this);
251
252         return result;
253     },
254
255     /**
256      * renders message
257      * @param {Integer} value
258      * @param {Object} metadata
259      * @param {Tine.Tinebase.data.Record} record
260      * @param {String} field
261      * @return {String}
262      *
263      * TODO should be generalized
264      */
265     messageRenderer: function (value, metadata, record) {
266         var messageRecord = record ? new Tine.Felamimail.Model.Message(record.get('message'), record.get('message').id) : null,
267             field = this.dataIndex,
268             result = messageRecord ? messageRecord.get(field) : '';
269
270         if ( field !== '') {
271             switch (field) {
272                 case 'size':
273                     result = Ext.util.Format.fileSize(result)
274                     break;
275                 case 'received':
276                 case 'sent':
277                     result = Tine.Tinebase.common.dateTimeRenderer(result);
278                     break;
279             }
280         }
281         return result;
282     },
283
284     /**
285      * init actions with actionToolbar, contextMenu and actionUpdater
286      * @private
287      *
288      * TODO use Filemanager actions if available
289      */
290     initActions: function() {
291
292         this.initMailActions();
293
294         this.action_createFolder = Tine.MailFiler.nodeActionsMgr.get('createFolder');
295         this.action_editFile = Tine.MailFiler.nodeActionsMgr.get('edit');
296         this.action_deleteRecord = Tine.MailFiler.nodeActionsMgr.get('delete');
297         this.action_download = Tine.MailFiler.nodeActionsMgr.get('download');
298
299         // FIXME: this is broken (see Filemanager)
300         //this.action_goUpFolder = new Ext.Action({
301         //    allowMultiple: true,
302         //    actionType: 'goUpFolder',
303         //    text: this.app.i18n._('Folder Up'),
304         //    handler: this.onLoadParentFolder,
305         //    iconCls: 'action_filemanager_folder_up',
306         //    actionUpdater: function(action) {
307         //        var _ = window.lodash,
308         //            path = _.get(this, 'currentFolderNode.attributes.path', false);
309         //
310         //        action.setDisabled(path == '/');
311         //    }.createDelegate(this),
312         //    scope: this
313         //});
314
315         this.contextMenu = Tine.MailFiler.GridContextMenu.getMenu({
316             nodeName: Tine.MailFiler.Model.Node.getRecordName(),
317             actions: ['delete', 'download', 'edit'],
318             scope: this,
319             backend: 'MailFiler',
320             backendModel: 'Node'
321         });
322         this.contextMenu.addItem(this.action_reply);
323         this.contextMenu.addItem(this.action_replyAll);
324         this.contextMenu.addItem(this.action_forward);
325
326         this.folderContextMenu = Tine.MailFiler.GridContextMenu.getMenu({
327             nodeName: this.app.i18n._(this.app.getMainScreen().getWestPanel().getContainerTreePanel().containerName),
328             actions: ['delete', 'rename', 'edit'],
329             scope: this,
330             backend: 'MailFiler',
331             backendModel: 'Node'
332         });
333
334         this.actionUpdater.addActions(this.contextMenu.items);
335         this.actionUpdater.addActions(this.folderContextMenu.items);
336
337         this.actionUpdater.addActions([
338             this.action_reply,
339             this.action_replyAll,
340             this.action_forward,
341             this.action_createFolder,
342             //this.action_goUpFolder,
343             this.action_download,
344             this.action_deleteRecord,
345             this.action_editFile,
346             this.action_printmessage
347        ]);
348     },
349
350     /**
351      * init mail actions
352      *
353      * @private
354      *
355      * TODO use action manager
356      */
357     initMailActions: function() {
358
359         var fMailApp = Tine.Tinebase.appMgr.get('Felamimail');
360
361         this.action_reply = new Ext.Action({
362             requiredGrant: 'readGrant',
363             actionType: 'reply',
364             text: this.app.i18n._('Reply'),
365             handler: this.onMessageReplyTo.createDelegate(this, [false]),
366             iconCls: 'action_email_reply',
367             actionUpdater: this.updateMessageAction,
368             disabled: true
369         });
370
371         this.action_replyAll = new Ext.Action({
372             requiredGrant: 'readGrant',
373             actionType: 'replyAll',
374             text: this.app.i18n._('Reply To All'),
375             handler: this.onMessageReplyTo.createDelegate(this, [true]),
376             iconCls: 'action_email_replyAll',
377             actionUpdater: this.updateMessageAction,
378             disabled: true
379         });
380
381         this.action_forward = new Ext.Action({
382             requiredGrant: 'readGrant',
383             actionType: 'forward',
384             text: this.app.i18n._('Forward'),
385             handler: this.onMessageForward.createDelegate(this),
386             iconCls: 'action_email_forward',
387             actionUpdater: this.updateMessageAction,
388             disabled: true
389         });
390
391         this.action_printmessage = new Ext.Action({
392             requiredGrant: 'readGrant',
393             text: this.app.i18n._('Print Message'),
394             handler: this.onPrint.createDelegate(this, []),
395             disabled: true,
396             iconCls:'action_print',
397             actionUpdater: this.updateMessageAction,
398             scope:this
399         });
400     },
401
402
403     /**
404      * Ripped off felamimail
405      *
406      * @param detailsPanel
407      */
408     onPrint: function(detailsPanel) {
409         var id = Ext.id(),
410             doc = document,
411             frame = doc.createElement('iframe');
412
413         Ext.fly(frame).set({
414             id: id,
415             name: id,
416             style: {
417                 position: 'absolute',
418                 width: '210mm',
419                 height: '297mm',
420                 top: '-10000px',
421                 left: '-10000px'
422             }
423         });
424
425         doc.body.appendChild(frame);
426
427         Ext.fly(frame).set({
428             src : Ext.SSL_SECURE_URL
429         });
430
431         var doc = frame.contentWindow.document || frame.contentDocument || WINDOW.frames[id].document,
432             content = this.getDetailsPanelContentForPrinting(detailsPanel || this.detailsPanel);
433
434         doc.open();
435         doc.write(content);
436         doc.close();
437
438         frame.contentWindow.focus();
439         frame.contentWindow.print();
440     },
441
442     /**
443      * get detail panel content
444      *
445      * @param {Tine.Felamimail.GridDetailsPanel} details panel
446      * @return {String}
447      */
448     getDetailsPanelContentForPrinting: function(detailsPanel) {
449         // TODO somehow we have two <div class="preview-panel-felamimail"> -> we need to fix that and get the first element found
450         var detailsPanels = detailsPanel.getEl().query('.preview-panel-felamimail');
451
452         var detailsPanelContent = (detailsPanels.length > 1) ? detailsPanels[1].innerHTML : detailsPanels[0].innerHTML;
453
454         var buffer = '<html><head>';
455         buffer += '<title>' + this.app.i18n._('Print Preview') + '</title>';
456         buffer += '</head><body>';
457         buffer += detailsPanelContent;
458         buffer += '</body></html>';
459
460         return buffer;
461     },
462
463     onMessageReplyTo: function(toAll) {
464         var sm = this.getGrid().getSelectionModel(),
465             node = sm.getSelected();
466             msg = node.get('message'),
467             msgBody = Ext.util.Format.nl2br(msg.body);
468
469         msgBody = '<br/>'
470             + '<blockquote class="felamimail-body-blockquote">' + msgBody + '</blockquote><br/>';
471
472         var date = msg.sent
473             ? msg.sent
474             : (msg.received) ? msg.received : new Date();
475
476         var quote = String.format(this.app.i18n._('On {0}, {1} wrote'),
477                 Tine.Tinebase.common.dateTimeRenderer(date),
478                 Ext.util.Format.htmlEncode(msg.from_name)
479             ) + ':';
480
481         // pass all relevant params (body, subject, ...) to prevent mail loading in Felamimail
482         var win = Tine.Felamimail.MessageEditDialog.openWindow({
483             //record: msg,
484             replyTo : Ext.encode(msg),
485             replyToAll: toAll,
486             msgBody: quote + msgBody
487         });
488     },
489
490     onMessageForward: function() {
491         var sm = this.getGrid().getSelectionModel(),
492             node = sm.getSelected();
493             msg = node.get('message'),
494             msgBody = Ext.util.Format.nl2br(msg.body),
495             quote = String.format('{0}-----' + this.app.i18n._('Original message') + '-----{1}',
496                 '<br /><b>',
497                 '</b><br />'),
498             attachments = msg.attachments;
499
500         Ext.each(attachments, function(attachment) {
501             // set name and MailFiler path for fetching attachment from filesystem when sending
502             attachment.name = attachment.filename;
503             attachment.type = 'filenode';
504             attachment.id = 'MailFiler'  + '|' + node.get('path') + '|' + msg.messageuid  + '|' + attachment.partId
505         }, this);
506
507         // pass all relevant params (body, subject, ...) to prevent mail loading in Felamimail
508         var win = Tine.Felamimail.MessageEditDialog.openWindow({
509             forwardMsgs : Ext.encode([msg]),
510             attachments: attachments,
511             msgBody: quote + msgBody
512         });
513     },
514
515     /**
516      * check file type for message actions
517      *
518      * @param action
519      * @param grants
520      * @param records
521      * @returns {boolean}
522      */
523     updateMessageAction: function(action, grants, records) {
524         var isFile = false;
525         Ext.each(records, function (record) {
526             if (record.get('type') == 'file') {
527                 isFile = true;
528                 return true;
529             }
530         });
531
532         var disable = records.length > 1 || ! isFile;
533         action.setDisabled(disable);
534         return false;
535     },
536
537     /**
538      * get action toolbar
539      *
540      * @return {Ext.Toolbar}
541      */
542     getActionToolbar: function() {
543         if (! this.actionToolbar) {
544             this.actionToolbar = new Ext.Toolbar({
545                 defaults: {height: 55},
546                 items: [{
547                     xtype: 'buttongroup',
548                     layout: 'toolbar',
549                     buttonAlign: 'left',
550                     columns: 9,
551                     defaults: {minWidth: 60},
552                     items: [
553                         Ext.apply(new Ext.Button(this.action_write), {
554                             scale: 'medium',
555                             rowspan: 2,
556                             iconAlign: 'top'
557                         }),
558                         Ext.apply(new Ext.Button(this.action_reply), {
559                             scale: 'medium',
560                             rowspan: 2,
561                             iconAlign: 'top'
562                         }),
563                         Ext.apply(new Ext.Button(this.action_replyAll), {
564                             scale: 'medium',
565                             rowspan: 2,
566                             iconAlign: 'top'
567                         }),
568                         Ext.apply(new Ext.Button(this.action_forward), {
569                             scale: 'medium',
570                             rowspan: 2,
571                             iconAlign: 'top'
572                         }),
573                         Ext.apply(new Ext.Button(this.action_printmessage), {
574                             scale: 'medium',
575                             rowspan: 2,
576                             iconAlign: 'top'
577                         }),
578                         Ext.apply(new Ext.Button(this.action_editFile), {
579                             scale: 'medium',
580                             rowspan: 2,
581                             iconAlign: 'top'
582                         }),
583                         Ext.apply(new Ext.Button(this.action_deleteRecord), {
584                             scale: 'medium',
585                             rowspan: 2,
586                             iconAlign: 'top'
587                         }),
588                         Ext.apply(new Ext.Button(this.action_createFolder), {
589                             scale: 'medium',
590                             rowspan: 2,
591                             iconAlign: 'top'
592                         }),
593                         //Ext.apply(new Ext.Button(this.action_goUpFolder), {
594                         //    scale: 'medium',
595                         //    rowspan: 2,
596                         //    iconAlign: 'top'
597                         //}),
598                         Ext.apply(new Ext.Button(this.action_download), {
599                             scale: 'medium',
600                             rowspan: 2,
601                             iconAlign: 'top'
602                         })
603                  ]
604                 }, this.getActionToolbarItems()]
605             });
606
607
608             if (this.filterToolbar && typeof this.filterToolbar.getQuickFilterField == 'function') {
609                 this.actionToolbar.add('->', this.filterToolbar.getQuickFilterField());
610             }
611         }
612
613         this.actionToolbar.on('resize', this.onActionToolbarResize, this, {buffer: 250});
614         this.actionToolbar.on('show', this.onActionToolbarResize, this);
615
616         return this.actionToolbar;
617     }
618 });