Skip to content
Snippets Groups Projects
redactor-osticket.js 14.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • if (typeof RedactorPlugins === 'undefined') var RedactorPlugins = {};
    
    /* 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.
     */
    
    RedactorPlugins.draft = function() {
      return {
    
            if (!this.opts.draftNamespace)
    
            this.opts.changeCallback = this.draft.hideDraftSaved;
    
            var autosave_url = 'ajax.php/draft/' + this.opts.draftNamespace;
            if (this.opts.draftObjectId)
                autosave_url += '.' + this.opts.draftObjectId;
    
            this.opts.autosave = this.opts.autoCreateUrl = autosave_url;
    
            this.opts.autosaveInterval = 30;
    
            this.opts.autosaveCallback = this.draft.afterUpdateDraft;
            this.opts.autosaveErrorCallback = this.draft.autosaveFailed;
            this.opts.imageUploadErrorCallback = this.draft.displayError;
    
            if (this.opts.draftId) {
                this.opts.autosave = 'ajax.php/draft/'+this.opts.draftId;
                this.opts.clipboardUploadUrl =
                this.opts.imageUpload =
                    'ajax.php/draft/'+this.opts.draftId+'/attach';
            }
    
                // Just upload the file. A draft will be created automatically
                // and will be configured locally in the afterUpateDraft()
                this.opts.clipboardUploadUrl =
                this.opts.imageUpload = this.opts.autoCreateUrl + '/attach';
                this.opts.imageUploadCallback = this.afterUpdateDraft;
    
            if (autosave_url)
                this.autosave.enable();
    
    
            this.$draft_saved = $('<span>')
    
                .addClass("pull-right draft-saved")
    
                .hide()
                .append($('<span>')
    
                    .text(__('Draft Saved')));
    
            // Float the [Draft Saved] box with the toolbar
            this.$toolbar.append(this.$draft_saved);
    
            // Add [Delete Draft] button to the toolbar
    
            if (this.opts.draftDelete) {
    
                var trash = this.draft.deleteButton =
    
                    this.button.add('deleteDraft', __('Delete Draft'))
                this.button.addCallback(trash, this.draft.deleteDraft);
    
                this.button.setAwesome('deleteDraft', 'icon-trash');
    
                trash.parent().addClass('pull-right');
                trash.addClass('delete-draft');
    
                if (!this.opts.draftId)
                    trash.hide();
    
            if (this.code.get())
    
                this.$box.trigger('draft:recovered');
    
        afterUpdateDraft: function(name, data) {
    
            // Slight workaround. Signal the 'keyup' event normally signaled
            // from typing in the <textarea>
    
                && this.$box.closest('form').find('input[name=lockCode]').val()
    
                && this.code.get()
            ) {
                $.autoLock.handleEvent();
    
            // 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.opts.draftId = data.draft_id;
                this.opts.autosave = 'ajax.php/draft/' + data.draft_id;
    
                this.opts.clipboardUploadUrl =
                this.opts.imageUpload =
                    'ajax.php/draft/'+this.opts.draftId+'/attach';
    
                if (!this.code.get())
                    this.code.set(' ', false);
    
            }
            // Only show the [Draft Saved] notice if there is content in the
            // field that has been touched
    
            if (!this.draft.firstSave) {
                this.draft.firstSave = true;
    
                // No change yet — dont't show the button
                return;
            }
    
            if (data && this.code.get()) {
    
                this.$draft_saved.show().delay(5000).fadeOut();
            }
            // Show the button if there is a draft to delete
            if (this.opts.draftId && this.opts.draftDelete)
    
                this.draft.deleteButton.show();
    
            this.$box.trigger('draft:saved');
    
        },
        autosaveFailed: function(error) {
            if (error.code == 422)
                // Unprocessable request (Empty message)
    
            this.displayError(error);
            // Cancel autosave
            clearInterval(this.autosaveInterval);
            this.hideDraftSaved();
    
            this.$box.trigger('draft:failed');
    
        },
    
        displayError: function(json) {
            alert(json.error);
        },
    
        hideDraftSaved: function() {
    
            this.$draft_saved.hide();
    
            if (!this.opts.draftId)
    
                // Nothing to delete
                return;
    
            $.ajax('ajax.php/draft/'+this.opts.draftId, {
    
                success: function() {
    
                    self.draft_id = self.opts.draftId = undefined;
    
                    self.draft.hideDraftSaved();
                    self.code.set(self.opts.draftOriginal || '', false, false);
    
                    self.opts.autosave = self.opts.autoCreateUrl;
    
                    self.draft.deleteButton.hide();
                    self.draft.firstSave = false;
    
                    this.$box.trigger('draft:deleted');
    
    RedactorPlugins.signature = function() {
      return {
    
        init: function() {
    
            var $el = $(this.$element.get(0)),
                inner = $('<div class="inner"></div>');
    
            if ($el.data('signatureField')) {
    
                this.$signatureBox = $('<div class="selected-signature"></div>')
    
                    .append(inner)
    
                    .appendTo(this.$box);
    
                if ($el.data('signature'))
                    inner.html($el.data('signature'));
                else
                    this.$signatureBox.hide();
    
                $('input[name='+$el.data('signatureField')+']', $el.closest('form'))
    
                    .on('change', false, false, $.proxy(this.signature.updateSignature, this));
    
                if ($el.data('deptField'))
                    $(':input[name='+$el.data('deptField')+']', $el.closest('form'))
    
                        .on('change', false, false, $.proxy(this.signature.updateSignature, this));
    
                // Expand on hover
                var outer = this.$signatureBox,
                    inner = $('.inner', this.$signatureBox).get(0),
                    originalHeight = outer.height(),
                    hoverTimeout = undefined,
                    originalShadow = this.$signatureBox.css('box-shadow');
                this.$signatureBox.hover(function() {
                    hoverTimeout = setTimeout($.proxy(function() {
    
                        originalHeight = Math.max(originalHeight, outer.height());
    
                        $(this).animate({
                            'height': inner.offsetHeight
                        }, 'fast');
                        $(this).css('box-shadow', 'none', 'important');
                    }, this), 250);
                }, function() {
                    clearTimeout(hoverTimeout);
                    $(this).stop().animate({
                        'height': Math.min(inner.offsetHeight, originalHeight)
                    }, 'fast');
                    $(this).css('box-shadow', originalShadow);
                });
    
                this.$box.find('.redactor_editor').css('border-bottom-style', 'none', true);
    
            }
        },
        updateSignature: function(e) {
            var $el = $(this.$element.get(0));
    
                selected = $(':input:checked[name='+$el.data('signatureField')+']', $el.closest('form')).val(),
                type = $(e.target).val(),
    
                dept = $(':input[name='+$el.data('deptField')+']', $el.closest('form')).val(),
    
                url = 'ajax.php/content/signature/',
                inner = $('.inner', this.$signatureBox);
    
            e.preventDefault && e.preventDefault();
    
            if (selected == 'dept' && $el.data('deptId'))
    
                url += 'dept/' + $el.data('deptId');
    
            else if (selected == 'dept' && $el.data('deptField')) {
                if (dept)
    
                    url += 'dept/' + dept;
    
                    return inner.empty().parent().hide();
    
            else if (selected == 'theirs' && $el.data('posterId')) {
                url += 'agent/' + $el.data('posterId');
            }
    
            else if (type == 'none')
    
               return inner.empty().parent().hide();
    
                url += selected;
    
            inner.load(url).parent().show();
    
    RedactorPlugins.autolock = function() {
      return {
        init: function() {
          var code = this.$box.closest('form').find('[name=lockCode]');
          if ($.autoLock && code.length)
            this.opts.keydownCallback = $.autoLock.handleEvent;
        }
      };
    }
    
    
    /* 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 = $.redact = function(el, options) {
    
                sizes = {'small': 75, 'medium': 150, 'large': 225},
                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')
                      ? ['formatting', '|', 'bold', 'italic', 'underline', 'deleted', '|', 'unorderedlist', 'orderedlist', 'outdent', 'indent', '|', 'image']
                      : ['html', '|', 'formatting', '|', 'bold',
    
                        'italic', 'underline', 'deleted', '|', 'unorderedlist',
                        'orderedlist', 'outdent', 'indent', '|', 'image', 'video',
                        'file', 'table', 'link', '|', 'alignment', '|',
                        'horizontalrule'],
    
                    'buttonSource': !el.hasClass('no-bar'),
    
                    'autoresize': !el.hasClass('no-bar') && !el.closest('.dialog').length,
                    'maxHeight': el.closest('.dialog').length ? selectedSize : false,
    
                    'minHeight': selectedSize,
    
                    'plugins': el.hasClass('no-bar')
    
                      ? ['imagemanager','definedlinks']
                      : ['imagemanager','imageannotate','table','video','definedlinks','autolock'],
    
                    'imageUpload': 'tbd',
                    'imageManagerJson': 'ajax.php/draft/images/browse',
    
                    'syncBeforeCallback': captureImageSizes,
                    'linebreaks': true,
    
    Jared Hancock's avatar
    Jared Hancock committed
                    'tabFocus': false,
    
                    'toolbarFixedBox': true,
    
                    'focusCallback': function() { this.$box.addClass('no-pjax'); },
    
                    'initCallback': function() {
    
                        if (this.$element.data('width'))
                            this.$editor.width(this.$element.data('width'));
    
                        this.$editor.attr('spellcheck', 'true');
                        var lang = this.$editor.closest('[lang]').attr('lang');
                        if (lang)
                            this.$editor.attr('lang', lang);
                    },
    
                    'linkSize': 100000,
    
                    'definedLinks': 'ajax.php/config/links'
    
            if (el.data('redactor')) return;
    
            var reset = $('input[type=reset]', el.closest('form'));
            if (reset) {
                reset.click(function() {
                    if (el.hasClass('draft'))
                        el.redactor('deleteDraft');
                    else
                        el.redactor('set', '', false, false);
                });
            }
    
            $('input[type=submit]', el.closest('form')).on('click', function() {
                // Some setups (IE v10 on Windows 7 at least) seem to have a bug
                // where Redactor does not sync properly after adding an image.
                // Therefore, the ::get() call will not include text added after
                // the image was inserted.
    
                el.redactor('code.sync');
    
            if (!$.clientPortal) {
                options['plugins'] = options['plugins'].concat(
                        'fontcolor', 'fontfamily', 'signature');
            }
    
            if (el.hasClass('draft')) {
                el.closest('form').append($('<input type="hidden" name="draft_id"/>'));
                options['plugins'].push('draft');
    
                options['plugins'].push('imagepaste');
    
                options.draftDelete = el.hasClass('draft-delete');
    
            if (true || 'scp') { // XXX: Add this to SCP only
                options['plugins'].push('contexttypeahead');
            }
    
            if (el.hasClass('fullscreen'))
                options['plugins'].push('fullscreen');
    
            if ($('#ticket_thread[data-thread-id]').length)
                options['imageManagerJson'] += '?threadId=' + $('#ticket_thread').data('threadId');
    
            getConfig().then(function(c) {
    
                if (c.lang && c.lang.toLowerCase() != 'en_us' &&
    
                        $.Redactor.opts.langs[c.short_lang])
                    options['lang'] = c.short_lang;
    
                //if (c.has_rtl)
                  //  options['plugins'].push('textdirection');
    
                if (el.find('rtl').length)
    
                    options['direction'] = 'rtl';
    
                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.destroy();
    
        };
        findRichtextBoxes();
    
        $(document).ajaxStop(findRichtextBoxes);
    
        $(document).on('pjax:success', findRichtextBoxes);
    
        $(document).on('pjax:start', cleanupRedactorElements);
    
    Peter Rotich's avatar
    Peter Rotich committed
    $(document).on('focusout.redactor', 'div.redactor_richtext', function (e) {
        $(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) {
                    clearInterval(redactor.autosaveInterval);
                }
            });
            $('#overlay').show();
    
            alert(__('Unable to save draft. Refresh the current page to restore and continue your draft.'));
    
            $('#overlay').hide();
        }
    });