Newer
Older
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 = {
init: function() {
if (!this.opts.draftNamespace)
return;
this.opts.changeCallback = this.hideDraftSaved;
var autosave_url = 'ajax.php/draft/' + this.opts.draftNamespace;
if (this.opts.draftObjectId)
autosave_url += '.' + this.opts.draftObjectId;
this.opts.autosave = autosave_url;
this.opts.autosaveInterval = 10;
this.opts.autosaveCallback = this.setupDraftUpdate;
this.opts.initCallback = this.recoverDraft;
this.$draft_saved = $('<span>')
.addClass("pull-right draft-saved")
.css({'position':'absolute','top':'3em','right':'0.5em'})
.hide()
.append($('<span>')
// Float the [Draft Saved] box with the toolbar
this.$toolbar.append(this.$draft_saved);
if (this.opts.draftDelete) {
var trash = this.buttonAdd('deleteDraft', __('Delete Draft'), this.deleteDraft);
this.buttonAwesome('deleteDraft', 'icon-trash');
trash.parent().addClass('pull-right');
trash.addClass('delete-draft');
},
recoverDraft: function() {
var self = this;
$.ajax(this.opts.autosave, {
dataType: 'json',
statusCode: {
200: function(json) {
self.draft_id = json.draft_id;
// Replace the current content with the draft, sync, and make
// images editable
self.setupDraftUpdate(json);
if (!json.body) return;
self.set(json.body, false);
self.observeStart();
},
205: function() {
// Save empty draft immediately;
var ai = self.opts.autosaveInterval;
// Save immediately -- capture the created autosave
// interval and clear it as soon as possible. Note that
// autosave()ing doesn't happen immediately. It happens
// async after the autosaveInterval expires.
self.opts.autosaveInterval = 0;
self.autosave();
var interval = self.autosaveInterval;
setTimeout(function() {
clearInterval(interval);
}, 1);
// Reinstate previous autosave interval timing
self.opts.autosaveInterval = ai;
}
}
});
},
setupDraftUpdate: function(data) {
if (this.get())
this.$draft_saved.show().delay(5000).fadeOut();
// Slight workaround. Signal the 'keyup' event normally signaled
// from typing in the <textarea>
if ($.autoLock && this.opts.draftNamespace == 'ticket.response')
if (this.get())
$.autoLock.handleEvent();
if (typeof data != 'object')
data = $.parseJSON(data);
if (!data || !data.draft_id)
return;
$('input[name=draft_id]', this.$box.closest('form'))
.val(data.draft_id);
this.draft_id = data.draft_id;
var self = this;
getConfig().then(function(c) {
if (c.allow_attachments) {
self.opts.clipboardUploadUrl =
self.opts.imageUpload =
'ajax.php/draft/'+data.draft_id+'/attach';
self.opts.imageUploadErrorCallback = self.displayError;
// XXX: This happens in ::buildBindKeyboard() from
// ::buildAfter(). However, the imageUpload option is not
// known when the Redactor is init()'d
self.$editor.on('drop.redactor', $.proxy(self.buildEventDrop, self));
this.opts.original_autosave = this.opts.autosave;
this.opts.autosave = 'ajax.php/draft/'+data.draft_id;
},
displayError: function(json) {
alert(json.error);
},
hideDraftSaved: function() {
},
deleteDraft: function() {
if (!this.draft_id)
// Nothing to delete
return;
var self = this;
$.ajax('ajax.php/draft/'+this.draft_id, {
type: 'delete',
success: function() {
self.draft_id = undefined;
self.hideDraftSaved();
self.opts.autosave = self.opts.original_autosave;
RedactorPlugins.signature = {
init: function() {
var $el = $(this.$element.get(0)),
inner = $('<div class="inner"></div>');
if ($el.data('signatureField')) {
this.$signatureBox = $('<div class="selected-signature"></div>')
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.updateSignature, this))
if ($el.data('deptField'))
$(':input[name='+$el.data('deptField')+']', $el.closest('form'))
.on('change', false, false, $.proxy(this.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);
});
}
},
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)
return inner.empty().parent().hide();
else if (type == 'none')
return inner.empty().parent().hide();
inner.load(url).parent().show();
/* 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);
});
// Drop <inline> elements if found in the text (shady mojo happening
// inside the Redactor editor)
// DELME: When this is fixed upstream in Redactor
html = html.replace(/<inline /, '<span ').replace(/<\/inline>/, '</span>');
redact = $.redact = function(el, options) {
options = $.extend({
'air': el.hasClass('no-bar'),
'airButtons': ['formatting', '|', 'bold', 'italic', 'underline', 'deleted', '|', 'unorderedlist', 'orderedlist', 'outdent', 'indent', '|', 'image'],
'buttons': ['html', '|', 'formatting', '|', 'bold',
'italic', 'underline', 'deleted', '|', 'unorderedlist',
'orderedlist', 'outdent', 'indent', '|', 'image', 'video',
'file', 'table', 'link', '|', 'alignment', '|',
'horizontalrule'],
'autoresize': !el.hasClass('no-bar'),
'minHeight': el.hasClass('small') ? 75 : 150,
'plugins': ['fontcolor','fontfamily', 'signature'],
'imageGetJson': 'ajax.php/draft/images/browse',
'syncBeforeCallback': captureImageSizes,
'linebreaks': true,
'toolbarFixedBox': true,
'focusCallback': function() { this.$box.addClass('no-pjax'); }
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);
});
}
if (el.hasClass('draft')) {
el.closest('form').append($('<input type="hidden" name="draft_id"/>'));
options['plugins'].push('draft');
options.draftDelete = el.hasClass('draft-delete');
if (c.lang && c.lang.toLowerCase() != 'en_us' &&
$.Redactor.opts.langs[c.lang.toLowerCase()])
options['lang'] = c.lang.toLowerCase();
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)
$(document).ajaxStop(findRichtextBoxes);
$(document).on('pjax:success', findRichtextBoxes);
$(document).on('pjax:start', cleanupRedactorElements);
$(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();
}
});