/* Redactor v9.2.4 Updated: May 15, 2014 http://imperavi.com/redactor/ Copyright (c) 2009-2014, Imperavi LLC. License: http://imperavi.com/redactor/license/ Usage: $('#content').redactor(); */ (function($) { var uuid = 0; "use strict"; var Range = function(range) { this[0] = range.startOffset; this[1] = range.endOffset; this.range = range; return this; }; Range.prototype.equals = function() { return this[0] === this[1]; }; var reUrlYoutube = /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w.-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig; var reUrlVimeo = /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/; // Plugin $.fn.redactor = function(options) { var val = []; var args = Array.prototype.slice.call(arguments, 1); if (typeof options === 'string') { this.each(function() { var instance = $.data(this, 'redactor'); if (typeof instance !== 'undefined' && $.isFunction(instance[options])) { var methodVal = instance[options].apply(instance, args); if (methodVal !== undefined && methodVal !== instance) val.push(methodVal); } else return $.error('No such method "' + options + '" for Redactor'); }); } else { this.each(function() { if (!$.data(this, 'redactor')) $.data(this, 'redactor', Redactor(this, options)); }); } if (val.length === 0) return this; else if (val.length === 1) return val[0]; else return val; }; // Initialization function Redactor(el, options) { return new Redactor.prototype.init(el, options); } $.Redactor = Redactor; $.Redactor.VERSION = '9.2.4'; $.Redactor.opts = { // settings rangy: false, iframe: false, fullpage: false, css: false, // url lang: 'en', direction: 'ltr', // ltr or rtl placeholder: false, typewriter: false, wym: false, mobile: true, cleanup: true, tidyHtml: true, pastePlainText: false, removeEmptyTags: true, cleanSpaces: true, cleanFontTag: true, templateVars: false, xhtml: false, visual: true, focus: false, tabindex: false, autoresize: true, minHeight: false, maxHeight: false, shortcuts: { 'ctrl+m, meta+m': "this.execCommand('removeFormat', false)", 'ctrl+b, meta+b': "this.execCommand('bold', false)", 'ctrl+i, meta+i': "this.execCommand('italic', false)", 'ctrl+h, meta+h': "this.execCommand('superscript', false)", 'ctrl+l, meta+l': "this.execCommand('subscript', false)", 'ctrl+k, meta+k': "this.linkShow()", 'ctrl+shift+7': "this.execCommand('insertorderedlist', false)", 'ctrl+shift+8': "this.execCommand('insertunorderedlist', false)" }, shortcutsAdd: { 'ctrl+3': "this.execCommand('removeFormat', false)" }, autosave: false, // false or url autosaveInterval: 60, // seconds plugins: false, // array //linkAnchor: true, //linkEmail: true, linkProtocol: 'http://', linkNofollow: false, linkSize: 50, predefinedLinks: false, // json url (ex. /some-url.json ) or false imageFloatMargin: '10px', imageGetJson: false, // json url (ex. /some-images.json ) or false dragUpload: true, // false imageTabLink: true, imageUpload: false, // url imageUploadParam: 'file', // input name imageResizable: true, fileUpload: false, // url fileUploadParam: 'file', // input name clipboardUpload: true, // or false clipboardUploadUrl: false, // url dnbImageTypes: ['image/png', 'image/jpeg', 'image/gif'], // or false s3: false, uploadFields: false, observeImages: true, observeLinks: true, modalOverlay: true, tabSpaces: false, // true or number of spaces tabFocus: true, air: false, airButtons: ['formatting', 'bold', 'italic', 'deleted', 'unorderedlist', 'orderedlist', 'outdent', 'indent'], toolbar: true, toolbarFixed: false, toolbarFixedTarget: document, toolbarFixedTopOffset: 0, // pixels toolbarFixedBox: false, toolbarExternal: false, // ID selector toolbarOverflow: false, buttonSource: true, buttons: ['html', 'formatting', 'bold', 'italic', 'deleted', 'unorderedlist', 'orderedlist', 'outdent', 'indent', 'image', 'video', 'file', 'table', 'link', 'alignment', '|', 'horizontalrule'], // 'underline', 'alignleft', 'aligncenter', 'alignright', 'justify' buttonsHideOnMobile: [], activeButtons: ['deleted', 'italic', 'bold', 'underline', 'unorderedlist', 'orderedlist', 'alignleft', 'aligncenter', 'alignright', 'justify', 'table'], activeButtonsStates: { b: 'bold', strong: 'bold', i: 'italic', em: 'italic', del: 'deleted', strike: 'deleted', ul: 'unorderedlist', ol: 'orderedlist', u: 'underline', tr: 'table', td: 'table', table: 'table' }, formattingTags: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'], linebreaks: false, paragraphy: true, convertDivs: true, convertLinks: true, convertImageLinks: false, convertVideoLinks: false, formattingPre: false, phpTags: false, allowedTags: false, deniedTags: ['html', 'head', 'link', 'body', 'meta', 'script', 'style', 'applet'], boldTag: 'strong', italicTag: 'em', // private indentValue: 20, buffer: [], rebuffer: [], textareamode: false, emptyHtml: '
', invisibleSpace: '', rBlockTest: /^(P|H[1-6]|LI|ADDRESS|SECTION|HEADER|FOOTER|ASIDE|ARTICLE)$/i, alignmentTags: ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DD', 'DL', 'DT', 'DIV', 'TD', 'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'ADDRESS', 'SECTION', 'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE'], ownLine: ['area', 'body', 'head', 'hr', 'i?frame', 'link', 'meta', 'noscript', 'style', 'script', 'table', 'tbody', 'thead', 'tfoot'], contOwnLine: ['li', 'dt', 'dt', 'h[1-6]', 'option', 'script'], newLevel: ['blockquote', 'div', 'dl', 'fieldset', 'form', 'frameset', 'map', 'ol', 'p', 'pre', 'select', 'td', 'th', 'tr', 'ul'], blockLevelElements: ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DD', 'DL', 'DT', 'DIV', 'LI', 'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'PRE', 'ADDRESS', 'SECTION', 'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE', 'TD'], // lang langs: { en: { html: 'HTML', video: 'Insert Video', image: 'Insert Image', table: 'Table', link: 'Link', link_insert: 'Insert link', link_edit: 'Edit link', unlink: 'Unlink', formatting: 'Formatting', paragraph: 'Normal text', quote: 'Quote', code: 'Code', header1: 'Header 1', header2: 'Header 2', header3: 'Header 3', header4: 'Header 4', header5: 'Header 5', bold: 'Bold', italic: 'Italic', fontcolor: 'Font Color', backcolor: 'Back Color', unorderedlist: 'Unordered List', orderedlist: 'Ordered List', outdent: 'Outdent', indent: 'Indent', cancel: 'Cancel', insert: 'Insert', save: 'Save', _delete: 'Delete', insert_table: 'Insert Table', insert_row_above: 'Add Row Above', insert_row_below: 'Add Row Below', insert_column_left: 'Add Column Left', insert_column_right: 'Add Column Right', delete_column: 'Delete Column', delete_row: 'Delete Row', delete_table: 'Delete Table', rows: 'Rows', columns: 'Columns', add_head: 'Add Head', delete_head: 'Delete Head', title: 'Title', image_position: 'Position', none: 'None', left: 'Left', right: 'Right', center: 'Center', image_web_link: 'Image Web Link', text: 'Text', mailto: 'Email', web: 'URL', video_html_code: 'Video Embed Code', file: 'Insert File', upload: 'Upload', download: 'Download', choose: 'Choose', or_choose: 'Or choose', drop_file_here: 'Drop file here', align_left: 'Align text to the left', align_center: 'Center text', align_right: 'Align text to the right', align_justify: 'Justify text', horizontalrule: 'Insert Horizontal Rule', deleted: 'Deleted', anchor: 'Anchor', link_new_tab: 'Open link in new tab', underline: 'Underline', alignment: 'Alignment', filename: 'Name (optional)', edit: 'Edit' } } }; // Functionality Redactor.fn = $.Redactor.prototype = { keyCode: { BACKSPACE: 8, DELETE: 46, DOWN: 40, ENTER: 13, ESC: 27, TAB: 9, CTRL: 17, META: 91, LEFT: 37, LEFT_WIN: 91 }, // Initialization init: function(el, options) { this.rtePaste = false; this.$element = this.$source = $(el); this.uuid = uuid++; // clonning options var opts = $.extend(true, {}, $.Redactor.opts); // current settings this.opts = $.extend( {}, opts, this.$element.data(), options ); this.start = true; this.dropdowns = []; // get sizes this.sourceHeight = this.$source.css('height'); this.sourceWidth = this.$source.css('width'); // dependency of the editor modes if (this.opts.fullpage) this.opts.iframe = true; if (this.opts.linebreaks) this.opts.paragraphy = false; if (this.opts.paragraphy) this.opts.linebreaks = false; if (this.opts.toolbarFixedBox) this.opts.toolbarFixed = true; // the alias for iframe mode this.document = document; this.window = window; // selection saved this.savedSel = false; // clean setup this.cleanlineBefore = new RegExp('^<(/?' + this.opts.ownLine.join('|/?' ) + '|' + this.opts.contOwnLine.join('|') + ')[ >]'); this.cleanlineAfter = new RegExp('^<(br|/?' + this.opts.ownLine.join('|/?' ) + '|/' + this.opts.contOwnLine.join('|/') + ')[ >]'); this.cleannewLevel = new RegExp('^?(' + this.opts.newLevel.join('|' ) + ')[ >]'); // block level this.rTestBlock = new RegExp('^(' + this.opts.blockLevelElements.join('|' ) + ')$', 'i'); // setup formatting permissions if (this.opts.linebreaks === false) { if (this.opts.allowedTags !== false) { var arrSearch = ['strong', 'em', 'del']; var arrAdd = ['b', 'i', 'strike']; if ($.inArray('p', this.opts.allowedTags) === '-1') this.opts.allowedTags.push('p'); for (i in arrSearch) { if ($.inArray(arrSearch[i], this.opts.allowedTags) != '-1') this.opts.allowedTags.push(arrAdd[i]); } } if (this.opts.deniedTags !== false) { var pos = $.inArray('p', this.opts.deniedTags); if (pos !== '-1') this.opts.deniedTags.splice(pos, pos); } } // ie & opera if (this.browser('msie') || this.browser('opera')) { this.opts.buttons = this.removeFromArrayByValue(this.opts.buttons, 'horizontalrule'); } // load lang this.opts.curLang = this.opts.langs[this.opts.lang]; // extend shortcuts $.extend(this.opts.shortcuts, this.opts.shortcutsAdd); // init placeholder this.placeholderInit(); // Build this.buildStart(); }, toolbarInit: function(lang) { return { html: { title: lang.html, func: 'toggle' }, formatting: { title: lang.formatting, func: 'show', dropdown: { p: { title: lang.paragraph, func: 'formatBlocks' }, blockquote: { title: lang.quote, func: 'formatQuote', className: 'redactor_format_blockquote' }, pre: { title: lang.code, func: 'formatBlocks', className: 'redactor_format_pre' }, h1: { title: lang.header1, func: 'formatBlocks', className: 'redactor_format_h1' }, h2: { title: lang.header2, func: 'formatBlocks', className: 'redactor_format_h2' }, h3: { title: lang.header3, func: 'formatBlocks', className: 'redactor_format_h3' }, h4: { title: lang.header4, func: 'formatBlocks', className: 'redactor_format_h4' }, h5: { title: lang.header5, func: 'formatBlocks', className: 'redactor_format_h5' } } }, bold: { title: lang.bold, exec: 'bold' }, italic: { title: lang.italic, exec: 'italic' }, deleted: { title: lang.deleted, exec: 'strikethrough' }, underline: { title: lang.underline, exec: 'underline' }, unorderedlist: { title: '• ' + lang.unorderedlist, exec: 'insertunorderedlist' }, orderedlist: { title: '1. ' + lang.orderedlist, exec: 'insertorderedlist' }, outdent: { title: '< ' + lang.outdent, func: 'indentingOutdent' }, indent: { title: '> ' + lang.indent, func: 'indentingIndent' }, image: { title: lang.image, func: 'imageShow' }, video: { title: lang.video, func: 'videoShow' }, file: { title: lang.file, func: 'fileShow' }, table: { title: lang.table, func: 'show', dropdown: { insert_table: { title: lang.insert_table, func: 'tableShow' }, separator_drop1: { name: 'separator' }, insert_row_above: { title: lang.insert_row_above, func: 'tableAddRowAbove' }, insert_row_below: { title: lang.insert_row_below, func: 'tableAddRowBelow' }, insert_column_left: { title: lang.insert_column_left, func: 'tableAddColumnLeft' }, insert_column_right: { title: lang.insert_column_right, func: 'tableAddColumnRight' }, separator_drop2: { name: 'separator' }, add_head: { title: lang.add_head, func: 'tableAddHead' }, delete_head: { title: lang.delete_head, func: 'tableDeleteHead' }, separator_drop3: { name: 'separator' }, delete_column: { title: lang.delete_column, func: 'tableDeleteColumn' }, delete_row: { title: lang.delete_row, func: 'tableDeleteRow' }, delete_table: { title: lang.delete_table, func: 'tableDeleteTable' } } }, link: { title: lang.link, func: 'show', dropdown: { link: { title: lang.link_insert, func: 'linkShow' }, unlink: { title: lang.unlink, exec: 'unlink' } } }, alignment: { title: lang.alignment, func: 'show', dropdown: { alignleft: { title: lang.align_left, func: 'alignmentLeft' }, aligncenter: { title: lang.align_center, func: 'alignmentCenter' }, alignright: { title: lang.align_right, func: 'alignmentRight' }, justify: { title: lang.align_justify, func: 'alignmentJustify' } } }, alignleft: { title: lang.align_left, func: 'alignmentLeft' }, aligncenter: { title: lang.align_center, func: 'alignmentCenter' }, alignright: { title: lang.align_right, func: 'alignmentRight' }, alignjustify: { title: lang.align_justify, func: 'alignmentJustify' }, horizontalrule: { exec: 'inserthorizontalrule', title: lang.horizontalrule } } }, // CALLBACKS callback: function(type, event, data) { var callback = this.opts[ type + 'Callback' ]; if ($.isFunction(callback)) { if (event === false) return callback.call(this, data); else return callback.call(this, event, data); } else return data; }, // DESTROY destroy: function() { clearInterval(this.autosaveInterval); $(window).off('.redactor'); this.$source.off('redactor-textarea'); this.$element.off('.redactor').removeData('redactor'); var html = this.get(); if (this.opts.textareamode) { this.$box.after(this.$source); this.$box.remove(); this.$source.val(html).show(); } else { var $elem = this.$editor; if (this.opts.iframe) $elem = this.$element; this.$box.after($elem); this.$box.remove(); $elem.removeClass('redactor_editor').removeClass('redactor_editor_wym').removeAttr('contenteditable').html(html).show(); } if (this.opts.toolbarExternal) { $(this.opts.toolbarExternal).html(''); } if (this.opts.air) { $('#redactor_air_' + this.uuid).remove(); } }, // API GET getObject: function() { return $.extend({}, this); }, getEditor: function() { return this.$editor; }, getBox: function() { return this.$box; }, getIframe: function() { return (this.opts.iframe) ? this.$frame : false; }, getToolbar: function() { return (this.$toolbar) ? this.$toolbar : false; }, // CODE GET & SET get: function() { return this.$source.val(); }, getCodeIframe: function() { this.$editor.removeAttr('contenteditable').removeAttr('dir'); var html = this.outerHtml(this.$frame.contents().children()); this.$editor.attr({ 'contenteditable': true, 'dir': this.opts.direction }); return html; }, set: function(html, strip, placeholderRemove) { html = html.toString(); html = html.replace(/\$/g, '$'); if (this.opts.fullpage) this.setCodeIframe(html); else this.setEditor(html, strip); if (html == '') placeholderRemove = false; if (placeholderRemove !== false) this.placeholderRemoveFromEditor(); }, setEditor: function(html, strip) { if (strip !== false) { html = this.cleanSavePreCode(html); html = this.cleanStripTags(html); html = this.cleanConvertProtected(html); html = this.cleanConvertInlineTags(html, true); if (this.opts.linebreaks === false) html = this.cleanConverters(html); else html = html.replace(/([\w\W]*?)<\/p>/gi, '$2 ' + this.opts.invisibleSpace + ' ' + this.opts.invisibleSpace + ' ' + this.opts.invisibleSpace + ' ' + this.opts.invisibleSpace + ' ').append($(current).clone());
$(current).replaceWith(node);
var next = $(node).next();
if (typeof(next[0]) !== 'undefined' && next[0].tagName == 'BR')
{
next.remove();
}
this.selectionEnd(node);
}
// convert links
if ((this.opts.convertLinks || this.opts.convertImageLinks || this.opts.convertVideoLinks) && key === this.keyCode.ENTER)
{
this.buildEventKeyupConverters();
}
// if empty
if (key === this.keyCode.DELETE || key === this.keyCode.BACKSPACE)
{
return this.formatEmpty(e);
}
this.callback('keyup', e);
this.sync(e);
},
buildEventKeyupConverters: function()
{
this.formatLinkify(this.opts.linkProtocol, this.opts.convertLinks, this.opts.convertImageLinks, this.opts.convertVideoLinks, this.opts.linkSize);
setTimeout($.proxy(function()
{
if (this.opts.convertImageLinks) this.observeImages();
if (this.opts.observeLinks) this.observeLinks();
}, this), 5);
},
buildPlugins: function()
{
if (!this.opts.plugins ) return;
$.each(this.opts.plugins, $.proxy(function(i, s)
{
if (RedactorPlugins[s])
{
$.extend(this, RedactorPlugins[s]);
if ($.isFunction( RedactorPlugins[ s ].init)) this.init();
}
}, this ));
},
// IFRAME
iframeStart: function()
{
this.iframeCreate();
if (this.opts.textareamode) this.iframeAppend(this.$source);
else
{
this.$sourceOld = this.$source.hide();
this.$source = this.buildCodearea(this.$sourceOld);
this.iframeAppend(this.$sourceOld);
}
},
iframeAppend: function(el)
{
this.$source.attr('dir', this.opts.direction).hide();
this.$box.insertAfter(el).append(this.$frame).append(this.$source);
},
iframeCreate: function()
{
this.$frame = $('').one('load', $.proxy(function()
{
if (this.opts.fullpage)
{
this.iframePage();
if (this.content === '') this.content = this.opts.invisibleSpace;
this.$frame.contents()[0].write(this.content);
this.$frame.contents()[0].close();
var timer = setInterval($.proxy(function()
{
if (this.$frame.contents().find('body').html())
{
clearInterval(timer);
this.iframeLoad();
}
}, this), 0);
}
else this.iframeLoad();
}, this));
},
iframeDoc: function()
{
return this.$frame[0].contentWindow.document;
},
iframePage: function()
{
var doc = this.iframeDoc();
if (doc.documentElement) doc.removeChild(doc.documentElement);
return doc;
},
iframeAddCss: function(css)
{
css = css || this.opts.css;
if (this.isString(css))
{
this.$frame.contents().find('head').append('');
}
if ($.isArray(css))
{
$.each(css, $.proxy(function(i, url)
{
this.iframeAddCss(url);
}, this));
}
},
iframeLoad: function()
{
this.$editor = this.$frame.contents().find('body').attr({ 'contenteditable': true, 'dir': this.opts.direction });
// set document & window
if (this.$editor[0])
{
this.document = this.$editor[0].ownerDocument;
this.window = this.document.defaultView || window;
}
// iframe css
this.iframeAddCss();
if (this.opts.fullpage)
{
this.setFullpageOnInit(this.$source.val());
}
else this.set(this.content, true, false);
this.buildOptions();
this.buildAfter();
},
// PLACEHOLDER
placeholderInit: function()
{
if (this.opts.placeholder !== false)
{
this.placeholderText = this.opts.placeholder;
this.opts.placeholder = true;
}
else
{
if (typeof this.$element.attr('placeholder') == 'undefined' || this.$element.attr('placeholder') == '')
{
this.opts.placeholder = false;
}
else
{
this.placeholderText = this.$element.attr('placeholder');
this.opts.placeholder = true;
}
}
},
placeholderStart: function(html)
{
if (this.opts.placeholder === false)
{
return false;
}
if (this.isEmpty(html))
{
this.opts.focus = false;
this.placeholderOnFocus();
this.placeholderOnBlur();
return this.placeholderGet();
}
else
{
this.placeholderOnBlur();
}
return false;
},
placeholderOnFocus: function()
{
this.$editor.on('focus.redactor_placeholder', $.proxy(this.placeholderFocus, this));
},
placeholderOnBlur: function()
{
this.$editor.on('blur.redactor_placeholder', $.proxy(this.placeholderBlur, this));
},
placeholderGet: function()
{
var ph = $('').data('redactor', 'verified')
.attr('contenteditable', false).text(this.placeholderText);
if (this.opts.linebreaks === false)
{
return $(' ').append(ph);
}
else return ph;
},
placeholderBlur: function()
{
var html = this.get();
if (this.isEmpty(html))
{
this.placeholderOnFocus();
this.$editor.html(this.placeholderGet());
}
},
placeholderFocus: function()
{
this.$editor.find('span.redactor_placeholder').remove();
var html = '';
if (this.opts.linebreaks === false)
{
html = this.opts.emptyHtml;
}
this.$editor.off('focus.redactor_placeholder');
this.$editor.html(html);
if (this.opts.linebreaks === false)
{
// place the cursor inside emptyHtml
this.selectionStart(this.$editor.children()[0]);
}
else
{
this.focus();
}
this.sync();
},
placeholderRemoveFromEditor: function()
{
this.$editor.find('span.redactor_placeholder').remove();
this.$editor.off('focus.redactor_placeholder');
},
placeholderRemoveFromCode: function(html)
{
return html.replace(/(.*?)<\/span>/i, '');
},
// SHORTCUTS
shortcuts: function(e, key)
{
// disable browser's hot keys for bold and italic
if (!this.opts.shortcuts)
{
if ((e.ctrlKey || e.metaKey) && (key === 66 || key === 73))
{
e.preventDefault();
}
return false;
}
$.each(this.opts.shortcuts, $.proxy(function(str, command)
{
var keys = str.split(',');
for (var i in keys)
{
if (typeof keys[i] === 'string')
{
this.shortcutsHandler(e, $.trim(keys[i]), $.proxy(function()
{
eval(command);
}, this));
}
}
}, this));
},
shortcutsHandler: function(e, keys, origHandler)
{
// based on https://github.com/jeresig/jquery.hotkeys
var hotkeysSpecialKeys =
{
8: "backspace", 9: "tab", 10: "return", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 59: ";", 61: "=",
96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 173: "-", 186: ";", 187: "=",
188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'"
};
var hotkeysShiftNums =
{
"`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
"8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
".": ">", "/": "?", "\\": "|"
};
keys = keys.toLowerCase().split(" ");
var special = hotkeysSpecialKeys[e.keyCode],
character = String.fromCharCode( e.which ).toLowerCase(),
modif = "", possible = {};
$.each([ "alt", "ctrl", "meta", "shift"], function(index, specialKey)
{
if (e[specialKey + 'Key'] && special !== specialKey)
{
modif += specialKey + '+';
}
});
if (special)
{
possible[modif + special] = true;
}
if (character)
{
possible[modif + character] = true;
possible[modif + hotkeysShiftNums[character]] = true;
// "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
if (modif === "shift+")
{
possible[hotkeysShiftNums[character]] = true;
}
}
for (var i = 0, l = keys.length; i < l; i++)
{
if (possible[keys[i]])
{
e.preventDefault();
return origHandler.apply(this, arguments);
}
}
},
// FOCUS
focus: function()
{
if (!this.browser('opera'))
{
this.window.setTimeout($.proxy(this.focusSet, this, true), 1);
}
else
{
this.$editor.focus();
}
},
focusWithSaveScroll: function()
{
if (this.browser('msie'))
{
var top = this.document.documentElement.scrollTop;
}
this.$editor.focus();
if (this.browser('msie'))
{
this.document.documentElement.scrollTop = top;
}
},
focusEnd: function()
{
if (!this.browser('mozilla'))
{
this.focusSet();
}
else
{
if (this.opts.linebreaks === false)
{
var last = this.$editor.children().last();
this.$editor.focus();
this.selectionEnd(last);
}
else
{
this.focusSet();
}
}
},
focusSet: function(collapse, element)
{
this.$editor.focus();
if (typeof element == 'undefined')
{
element = this.$editor[0];
}
var range = this.getRange();
range.selectNodeContents(element);
// collapse - controls the position of focus: the beginning (true), at the end (false).
range.collapse(collapse || false);
var sel = this.getSelection();
sel.removeAllRanges();
sel.addRange(range);
},
// TOGGLE
toggle: function(direct)
{
if (this.opts.visual) this.toggleCode(direct);
else this.toggleVisual();
},
toggleVisual: function()
{
var html = this.$source.hide().val();;
if (typeof this.modified !== 'undefined')
{
var modified = this.modified.replace(/\n/g, '');
var thtml = html.replace(/\n/g, '');
thtml = this.cleanRemoveSpaces(thtml, false);
this.modified = this.cleanRemoveSpaces(modified, false) !== thtml;
}
if (this.modified)
{
// don't remove the iframe even if cleared all.
if (this.opts.fullpage && html === '')
{
this.setFullpageOnInit(html);
}
else
{
this.set(html);
if (this.opts.fullpage)
{
this.buildBindKeyboard();
}
}
this.callback('change', false, html);
}
if (this.opts.iframe) this.$frame.show();
else this.$editor.show();
if (this.opts.fullpage) this.$editor.attr('contenteditable', true );
this.$source.off('keydown.redactor-textarea-indenting');
this.$editor.focus();
this.selectionRestore();
this.observeStart();
this.buttonActiveVisual();
this.buttonInactive('html');
this.opts.visual = true;
},
toggleCode: function(direct)
{
if (direct !== false) this.selectionSave();
var height = null;
if (this.opts.iframe)
{
height = this.$frame.height();
if (this.opts.fullpage) this.$editor.removeAttr('contenteditable');
this.$frame.hide();
}
else
{
height = this.$editor.innerHeight();
this.$editor.hide();
}
var html = this.$source.val();
// tidy html
if (html !== '' && this.opts.tidyHtml)
{
this.$source.val(this.cleanHtml(html));
}
this.modified = html;
this.$source.height(height).show().focus();
// textarea indenting
this.$source.on('keydown.redactor-textarea-indenting', this.textareaIndenting);
this.buttonInactiveVisual();
this.buttonActive('html');
this.opts.visual = false;
},
textareaIndenting: function(e)
{
if (e.keyCode === 9)
{
var $el = $(this);
var start = $el.get(0).selectionStart;
$el.val($el.val().substring(0, start) + "\t" + $el.val().substring($el.get(0).selectionEnd));
$el.get(0).selectionStart = $el.get(0).selectionEnd = start + 1;
return false;
}
},
// AUTOSAVE
autosave: function()
{
var savedHtml = false;
this.autosaveInterval = setInterval($.proxy(function()
{
var html = this.get();
if (savedHtml !== html)
{
var name = this.$source.attr('name');
$.ajax({
url: this.opts.autosave,
type: 'post',
data: 'name=' + name + '&' + name + '=' + escape(encodeURIComponent(html)),
success: $.proxy(function(data)
{
var json = $.parseJSON(data);
if (typeof json.error == 'undefined')
{
// success
this.callback('autosave', false, json);
}
else
{
// error
this.callback('autosaveError', false, json);
}
savedHtml = html;
}, this)
});
}
}, this), this.opts.autosaveInterval*1000);
},
// TOOLBAR
toolbarBuild: function()
{
// hide on mobile
if (this.isMobile() && this.opts.buttonsHideOnMobile.length > 0)
{
$.each(this.opts.buttonsHideOnMobile, $.proxy(function(i, s)
{
var index = this.opts.buttons.indexOf(s);
this.opts.buttons.splice(index, 1);
}, this));
}
// extend buttons
if (this.opts.air)
{
this.opts.buttons = this.opts.airButtons;
}
else
{
if (!this.opts.buttonSource)
{
var index = this.opts.buttons.indexOf('html');
this.opts.buttons.splice(index, 1);
}
}
// formatting tags
if (this.opts.toolbar)
{
$.each(this.opts.toolbar.formatting.dropdown, $.proxy(function (i, s)
{
if ($.inArray(i, this.opts.formattingTags ) == '-1') delete this.opts.toolbar.formatting.dropdown[i];
}, this));
}
// if no buttons don't create a toolbar
if (this.opts.buttons.length === 0) return false;
// air enable
this.airEnable();
// toolbar build
this.$toolbar = $('
');
}
// $ fix
html = html.replace(/$/g, '$');
html = this.cleanEmpty(html);
this.$editor.html(html);
// set no editable
this.setNonEditable();
this.setSpansVerified();
this.sync();
},
setCodeIframe: function(html)
{
var doc = this.iframePage();
this.$frame[0].src = "about:blank";
html = this.cleanConvertProtected(html);
html = this.cleanConvertInlineTags(html);
html = this.cleanRemoveSpaces(html);
doc.open();
doc.write(html);
doc.close();
// redefine editor for fullpage mode
if (this.opts.fullpage)
{
this.$editor = this.$frame.contents().find('body').attr({ 'contenteditable': true, 'dir': this.opts.direction });
}
// set no editable
this.setNonEditable();
this.setSpansVerified();
this.sync();
},
setFullpageOnInit: function(html)
{
this.fullpageDoctype = html.match(/^<\!doctype[^>]*>/i);
if (this.fullpageDoctype && this.fullpageDoctype.length == 1)
{
html = html.replace(/^<\!doctype[^>]*>/i, '');
}
html = this.cleanSavePreCode(html, true);
html = this.cleanConverters(html);
html = this.cleanEmpty(html);
// set code
this.$editor.html(html);
// set no editable
this.setNonEditable();
this.setSpansVerified();
this.sync();
},
setFullpageDoctype: function()
{
if (this.fullpageDoctype && this.fullpageDoctype.length == 1)
{
var source = this.fullpageDoctype[0] + '\n' + this.$source.val();
this.$source.val(source);
}
},
setSpansVerified: function()
{
var spans = this.$editor.find('span');
var replacementTag = 'inline';
$.each(spans, function() {
var outer = this.outerHTML;
// Replace opening tag
var regex = new RegExp('<' + this.tagName, 'gi');
var newTag = outer.replace(regex, '<' + replacementTag);
// Replace closing tag
regex = new RegExp('' + this.tagName, 'gi');
newTag = newTag.replace(regex, '' + replacementTag);
$(this).replaceWith(newTag);
});
},
setSpansVerifiedHtml: function(html)
{
html = html.replace(//, '
') html = '';
// xhtml
if (this.opts.xhtml)
{
var xhtmlTags = ['br', 'hr', 'img', 'link', 'input', 'meta'];
$.each(xhtmlTags, function(i,s)
{
html = html.replace(new RegExp('<' + s + '(.*?[^\/$]?)>', 'gi'), '<' + s + '$1 />');
});
}
// before callback
html = this.callback('syncBefore', false, html);
this.$source.val(html);
this.setFullpageDoctype();
// onchange & after callback
this.callback('syncAfter', false, html);
if (this.start === false)
{
if (typeof e != 'undefined')
{
switch(e.which)
{
case 37: // left
break;
case 38: // up
break;
case 39: // right
break;
case 40: // down
break;
default: this.callback('change', false, html);
}
}
else
{
this.callback('change', false, html);
}
}
},
syncClean: function(html)
{
if (!this.opts.fullpage) html = this.cleanStripTags(html);
// trim
html = $.trim(html);
// removeplaceholder
html = this.placeholderRemoveFromCode(html);
// remove space
html = html.replace(//gi, '');
html = html.replace(//gi, '');
html = html.replace(/<\/a> /gi, '<\/a> ');
html = html.replace(/\u200B/g, '');
if (html == '' || html == '
\n?<\/(P|H[1-6]|LI|ADDRESS|SECTION|HEADER|FOOTER|ASIDE|ARTICLE)>/gi, '$1>');
// remove image resize
html = html.replace(/([\w\W]*?)<\/span>/gi, '$3
');
html = html.replace(/(.*?)<\/span>/gi, '');
html = html.replace(/(.*?)<\/span>/gi, '');
// remove empty lists
html = html.replace(/<(ul|ol)>\s*\t*\n*<\/(ul|ol)>/gi, '');
// remove font
if (this.opts.cleanFontTag)
{
html = html.replace(/([\w\W]*?)<\/font>/gi, '$2');
}
// remove spans
html = html.replace(/([\w\W]*?)<\/span>/gi, '$2');
html = html.replace(/
/gi, '
');
// special characters
html = html.replace(/&/gi, '&');
html = html.replace(/\u2122/gi, '™');
html = html.replace(/\u00a9/gi, '©');
html = html.replace(/\u2026/gi, '…');
html = html.replace(/\u2014/gi, '—');
html = html.replace(/\u2010/gi, '‐');
html = this.cleanReConvertProtected(html);
return html;
},
// BUILD
buildStart: function()
{
// content
this.content = '';
// container
this.$box = $('');
// textarea test
if (this.$source[0].tagName === 'TEXTAREA') this.opts.textareamode = true;
// mobile
if (this.opts.mobile === false && this.isMobile())
{
this.buildMobile();
}
else
{
// get the content at the start
this.buildContent();
if (this.opts.iframe)
{
// build as iframe
this.opts.autoresize = false;
this.iframeStart();
}
else if (this.opts.textareamode) this.buildFromTextarea();
else this.buildFromElement();
// options and final setup
if (!this.opts.iframe)
{
this.buildOptions();
this.buildAfter();
}
}
},
buildMobile: function()
{
if (!this.opts.textareamode)
{
this.$editor = this.$source;
this.$editor.hide();
this.$source = this.buildCodearea(this.$editor);
this.$source.val(this.content);
}
this.$box.insertAfter(this.$source).append(this.$source);
},
buildContent: function()
{
if (this.opts.textareamode) this.content = $.trim(this.$source.val());
else this.content = $.trim(this.$source.html());
},
buildFromTextarea: function()
{
this.$editor = $('');
this.$box.insertAfter(this.$source).append(this.$editor).append(this.$source);
// enable
this.buildAddClasses(this.$editor);
this.buildEnable();
},
buildFromElement: function()
{
this.$editor = this.$source;
this.$source = this.buildCodearea(this.$editor);
this.$box.insertAfter(this.$editor).append(this.$editor).append(this.$source);
// enable
this.buildEnable();
},
buildCodearea: function($source)
{
return $('').attr('name', $source.attr('id')).css('height', this.sourceHeight);
},
buildAddClasses: function(el)
{
// append textarea classes to editable layer
$.each(this.$source.get(0).className.split(/\s+/), function(i,s)
{
el.addClass('redactor_' + s);
});
},
buildEnable: function()
{
this.$editor.addClass('redactor_editor').attr({ 'contenteditable': true, 'dir': this.opts.direction });
this.$source.attr('dir', this.opts.direction).hide();
// set code
this.set(this.content, true, false);
},
buildOptions: function()
{
var $source = this.$editor;
if (this.opts.iframe) $source = this.$frame;
// options
if (this.opts.tabindex) $source.attr('tabindex', this.opts.tabindex);
if (this.opts.minHeight) $source.css('min-height', this.opts.minHeight + 'px');
// FF fix bug with line-height rendering
else if (this.browser('mozilla') && this.opts.linebreaks)
{
this.$editor.css('min-height', '45px');
}
// FF fix bug with line-height rendering
if (this.browser('mozilla') && this.opts.linebreaks)
{
this.$editor.css('padding-bottom', '10px');
}
if (this.opts.maxHeight)
{
this.opts.autoresize = false;
this.sourceHeight = this.opts.maxHeight;
}
if (this.opts.wym) this.$editor.addClass('redactor_editor_wym');
if (this.opts.typewriter) this.$editor.addClass('redactor-editor-typewriter');
if (!this.opts.autoresize) $source.css('height', this.sourceHeight);
},
buildAfter: function()
{
this.start = false;
// load toolbar
if (this.opts.toolbar)
{
this.opts.toolbar = this.toolbarInit(this.opts.curLang);
this.toolbarBuild();
}
// modal templates
this.modalTemplatesInit();
// plugins
this.buildPlugins();
// enter, tab, etc.
this.buildBindKeyboard();
// autosave
if (this.opts.autosave) this.autosave();
// observers
setTimeout($.proxy(this.observeStart, this), 4);
// FF fix
if (this.browser('mozilla'))
{
try {
this.document.execCommand('enableObjectResizing', false, false);
this.document.execCommand('enableInlineTableEditing', false, false);
} catch (e) {}
}
// focus
if (this.opts.focus) setTimeout($.proxy(this.focus, this), 100);
// code mode
if (!this.opts.visual)
{
setTimeout($.proxy(function()
{
this.opts.visual = true;
this.toggle(false);
}, this), 200);
}
// init callback
this.callback('init');
},
buildBindKeyboard: function()
{
this.dblEnter = 0;
if (this.opts.dragUpload && (this.opts.imageUpload !== false || this.opts.s3 !== false))
{
this.$editor.on('drop.redactor', $.proxy(this.buildEventDrop, this));
}
this.$editor.on('click.redactor', $.proxy(function()
{
this.selectall = false;
}, this));
this.$editor.on('input.redactor', $.proxy(this.sync, this));
this.$editor.on('paste.redactor', $.proxy(this.buildEventPaste, this));
this.$editor.on('keydown.redactor', $.proxy(this.buildEventKeydown, this));
this.$editor.on('keyup.redactor', $.proxy(this.buildEventKeyup, this));
// textarea callback
if ($.isFunction(this.opts.textareaKeydownCallback))
{
this.$source.on('keydown.redactor-textarea', $.proxy(this.opts.textareaKeydownCallback, this));
}
// focus callback
if ($.isFunction(this.opts.focusCallback))
{
this.$editor.on('focus.redactor', $.proxy(this.opts.focusCallback, this));
}
var clickedElement;
$(document).mousedown(function(e) {
clickedElement = $(e.target);
});
// blur callback
this.$editor.on('blur.redactor', $.proxy(function(e)
{
if (!$(clickedElement).hasClass('redactor_toolbar') && $(clickedElement).parents('.redactor_toolbar').size() == 0)
{
this.selectall = false;
if ($.isFunction(this.opts.blurCallback)) this.callback('blur', e);
}
}, this));
},
buildEventDrop: function(e)
{
e = e.originalEvent || e;
if (window.FormData === undefined || !e.dataTransfer) return true;
var length = e.dataTransfer.files.length;
if (length == 0) return true;
e.preventDefault();
var file = e.dataTransfer.files[0];
if (this.opts.dnbImageTypes !== false && this.opts.dnbImageTypes.indexOf(file.type) == -1)
{
return true;
}
this.bufferSet();
this.showProgressBar();
if (this.opts.s3 === false)
{
this.dragUploadAjax(this.opts.imageUpload, file, true, e, this.opts.imageUploadParam);
}
else
{
this.s3uploadFile(file);
}
},
buildEventPaste: function(e)
{
var oldsafari = false;
if (this.browser('webkit') && navigator.userAgent.indexOf('Chrome') === -1)
{
var arr = this.browser('version').split('.');
if (arr[0] < 536) oldsafari = true;
}
if (oldsafari) return true;
// paste except opera (not webkit)
if (this.browser('opera')) return true;
// clipboard upload
if (this.opts.clipboardUpload && this.buildEventClipboardUpload(e)) return true;
if (this.opts.cleanup)
{
this.rtePaste = true;
this.selectionSave();
if (!this.selectall)
{
if (this.opts.autoresize === true && this.fullscreen !== true)
{
this.$editor.height(this.$editor.height());
this.saveScroll = this.document.body.scrollTop;
}
else
{
this.saveScroll = this.$editor.scrollTop();
}
}
var frag = this.extractContent();
setTimeout($.proxy(function()
{
var pastedFrag = this.extractContent();
this.$editor.append(frag);
this.selectionRestore();
var html = this.getFragmentHtml(pastedFrag);
this.pasteClean(html);
if (this.opts.autoresize === true && this.fullscreen !== true) this.$editor.css('height', 'auto');
}, this), 1);
}
},
buildEventClipboardUpload: function(e)
{
var event = e.originalEvent || e;
this.clipboardFilePaste = false;
if (typeof(event.clipboardData) === 'undefined') return false;
if (event.clipboardData.items)
{
var file = event.clipboardData.items[0].getAsFile();
if (file !== null)
{
this.bufferSet();
this.clipboardFilePaste = true;
var reader = new FileReader();
reader.onload = $.proxy(this.pasteClipboardUpload, this);
reader.readAsDataURL(file);
return true;
}
}
return false;
},
buildEventKeydown: function(e)
{
if (this.rtePaste) return false;
var key = e.which;
var ctrl = e.ctrlKey || e.metaKey;
var parent = this.getParent();
var current = this.getCurrent();
var block = this.getBlock();
var pre = false;
this.callback('keydown', e);
/*
firefox cmd+left/Cmd+right browser back/forward fix -
http://joshrhoderick.wordpress.com/2010/05/05/how-firefoxs-command-key-bug-kills-usability-on-the-mac/
*/
if (this.browser('mozilla') && "modify" in window.getSelection())
{
if ((ctrl) && (e.keyCode===37 || e.keyCode===39))
{
var selection = this.getSelection();
var lineOrWord = (e.metaKey ? "line" : "word");
if (e.keyCode===37)
{
selection.modify("extend","left",lineOrWord);
if (!e.shiftKey)
{
selection.collapseToStart();
}
}
if (e.keyCode===39)
{
selection.modify("extend","right",lineOrWord);
if (!e.shiftKey)
{
selection.collapseToEnd();
}
}
e.preventDefault();
}
}
this.imageResizeHide(false);
// pre & down
if ((parent && $(parent).get(0).tagName === 'PRE') || (current && $(current).get(0).tagName === 'PRE'))
{
pre = true;
if (key === this.keyCode.DOWN) this.insertAfterLastElement(block);
}
// down
if (key === this.keyCode.DOWN)
{
if (parent && $(parent)[0].tagName === 'BLOCKQUOTE') this.insertAfterLastElement(parent);
if (current && $(current)[0].tagName === 'BLOCKQUOTE') this.insertAfterLastElement(current);
if (parent && $(parent)[0].tagName === 'P' && $(parent).parent()[0].tagName == 'BLOCKQUOTE')
{
this.insertAfterLastElement(parent, $(parent).parent()[0]);
}
if (current && $(current)[0].tagName === 'P' && parent && $(parent)[0].tagName == 'BLOCKQUOTE')
{
this.insertAfterLastElement(current, parent);
}
}
// shortcuts setup
this.shortcuts(e, key);
// buffer setup
if (ctrl && key === 90 && !e.shiftKey && !e.altKey) // z key
{
e.preventDefault();
if (this.opts.buffer.length) this.bufferUndo();
else this.document.execCommand('undo', false, false);
return;
}
// undo
else if (ctrl && key === 90 && e.shiftKey && !e.altKey)
{
e.preventDefault();
if (this.opts.rebuffer.length != 0) this.bufferRedo();
else this.document.execCommand('redo', false, false);
return;
}
// space
if (key == 32)
{
this.bufferSet();
}
// select all
if (ctrl && key === 65)
{
this.bufferSet();
this.selectall = true;
}
else if (key != this.keyCode.LEFT_WIN && !ctrl)
{
this.selectall = false;
}
// enter
if (key == this.keyCode.ENTER && !e.shiftKey && !e.ctrlKey && !e.metaKey)
{
// remove selected content on enter
var range = this.getRange();
if (range && range.collapsed === false)
{
sel = this.getSelection();
if (sel.rangeCount)
{
range.deleteContents();
}
}
// In ie, opera in the tables are created paragraphs, fix it.
if (this.browser('msie') && (parent.nodeType == 1 && (parent.tagName == 'TD' || parent.tagName == 'TH')))
{
e.preventDefault();
this.bufferSet();
this.insertNode(document.createElement('br'));
this.callback('enter', e);
return false;
}
// blockquote exit
if (block && (block.tagName == 'BLOCKQUOTE' || $(block).parent()[0].tagName == 'BLOCKQUOTE'))
{
if (this.isEndOfElement())
{
if (this.dblEnter == 1)
{
var element;
var last;
if (block.tagName == 'BLOCKQUOTE')
{
last = 'br';
element = block;
}
else
{
last = 'p';
element = $(block).parent()[0];
}
e.preventDefault();
this.insertingAfterLastElement(element);
this.dblEnter = 0;
if (last == 'p')
{
$(block).parent().find('p').last().remove();
}
else
{
var tmp = $.trim($(block).html());
$(block).html(tmp.replace(/
$/i, ''));
}
return;
}
else this.dblEnter++;
}
else this.dblEnter++;
}
// pre
if (pre === true)
{
return this.buildEventKeydownPre(e, current);
}
else
{
if (!this.opts.linebreaks)
{
// lists exit
if (block && block.tagName == 'LI')
{
var listCurrent = this.getBlock();
if (listCurrent !== false || listCurrent.tagName === 'LI')
{
var listText = $.trim($(block).text());
var listCurrentText = $.trim($(listCurrent).text());
if (listText == ''
&& listCurrentText == ''
&& $(listCurrent).next('li').size() == 0
&& $(listCurrent).parents('li').size() == 0)
{
this.bufferSet();
var $list = $(listCurrent).closest('ol, ul');
$(listCurrent).remove();
var node = $('
' + this.opts.invisibleSpace);
$(current).replaceWith(node);
this.selectionStart(node);
this.sync();
}
if (typeof current.nodeValue !== 'undefined' && current.nodeValue !== null)
{
if (current.remove && current.nodeType === 3 && current.nodeValue.match(/[^\u200B]/g) == null)
{
$(current).prev().remove();
this.sync();
}
}
},
buildEventKeydownInsertLineBreak: function(e)
{
this.bufferSet();
e.preventDefault();
this.insertLineBreak();
this.callback('enter', e);
return;
},
buildEventKeyup: function(e)
{
if (this.rtePaste) return false;
var key = e.which;
var parent = this.getParent();
var current = this.getCurrent();
// replace to p before / after the table or body
if (!this.opts.linebreaks && current.nodeType == 3 && (parent == false || parent.tagName == 'BODY'))
{
var node = $('').addClass('redactor_toolbar').attr('id', 'redactor_toolbar_' + this.uuid);
if (this.opts.typewriter)
{
this.$toolbar.addClass('redactor-toolbar-typewriter');
}
if (this.opts.toolbarOverflow && this.isMobile())
{
this.$toolbar.addClass('redactor-toolbar-overflow');
}
if (this.opts.air)
{
// air box
this.$air = $('