text: /* Generic draft support for osTicket. The plugins supports draft retrieval
   * automatically, along with draft autosave, and image uploading.
   *
   * Configuration:
   * draftNamespace: namespace for the draft retrieval
   * draftObjectId: extension to the namespace for draft retrieval
   *
   * Caveats:
   * Login (staff only currently) is required server-side for drafts and image
   * uploads. Furthermore, the id of the staff is considered for the drafts,
   * so one user will not retrieve drafts for another user.
   */
  (function(R$) {
    // Monkey patch incorrect code in the inpection module
    var stockInspectorParser = $R[$R.env['class']]['inspector.parser'];
    R$.add('class', 'inspector.parser', $R.extend(stockInspectorParser.prototype, {
      _getClosestUpNode: function(selector)
      {
          var $el = this.$el.parents(selector, '.redactor-in-' + this.uuid).last();
          return ($el.length !== 0) ? $el.get() : false;
      },
      _getClosestNode: function(selector)
      {
          var $el = this.$el.closest(selector, '.redactor-in-' + this.uuid);
          return ($el.length !== 0) ? $el.get() : false;
      },
      _getClosestElement: function(selector)
      {
          var $el = this.$el.closest(selector, '.redactor-in-' + this.uuid);
          return ($el.length !== 0) ? $el : false;
      },
    }));
  
    R$.add('plugin', 'draft', {
      init: function (app) {
          this.app = app;
          this.$textarea = $(this.app.rootElement);
          this.toolbar = this.app.toolbar;
          this.opts = app.opts;
          this.lastUpdate = 0;
          this.statusbar = app.statusbar;
      },
  
      start: function() {
          if (!this.opts.draftNamespace)
              return;
  
          var autosave_url = 'ajax.php/draft/' + this.opts.draftNamespace;
          if (this.opts.draftObjectId)
              autosave_url += '.' + this.opts.draftObjectId;
          this.opts.autosave = this.autoCreateUrl = autosave_url;
          this.opts.autosaveDelay = 10000;
          if (this.opts.draftId) {
              this.statusbar.add('draft', __('all changes saved'));
              this._setup(this.opts.draftId);
          }
          else if (this.$textarea.hasClass('draft')) {
              // Just upload the file. A draft will be created automatically
              // and will be configured locally in the afterUpateDraft()
              this.opts.clipboardUpload =
              this.opts.imageUpload = this.autoCreateUrl + '/attach';
          }
          this.opts.autosaveData = {
              '__CSRFToken__': $("meta[name=csrf_token]").attr("content")
          };
  
          if (autosave_url)
              this.app.api('module.autosave.enable');
  
          if (this.app.source.getCode())
              this.app.broadcast('draft.recovered');
      },
  
      stop: function() {
          this.app.statusbar.remove('draft');
      },
  
      _setup: function (draft_id) {
          this.opts.draftId = draft_id;
          this.opts.autosave = 'ajax.php/draft/' + draft_id;
          this.opts.clipboardUpload =
          this.opts.imageUpload =
              'ajax.php/draft/' + draft_id + '/attach';
  
          // Add [Delete Draft] button to the toolbar
          if (this.opts.draftDelete) {
              this.opts.draftSave = true;
              var trash = this.deleteButton =
                  this.toolbar.addButton('deletedraft', {
                      title: __('Delete Draft'),
                      api: 'plugin.draft.deleteDraft',
                      icon: 'icon-trash',
                  });
              trash.addClass('pull-right icon-trash');
  
          }
  
          // Add [Save Draft] button to the toolbar
          if (this.opts.draftSave) {
              var save = this.saveButton =
                  this.toolbar.addButton('savedraft', {
                      title: __('Save Draft'),
                      api: 'plugin.draft.saveDraft',
                      icon: 'icon-save',
                  });
              save.addClass('pull-right icon-save');
          }
      },
  
      onautosave: function(name, _, data) {
          // If the draft was created, a draft_id will be sent back — update
          // the URL to send updates in the future
          if (!this.opts.draftId && data.draft_id) {
              this._setup(data.draft_id);
              $(this.app.rootElement).attr('data-draft-id', this.opts.draftId);
          }
  
          this.statusbar.add('draft', __('all changes saved'));
          this.app.broadcast('draft.saved');
  
      },
  
      onautosaveSend: function() {
          this.statusbar.add('draft', __('saving...'));
      },
  
      onautosaveError: function(error) {
          if (error.code == 422)
              // Unprocessable request (Empty message)
              return;
  
          this.displayError(error);
          // Cancel autosave
          this.app.api('module.autosave.disable');
          this.statusbar.add('draft', '<span style="color:red">{}</span>'.replace('{}', __('save error')));
          this.app.broadcast('draft.failed');
      },
  
      onimage: {
          uploaded: function(image, response) {
              this.onautosave(null, null, response);
          },
          uploadError: function (response) {
              this.displayError(response);
          }
      },
  
      displayError: function(json) {
          $.sysAlert(json.error,
              __('Unable to save draft.')
            + __('Refresh the current page to restore and continue your draft.'));
      },
  
      onchanged: function() {
          this.statusbar.add('draft', __('unsaved'));
      },
  
      showDraftSaved: function() {
          this.$draft_saved.show();
      },
  
      saveDraft: function() {
          if (!this.opts.draftId)
              return;
  
          response = $(".draft").val()
          if (response) {
              var data = {
                  name: 'response',
                  response: response,
              };
  
              var self = this;
              $.ajax('ajax.php/draft/'+this.opts.draftId, {
                  type: 'POST',
                  data: data,
                  dataType: 'json',
                  success: function() {
                      self.draft_id = self.opts.draftId;
                      self.opts.autosave = self.autoCreateUrl;
                      self.app.statusbar.add('draft', __('all changes saved'));
                  }
              });
          }
      },
  
      deleteDraft: function() {
          if (!this.opts.draftId)
              // Nothing to delete
              return;
          var self = this;
          $.ajax('ajax.php/draft/'+this.opts.draftId, {
              type: 'delete',
              success: function() {
                  self.draft_id = self.opts.draftId = undefined;
                  self.app.statusbar.remove('draft');
                  self.app.source.setCode(self.opts.draftOriginal || '');
                  self.opts.autosave = self.autoCreateUrl;
                  self.opts.clipboardUpload =
                  self.opts.imageUpload = self.autoCreateUrl + '/attach';
                  self.deleteButton.hide();
                  self.saveButton.hide();
                  self.app.broadcast('draft.deleted');
              }
          });
      }
    });
  
    // Monkey patch the autosave module to include an `autosaveBefore` signal
    // and an delay option to limit calls to the backend.
    var stockAutosave = $R[$R.env['module']]['autosave'];
    R$.add('module', 'autosave', $R.extend(stockAutosave.prototype, {
      onsynced: function() {
          if (this.opts.autosave) {
              // Don't send to backend if empty
              if (!this.source.getCode())
                  return;
              if (this.opts.autosaveDelay) {
                  if (this.delayTimer)
                      clearInterval(this.delayTimer);
                  this.delayTimer = setTimeout(this._sendDelayed.bind(this),
                      this.opts.autosaveDelay);
              }
              else {
                  this._sendDelayed();
              }
          }
      },
      _sendDelayed: function() {
          this.app.broadcast('autosaveSend');
          this._send.call(this);
      },
    }));
  
    // Monkey patch the toolbar server to support adding buttons in an automatic
    // position based on the `buttons` setting
    var stockToolbar = $R[$R.env['service']]['toolbar'];
    R$.add('service', 'toolbar', $R.extend(stockToolbar.prototype, {
        addButtonAuto: function(name, btnObj) {
            var pos = this.opts.buttons.indexOf(name);
  
            if (pos === -1)
                return this.addButton(name, btnObj);
            if (pos === 0)
                return this.addButtonFirst(name, btnObj);
            return this.addButtonAfter(this.opts.buttons[pos - 1], name, btnObj);
        },
    }));
  
    R$.add('plugin', 'autolock', {
      init: function (app) {
          this.app = app;
      },
      start: function () {
          var root = $(this.app.rootElement),
              code = root.closest('form').find('[name=lockCode]');
          if (code.length)
              this.lock = root.closest('[data-lock-object-id]');
      },
      onchanged: function(e) {
          if (this.lock)
              this.lock.exclusive('acquire');
      }
    });
  
    R$.add('plugin', 'signature', {
      init: function (app) {
          this.app = app;
      },
      start: function() {
          var $el = $R.dom(this.app.rootElement),
              $box = this.app.editor.getElement(),
              inner = $R.dom('<div class="inner"></div>'),
              $form = $el.closest('form'),
              signatureField = $el.data('signature-field');
          if (signatureField) {
              this.$signatureBox = $R.dom('<div class="selected-signature"></div>')
                  .append(inner);
              this.app.editor.getElement().parent().find('.redactor-statusbar').before(this.$signatureBox);
              if ($el.data('signature'))
                  inner.html($el.data('signature'));
              else
                  this.$signatureBox.hide();
              $R.dom('input[name='+signatureField+']', $form)
                  .on('change', this.updateSignature.bind(this));
              // Expand on hover
              var outer = this.$signatureBox,
                  inner = $('.inner', this.$signatureBox.get(0)).get(0),
                  originalHeight = outer.height(),
                  hoverTimeout = undefined,
                  originalShadow = this.$signatureBox.css('box-shadow');
              this.$signatureBox.on('mouseenter', function() {
                  hoverTimeout = setTimeout(function() {
                      $(this).animate({
                          'height': inner.offsetHeight + 25
                      }, 'fast');
                      $(this).css('box-shadow', 'none', 'important');
                  }.bind(this), 250);
              }).on('mouseleave', function() {
                  clearTimeout(hoverTimeout);
                  $(this).stop().animate({
                      'height': Math.min(inner.offsetHeight, originalHeight - 10)
                  }, 'fast');
                  $(this).css('box-shadow', originalShadow);
              });
              $el.find('.redactor-box').css('border-bottom-style', 'none', true);
          }
      },
      updateSignature: function(e) {
          var $el = $(this.app.rootElement),
              signatureField = $el.data('signature-field'),
              $form = $el.closest('form'),
              selected = $(':input:checked[name='+signatureField+']', $form).val(),
              type = $R.dom(e.target).val(),
              dept = $R.dom(':input[name='+$el.data('dept-field')+']', $form).val(),
              url = 'ajax.php/content/signature/',
              inner = $R.dom('.inner', this.$signatureBox);
          e.preventDefault && e.preventDefault();
          if (selected == 'dept' && $el.data('dept-id'))
              url += 'dept/' + $el.data('dept-id');
          else if (selected == 'dept' && $el.data('dept-field')) {
              if (dept)
                  url += 'dept/' + dept;
              else
                  return inner.empty().parent().hide();
          }
          else if (selected == 'theirs' && $el.data('poster-id')) {
              url += 'agent/' + $el.data('poster-id');
          }
          else if (type == 'none')
             return inner.empty().parent().hide();
          else
              url += selected;
  
          $R.ajax.get({
              url: url,
              success: function(html) {
                  inner.html(html).parent().show();
              }
          });
      }
    });
  })(Redactor);
  
  /* Redactor richtext init */
  $(function() {
      var captureImageSizes = function(html) {
          $('img', this.$box).each(function(i, img) {
              // TODO: Rewrite the entire <img> tag. Otherwise the @width
              // and @height attributes will begin to accumulate
              before = img.outerHTML;
              if (img.clientWidth && img.clientHeight)
                  $(img).attr('width', img.clientWidth)
                        .attr('height',img.clientHeight);
              html = html.replace(before, img.outerHTML);
          });
          return html;
      },
      redact = $.fn.redact = function(el, options) {
          var el = $(el),
              sizes = {'small': '75px', 'medium': '150px', 'large': '225px'},
              selectedSize = sizes['medium'];
          $.each(sizes, function(k, v) {
              if (el.hasClass(k)) selectedSize = v;
          });
          var options = $.extend({
                  'air': el.hasClass('no-bar'),
                  'buttons': el.hasClass('no-bar')
                    ? ['format', '|', 'bold', 'italic', 'underline', 'deleted', 'lists', 'link', 'image']
                    : ['html', 'format', 'fontcolor', 'fontfamily', 'bold',
                      'italic', 'underline', 'deleted', 'lists', 'image', 'video',
                      'file', 'table', 'link', 'line', 'fullscreen'],
                  'buttonSource': !el.hasClass('no-bar'),
                  'autoresize': !el.hasClass('no-bar') && !el.closest('.dialog').length,
                  'maxHeight': el.closest('.dialog').length ? selectedSize : false,
                  'minHeight': selectedSize,
                  'maxWidth': el.hasClass('fullscreen') ? '950px' : false,
                  'focus': false,
                  'plugins': el.hasClass('no-bar')
                    ? ['imagemanager','definedlinks']
                    : ['imagemanager','table','video','definedlinks','autolock', 'fontcolor', 'fontfamily'],
                  'imageUpload': el.hasClass('draft'),
                  'imageManagerJson': 'ajax.php/draft/images/browse',
                  'imagePosition': true,
                  'imageUploadData': {
                      '__CSRFToken__': $("meta[name=csrf_token]").attr("content")
                  },
                  'imageResizable': true,
                  'syncBeforeCallback': captureImageSizes,
                  'tabFocus': false,
                  'toolbarFixed': true,
                  'callbacks': {
                      'start': function() {
                          var $element = $R.dom(this.rootElement),
                              $editor = this.editor.$editor;
                          if ($element.data('width'))
                              $editor.width($element.data('width'));
                          $editor.addClass('no-pjax');
                          $editor.attr('spellcheck', 'true');
                          var lang = $element.closest('[lang]').attr('lang');
                          if (lang)
                              $editor.attr('lang', lang);
                          // Fixup class for
                          $element.parent().closest(':not(.redactor-box)').addClass('-redactor-container')
                      },
                  },
                  'linkSize': 100000,
                  'definedlinks': 'ajax.php/config/links'
              }, options||{});
          if (el.data('redactor')) return;
          var reset = $('input[type=reset]', el.closest('form'));
          if (reset) {
              reset.click(function() {
                  var file = $('.file', el.closest('form'));
                  if (file)
                      file.remove();
                  if (el.attr('data-draft-id')) {
                      el.redactor('plugin.draft.deleteDraft');
                      el.attr('data-draft-id', '');
                  }
                  else {
                      try {
                          el.redactor('source.setCode', '');
                      }
                      catch (error) {
                          el.redactor(); //reinitialize redactor
                          el.redactor('source.setCode', '');
                      }
                  }
              });
          }
          if (!$.clientPortal) {
              options['plugins'].push('signature');
          }
          if (el.hasClass('draft')) {
              el.closest('form').append($('<input type="hidden" name="draft_id"/>'));
              options['plugins'].push('draft');
              options['plugins'].push('imageannotate');
              options.draftDelete = el.hasClass('draft-delete');
              options.draftSave = el.hasClass('draft-save');
          }
          if (true || 'scp') { // XXX: Add this to SCP only
              options['plugins'].push('contexttypeahead');
          }
          if (el.hasClass('fullscreen'))
              options['plugins'].push('fullscreen');
          if (el.data('translateTag'))
              options['plugins'].push('translatable');
          if ($('#thread-items[data-thread-id]').length)
              options['imageManagerJson'] += '?threadId=' + $('#thread-items').data('threadId');
          getConfig().then(function(c) {
              if (c.lang && c.lang.toLowerCase() != 'en_us' &&
                      Redactor.lang[c.short_lang])
                  options['lang'] = c.short_lang;
              if (c.has_rtl)
                  options['plugins'].push('textdirection');
              if (el.find('rtl').length)
                  options['direction'] = 'rtl';
              if (c.editor_spacing == 'single')
                  options.breakline = true;
              el.data('redactor', el.redactor(options));
          });
      },
      findRichtextBoxes = function() {
          $('.richtext').each(function(i,el) {
              if ($(el).hasClass('ifhtml'))
                  // Check if html_thread is enabled first
                  getConfig().then(function(c) {
                      if (c.html_thread)
                          redact(el);
                  });
              else
                  // Make a rich text editor immediately
                  redact(el);
          });
      },
      cleanupRedactorElements = function() {
          // Tear down redactor editors on this page
          $('.richtext').each(function() {
              var redactor = $(this).data('redactor');
              if (redactor)
                  redactor.stop();
          });
      };
      findRichtextBoxes();
      $(document).ajaxStop(findRichtextBoxes);
      $(document).on('pjax:start', cleanupRedactorElements);
  });
  
  $(document).on('focusout.redactor', 'div.redactor_richtext', function (e) {
      alert('focusout.redactor');
      $(this).siblings('textarea').trigger('change');
  });
  
  $(document).ajaxError(function(event, request, settings) {
      if (settings.url.indexOf('ajax.php/draft') != -1
              && settings.type.toUpperCase() == 'POST') {
          $('.richtext').each(function() {
              var redactor = $(this).data('redactor');
              if (redactor) {
                  redactor.autosave.disable();
              }
          });
          $.sysAlert(__('Unable to save draft.'),
              __('Refresh the current page to restore and continue your draft.'));
      }
  });
  