JAVASCRIPT 7
Mailer.js Guest on 18th October 2020 05:48:22 AM
  1. (function ($) {
  2.     "use strict";
  3.  
  4.     $.storage = new $.store();
  5.     $.wa.mailer = {
  6.         options: {
  7.             lang: 'en'
  8.         },
  9.         init: function (options) {
  10.             if ("undefined" !== typeof($.History)) {
  11.                 $.History.bind(function () {
  12.                     $.wa.mailer.dispatch();
  13.                 });
  14.             }
  15.             this.options = $.extend(this.options, options);
  16.             var hash = window.location.hash;
  17.             if (hash === '#/' || !hash) {
  18.                 hash = $.storage.get('mailer/hash');
  19.                 if (hash && hash != null) {
  20.                     $.wa.setHash('#/' + hash);
  21.                 } else {
  22.                     this.dispatch();
  23.                 }
  24.             } else {
  25.                 $.wa.setHash(hash);
  26.             }
  27.  
  28.             $(window).bind('wa.dispatched', function () {
  29.                 // Highlight current item in sidebar, if exists
  30.                 $.wa.mailer.highlightSidebar();
  31.  
  32.                 // Remove all non-persistent dialogs
  33.                 $('.dialog:not(.persistent)').empty().remove();
  34.             });
  35.  
  36.             // Set up AJAX error handler
  37.             $.wa.errorHandler = function () {
  38.             };
  39.             $(document).ajaxError(function (e, xhr, settings, exception) {
  40.                 // Ignore 502 error in background process
  41.                 if (xhr.status === 502 && exception == 'abort' || (settings.url && settings.url.indexOf('background_process') >= 0) || (settings.data && settings.data.indexOf('background_process') >= 0)) {
  42.                     console && console.log && console.log('Notice: XHR failed on load: '+ settings.url);
  43.                     return;
  44.                 }
  45.  
  46.                 // Never save pages causing an error as last hashes
  47.                 $.storage.del('mailer/hash');
  48.                 $.wa.mailer.stopDispatch(1);
  49.                 window.location.hash = '';
  50.  
  51.                 // Show error in a nice safe iframe
  52.                 if (xhr.responseText) {
  53.                     var iframe = $('<iframe src="about:blank" style="width:100%;height:auto;min-height:500px;"></iframe>');
  54.                     $("#content").addClass('shadowed').empty().append(iframe);
  55.                     var ifrm = (iframe[0].contentWindow) ? iframe[0].contentWindow : (iframe[0].contentDocument.document) ? iframe[0].contentDocument.document : iframe[0].contentDocument;
  56.                     ifrm.document.open();
  57.                     ifrm.document.write(xhr.responseText);
  58.                     ifrm.document.close();
  59.  
  60.                     // Close all existing dialogs
  61.                     $('.dialog:visible').trigger('close').remove();
  62.                 }
  63.             });
  64.  
  65.             // Collapsible sidebar sections
  66.             var toggleCollapse = function () {
  67.                 $.wa.mailer.collapseSidebarSection(this, 'toggle');
  68.             };
  69.             $(".collapse-handler", $('#wa-app')).die('click').live('click', toggleCollapse);
  70.             this.restoreCollapsibleStatusInSidebar();
  71.  
  72.             // Reload sidebar once a minute when there are campaigns currently sending
  73.             setTimeout($.wa.mailer.updateSendingSidebar, 60000);
  74.  
  75.             // development hotkeys for redispatch and sidebar reloading
  76.             $(document).keypress(function (e) {
  77.                 if ((e.which == 10 || e.which == 13) && e.shiftKey) {
  78.                     $('#wa-app .sidebar .icon16').first().attr('class', 'icon16 loading');
  79.                     $.wa.mailer.reloadSidebar();
  80.                 }
  81.                 if ((e.which == 10 || e.which == 13) && e.ctrlKey) {
  82.                     $.wa.mailer.redispatch();
  83.                 }
  84.             });
  85.         },
  86.  
  87.         // if this is > 0 then this.dispatch() decrements it and ignores a call
  88.         skipDispatch: 0,
  89.  
  90.         /** Cancel the next n automatic dispatches when window.location.hash changes */
  91.         stopDispatch: function (n) {
  92.             this.skipDispatch = n;
  93.         },
  94.  
  95.         /** Force reload current hash-based 'page'. */
  96.         redispatch: function () {
  97.             this.currentHash = null;
  98.             this.dispatch();
  99.         },
  100.  
  101.         // last hash processed by this.dispatch()
  102.         currentHash: null,
  103.  
  104.         /**
  105.          * Called automatically when window.location.hash changes. Should not be called directly.
  106.          * Call a corresponding handler by concatenating leading non-int parts of hash,
  107.          * e.g. for #/aaa/bbb/ccc/111/dd/12/ee/ff
  108.          * a method $.wa.controller.AaaBbbCccAction(['111', 'dd', '12', 'ee', 'ff']) will be called.
  109.          */
  110.         dispatch: function (hash) {
  111.             if (this.skipDispatch > 0) {
  112.                 this.skipDispatch--;
  113.                 this.currentHash = null;
  114.                 return false;
  115.             }
  116.             if (hash == undefined) {
  117.                 hash = this.getHash();
  118.             } else {
  119.                 hash = this.cleanHash(hash);
  120.             }
  121.  
  122.             if (this.currentHash == hash) {
  123.                 return;
  124.             }
  125.             var old_hash = this.currentHash;
  126.             this.currentHash = hash;
  127.  
  128.             var e = new $.Event('wa.before_dispatched');
  129.             $(window).trigger(e);
  130.             if (e.isDefaultPrevented()) {
  131.                 this.currentHash = old_hash;
  132.                 window.location.hash = old_hash;
  133.                 return false;
  134.             }
  135.  
  136.             hash = hash.replace(/^[^#]*#\/*/, '');
  137.             /* */
  138.             if (hash) {
  139.                 hash = hash.split('/');
  140.                 if (hash[0]) {
  141.                     var actionName = "";
  142.                     var attrMarker = hash.length;
  143.                     var i = 0;
  144.                     for (i; i < hash.length; i++) {
  145.                         var h = hash[i];
  146.                         if (i < 2) {
  147.                             if (i === 0) {
  148.                                 actionName = h;
  149.                             } else if (parseInt(h, 10) != h && h.indexOf('=') == -1) {
  150.                                 actionName += h.substr(0, 1).toUpperCase() + h.substr(1);
  151.                             } else {
  152.                                 attrMarker = i;
  153.                                 break;
  154.                             }
  155.                         } else {
  156.                             attrMarker = i;
  157.                             break;
  158.                         }
  159.                     }
  160.  
  161.                     var attr = hash.slice(attrMarker);
  162.  
  163.                     if (this[actionName + 'Action']) {
  164.                         this[actionName + 'Action'].apply(this, attr);
  165.                         // save last page to return to by default later
  166.                         $.storage.set('mailer/hash', hash.join('/'));
  167.                     } else {
  168.                         if (console) {
  169.                             console.log('Invalid action name:', actionName + 'Action');
  170.                         }
  171.                     }
  172.                 } else {
  173.                     this.defaultAction();
  174.                 }
  175.             } else {
  176.                 this.defaultAction();
  177.             }
  178.  
  179.             $(window).trigger('wa.dispatched');
  180.         },
  181.  
  182.         //
  183.         // Actions called from this.dispatch()
  184.         //
  185.  
  186.         defaultAction: function () {
  187.             this.campaignsNewAction();
  188.         },
  189.  
  190.         // List of templates
  191.         templatesAction: function () {
  192.             this.load("?module=templates");
  193.         },
  194.  
  195.         // Import template from archive page
  196.         templatesImportAction: function () {
  197.             this.load("?module=templates&action=import1");
  198.         },
  199.  
  200.         // Template editor
  201.         templateAction: function (id) {
  202.             this.load("?module=templates&action=edit&id=" + id);
  203.         },
  204.  
  205.         // New template page
  206.         templatesAddAction: function () {
  207.             this.load("?module=templates&action=add");
  208.         },
  209.  
  210.         // New campaign page: select template to use
  211.         campaignsNewAction: function () {
  212.             this.load("?module=campaigns&action=step0");
  213.         },
  214.  
  215.         // Campaign editor: subject and body
  216.         campaignsLetterAction: function (campaign_id, template_id) {
  217.             if (campaign_id && campaign_id !== 'new') {
  218.                 this.load("?module=campaigns&action=step1&campaign_id=" + campaign_id);
  219.                 return;
  220.             }
  221.  
  222.             var template = '';
  223.             if (template_id) {
  224.                 template = '&template_id=' + template_id;
  225.             }
  226.             this.load("?module=campaigns&action=step1" + template);
  227.         },
  228.  
  229.         // Campaign editor: recipients selection page
  230.         campaignsRecipientsAction: function (p) {
  231.             this.load("?module=campaigns&action=recipients&campaign_id=" + p);
  232.         },
  233.  
  234.         // Campaign editor: settings
  235.         campaignsSendAction: function (campaign_id) {
  236.             this.load("?module=campaigns&action=settings&campaign_id=" + campaign_id);
  237.         },
  238.  
  239.         // Report page for campaign sent or being sent
  240.         campaignsReportAction: function (campaign_id) {
  241.             this.load("?module=campaigns&action=report&campaign_id=" + campaign_id);
  242.         },
  243.  
  244.         // Options used for campaign sent or being sent
  245.         campaignsOptionsAction: function (campaign_id) {
  246.             this.load("?module=campaigns&action=settingsReadOnly&campaign_id=" + campaign_id);
  247.         },
  248.  
  249.         // List of campaigns currently sending
  250.         campaignsSendingAction: function () {
  251.             this.load("?module=campaigns&action=sending");
  252.         },
  253.  
  254.         // List of campaigns successfully sent
  255.         campaignsArchiveAction: function (start, order) {
  256.             var search = decodeURIComponent($.wa.mailer.getHash().substr(('#/campaigns/archive/' + start + '/' + order + '/').length).replace('/', '')) || '';
  257.             if (search) {
  258.                 search = '&search=' + encodeURIComponent(search);
  259.             }
  260.             start = start || '';
  261.             if (start) {
  262.                 start = '&start=' + encodeURIComponent(start);
  263.             }
  264.             order = order || $.storage.get('mailer/archive_order') || '';
  265.             if (order) {
  266.                 $.storage.set('mailer/archive_order', order);
  267.                 order = '&order=' + encodeURIComponent(order);
  268.             }
  269.             this.load("?module=campaigns&action=archive" + start + search + order);
  270.         },
  271.  
  272.         // List of contacts unsubscribed from campaigns
  273.         unsubscribedAction: function (start, records, order) {
  274.             var search = decodeURIComponent($.wa.mailer.getHash().substr(('#/unsubscribed/' + start + '/' + records + '/' + order + '/').length).replace('/', '')) || '';
  275.             if (search) {
  276.                 search = '&search=' + encodeURIComponent(search);
  277.             }
  278.             start = start || '';
  279.             if (start) {
  280.                 start = '&start=' + encodeURIComponent(start);
  281.             }
  282.             records = records || $.storage.get('mailer/unsubscribed_records') || '';
  283.             if (records) {
  284.                 records = '&records=' + encodeURIComponent(records);
  285.             }
  286.             order = order || $.storage.get('mailer/unsubscribed_order') || '';
  287.             if (order) {
  288.                 $.storage.set('mailer/unsubscribed_order', order);
  289.                 order = '&order=' + encodeURIComponent(order);
  290.             }
  291.             this.load("?module=unsubscribed&action=list" + start + search + order + records);
  292.         },
  293.  
  294.         // List of emails used to have delivering errors in the past
  295.         undeliverableAction: function (start, records, order) {
  296.             var search = decodeURIComponent($.wa.mailer.getHash().substr(('#/undeliverable/' + start + '/' + records + '/' + order + '/').length).replace('/', '')) || '';
  297.             if (search) {
  298.                 search = '&search=' + encodeURIComponent(search);
  299.             }
  300.             start = start || '';
  301.             if (start) {
  302.                 start = '&start=' + encodeURIComponent(start);
  303.             }
  304.             records = records || $.storage.get('mailer/undeliverable_records') || '';
  305.             if (records) {
  306.                 records = '&records=' + encodeURIComponent(records);
  307.             }
  308.             order = order || $.storage.get('mailer/undeliverable_order') || '';
  309.             if (order) {
  310.                 $.storage.set('mailer/undeliverable_order', order);
  311.                 order = '&order=' + encodeURIComponent(order);
  312.             }
  313.             this.load("?module=undeliverable&action=list" + start + search + order + records);
  314.         },
  315.  
  316.         subscribersAction: function (list_id, start, order, records) {
  317.             this.subscribersListAction(list_id, start, order, records);
  318.         },
  319.  
  320.         subscribersFormAction: function (form_id) {
  321.             if (form_id === 'new') {
  322.                 form_id = -1;
  323.             }
  324.             this.load("?module=subscribers&form_id=" + form_id);
  325.         },
  326.  
  327.         // Show list of subscribers
  328.         subscribersListAction: function (list_id, start, order, records) {
  329.             var search = decodeURIComponent($.wa.mailer.getHash().substr(('#/subscribers/list/' + list_id + '/' + start + '/' + order + '/' + records + '/').length).replace('/', '')) || '';
  330.             if (search) {
  331.                 search = '&search=' + encodeURIComponent(search);
  332.             }
  333.             start = start || '';
  334.             if (start) {
  335.                 start = '&start=' + encodeURIComponent(start);
  336.             }
  337.             records = records || $.storage.get('mailer/subscribers_records') || '';
  338.             if (records) {
  339.                 records = '&records=' + encodeURIComponent(records);
  340.             }
  341.             order = order || $.storage.get('mailer/subscribers_order') || '';
  342.             if (order) {
  343.                 $.storage.set('mailer/subscribers_order', order);
  344.                 order = '&order=' + encodeURIComponent(order);
  345.             }
  346.  
  347.             list_id = list_id || '';
  348.             if (list_id === 'new') {
  349.                 list_id = '&id=-1';
  350.             }
  351.             else if (list_id) {
  352.                 list_id = '&id=' + list_id;
  353.             }
  354.  
  355.             this.load("?module=subscribers" + list_id + start + search + order + records);
  356. //        this.load("?module=subscribers&action=list"+list_id+start+search+order);
  357.         },
  358.  
  359.         subscribersoldAction: function (start, order) {
  360.             var search = decodeURIComponent($.wa.mailer.getHash().substr(('#/subscribersold/' + start + '/' + order + '/').length).replace('/', '')) || '';
  361.             if (search) {
  362.                 search = '&search=' + encodeURIComponent(search);
  363.             }
  364.             start = start || '';
  365.             if (start) {
  366.                 start = '&start=' + encodeURIComponent(start);
  367.             }
  368.             order = order || $.storage.get('mailer/subscribers_order') || '';
  369.             if (order) {
  370.                 $.storage.set('mailer/subscribers_order', order);
  371.                 order = '&order=' + encodeURIComponent(order);
  372.             }
  373.  
  374.             this.load("?module=subscribers&action=listold" + start + search + order);
  375.         },
  376.  
  377.         // Test message with SpamAssassin
  378.         spamtestAction: function (id) {
  379.             this.load("?module=spamtest&action=assassin&id=" + id);
  380.         },
  381.  
  382.         designAction: function(params) {
  383.             if (params) {
  384.                 if ($('#wa-design-container').length) {
  385.                     waDesignLoad();
  386.                 } else {
  387.                     this.load('?module=design', function() {
  388.                         waDesignLoad(params);
  389.                     });
  390.                 }
  391.             } else {
  392.                 this.load('?module=design', function() {
  393.                     waDesignLoad('');
  394.                 });
  395.             }
  396.         },
  397.  
  398.         designThemesAction: function (params) {
  399.             if ($('#wa-design-container').length) {
  400.                 waDesignLoad();
  401.             } else {
  402.                 $("#content").load('?module=design', function () {
  403.                     waDesignLoad();
  404.                 });
  405.             }
  406.         },
  407.  
  408.         pluginsAction: function(params) {
  409.             if (!$('#wa-plugins-container').length) {
  410.                 this.load("?module=plugins");
  411.             } else {
  412.                 this.dispatch('#/plugins/' + params);
  413.             }
  414.         },
  415.  
  416.         //
  417.         // Helper functions
  418.         //
  419.  
  420.         /* Show "Search results" link above the campaign page, if needed. */
  421.         showLastSearchBreadcrumb: function (campaign_id) {
  422.             if (!($.wa.mailer.showLastSearchBreadcrumb.last_search_ids || {})[campaign_id]) {
  423.                 return false;
  424.             }
  425.             var div = $('#content .m-envelope-stripes-2 .m-core-header');
  426.             var a = div.find('a.last-search');
  427.             if (!a.length) {
  428.                 a = $('<a href="" class="no-underline">' + $_('Search results') + '</a>');
  429.                 div.append('<i class="icon10 larr"></i>').append(a);
  430.             }
  431.             a.attr('href', '#/campaigns/archive/0/!id/' + encodeURIComponent($.wa.mailer.showLastSearchBreadcrumb.last_search_string || '') + '/').show();
  432.             return true;
  433.         },
  434.  
  435.         // Helper used across the campaign editor pages to save campaign via XHR.
  436.         saveCampaign: function (form, button, callback, no_saved_hint) {
  437.             var were_disabled = button.attr('disabled');
  438.             button.attr('disabled', true).siblings('.process-message').remove();
  439.             var process_message = $('<span class="process-message"><i class="icon16 loading" style="margin:6px 0 0 .5em"></i></span>');
  440.             button.parent().append(process_message);
  441.             $.post(form.attr('action'), form.serialize(), function (r) {
  442.                 if (!were_disabled) {
  443.                     button.attr('disabled', false);
  444.                 }
  445.                 if (r.status == 'ok') {
  446.                     var message_id = r.data;
  447.                     form.find('input[name="id"]').val(message_id);
  448.                     if (no_saved_hint) {
  449.                         process_message.remove();
  450.                     } else {
  451.                         process_message.find('.loading').removeClass('loading').addClass('yes');
  452.                         process_message.append('<span> ' + $_('Saved') + '</span>');
  453.                         process_message.animate({opacity: 0}, 2000, function () {
  454.                             process_message.remove();
  455.                         });
  456.                     }
  457.                 } else {
  458.                     process_message.find('.loading').removeClass('loading').addClass('exclamation');
  459.                     console.log('Error saving campaign:', r);
  460.                 }
  461.  
  462.                 if (callback) {
  463.                     callback.call(this, r);
  464.                 }
  465.             }, 'json');
  466.             return false;
  467.         },
  468.  
  469.         // Helper to show number of campaign recipients in right sidebar
  470.         showRecipientsInRightSidebar: function (rnum) {
  471.             if (rnum && rnum !== '0' && rnum !== 'null') {
  472.                 if (rnum === true) {
  473.                     rnum = '1';
  474.                     $('#right-sidebar-recipients-number').hide();
  475.                 } else {
  476.                     $('#right-sidebar-recipients-number').show();
  477.                 }
  478.                 $('#right-sidebar-recipients-number').html('(' + rnum + ')');
  479.                 //$('#right-sidebar-recipients-number').html($_("Total selected:")+' '+rnum).removeClass('red').addClass('green');
  480.             } else {
  481.                 rnum = '0';
  482.                 $('#right-sidebar-recipients-number').html('(' + rnum + ')').show();
  483.                 //$('#right-sidebar-recipients-number').html($_("not specified yet")).removeClass('green').addClass('red').show();
  484.             }
  485.         },
  486.  
  487.         /** Gracefully reload sidebar. */
  488.         reloadSidebar: function () {
  489.             $.get("?module=backend&action=sidebar", null, function (response) {
  490.                 var sb = $("#wa-app > .sidebar");
  491.                 sb.css('height', sb.height() + 'px').html(response).css('height', ''); // prevents blinking in some browsers
  492.                 $.wa.mailer.highlightSidebar();
  493.                 $.wa.mailer.restoreCollapsibleStatusInSidebar();
  494.             });
  495.         },
  496.  
  497.         /**
  498.          * Reload sidebar when there are campaigns currently sending.
  499.          * Called every minute, see init().
  500.          */
  501.         updateSendingSidebar: function () {
  502.             if ($.wa.mailer.sending_count && $.wa.mailer.getHash().substr(0, 19) != '#/campaigns/archive') {
  503.                 $.wa.mailer.reloadSidebar();
  504.             }
  505.             setTimeout($.wa.mailer.updateSendingSidebar, 60000);
  506.         },
  507.  
  508.         /** Initialize Redactor.js in template editor page and in step 1 page. */
  509.         initWYSIWYG: function (textarea, saveButton, height, csrf, lang) {
  510.             if (!RedactorPlugins) var RedactorPlugins = {};
  511.  
  512.             textarea.waEditor({
  513.                 lang: lang,
  514.                 saveButton: saveButton,
  515.                 iframe: true,
  516.                 minHeight: height,
  517.                 buttons: ['format', 'bold', 'italic', 'underline', 'deleted', 'lists', 'image',
  518.                     'table', 'link', 'alignment', 'horizontalrule',  'fontcolor', 'fontsize', 'fontfamily', 'vars'],
  519.                 plugins: ['table', 'fontcolor', 'fontsize', 'fontfamily', 'vars'],
  520.                 imageUpload: '?module=files&action=uploadimage&filelink=1',
  521.                 imageUploadFields: {
  522.                     '_csrf': csrf
  523.                 }
  524.             });
  525.             textarea.data('ace').setOption("minLines", null);
  526.             textarea.data('ace').setOption("maxLines", null);
  527.             textarea.data('ace').setAutoScrollEditorIntoView(false);
  528.  
  529.             this.autoresizeWYSIWYG(height, textarea);
  530.  
  531.             return textarea;
  532.         },
  533.  
  534.         resizeWYSIWYG: function (min_height, random, resizer, textarea) {
  535.             if ($('.wa-editor-core-wrapper').length === 0) {
  536.                 $(window).off('resize', resizer);
  537.                 return;
  538.             }
  539.             var $window = $(window),
  540.                 window_h = $window.height(),
  541.                 button_container_h = $('#editor-step-1-buttons').outerHeight(),
  542.                 editor_offset_offset = $('.wa-editor-core-wrapper').offset(),
  543.                 editor_offset_top = editor_offset_offset.top,
  544.                 ace_padding_h = $('.ace').outerHeight() - $('.ace').height(),
  545.                 editor_toolbar_h = $('.redactor_toolbar').outerHeight(),
  546.                 editor_h = window_h - button_container_h - editor_offset_top - 5;
  547.  
  548.             if ($.wa.mailer.step1_random != random) {
  549.                 $(window).off('resize', resizer);
  550.                 return;
  551.             }
  552.             if (editor_h > min_height) {
  553.                 $('.wa-editor-core-wrapper, .ace').outerHeight(editor_h);
  554.                 $('.redactor_box iframe').height(editor_h - editor_toolbar_h - 5);
  555.                 $('.ace_editor').height(editor_h - ace_padding_h).css('max-height', editor_h - ace_padding_h);
  556.                 if (textarea !== undefined) {
  557.                     textarea.data('ace').resize(true);
  558.                 }
  559.             }
  560.         },
  561.  
  562.         /** Resize the editor depending on window height */
  563.         autoresizeWYSIWYG: function (min_h, textarea) {
  564.             var min_h = min_h + $('#wa-app .m-editor .block.double-padded:last').outerHeight(),
  565.                 resizeTimer = null,
  566.                 r = Math.random();
  567.  
  568.             $.wa.mailer.step1_random = r;
  569.  
  570.             var resizer = function () {
  571.                 clearTimeout(resizeTimer);
  572.                 resizeTimer = setTimeout(function () {
  573.                     $.wa.mailer.resizeWYSIWYG(min_h, r, resizer, textarea);
  574.                 }, 100);
  575.             };
  576.  
  577.             $(window).on('resize', resizer);
  578.  
  579.             setTimeout(function () {
  580.                 $.wa.mailer.resizeWYSIWYG(min_h, r, null, textarea);
  581.             }, 300);
  582. //
  583. //        var statusbar = workzone.siblings('.statusbar');
  584. //        var r = Math.random();
  585. //        $.wa.mailer.step1_random = r;
  586. //        var resizeEditor = function() {
  587. //            // Unbind self if user left current page
  588. //            var buttons = $('#editor-step-1-buttons');
  589. //            if ($.wa.mailer.step1_random != r) {
  590. //                $(window).unbind('resize', resizeEditor);
  591. //                return;
  592. //            }
  593. //
  594. //            // Calculate and update editor height
  595. //            var new_workzone_height = $(window).height() - workzone.offset().top - (statusbar.outerHeight() || statusbar_height || 0) - buttons.outerHeight() - 5;
  596. //            new_workzone_height = Math.max(300, new_workzone_height);
  597. //            workzone.height(new_workzone_height).find('iframe').height(new_workzone_height);
  598. //            workzone.find('textarea').height(new_workzone_height - 12);
  599. //            workzone.find('.CodeMirror-scroll').height(new_workzone_height);
  600. //        };
  601. //        $(window).resize(resizeEditor);
  602. //        setTimeout(resizeEditor, 1);
  603.         },
  604.  
  605.         style_html: function (v) {
  606.             if (typeof style_html === 'function') {
  607.                 return style_html(v, {
  608.                     max_char: 0
  609.                 });
  610.             }
  611.             return v;
  612.         },
  613.  
  614.         /** Animates campaign report progressbar and countdown. */
  615.         updateCampaignReportProgress: function (campaign_id, percent_complete_precise, campaign_estimated_finish_timestamp, campaign_send_datetime, php_time, campaign_paused) {
  616.  
  617.             var start_ts = (new Date()).getTime();
  618.             $.wa.mailer.random = start_ts;
  619.  
  620.             // Delay between page reloads in milliseconds and seconds
  621.             var RELOAD_TIME = 5000;
  622.             var RELOAD_TIME_SEC = RELOAD_TIME / 1000;
  623.  
  624.             // Set up progressbar
  625.             var previous_time = $.storage.get('mailer/campaign/' + campaign_id + '/time');
  626.             var previous_value = $.storage.get('mailer/campaign/' + campaign_id + '/value');
  627.             var current_time = php_time;
  628.             var current_value = percent_complete_precise;
  629.             if (!previous_time) {
  630.                 previous_time = campaign_send_datetime;
  631.                 previous_value = 0;
  632.             }
  633.             var set_time, set_value;
  634.             if (current_time - previous_time < RELOAD_TIME_SEC) {
  635.                 set_time = current_time;
  636.                 set_value = previous_value;
  637.             } else {
  638.                 set_time = current_time - RELOAD_TIME_SEC;
  639.                 set_value = previous_value + (current_value - previous_value) * (current_time - previous_time - RELOAD_TIME_SEC) / (current_time - previous_time);
  640.             }
  641.  
  642.             // When campaign is paused, everything is simple.
  643.             if (campaign_paused) {
  644.                 $('#progressbar-text').text('' + Math.round(current_value) + '%');
  645.                 $('#progressbar-status').css('width', '' + Math.round(current_value) + '%');
  646.                 $.storage.set('mailer/campaign/' + campaign_id + '/value', current_value);
  647.                 $('#campaign-sending-time-left').parent().hide();
  648.                 return;
  649.             }
  650.  
  651.             // Make sure progress-bar always moves (even if in reality there's no visible progress yet)
  652.             if (current_value <= set_value) {
  653.                 if (set_value > previous_value) {
  654.                     // This is safe: user didn't see anything greater than previous_value yet
  655.                     set_value = Math.max(set_value - 1, previous_value);
  656.                 } else {
  657.                     // This is a cheat: real progress is less than what we'll show.
  658.                     // Although after about 50% this makes current_value = set_value
  659.                     current_value = set_value + Math.max(0, 0.4 - Math.log(set_value + 2) / 10) * 2;
  660.                 }
  661.             }
  662.  
  663.             // Animate progressbar
  664.             var progressbar_text = $('#progressbar-text').text('' + Math.round(set_value) + '%');
  665.             $('#progressbar-status').width('' + set_value + '%').animate({
  666.                 width: '' + current_value + '%'
  667.             }, {
  668.                 easing: 'linear',
  669.                 duration: RELOAD_TIME * 1.1,
  670.                 step: function (value) {
  671.                     var current_value = progressbar_text.text();
  672.                     $.storage.set('mailer/campaign/' + campaign_id + '/value', value);
  673.                     if (current_value && current_value.replace(/[^0-9]/g, '') - 0 < value) {
  674.                         if (!value || value <= 1) {
  675.                             value = 'ā‰ˆ1';
  676.                         } else {
  677.                             value = Math.round(value);
  678.                         }
  679.                         progressbar_text.text('' + value + '%');
  680.                     }
  681.                 }
  682.             });
  683.  
  684.             // Helper to pad string with zeros
  685.             var strPad = function (i, l, s) {
  686.                 var o = i.toString();
  687.                 if (!s) {
  688.                     s = '0';
  689.                 }
  690.                 while (o.length < l) {
  691.                     o = s + o;
  692.                 }
  693.                 return o;
  694.             };
  695.  
  696.             // Helper to format time in [hh:]mm:ss format.
  697.             var formatTime = function (fullseconds) {
  698.                 if (fullseconds < 60) {
  699.                     return $_('%ds').replace('%d', fullseconds);
  700.                 } else if (fullseconds < 60 * 60) {
  701.                     var seconds = fullseconds % 60;
  702.                     var minutes = Math.floor(fullseconds / 60);
  703.                     return $_('%dm').replace('%d', minutes) + ' ' + $_('%ds').replace('%d', strPad(seconds, 2));
  704.                 } else {
  705.                     var seconds = fullseconds % 60;
  706.                     var minutes = Math.floor(fullseconds / 60) % 60;
  707.                     var hours = Math.floor(fullseconds / (60 * 60));
  708.                     return $_('%dh').replace('%d', hours) + ' ' + $_('%dm').replace('%d', strPad(minutes, 2)) + ' ' + $_('%ds').replace('%d', strPad(seconds, 2));
  709.                 }
  710.             };
  711.  
  712.             // Helper to update total campaign duration and time left
  713.             var updateDuration = function () {
  714.                 if (campaign_estimated_finish_timestamp) {
  715.                     var current_time_ms = (new Date()).getTime();
  716.                     var seconds_left = Math.max(0, campaign_estimated_finish_timestamp - current_time_ms / 1000);
  717.  
  718.                     var old = $.wa.mailer.campaign_countdown;
  719.                     if (old && old.campaign_id == campaign_id) {
  720.                         // When old estimate differs from reality too much (more than 20%), then reset the estimate
  721.                         if (old.seconds_left > 10 && ((old.seconds_left * 1.2 < seconds_left) || (seconds_left * 1.2 < old.seconds_left))) {
  722.                             $('#campaign-sending-time-left').html('<i class="icon16 loading"></i>');
  723.                             $.wa.mailer.campaign_countdown = null;
  724.                             return;
  725.                         }
  726.                         // Otherwise, decrement old predition by 1 "fake second" whose length depends on difference
  727.                         // between old and new estimate. This makes the timer run smoothly.
  728.                         else if (old.seconds_left > 0 && current_time_ms - old.current_time_ms < 10000) {
  729.                             // How much real time (in seconds) passed since `old` update
  730.                             var time_diff = Math.min(seconds_left, (current_time_ms - old.current_time_ms) / 1000);
  731.                             // What would the old estimate be if we simply decrement it by real time_diff
  732.                             var bad_estimate = old.seconds_left - time_diff;
  733.                             // New estimate is calculated from previous one (which makes the timer run smoothly)
  734.                             // with a small addition depending on how far our old estimate is from new, updated estimate.
  735.                             seconds_left = bad_estimate + (seconds_left - bad_estimate) * time_diff / old.seconds_left;
  736.                             // Make sure the estimate never goes beyond zero and never increases.
  737.                             seconds_left = Math.max(0, Math.min(seconds_left, old.seconds_left));
  738.                         }
  739.                     }
  740.                     $.wa.mailer.campaign_countdown = {
  741.                         campaign_id: campaign_id,
  742.                         current_time_ms: current_time_ms,
  743.                         seconds_left: seconds_left
  744.                     };
  745.                     $('#campaign-sending-time-left').html(formatTime(Math.floor(seconds_left))).parent().show();
  746.                 }
  747.             };
  748.             updateDuration();
  749.  
  750.             // Updates duration counters and progressbar last update time in localStorage
  751.             var interval = setInterval(function () {
  752.                 if ($.wa.mailer.random != start_ts) {
  753.                     window.clearInterval(interval);
  754.                     return;
  755.                 }
  756.                 var time_passed = (new Date()).getTime() - start_ts;
  757.                 var time;
  758.                 if (time_passed >= RELOAD_TIME * 1.1) {
  759.                     window.clearInterval(interval);
  760.                     interval = null;
  761.                     time = current_time;
  762.                 } else {
  763.                     time = set_time + (current_time - set_time) * time_passed / (RELOAD_TIME * 1.1);
  764.                 }
  765.                 $.storage.set('mailer/campaign/' + campaign_id + '/time', time);
  766.                 updateDuration();
  767.             }, 250);
  768.  
  769.             // Reload once every several seconds to keep numbers and progressbar fresh
  770.             setTimeout(function () {
  771.                 //if ($.wa.mailer.random != start_ts) {
  772.                 //    window.clearInterval(reloadInterval);
  773.                 //    return;
  774.                 //}
  775.                 $.get('?module=campaigns&action=reportUpdate&campaign_id=' + campaign_id,
  776.                     function (html) {
  777.                         if ($.wa.mailer.random != start_ts) {
  778.                             //window.clearInterval(reloadInterval);
  779.                             return;
  780.                         }
  781.                         $('#update-container').html(html);
  782.                     });
  783.             }, RELOAD_TIME);
  784.         },
  785.  
  786.         /**
  787.          * Remove from localStorage any data that no longer needed for archived campaign,
  788.          * Pand remove progressbar from report page.
  789.          */
  790.         cleanupSentCampaign: function (campaign_id) {
  791.             $.storage.del('mailer/campaign/' + campaign_id + '/time');
  792.             $.storage.del('mailer/campaign/' + campaign_id + '/value');
  793.  
  794.             var progressbar_text = $('#progressbar-text');
  795.             if ($('#report-wrapper .progressbar').length > 0) {
  796.                 $('#progressbar-status').animate({
  797.                     width: '100%'
  798.                 }, {
  799.                     easing: 'linear',
  800.                     duration: 3000,
  801.                     complete: function () {
  802.                         $.wa.mailer.redispatch();
  803.                     },
  804.                     step: function (value) {
  805.                         if (!value || value <= 0.1) {
  806.                             value = 'ā‰ˆ0';
  807.                         } else if (value <= 1) {
  808.                             value = '<1';
  809.                         } else {
  810.                             value = Math.round(value);
  811.                         }
  812.                         progressbar_text.text('' + value + '%');
  813.                     }
  814.                 });
  815.             }
  816.         },
  817.  
  818.         /** Draws pie chart for campaign report in #pie-graph. */
  819.         drawReportPie: function (app_static_url, stats) {
  820.             var r = Raphael($('#pie-graph').empty()[0]);
  821.             var pie = r.piechart(
  822.                 120, 120, 100, // center_x, center_y, radius
  823.                 stats, {
  824.                     colors: [
  825.                         'green',
  826.                         'blue',
  827.                         'red',
  828. //                    'url('+app_static_url+'img/strokegreen.png)',
  829. //                    'url('+app_static_url+'img/strokered.png)',
  830. //                    'white',
  831.                         '#ccc'
  832.                     ],
  833.                     no_sort: true,
  834.                     minPercent: -1,
  835.                     matchColors: true
  836.                 }
  837.             );
  838.             $.wa.mailer.pie = pie; // debugging helper
  839.  
  840.             pie.hover(function () {
  841.                 this.sector.stop().transform('S' + [1.1, 1.1, this.cx, this.cy].join(','));
  842.                 var dot = $('#pie-legend .legend-dot')[this.value.order];
  843.                 if (!dot) {
  844.                     return;
  845.                 }
  846.                 var dot = $(dot);
  847.                 var large_dot = $('<b class="large legend-dot"></b>').appendTo(dot);
  848.                 large_dot.css({
  849.                     'background-color': dot.css('background-color'),
  850.                     'background-image': dot.css('background-image')
  851.                 });
  852.             }, function () {
  853.                 this.sector.animate({transform: 's1 1 ' + this.cx + ' ' + this.cy}, 500, "bounce");
  854.                 $('#pie-legend .legend-dot .legend-dot').remove();
  855.             });
  856.         },
  857.  
  858.         /** Helper to add submit handler. Disables submit button until response is received. */
  859.         onSubmit: function (form, callback) {
  860.             form.submit(function () {
  861.                 var $f = $(this);
  862.                 var submits = $f.find("input[type=submit]:enabled").attr('disabled', 'disabled');
  863.                 $.post($f.attr('action'), $f.serialize(), function (data, textStatus, jqXHR) {
  864.                     submits.attr('disabled', false);
  865.                     callback.call(this, data, textStatus, jqXHR);
  866.                 }, "json");
  867.                 return false;
  868.             });
  869.         },
  870.  
  871.         /** Load HTML content from url and put it into main #content div */
  872.         load: function (url, callback, params) {
  873.             this.showLoading();
  874.             params = params || null;
  875.  
  876.             var r = Math.random();
  877.             $.wa.mailer.random = r;
  878.             $.get(url, params, function (result) {
  879.                 if ($.wa.mailer.random != r) {
  880.                     // too late: user clicked something else.
  881.                     return;
  882.                 }
  883.                 $("#content").addClass('shadowed').addClass('blank').html(result);
  884.                 $.wa.mailer.hideLoading();
  885.                 $('html, body').animate({scrollTop: 0}, 200);
  886.                 if (callback) {
  887.                     callback.call(this);
  888.                 }
  889.             });
  890.         },
  891.  
  892.         /** Show loading indicator in the header */
  893.         showLoading: function () {
  894.             var h1 = $('h1:visible').first();
  895.             if (h1.size() <= 0) {
  896.                 $('#c-core-content .block').first().prepend('<i class="icon16 loading"></i>');
  897.                 return;
  898.             }
  899.             if (h1.find('.loading').show().size() > 0) {
  900.                 return;
  901.             }
  902.             h1.append('<i class="icon16 loading"></i>');
  903.         },
  904.  
  905.         /** Hide all loading indicators in h1 headers */
  906.         hideLoading: function () {
  907.             $('h1 .loading').hide();
  908.         },
  909.  
  910.         /** Add .selected css class to li with <a> whose href attribute matches current hash.
  911.          * If no such <a> found, then the first partial match is highlighted.
  912.          * Hashes are compared after this.cleanHash() applied to them. */
  913.         highlightSidebar: function (sidebar) {
  914.             var currentHash = this.cleanHash(location.hash);
  915.             var partialMatch = false;
  916.             var partialMatchLength = 2;
  917.             var match = false;
  918.             var index = 0;
  919.  
  920.             if (!sidebar) {
  921.                 sidebar = $('#wa-app > .sidebar');
  922.             }
  923.  
  924.             sidebar.find('li a').each(function (k, v) {
  925.                 v = $(v);
  926.                 index = k;
  927.                 if (!v.attr('href')) {
  928.                     return;
  929.                 }
  930.                 var h = $.wa.mailer.cleanHash(v.attr('href'));
  931.  
  932.                 // Perfect match?
  933.                 if (h == currentHash) {
  934.                     match = v;
  935.                     return false;
  936.                 }
  937.  
  938.                 // Partial match? (e.g. for urls that differ in paging only)
  939.                 if (h.length > partialMatchLength && currentHash.substr(0, h.length) === h) {
  940.                     partialMatch = v;
  941.                     partialMatchLength = h.length;
  942.                 }
  943.             });
  944.  
  945.             if (!match && partialMatch) {
  946.                 match = partialMatch;
  947.             }
  948.  
  949.             if (match) {
  950.                 sidebar.find('.selected').removeClass('selected');
  951.                 if (index > 9) {
  952.                     $('#b-all-drafts a').trigger('click');
  953.                 }
  954.                 // Only highlight items that are outside of dropdown menus
  955.                 if (match.parents('ul.dropdown').size() <= 0) {
  956.                     var p = match.parent();
  957.                     while (p.size() > 0 && p[0].tagName.toLowerCase() != 'li') {
  958.                         p = p.parent();
  959.                     }
  960.                     if (p.size() > 0) {
  961.                         p.addClass('selected');
  962.                     }
  963.                 }
  964.             }
  965.         },
  966.  
  967.         /** Collapse sections in sidebar according to status previously set in $.storage */
  968.         restoreCollapsibleStatusInSidebar: function () {
  969.             // collapsibles
  970.             $("#wa-app .collapse-handler").each(function (i, el) {
  971.                 $.wa.mailer.collapseSidebarSection(el, 'restore');
  972.             });
  973.         },
  974.  
  975.         /** Collapse/uncollapse section in sidebar. */
  976.         collapseSidebarSection: function (el, action) {
  977.             if (!action) {
  978.                 action = 'coollapse';
  979.             }
  980.             el = $(el);
  981.             if (el.size() <= 0) {
  982.                 return;
  983.             }
  984.  
  985.             var arr = el.find('.darr, .rarr');
  986.             if (arr.size() <= 0) {
  987.                 arr = $('<i class="icon16 darr">');
  988.                 el.prepend(arr);
  989.             }
  990.             var newStatus;
  991.             var id = el.attr('id');
  992.             var oldStatus = arr.hasClass('darr') ? 'shown' : 'hidden';
  993.  
  994.             var hide = function () {
  995.                 el.nextAll('.collapsible, .collapsible1').first().hide();
  996.                 arr.removeClass('darr').addClass('rarr');
  997.                 newStatus = 'hidden';
  998.             };
  999.             var show = function () {
  1000.                 var $collapsible = el.nextAll('.collapsible, .collapsible1').first();
  1001.                 $collapsible.show();
  1002.                 $collapsible.find('[data-list="hidden"]').hide();
  1003.                 $collapsible.find('[data-list="shown"]').show();
  1004.                 arr.removeClass('rarr').addClass('darr');
  1005.                 newStatus = 'shown';
  1006.             };
  1007.  
  1008.             switch (action) {
  1009.                 case 'toggle':
  1010.                     if (oldStatus == 'shown') {
  1011.                         hide();
  1012.                     } else {
  1013.                         show();
  1014.                     }
  1015.                     break;
  1016.                 case 'restore':
  1017.                     if (id) {
  1018.                         var status = $.storage.get('mailer/collapsible/' + id);
  1019.                         if (status == 'hidden') {
  1020.                             hide();
  1021.                         } else {
  1022.                             show();
  1023.                         }
  1024.                     }
  1025.                     break;
  1026.                 case 'uncollapse':
  1027.                     show();
  1028.                     break;
  1029.                 //case 'collapse':
  1030.                 default:
  1031.                     hide();
  1032.                     break;
  1033.             }
  1034.  
  1035.             // save status in persistent storage
  1036.             if (id && newStatus) {
  1037.                 $.storage.set('mailer/collapsible/' + id, newStatus);
  1038.             }
  1039.         },
  1040.  
  1041.         /**
  1042.          * Helper to make elements (usually table td's) clickable by ctearing <a> elements that wrap their content.
  1043.          * @param elements jQuery collection
  1044.          * @param getLink callback(el) to get href attribute
  1045.          */
  1046.         makeClickable: function (elements, getLink) {
  1047.             elements.each(function () {
  1048.                 var el = $(this);
  1049.                 var a = $('<a href="' + getLink(el) + '" style="color:inherit !important"></a>').html(el.html());
  1050.                 el.empty().append(a);
  1051.             });
  1052.         },
  1053.  
  1054.         /** Current hash. No URI decoding is performed. */
  1055.         getHash: function () {
  1056.             return this.cleanHash();
  1057.         },
  1058.  
  1059.         /** Make sure hash has a # in the begining and exactly one / at the end.
  1060.          * For empty hashes (including #, #/, #// etc.) return an empty string.
  1061.          * Otherwise, return the cleaned hash.
  1062.          * When hash is not specified, current hash is used. No URI decoding is performed. */
  1063.         cleanHash: function (hash) {
  1064.             if (typeof hash == 'undefined') {
  1065.                 // cross-browser way to get current hash as is, with no URI decoding
  1066.                 hash = window.location.toString().split('#')[1] || '';
  1067.             }
  1068.  
  1069.             if (!hash) {
  1070.                 return '';
  1071.             } else if (!hash.length) {
  1072.                 hash = '' + hash;
  1073.             }
  1074.             while (hash.length > 0 && hash[hash.length - 1] === '/') {
  1075.                 hash = hash.substr(0, hash.length - 1);
  1076.             }
  1077.             hash += '/';
  1078.  
  1079.             if (hash[0] != '#') {
  1080.                 if (hash[0] != '/') {
  1081.                     hash = '/' + hash;
  1082.                 }
  1083.                 hash = '#' + hash;
  1084.             } else if (hash[1] && hash[1] != '/') {
  1085.                 hash = '#/' + hash.substr(1);
  1086.             }
  1087.  
  1088.             if (hash == '#/') {
  1089.                 return '';
  1090.             }
  1091.  
  1092.             return hash;
  1093.         },
  1094.  
  1095.  
  1096.         /** Helper to initialize iButtons */
  1097.         iButton: function ($checkboxes, options) {
  1098.             options = options || {};
  1099.             return $checkboxes.each(function () {
  1100.                 var cb = $(this);
  1101.                 var id = cb.attr('id');
  1102.                 if (!id) {
  1103.                     do {
  1104.                         id = 'cb' + Date.now() + '-' + (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
  1105.                     } while (document.getElementById(id));
  1106.                     cb.attr('id', id);
  1107.                 }
  1108.                 if (!options.inside_labels) {
  1109.                     $($.parseHTML(
  1110.                         '<ul class="menu-h ibutton-wrapper">' +
  1111.                         '<li><label class="gray" for="' + id + '">' + (options.labelOff || '') + '</label></li>' +
  1112.                         '<li class="ibutton-li"></li>' +
  1113.                         '<li><label for="' + id + '">' + (options.labelOn || '') + '</label></li>' +
  1114.                         '</ul>'
  1115.                     )).insertAfter(cb).find('.ibutton-li').append(cb);
  1116.                 }
  1117.             }).iButton($.extend({
  1118.                 className: 'mini'
  1119.             }, options, options.inside_labels ? {} : {
  1120.                 labelOn: "",
  1121.                 labelOff: ""
  1122.             }));
  1123.         },
  1124.  
  1125.         /** Helper to insert text into textarea */
  1126.         insertAtCursor: function (myField, myValue) {
  1127.             // IE support
  1128.             if (document.selection) {
  1129.                 myField.focus();
  1130.                 var sel = document.selection.createRange();
  1131.                 sel.text = myValue;
  1132.             }
  1133.             // MOZILLA and others
  1134.             else if (myField.selectionStart || myField.selectionStart == '0') {
  1135.                 var startPos = myField.selectionStart;
  1136.                 var endPos = myField.selectionEnd;
  1137.                 myField.value = myField.value.substring(0, startPos)
  1138.                 + myValue
  1139.                 + myField.value.substring(endPos, myField.value.length);
  1140.                 myField.selectionStart = startPos + myValue.length;
  1141.                 myField.selectionEnd = startPos + myValue.length;
  1142.             } else {
  1143.                 myField.value += myValue;
  1144.             }
  1145.         },
  1146.  
  1147.         /** Shows a confirmation dialog when user tries to navigate away from current page or current hash. */
  1148.         confirmLeave: function (is_relevant, warning_message, confirm_question) {
  1149.             var h, h2, $window = $(window);
  1150.  
  1151.             $window.on('beforeunload', h = function (e) {
  1152.                 if (is_relevant()) {
  1153.                     return warning_message;
  1154.                 }
  1155.             });
  1156.  
  1157.             $window.on('wa_before_dispatched', h2 = function (e) {
  1158.                 if (!is_relevant()) {
  1159.                     $window.off('unload', h).off('wa_before_dispatched', h2);
  1160.                     return;
  1161.                 }
  1162.                 if (!confirm(warning_message + " " + confirm_question)) {
  1163.                     e.preventDefault();
  1164.                 }
  1165.             });
  1166.         },
  1167.  
  1168.         plotGraph: function ($el, data, color, label, dates_range, zeroline, x_tick_interval, xticks) {
  1169.             var getMaxY = function (arr) {
  1170.                 var m = Math.max.apply(null, $.map(arr, function (el, i) {
  1171.                     return parseInt(el[1]);
  1172.                 }));
  1173.                 return Math.round(m + 0.5);
  1174.             };
  1175.             var getYTickInterval = function (max, delimiter) {
  1176.                 delimiter = delimiter ? delimiter : 10;
  1177.                 if (max < delimiter) {
  1178.                     return 1;
  1179.                 } else {
  1180.                     return Math.floor(max / delimiter);
  1181.                 }
  1182.             };
  1183. //        var extend_options = function(colors, labels) {
  1184. ////            var y_max = getMax(data);
  1185. ////            var y_tick_interval = getYTickInterval(y_max);
  1186. ////            opt.axes.yaxis.max = y_max;
  1187. ////            opt.axes.yaxis.tickInterval = y_tick_interval;
  1188. //
  1189. ////            opt.seriesDefaults.renderer = $.jqplot.LineRenderer;
  1190. ////            opt.seriesDefaults.rendererOptions = {};
  1191. //            graph_options.series = [];
  1192. //            $.each(colors, function(i, color) {
  1193. //                graph_options.series[i] = {
  1194. //                    color: colors[i],
  1195. //                    label: ' '
  1196. //                };
  1197. //            });
  1198. //        };
  1199.             var clear_graph = function () {
  1200.                 if ($el.data('jqplot')) {
  1201. //                $('#shown-hours').hide().find('span').text('');
  1202.                     $el.data('jqplot').destroy();
  1203.                 }
  1204.             };
  1205.             var replot_graph = function () {
  1206.                 if ($el.data('jqplot')) {
  1207.                     $el.data('jqplot').replot();
  1208.                 }
  1209.             };
  1210.             var bind_onresize = function () {
  1211.                 $(window).off('resize.mailer.plotGraph').on('resize.mailer.plotGraph', replot_graph);
  1212.             };
  1213.  
  1214.             $.jqplot.config.enablePlugins = true;
  1215.             var graph_options = {
  1216.                 height: 200,
  1217.                 canvasOverlay: {
  1218.                     show: true,
  1219.                     objects: [{
  1220.                         verticalLine: {
  1221.                             name: 'Start',
  1222.                             x: zeroline * 1000,
  1223.                             lineWidth: 3,
  1224.                             yOffset: 0,
  1225.                             color: 'rgb(89, 198, 154)',
  1226.                             shadow: false,
  1227.                             showTooltip: true,
  1228.                             tooltipFormatString: "Start",
  1229.                             tooltipLocation: 'se'
  1230.                         }
  1231.                     }]
  1232.                 },
  1233.                 gridPadding: {
  1234.                     right: 40,
  1235.                     left: 40
  1236.                 },
  1237.                 seriesDefaults: {
  1238.                     pointLabels: {
  1239.                         show: false
  1240.                     },
  1241.                     markerOptions: {
  1242.                         show: true,
  1243.                         size: 5
  1244.                     }
  1245.                 },
  1246.                 series: [
  1247.                     {
  1248.                         color: color,
  1249.                         label: " "
  1250.                     }
  1251.                 ],
  1252.                 grid: {
  1253.                     borderWidth: 1,
  1254.                     shadow: false,
  1255.                     background: '#ffffff',
  1256.                     gridLineColor: '#eeeeee'
  1257.                 },
  1258.                 axes: {
  1259.                     xaxis: {
  1260.                         min: dates_range[0] * 1000,
  1261.                         max: dates_range[1] * 1000,
  1262.                         renderer: $.jqplot.DateAxisRenderer,
  1263. //                  tickInterval: x_tick_interval,
  1264.                         tickOptions: {
  1265.                             formatString: '%b %e%n%R'
  1266.                         },
  1267. //                    ticks: xticks,
  1268.                         numberTicks: 10,
  1269.                         pad: 0,
  1270.                         showTickMarks: false
  1271.                     },
  1272.                     yaxis: {
  1273.                         min: 0,
  1274.                         max: 0,
  1275.                         pad: 0,
  1276.                         tickInterval: 0,
  1277.                         showTickMarks: false
  1278.                     }
  1279.                 },
  1280.                 highlighter: {
  1281.                     show: false
  1282.                 },
  1283.                 cursor: {
  1284.                     zoom: true,
  1285.                     show: true,
  1286.                     showTooltip: true,
  1287.                     looseZoom: true,
  1288.                     followMouse: true,
  1289.                     showVerticalLine: true,
  1290.                     showTooltipDataPosition: true,
  1291.                     useAxesFormatters: true,
  1292.                     tooltipFormatString: '<div style="font-size: 1.3em; background-color: #ffffff;padding: 5px;"><div>%s%s<br/><span style="color:' + color + '">' + label + ': <b style="color:#000">%s</b></span></div>',
  1293.                     constrainZoomTo: 'x'
  1294.                 }
  1295.             };
  1296.             graph_options.axes.yaxis.max = getMaxY(data);
  1297.             graph_options.axes.yaxis.tickInterval = getYTickInterval(graph_options.axes.yaxis.max);
  1298. //        graph_options.axes.xaxis.tickInterval = x_tick === 1 ? x_tick+' minute' : x_tick+' minutes';
  1299.  
  1300.             clear_graph();
  1301. //        extend_options([color], [label]);
  1302. //        console.log([data]);
  1303. //        console.log(graph_options);
  1304.             $el.jqplot([data], graph_options);
  1305.  
  1306.             bind_onresize();
  1307. //        return graph;
  1308.         },
  1309.  
  1310.         getAndPlotData: function (campaign_id, graph_id, start, end, quantum, online) {
  1311.             $.get('?module=campaigns&action=reportGraphData&campaign_id=' + campaign_id,
  1312.                 {
  1313.                     intervalstart: start,
  1314.                     intervalend: end,
  1315.                     status: graph_id,
  1316.                     quantum: quantum
  1317.                 },
  1318.                 function (r) {
  1319.                     if (r.status === 'ok') {
  1320.                         var graph = r.data,
  1321.                             $current_link_pie_legend = $('#report-wrapper').find('.pie-graph-legend .show-recipients-link.disabled:first'),
  1322.                             graph_color = $current_link_pie_legend.data('graph-color'),
  1323.                             graph_label = $current_link_pie_legend.data('graph-label'),
  1324.                             graph_wrapper = $('#graph-wrapper-all');
  1325.  
  1326.                         $('#quantum').val(graph.quantum);
  1327.  
  1328.                         if (online) {
  1329.                             $('.m-graph-times').hide();
  1330.                             $('.m-graph-title').hide();
  1331.                         } else {
  1332.                             $('.m-graph-times').show();
  1333.                             $('.m-graph-title').show();
  1334.  
  1335.                             // select datetime interval
  1336.                             $('.m-graph-times').find('[data-time]').each(function (i, el) {
  1337.                                 var time = $(el).data('time');
  1338.  
  1339.                                 if (time <= graph.days * 24) {
  1340.                                     $(el).removeClass('hidden');
  1341.                                 }
  1342.                             });
  1343.  
  1344.                             // select minute/hour quantum
  1345.                             var $quantum_link = $('[data-quantum="' + graph.quantum + '"]');
  1346.                             $quantum_link.addClass('active')
  1347.                                 .insertBefore($quantum_link.siblings('a'));
  1348.                             $('[data-quantum-separator]').insertAfter($quantum_link);
  1349.                         }
  1350.  
  1351.                         if (graph.data.length === 0) {
  1352.                             $('.m-graph').hide();
  1353.                         } else {
  1354.                             $('.m-graph').show();
  1355.                             $.wa.mailer.plotGraph(
  1356.                                 graph_wrapper,
  1357.                                 graph.data,
  1358.                                 graph_color,
  1359.                                 graph_label,
  1360.                                 [graph.mindate.toString(), graph.maxdate.toString()],
  1361.                                 graph.zeroline,
  1362.                                 graph.tickinterval,
  1363.                                 graph.ticks
  1364.                             );
  1365.                         }
  1366.                     } else {
  1367.  
  1368.                     }
  1369.                 }, 'json')
  1370.                 .always(function () {
  1371.                     $('.m-graph').find('.loading:first').hide();
  1372.                 });
  1373.         },
  1374.  
  1375.         loadNext50Recipients: function (campaign_id, params, callback_before, callback_after) {
  1376.             var recipient_lists_wrapper = $('#recipient-lists-wrapper'),
  1377.                 report_wrapper = $('#report-wrapper');
  1378.  
  1379.             recipient_lists_wrapper.append('<i class="icon16 loading" style="margin:0 0 0 27px;"></i>');
  1380.             $.get('?module=campaigns&action=recipientsReport&campaign_id=' + campaign_id, params, function (r) {
  1381.                 var active_link_rel = [
  1382.                     report_wrapper.find('.show-recipients-link.disabled:first').attr('rel'),
  1383.                     recipient_lists_wrapper.find('.show-recipients-link.disabled:first').attr('rel')
  1384.                 ];
  1385.  
  1386.                 recipient_lists_wrapper.children('.loading').remove();
  1387.                 if (typeof callback_before == 'function') {
  1388.                     callback_before();
  1389.                 }
  1390.                 var existing_table = recipient_lists_wrapper.find('table.recipients-report:first');
  1391.                 var container = $('<div></div>').appendTo(recipient_lists_wrapper);
  1392.  
  1393.                 container.html(r);
  1394.  
  1395.                 // Retain active link selection in chart legend
  1396.  
  1397.                 report_wrapper
  1398.                     .find('.show-recipients-link')
  1399.                     .filter('[rel="' + active_link_rel[0] + '"]')
  1400.                     .addClass('disabled');
  1401.                 if (active_link_rel[1]) {
  1402.                     recipient_lists_wrapper
  1403.                         .find('.show-recipients-link')
  1404.                         .removeClass('disabled')
  1405.                         .filter('[rel="' + active_link_rel[1] + '"]')
  1406.                         .addClass('disabled');
  1407.                 }
  1408.  
  1409.                 container.find('td:last-child').each(function () {
  1410.                     var self = $(this);
  1411.                     if (self.children('div.hidden').length > 0) {
  1412.                         self.children('span').addClass('show-full-error-text');
  1413.                     }
  1414.                 });
  1415.                 if (existing_table.length > 0) {
  1416.                     existing_table.find('tr:last').after(container.find('tr'));
  1417.                     container.find('table').remove();
  1418.                 }
  1419.             });
  1420.         },
  1421.  
  1422.         setTitle: function (title) {
  1423.             var $h1 = $('#wa-app .content h1:first');
  1424.             if ((typeof title === 'undefined' || !title.length) && $h1.length > 0) {
  1425.                 var $h1_input = $h1.find('input');
  1426.                 if ($h1_input.length > 0) {
  1427.                     title = $h1_input.val().trim();
  1428.                 }
  1429.                 else {
  1430.                     title = $h1.contents().filter(function () {
  1431.                         return this.nodeType == 3 && this.nodeValue.trim().length > 0;
  1432.                     })[0].nodeValue.trim()
  1433.                 }
  1434.             }
  1435.  
  1436.             if (title) {
  1437.                 $('title').html(title + " &mdash; Webasyst");
  1438.             }
  1439.         },
  1440.  
  1441.         /**
  1442.          * @param {Object} options
  1443.          * */
  1444.         initStickyBlock: function(options) {
  1445.             if ( !(options && options.$wrapper && options.$wrapper.length) ) {
  1446.                 log("Bad options for initStickyBlock");
  1447.                 return false;
  1448.             }
  1449.  
  1450.             var FixedBlock = ( function($) {
  1451.  
  1452.                 FixedBlock = function(options) {
  1453.                     var that = this;
  1454.  
  1455.                     // DOM
  1456.                     that.$window = $(window);
  1457.                     that.$wrapper = options["$wrapper"];
  1458.                     that.$section = options["$section"];
  1459.                     that.$wrapperContainer = that.$wrapper.parent();
  1460.  
  1461.                     // VARS
  1462.                     that.type = (options["type"] || "bottom");
  1463.  
  1464.                     // DYNAMIC VARS
  1465.                     that.offset = {};
  1466.                     that.$clone = false;
  1467.                     that.is_fixed = false;
  1468.  
  1469.                     // INIT
  1470.                     that.initClass();
  1471.                 };
  1472.  
  1473.                 FixedBlock.prototype.initClass = function() {
  1474.                     var that = this,
  1475.                         $window = that.$window,
  1476.                         resize_timeout = 0;
  1477.  
  1478.                     $window.on("resize", function() {
  1479.                         clearTimeout(resize_timeout);
  1480.                         resize_timeout = setTimeout( function() {
  1481.                             that.resize();
  1482.                         }, 100);
  1483.                     });
  1484.  
  1485.                     $window.on("scroll", watcher);
  1486.  
  1487.                     that.$wrapper.on("resize", function() {
  1488.                         that.resize();
  1489.                     });
  1490.  
  1491.                     that.$wrapper.on("watch", watcher);
  1492.  
  1493.                     that.init();
  1494.  
  1495.                     watcher();
  1496.  
  1497.                     function watcher() {
  1498.                         var is_exist = $.contains($window[0].document, that.$wrapper[0]);
  1499.                         if (is_exist) {
  1500.                             that.onScroll($window.scrollTop());
  1501.                         } else {
  1502.                             $window.off("scroll", watcher);
  1503.                         }
  1504.                     }
  1505.  
  1506.                     that.$wrapper.data("block", that);
  1507.                 };
  1508.  
  1509.                 FixedBlock.prototype.init = function() {
  1510.                     var that = this;
  1511.  
  1512.                     if (!that.$clone) {
  1513.                         var $clone = $("<div />");
  1514.                         that.$wrapperContainer.append($clone);
  1515.                         that.$clone = $clone;
  1516.                     }
  1517.  
  1518.                     that.$clone.hide();
  1519.  
  1520.                     var offset = that.$wrapper.offset();
  1521.                     that.offset = {
  1522.                         left: offset.left,
  1523.                         top: offset.top,
  1524.                         width: that.$wrapper.outerWidth(),
  1525.                         height: that.$wrapper.outerHeight()
  1526.                     };
  1527.                 };
  1528.  
  1529.                 FixedBlock.prototype.resize = function() {
  1530.                     var that = this;
  1531.  
  1532.                     switch (that.type) {
  1533.                         case "top":
  1534.                             that.fix2top(false);
  1535.                             break;
  1536.                         case "bottom":
  1537.                             that.fix2bottom(false);
  1538.                             break;
  1539.                     }
  1540.  
  1541.                     var offset = that.$wrapper.offset();
  1542.                     that.offset = {
  1543.                         left: offset.left,
  1544.                         top: offset.top,
  1545.                         width: that.$wrapper.outerWidth(),
  1546.                         height: that.$wrapper.outerHeight()
  1547.                     };
  1548.  
  1549.                     that.$wrapper.trigger("watch");
  1550.                 };
  1551.  
  1552.                 /**
  1553.                  * @param {Number} scroll_top
  1554.                  * */
  1555.                 FixedBlock.prototype.onScroll = function(scroll_top) {
  1556.                     var that = this,
  1557.                         window_w = that.$window.width(),
  1558.                         window_h = that.$window.height();
  1559.  
  1560.                     // update top for dynamic content
  1561.                     that.offset.top = that.$wrapperContainer.offset().top;
  1562.                     that.offset.width = that.$wrapperContainer.width();
  1563.  
  1564.                     switch (that.type) {
  1565.                         case "top":
  1566.                             var bottom_case = (that.$section ? ((scroll_top + that.offset.height) < that.$section.height() + that.$section.offset().top) : true),
  1567.                                 use_top_fix = (that.offset.top < scroll_top && bottom_case);
  1568.  
  1569.                             that.fix2top(use_top_fix);
  1570.                             break;
  1571.                         case "bottom":
  1572.                             var use_bottom_fix = (that.offset.top && scroll_top + window_h < that.offset.top + that.offset.height);
  1573.                             that.fix2bottom(use_bottom_fix);
  1574.                             break;
  1575.                     }
  1576.  
  1577.                 };
  1578.  
  1579.                 /**
  1580.                  * @param {Boolean|Object} set
  1581.                  * */
  1582.                 FixedBlock.prototype.fix2top = function(set) {
  1583.                     var that = this,
  1584.                         fixed_class = "is-top-fixed";
  1585.  
  1586.                     if (set) {
  1587.                         that.$wrapper
  1588.                             .css({
  1589.                                 left: that.offset.left,
  1590.                                 width: that.offset.width
  1591.                             })
  1592.                             .addClass(fixed_class);
  1593.  
  1594.                         that.$clone.css({
  1595.                             height: that.offset.height
  1596.                         }).show();
  1597.  
  1598.                     } else {
  1599.                         that.$wrapper.removeClass(fixed_class).removeAttr("style");
  1600.                         that.$clone.removeAttr("style").hide();
  1601.                     }
  1602.  
  1603.                     that.is_fixed = !!set;
  1604.                 };
  1605.  
  1606.                 /**
  1607.                  * @param {Boolean|Object} set
  1608.                  * */
  1609.                 FixedBlock.prototype.fix2bottom = function(set) {
  1610.                     var that = this,
  1611.                         fixed_class = "is-bottom-fixed";
  1612.  
  1613.                     if (set) {
  1614.                         that.$wrapper
  1615.                             .css({
  1616.                                 left: that.offset.left,
  1617.                                 width: that.offset.width
  1618.                             })
  1619.                             .addClass(fixed_class);
  1620.  
  1621.                         that.$clone.css({
  1622.                             height: that.offset.height
  1623.                         }).show();
  1624.  
  1625.                     } else {
  1626.                         that.$wrapper.removeClass(fixed_class).removeAttr("style");
  1627.                         that.$clone.removeAttr("style").hide();
  1628.                     }
  1629.  
  1630.                     that.is_fixed = !!set;
  1631.                 };
  1632.  
  1633.                 return FixedBlock;
  1634.  
  1635.             })(jQuery);
  1636.  
  1637.             return new FixedBlock(options);
  1638.         }
  1639.  
  1640.     }; // end of $.wa.mailer
  1641.  
  1642.     function log(params) {
  1643.         if (console && "log" in console) {
  1644.             console.log(params);
  1645.         } else {
  1646.             alert(params);
  1647.         }
  1648.     }
  1649.  
  1650. })(jQuery);

Paste is for source code and general debugging text.

Login or Register to edit, delete and keep track of your pastes and more.

Raw Paste

Login or Register to edit or fork this paste. It's free.