/* Redactor v10.0.2 Updated: October 12, 2014 http://imperavi.com/redactor/ Copyright (c) 2009-2014, Imperavi LLC. License: http://imperavi.com/redactor/license/ Usage: $('#content').redactor(); */ (function($) { 'use strict'; if (!Function.prototype.bind) { Function.prototype.bind = function(scope) { var fn = this; return function() { return fn.apply(scope); }; }; } var uuid = 0; 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'); var func; if (options.search(/\./) != '-1') { func = options.split('.'); if (typeof instance[func[0]] != 'undefined') { func = instance[func[0]][func[1]]; } } else { func = instance[options]; } if (typeof instance !== 'undefined' && $.isFunction(func)) { var methodVal = func.apply(instance, args); if (methodVal !== undefined && methodVal !== instance) { val.push(methodVal); } } else { $.error('No such method "' + options + '" for Redactor'); } }); } else { this.each(function() { $.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); } // Functionality $.Redactor = Redactor; $.Redactor.VERSION = '10.0.2'; $.Redactor.modules = ['core', 'build', 'lang', 'toolbar', 'button', 'dropdown', 'code', 'clean', 'tidy', 'paragraphize', 'tabifier', 'focus', 'placeholder', 'autosave', 'buffer', 'indent', 'alignment', 'paste', 'keydown', 'keyup', 'shortcuts', 'line', 'list', 'block', 'inline', 'insert', 'caret', 'selection', 'observe', 'link', 'image', 'file', 'modal', 'progress', 'upload', 'utils']; $.Redactor.opts = { // settings lang: 'en', direction: 'ltr', // ltr or rtl plugins: false, // array focus: false, focusEnd: false, placeholder: false, visual: true, tabindex: false, minHeight: false, maxHeight: false, linebreaks: false, replaceDivs: true, paragraphize: true, cleanStyleOnEnter: false, enterKey: true, cleanOnPaste: true, cleanSpaces: true, pastePlainText: false, autosave: false, // false or url autosaveName: false, autosaveInterval: 60, // seconds autosaveOnChange: false, linkTooltip: true, linkProtocol: 'http', linkNofollow: false, linkSize: 50, imageEditable: true, imageLink: true, imagePosition: true, imageFloatMargin: '10px', imageResizable: true, imageUpload: false, imageUploadParam: 'file', uploadImageField: false, dragImageUpload: true, fileUpload: false, fileUploadParam: 'file', dragFileUpload: true, s3: false, convertLinks: true, convertUrlLinks: true, convertImageLinks: true, convertVideoLinks: true, preSpaces: 4, // or false tabAsSpaces: false, // true or number of spaces tabFocus: true, scrollTarget: false, toolbar: true, toolbarFixed: true, toolbarFixedTarget: document, toolbarFixedTopOffset: 0, // pixels toolbarExternal: false, // ID selector toolbarOverflow: false, buttonSource: false, buttons: ['html', 'formatting', 'bold', 'italic', 'deleted', 'unorderedlist', 'orderedlist', 'outdent', 'indent', 'image', 'file', 'link', 'alignment', 'horizontalrule'], // + 'underline' buttonsHide: [], buttonsHideOnMobile: [], formatting: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'], formattingAdd: false, tabifier: true, deniedTags: ['html', 'head', 'link', 'body', 'meta', 'script', 'style', 'applet'], allowedTags: false, // or array removeComments: false, replaceTags: [ ['strike', 'del'] ], replaceStyles: [ ['font-weight:\\s?bold', "strong"], ['font-style:\\s?italic', "em"], ['text-decoration:\\s?underline', "u"], ['text-decoration:\\s?line-through', 'del'] ], removeDataAttr: false, removeAttr: false, // or multi array allowedAttr: false, // or multi array removeWithoutAttr: ['span'], // or false removeEmpty: ['p'], // or false; activeButtons: ['deleted', 'italic', 'bold', 'underline', 'unorderedlist', 'orderedlist', 'alignleft', 'aligncenter', 'alignright', 'justify'], activeButtonsStates: { b: 'bold', strong: 'bold', i: 'italic', em: 'italic', del: 'deleted', strike: 'deleted', ul: 'unorderedlist', ol: 'orderedlist', u: 'underline' }, shortcuts: { 'ctrl+shift+m, meta+shift+m': { func: 'inline.removeFormat' }, 'ctrl+b, meta+b': { func: 'inline.format', params: ['bold'] }, 'ctrl+i, meta+i': { func: 'inline.format', params: ['italic'] }, 'ctrl+h, meta+h': { func: 'inline.format', params: ['superscript'] }, 'ctrl+l, meta+l': { func: 'inline.format', params: ['subscript'] }, 'ctrl+k, meta+k': { func: 'link.show' }, 'ctrl+shift+7': { func: 'list.toggle', params: ['orderedlist'] }, 'ctrl+shift+8': { func: 'list.toggle', params: ['unorderedlist'] } }, shortcutsAdd: false, // private buffer: [], rebuffer: [], emptyHtml: '
', invisibleSpace: '', imageTypes: ['image/png', 'image/jpeg', 'image/gif'], indentValue: 20, verifiedTags: ['a', 'img', 'b', 'strong', 'sub', 'sup', 'i', 'em', 'u', 'small', 'strike', 'del', 'cite', 'ul', 'ol', 'li'], // and for span tag special rule inlineTags: ['strong', 'b', 'u', 'em', 'i', 'code', 'del', 'ins', 'samp', 'kbd', 'sup', 'sub', 'mark', 'var', 'cite', 'small'], alignmentTags: ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DL', 'DT', 'DD', 'DIV', 'TD', 'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'ADDRESS', 'SECTION', 'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE'], blockLevelElements: ['PRE', 'UL', 'OL', 'LI'], // 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 or Youtube/Vimeo Link', 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, SPACE: 32, ESC: 27, TAB: 9, CTRL: 17, META: 91, SHIFT: 16, ALT: 18, LEFT: 37, LEFT_WIN: 91 }, // Initialization init: function(el, options) { this.$element = $(el); this.uuid = uuid++; // if paste event detected = true this.rtePaste = false; this.$pasteBox = false; this.loadOptions(options); this.loadModules(); // formatting storage this.formatting = {}; // block level tags $.merge(this.opts.blockLevelElements, this.opts.alignmentTags); this.reIsBlock = new RegExp('^(' + this.opts.blockLevelElements.join('|' ) + ')$', 'i'); // setup allowed and denied tags this.tidy.setupAllowed(); // load lang this.lang.load(); // extend shortcuts $.extend(this.opts.shortcuts, this.opts.shortcutsAdd); // start callback this.core.setCallback('start'); // build this.start = true; this.build.run(); }, loadOptions: function(options) { this.opts = $.extend( {}, $.extend(true, {}, $.Redactor.opts), this.$element.data(), options ); }, getModuleMethods: function(object) { return Object.getOwnPropertyNames(object).filter(function(property) { return typeof object[property] == 'function'; }); }, loadModules: function() { var len = $.Redactor.modules.length; for (var i = 0; i < len; i++) { this.bindModuleMethods($.Redactor.modules[i]); } }, bindModuleMethods: function(module) { if (typeof this[module] == 'undefined') return; // init module this[module] = this[module](); var methods = this.getModuleMethods(this[module]); var len = methods.length; // bind methods for (var z = 0; z < len; z++) { this[module][methods[z]] = this[module][methods[z]].bind(this); } }, core: function() { return { getObject: function() { return $.extend({}, this); }, getEditor: function() { return this.$editor; }, getBox: function() { return this.$box; }, getElement: function() { return this.$element; }, getTextarea: function() { return this.$textarea; }, getToolbar: function() { return (this.$toolbar) ? this.$toolbar : false; }, addEvent: function(name) { this.core.event = name; }, getEvent: function() { return this.core.event; }, setCallback: function(type, e, data) { var callback = this.opts[type + 'Callback']; if ($.isFunction(callback)) { return (typeof data == 'undefined') ? callback.call(this, e) : callback.call(this, e, data); } else { return (typeof data == 'undefined') ? e : data; } }, destroy: function() { this.core.setCallback('destroy'); // off events and remove data this.$element.off('.redactor').removeData('redactor'); this.$editor.off('.redactor'); // common this.$editor.removeClass('redactor-editor redactor-linebreaks redactor-placeholder'); this.$editor.removeAttr('contenteditable'); var html = this.code.get(); if (this.build.isTextarea()) { this.$box.after(this.$element); this.$box.remove(); this.$element.val(html).show(); } else { this.$box.after(this.$editor); this.$box.remove(); this.$element.html(html).show(); } // paste box if (this.$pasteBox) this.$pasteBox.remove(); // modal if (this.$modalBox) this.$modalBox.remove(); if (this.$modalOverlay) this.$modalOverlay.remove(); // buttons tooltip $('.redactor-toolbar-tooltip').remove(); // autosave clearInterval(this.autosaveInterval); } }; }, build: function() { return { run: function() { this.build.createContainerBox(); this.build.loadContent(); this.build.loadEditor(); this.build.enableEditor(); this.build.setCodeAndCall(); }, isTextarea: function() { return (this.$element[0].tagName === 'TEXTAREA'); }, createContainerBox: function() { this.$box = $(''); }, createTextarea: function() { this.$textarea = $('').attr('name', this.build.getTextareaName()); }, getTextareaName: function() { var name = this.$element.attr('id'); if (typeof(name) == 'undefined') { name = 'content-' + this.uuid; } return name; }, loadContent: function() { var func = (this.build.isTextarea()) ? 'val' : 'html'; this.content = $.trim(this.$element[func]()); }, enableEditor: function() { this.$editor.attr({ 'contenteditable': true, 'dir': this.opts.direction }); }, loadEditor: function() { var func = (this.build.isTextarea()) ? 'fromTextarea' : 'fromElement'; this.build[func](); }, fromTextarea: function() { this.$editor = $(''); this.$textarea = this.$element; this.$box.insertAfter(this.$element).append(this.$editor).append(this.$element); this.$editor.addClass('redactor-editor'); this.$element.hide(); }, fromElement: function() { this.$editor = this.$element; this.build.createTextarea(); this.$box.insertAfter(this.$editor).append(this.$editor).append(this.$textarea); this.$editor.addClass('redactor-editor'); this.$textarea.hide(); }, setCodeAndCall: function() { // set code this.code.set(this.content); this.build.setOptions(); this.build.callEditor(); // code mode if (!this.opts.visual) { setTimeout($.proxy(this.code.showCode, this), 200); } }, callEditor: function() { this.build.disableMozillaEditing(); this.build.setEvents(); this.build.setHelpers(); // load toolbar if (this.opts.toolbar) { this.opts.toolbar = this.toolbar.init(); this.toolbar.build(); } // modal templates init this.modal.loadTemplates(); // plugins this.build.plugins(); // observers setTimeout($.proxy(this.observe.load, this), 4); // init callback this.core.setCallback('init'); }, setOptions: function() { // textarea direction $(this.$textarea).attr('dir', this.opts.direction); if (this.opts.linebreaks) this.$editor.addClass('redactor-linebreaks'); if (this.opts.tabindex) this.$editor.attr('tabindex', this.opts.tabindex); if (this.opts.minHeight) this.$editor.css('minHeight', this.opts.minHeight); if (this.opts.maxHeight) this.$editor.css('maxHeight', this.opts.maxHeight); }, setEvents: function() { // drop this.$editor.on('drop.redactor', $.proxy(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; else { e.preventDefault(); if (this.opts.dragImageUpload || this.opts.dragFileUpload) { var files = e.dataTransfer.files; this.upload.directUpload(files[0], e); } } setTimeout($.proxy(this.clean.clearUnverified, this), 1); this.core.setCallback('drop', e); }, this)); // click this.$editor.on('click.redactor', $.proxy(function(e) { var type = 'click'; if ((this.core.getEvent() == 'click' || this.core.getEvent() == 'arrow')) { type = false; } this.core.addEvent(type); this.utils.disableSelectAll(); this.core.setCallback('click', e); }, this)); // paste this.$editor.on('paste.redactor', $.proxy(this.paste.init, this)); // keydown this.$editor.on('keydown.redactor', $.proxy(this.keydown.init, this)); // keyup this.$editor.on('keyup.redactor', $.proxy(this.keyup.init, this)); // textarea keydown if ($.isFunction(this.opts.codeKeydownCallback)) { this.$textarea.on('keydown.redactor-textarea', $.proxy(this.opts.codeKeydownCallback, this)); } // textarea keyup if ($.isFunction(this.opts.codeKeyupCallback)) { this.$textarea.on('keyup.redactor-textarea', $.proxy(this.opts.codeKeyupCallback, this)); } // focus if ($.isFunction(this.opts.focusCallback)) { this.$editor.on('focus.redactor', $.proxy(this.opts.focusCallback, this)); } var clickedElement; $(document).on('mousedown', function(e) { clickedElement = $(e.target); }); // blur this.$editor.on('blur.redactor', $.proxy(function(e) { if (this.rtePaste) return; var $el = $(clickedElement); if (!$el.hasClass('redactor-toolbar, redactor-dropdown') && !$el.is('#redactor-modal') && $el.parents('.redactor-toolbar, .redactor-dropdown, #redactor-modal').size() === 0) { this.utils.disableSelectAll(); if ($.isFunction(this.opts.blurCallback)) this.core.setCallback('blur', e); } }, this)); }, setHelpers: function() { // autosave this.autosave.enable(); // placeholder this.placeholder.enable(); // focus if (this.opts.focus) setTimeout($.proxy(this.focus.setStart, this), 100); if (this.opts.focusEnd) setTimeout($.proxy(this.focus.setEnd, this), 100); }, plugins: function() { if (!this.opts.plugins) return; if (!RedactorPlugins) return; $.each(this.opts.plugins, $.proxy(function(i, s) { if (RedactorPlugins[s]) { if (!$.isFunction(RedactorPlugins[s])) return; this[s] = RedactorPlugins[s](); var methods = this.getModuleMethods(this[s]); var len = methods.length; // bind methods for (var z = 0; z < len; z++) { this[s][methods[z]] = this[s][methods[z]].bind(this); } if ($.isFunction(this[s].init)) this[s].init(); } }, this)); }, disableMozillaEditing: function() { if (!this.utils.browser('mozilla')) return; // FF fix try { document.execCommand('enableObjectResizing', false, false); document.execCommand('enableInlineTableEditing', false, false); } catch (e) {} } }; }, lang: function() { return { load: function() { this.opts.curLang = this.opts.langs[this.opts.lang]; }, get: function(name) { return (typeof this.opts.curLang[name] != 'undefined') ? this.opts.curLang[name] : ''; } }; }, toolbar: function() { return { init: function() { return { html: { title: this.lang.get('html'), func: 'code.toggle' }, formatting: { title: this.lang.get('formatting'), dropdown: { p: { title: this.lang.get('paragraph'), func: 'block.format' }, blockquote: { title: this.lang.get('quote'), func: 'block.format' }, pre: { title: this.lang.get('code'), func: 'block.format' }, h1: { title: this.lang.get('header1'), func: 'block.format' }, h2: { title: this.lang.get('header2'), func: 'block.format' }, h3: { title: this.lang.get('header3'), func: 'block.format' }, h4: { title: this.lang.get('header4'), func: 'block.format' }, h5: { title: this.lang.get('header5'), func: 'block.format' } } }, bold: { title: this.lang.get('bold'), func: 'inline.format' }, italic: { title: this.lang.get('italic'), func: 'inline.format' }, deleted: { title: this.lang.get('deleted'), func: 'inline.format' }, underline: { title: this.lang.get('underline'), func: 'inline.format' }, unorderedlist: { title: '• ' + this.lang.get('unorderedlist'), func: 'list.toggle' }, orderedlist: { title: '1. ' + this.lang.get('orderedlist'), func: 'list.toggle' }, outdent: { title: '< ' + this.lang.get('outdent'), func: 'indent.decrease' }, indent: { title: '> ' + this.lang.get('indent'), func: 'indent.increase' }, image: { title: this.lang.get('image'), func: 'image.show' }, file: { title: this.lang.get('file'), func: 'file.show' }, link: { title: this.lang.get('link'), dropdown: { link: { title: this.lang.get('link_insert'), func: 'link.show' }, unlink: { title: this.lang.get('unlink'), func: 'link.unlink' } } }, alignment: { title: this.lang.get('alignment'), dropdown: { left: { title: this.lang.get('align_left'), func: 'alignment.left' }, center: { title: this.lang.get('align_center'), func: 'alignment.center' }, right: { title: this.lang.get('align_right'), func: 'alignment.right' }, justify: { title: this.lang.get('align_justify'), func: 'alignment.justify' } } }, horizontalrule: { title: this.lang.get('horizontalrule'), func: 'line.insert' } }; }, build: function() { this.toolbar.hideButtons(); this.toolbar.hideButtonsOnMobile(); this.toolbar.isButtonSourceNeeded(); if (this.opts.buttons.length === 0) return; this.$toolbar = this.toolbar.createContainer(); this.toolbar.setOverflow(); this.toolbar.append(); this.toolbar.setFormattingTags(); this.toolbar.loadButtons(); this.toolbar.setTabindex(); this.toolbar.setFixed(); // buttons response if (this.opts.activeButtons) { this.$editor.on('mouseup.redactor keyup.redactor focus.redactor', $.proxy(this.observe.buttons, this)); } }, createContainer: function() { return $('