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