/* Redactor II Version 2.10 Updated: September 4, 2017 http://imperavi.com/redactor/ Copyright (c) 2009-2017, 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; // 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); } // Options $.Redactor = Redactor; $.Redactor.VERSION = '2.10'; $.Redactor.modules = ['air', 'autosave', 'block', 'buffer', 'build', 'button', 'caret', 'clean', 'code', 'core', 'detect', 'dropdown', 'events', 'file', 'focus', 'image', 'indent', 'inline', 'insert', 'keydown', 'keyup', 'lang', 'line', 'link', 'linkify', 'list', 'marker', 'modal', 'observe', 'offset', 'paragraphize', 'paste', 'placeholder', 'progress', 'selection', 'shortcuts', 'storage', 'toolbar', 'upload', 'uploads3', 'utils', 'browser' // deprecated ]; $.Redactor.settings = {}; $.Redactor.opts = { // settings animation: false, lang: 'en', direction: 'ltr', spellcheck: true, overrideStyles: true, stylesClass: false, scrollTarget: document, focus: false, focusEnd: false, clickToEdit: false, structure: false, tabindex: false, minHeight: false, // string maxHeight: false, // string maxWidth: false, // string plugins: false, // array callbacks: {}, placeholder: false, linkify: true, enterKey: true, pastePlainText: false, pasteImages: true, pasteLinks: true, pasteBlockTags: ['pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'table', 'tbody', 'thead', 'tfoot', 'th', 'tr', 'td', 'ul', 'ol', 'li', 'blockquote', 'p', 'figure', 'figcaption'], pasteInlineTags: ['br', 'strong', 'ins', 'code', 'del', 'span', 'samp', 'kbd', 'sup', 'sub', 'mark', 'var', 'cite', 'small', 'b', 'u', 'em', 'i'], preClass: false, // string preSpaces: 4, // or false tabAsSpaces: false, // true or number of spaces tabKey: true, autosave: false, // false or url autosaveName: false, autosaveFields: false, imageUpload: null, imageUploadParam: 'file', imageUploadFields: false, imageUploadForms: false, imageTag: 'figure', imageEditable: true, imageCaption: true, imagePosition: false, imageResizable: false, imageFloatMargin: '10px', dragImageUpload: true, multipleImageUpload: true, clipboardImageUpload: true, fileUpload: null, fileUploadParam: 'file', fileUploadFields: false, fileUploadForms: false, dragFileUpload: true, s3: false, linkNewTab: false, linkTooltip: true, linkNofollow: false, linkSize: 30, linkValidation: true, pasteLinkTarget: false, videoContainerClass: 'video-container', toolbar: true, toolbarFixed: true, toolbarFixedTarget: document, toolbarFixedTopOffset: 0, // pixels toolbarExternal: false, // ID selector toolbarOverflow: false, air: false, airWidth: false, formatting: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'], formattingAdd: false, buttons: ['format', 'bold', 'italic', 'deleted', 'lists', 'image', 'file', 'link', 'horizontalrule'], // + 'horizontalrule', 'underline', 'ol', 'ul', 'indent', 'outdent' buttonsTextLabeled: false, buttonsHide: [], buttonsHideOnMobile: [], script: true, removeNewlines: false, removeComments: true, replaceTags: { 'b': 'strong', 'i': 'em', 'strike': 'del' }, keepStyleAttr: [], // tag name array keepInlineOnEnter: false, // shortcuts 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, activeButtons: ['deleted', 'italic', 'bold'], activeButtonsStates: { b: 'bold', strong: 'bold', i: 'italic', em: 'italic', del: 'deleted', strike: 'deleted' }, // private lang langs: { en: { "format": "Format", "image": "Image", "file": "File", "link": "Link", "bold": "Bold", "italic": "Italic", "deleted": "Strikethrough", "underline": "Underline", "bold-abbr": "B", "italic-abbr": "I", "deleted-abbr": "S", "underline-abbr": "U", "lists": "Lists", "link-insert": "Insert link", "link-edit": "Edit link", "link-in-new-tab": "Open link in new tab", "unlink": "Unlink", "cancel": "Cancel", "close": "Close", "insert": "Insert", "save": "Save", "delete": "Delete", "text": "Text", "edit": "Edit", "title": "Title", "paragraph": "Normal text", "quote": "Quote", "code": "Code", "heading1": "Heading 1", "heading2": "Heading 2", "heading3": "Heading 3", "heading4": "Heading 4", "heading5": "Heading 5", "heading6": "Heading 6", "filename": "Name", "optional": "optional", "unorderedlist": "Unordered List", "orderedlist": "Ordered List", "outdent": "Outdent", "indent": "Indent", "horizontalrule": "Line", "upload-label": "Drop file here or ", "caption": "Caption", "bulletslist": "Bullets", "numberslist": "Numbers", "image-position": "Position", "none": "None", "left": "Left", "right": "Right", "center": "Center", "accessibility-help-label": "Rich text editor" } }, // private type: 'textarea', // textarea, div, inline, pre inline: false, inlineTags: ['a', 'span', 'strong', 'strike', 'b', 'u', 'em', 'i', 'code', 'del', 'ins', 'samp', 'kbd', 'sup', 'sub', 'mark', 'var', 'cite', 'small'], blockTags: ['pre', 'ul', 'ol', 'li', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'dl', 'dt', 'dd', 'div', 'td', 'blockquote', 'output', 'figcaption', 'figure', 'address', 'section', 'header', 'footer', 'aside', 'article', 'iframe'], paragraphize: true, paragraphizeBlocks: ['table', 'div', 'pre', 'form', 'ul', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'dl', 'blockquote', 'figcaption', 'address', 'section', 'header', 'footer', 'aside', 'article', 'object', 'style', 'script', 'iframe', 'select', 'input', 'textarea', 'button', 'option', 'map', 'area', 'math', 'hr', 'fieldset', 'legend', 'hgroup', 'nav', 'figure', 'details', 'menu', 'summary', 'p'], emptyHtml: '<p>​</p>', invisibleSpace: '​', emptyHtmlRendered: $('').html('').html(), imageTypes: ['image/png', 'image/jpeg', 'image/gif'], userAgent: navigator.userAgent.toLowerCase(), observe: { dropdowns: [] }, regexps: { linkyoutube: /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w.\-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig, linkvimeo: /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/, linkimage: /((https?|www)[^\s]+\.)(jpe?g|png|gif)(\?[^\s-]+)?/ig, url: /(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/ig } }; // Functionality Redactor.fn = $.Redactor.prototype = { keyCode: { BACKSPACE: 8, DELETE: 46, UP: 38, DOWN: 40, ENTER: 13, SPACE: 32, ESC: 27, TAB: 9, CTRL: 17, META: 91, SHIFT: 16, ALT: 18, RIGHT: 39, LEFT: 37, LEFT_WIN: 91 }, // =init init: function(el, options) { this.$element = $(el); this.uuid = uuid++; this.sBuffer = []; this.sRebuffer = []; this.loadOptions(options); this.loadModules(); // click to edit if (this.opts.clickToEdit && !this.$element.hasClass('redactor-click-to-edit')) { return this.loadToEdit(options); } else if (this.$element.hasClass('redactor-click-to-edit')) { this.$element.removeClass('redactor-click-to-edit'); } // block & inline test tag regexp this.reIsBlock = new RegExp('^(' + this.opts.blockTags.join('|' ).toUpperCase() + ')$', 'i'); this.reIsInline = new RegExp('^(' + this.opts.inlineTags.join('|' ).toUpperCase() + ')$', 'i'); // set up drag upload this.opts.dragImageUpload = (this.opts.imageUpload === null) ? false : this.opts.dragImageUpload; this.opts.dragFileUpload = (this.opts.fileUpload === null) ? false : this.opts.dragFileUpload; // formatting storage this.formatting = {}; // load lang this.lang.load(); // extend shortcuts $.extend(this.opts.shortcuts, this.opts.shortcutsAdd); // set editor this.$editor = this.$element; // detect type of editor this.detectType(); // start callback this.core.callback('start'); this.core.callback('startToEdit'); // build this.start = true; this.build.start(); }, detectType: function() { if (this.build.isInline() || this.opts.inline) { this.opts.type = 'inline'; } else if (this.build.isTag('DIV')) { this.opts.type = 'div'; } else if (this.build.isTag('PRE')) { this.opts.type = 'pre'; } }, loadToEdit: function(options) { this.$element.on('click.redactor-click-to-edit', $.proxy(function() { this.initToEdit(options); }, this)); this.$element.addClass('redactor-click-to-edit'); return; }, initToEdit: function(options) { $.extend(options.callbacks, { startToEdit: function() { this.insert.node(this.marker.get(), false); }, initToEdit: function() { this.selection.restore(); this.clickToCancelStorage = this.code.get(); // cancel $(this.opts.clickToCancel).off('.redactor-click-to-edit'); $(this.opts.clickToCancel).show().on('click.redactor-click-to-edit', $.proxy(function(e) { e.preventDefault(); this.core.destroy(); this.events.syncFire = false; this.$element.html(this.clickToCancelStorage); this.core.callback('cancel', this.clickToCancelStorage); this.events.syncFire = true; this.clickToCancelStorage = ''; $(this.opts.clickToCancel).hide(); $(this.opts.clickToSave).hide(); this.$element.on('click.redactor-click-to-edit', $.proxy(function() { this.initToEdit(options); }, this)); this.$element.addClass('redactor-click-to-edit'); }, this)); // save $(this.opts.clickToSave).off('.redactor-click-to-edit'); $(this.opts.clickToSave).show().on('click.redactor-click-to-edit', $.proxy(function(e) { e.preventDefault(); this.core.destroy(); this.core.callback('save', this.code.get()); $(this.opts.clickToCancel).hide(); $(this.opts.clickToSave).hide(); this.$element.on('click.redactor-click-to-edit', $.proxy(function() { this.initToEdit(options); }, this)); this.$element.addClass('redactor-click-to-edit'); }, this)); } }); this.$element.redactor(options); this.$element.off('.redactor-click-to-edit'); }, loadOptions: function(options) { var settings = {}; // check namespace if (typeof $.Redactor.settings.namespace !== 'undefined') { if (this.$element.hasClass($.Redactor.settings.namespace)) { settings = $.Redactor.settings; } } else { settings = $.Redactor.settings; } this.opts = $.extend( {}, $.Redactor.opts, this.$element.data(), options ); this.opts = $.extend({}, this.opts, settings); }, 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); } }, // =air air: function() { return { enabled: false, collapsed: function() { if (this.opts.air) { this.selection.get().collapseToStart(); } }, collapsedEnd: function() { if (this.opts.air) { this.selection.get().collapseToEnd(); } }, build: function() { if (this.detect.isMobile()) { return; } this.button.hideButtons(); this.button.hideButtonsOnMobile(); if (this.opts.buttons.length === 0) { return; } this.$air = this.air.createContainer(); if (this.opts.airWidth !== false) { this.$air.css('width', this.opts.airWidth); } this.air.append(); this.button.$toolbar = this.$air; this.button.setFormatting(); this.button.load(this.$air); this.core.editor().on('mouseup.redactor', this, $.proxy(function(e) { if (this.selection.text() !== '') { this.air.show(e); } }, this)); }, append: function() { this.$air.appendTo('body'); }, createContainer: function() { return $('<ul>').addClass('redactor-air').attr({ 'id': 'redactor-air-' + this.uuid, 'role': 'toolbar' }).hide(); }, show: function (e) { //this.marker.remove(); this.selection.saveInstant(); //this.selection.restore(false); $('.redactor-air').hide(); var leftFix = 0; var width = this.$air.innerWidth(); if ($(window).width() < (e.clientX + width)) { leftFix = 200; } this.$air.css({ left: (e.clientX - leftFix) + 'px', top: (e.clientY + 10 + $(document).scrollTop()) + 'px' }).show(); this.air.enabled = true; this.air.bindHide(); }, bindHide: function() { $(document).on('mousedown.redactor-air.' + this.uuid, $.proxy(function(e) { var dropdown = $(e.target).closest('.redactor-dropdown').length; if ($(e.target).closest(this.$air).length === 0 && dropdown === 0) { var hide = this.air.hide(e); if (hide !== false) { this.marker.remove(); } } }, this)).on('keydown.redactor-air.' + this.uuid, $.proxy(function(e) { var key = e.which; if ((!this.utils.isRedactorParent(e.target) && !$(e.target).hasClass('redactor-in')) || $(e.target).closest('#redactor-modal').length !== 0) { return; } if (key === this.keyCode.ESC) { this.selection.get().collapseToStart(); //this.marker.remove(); } else if (key === this.keyCode.BACKSPACE || key === this.keyCode.DELETE) { var sel = this.selection.get(); var range = this.selection.range(sel); range.deleteContents(); //this.marker.remove(); } else if (key === this.keyCode.ENTER) { this.selection.get().collapseToEnd(); //this.marker.remove(); } if (this.air.enabled) { this.air.hide(e); } else { this.selection.get().collapseToStart(); //this.marker.remove(); } }, this)); }, hide: function(e) { var ctrl = e.ctrlKey || e.metaKey || (e.shiftKey && e.altKey); if (ctrl) { return false; } this.button.setInactiveAll(); this.$air.fadeOut(100); this.air.enabled = false; $(document).off('mousedown.redactor-air.' + this.uuid); $(document).off('keydown.redactor-air.' + this.uuid); } }; }, // =autosave autosave: function() { return { enabled: false, html: false, init: function() { if (!this.opts.autosave) { return; } this.autosave.enabled = true; this.autosave.name = (this.opts.autosaveName) ? this.opts.autosaveName : this.$textarea.attr('name'); }, is: function() { return this.autosave.enabled; }, send: function() { if (!this.opts.autosave) { return; } this.autosave.source = this.code.get(); if (this.autosave.html === this.autosave.source) { return; } // data var data = {}; data.name = this.autosave.name; data[this.autosave.name] = this.autosave.source; data = this.autosave.getHiddenFields(data); // ajax var jsxhr = $.ajax({ url: this.opts.autosave, type: 'post', data: data }); jsxhr.done(this.autosave.success); }, getHiddenFields: function(data) { if (this.opts.autosaveFields === false || typeof this.opts.autosaveFields !== 'object') { return data; } $.each(this.opts.autosaveFields, $.proxy(function(k, v) { if (v !== null && v.toString().indexOf('#') === 0) { v = $(v).val(); } data[k] = v; }, this)); return data; }, success: function(data) { var json; try { json = JSON.parse(data); } catch(e) { //data has already been parsed json = data; } var callbackName = (typeof json.error === 'undefined') ? 'autosave' : 'autosaveError'; this.core.callback(callbackName, this.autosave.name, json); this.autosave.html = this.autosave.source; }, disable: function() { this.autosave.enabled = false; clearInterval(this.autosaveTimeout); } }; }, // =block block: function() { return { format: function(tag, attr, value, type) { tag = (tag === 'quote') ? 'blockquote' : tag; this.block.tags = ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'figure']; if ($.inArray(tag, this.block.tags) === -1) { return; } if (tag === 'p' && typeof attr === 'undefined') { // remove all attr = 'class'; } this.placeholder.hide(); this.buffer.set(); return (this.utils.isCollapsed()) ? this.block.formatCollapsed(tag, attr, value, type) : this.block.formatUncollapsed(tag, attr, value, type); }, formatCollapsed: function(tag, attr, value, type) { this.selection.save(); var block = this.selection.block(); var currentTag = block.tagName.toLowerCase(); if ($.inArray(currentTag, this.block.tags) === -1) { this.selection.restore(); return; } var clearAllAttrs = false; if (currentTag === tag && attr === undefined) { tag = 'p'; clearAllAttrs = true; } if (clearAllAttrs) { this.block.removeAllClass(); this.block.removeAllAttr(); } var replaced; if (currentTag === 'blockquote' && this.utils.isEndOfElement(block)) { this.marker.remove(); replaced = document.createElement('p'); replaced.innerHTML = this.opts.invisibleSpace; $(block).after(replaced); this.caret.start(replaced); var $last = $(block).children().last(); if ($last.length !== 0 && $last[0].tagName === 'BR') { $last.remove(); } } else { replaced = this.utils.replaceToTag(block, tag); } if (typeof attr === 'object') { type = value; for (var key in attr) { replaced = this.block.setAttr(replaced, key, attr[key], type); } } else { replaced = this.block.setAttr(replaced, attr, value, type); } // trim pre if (tag === 'pre' && replaced.length === 1) { $(replaced).html($.trim($(replaced).html())); } this.selection.restore(); this.block.removeInlineTags(replaced); return replaced; }, formatUncollapsed: function(tag, attr, value, type) { this.selection.save(); var replaced = []; var blocks = this.selection.blocks(); if (blocks[0] && ($(blocks[0]).hasClass('redactor-in') || $(blocks[0]).hasClass('redactor-box'))) { blocks = this.core.editor().find(this.opts.blockTags.join(', ')); } var len = blocks.length; for (var i = 0; i < len; i++) { var currentTag = blocks[i].tagName.toLowerCase(); if ($.inArray(currentTag, this.block.tags) !== -1 && currentTag !== 'figure') { var block = this.utils.replaceToTag(blocks[i], tag); if (typeof attr === 'object') { type = value; for (var key in attr) { block = this.block.setAttr(block, key, attr[key], type); } } else { block = this.block.setAttr(block, attr, value, type); } replaced.push(block); this.block.removeInlineTags(block); } } this.selection.restore(); // combine pre if (tag === 'pre' && replaced.length !== 0) { var first = replaced[0]; $.each(replaced, function(i,s) { if (i !== 0) { $(first).append("\n" + $.trim(s.html())); $(s).remove(); } }); replaced = []; replaced.push(first); } return replaced; }, removeInlineTags: function(node) { node = node[0] || node; var tags = this.opts.inlineTags; var blocks = ['PRE', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6']; if ($.inArray(node.tagName, blocks) === - 1) { return; } if (node.tagName !== 'PRE') { var index = tags.indexOf('a'); tags.splice(index, 1); } $(node).find(tags.join(',')).not('.redactor-selection-marker').contents().unwrap(); }, setAttr: function(block, attr, value, type) { if (typeof attr === 'undefined') { return block; } var func = (typeof type === 'undefined') ? 'replace' : type; if (attr === 'class') { block = this.block[func + 'Class'](value, block); } else { if (func === 'remove') { block = this.block[func + 'Attr'](attr, block); } else if (func === 'removeAll') { block = this.block[func + 'Attr'](attr, block); } else { block = this.block[func + 'Attr'](attr, value, block); } } return block; }, getBlocks: function(block) { block = (typeof block === 'undefined') ? this.selection.blocks() : block; if ($(block).hasClass('redactor-box')) { var blocks = []; var nodes = this.core.editor().children(); $.each(nodes, $.proxy(function(i,node) { if (this.utils.isBlock(node)) { blocks.push(node); } }, this)); return blocks; } return block }, replaceClass: function(value, block) { return $(this.block.getBlocks(block)).removeAttr('class').addClass(value)[0]; }, toggleClass: function(value, block) { return $(this.block.getBlocks(block)).toggleClass(value)[0]; }, addClass: function(value, block) { return $(this.block.getBlocks(block)).addClass(value)[0]; }, removeClass: function(value, block) { return $(this.block.getBlocks(block)).removeClass(value)[0]; }, removeAllClass: function(block) { return $(this.block.getBlocks(block)).removeAttr('class')[0]; }, replaceAttr: function(attr, value, block) { block = this.block.removeAttr(attr, block); return $(block).attr(attr, value)[0]; }, toggleAttr: function(attr, value, block) { block = this.block.getBlocks(block); var self = this; var returned = []; $.each(block, function(i,s) { var $el = $(s); if ($el.attr(attr)) { returned.push(self.block.removeAttr(attr, s)); } else { returned.push(self.block.addAttr(attr, value, s)); } }); return returned; }, addAttr: function(attr, value, block) { return $(this.block.getBlocks(block)).attr(attr, value)[0]; }, removeAttr: function(attr, block) { return $(this.block.getBlocks(block)).removeAttr(attr)[0]; }, removeAllAttr: function(block) { block = this.block.getBlocks(block); var returned = []; $.each(block, function(i,s) { if (typeof s.attributes !== 'undefined') { while (s.attributes.length) { s.removeAttribute(s.attributes[0].name); } } returned.push(s); }); return returned; } }; }, // buffer buffer: function() { return { set: function(type) { if (typeof type === 'undefined') { this.buffer.clear(); } if (typeof type === 'undefined' || type === 'undo') { this.buffer.setUndo(); } else { this.buffer.setRedo(); } }, setUndo: function() { var saved = this.selection.saveInstant(); var last = this.sBuffer[this.sBuffer.length-1]; var current = this.core.editor().html(); var save = (typeof last !== 'undefined' && (last[0] === current)) ? false : true; if (save) { this.sBuffer.push([current, saved]); } //this.selection.restore(); }, setRedo: function() { var saved = this.selection.saveInstant(); this.sRebuffer.push([this.core.editor().html(), saved]); //this.selection.restore(); }, add: function() { this.sBuffer.push([this.core.editor().html(), 0]); }, undo: function() { if (this.sBuffer.length === 0) { return; } var buffer = this.sBuffer.pop(); this.buffer.set('redo'); this.core.editor().html(buffer[0]); this.selection.restoreInstant(buffer[1]); this.selection.restore(); this.observe.load(); }, redo: function() { if (this.sRebuffer.length === 0) { return; } var buffer = this.sRebuffer.pop(); this.buffer.set('undo'); this.core.editor().html(buffer[0]); this.selection.restoreInstant(buffer[1]); this.selection.restore(); this.observe.load(); }, clear: function() { this.sRebuffer = []; } }; }, // =build build: function() { return { start: function() { if (this.opts.type === 'inline') { this.opts.type = 'inline'; } else if (this.opts.type === 'div') { // empty var html = $.trim(this.$editor.html()); if (html === '') { this.$editor.html(this.opts.emptyHtml); } this.build.buildTextarea(); } else if (this.opts.type === 'textarea') { this.build.startTextarea(); } // set in this.build.setIn(); // set id this.build.setId(); // enable this.build.enableEditor(); // options this.build.setOptions(); // call this.build.callEditor(); }, createContainerBox: function() { this.$box = $('<div class="redactor-box" role="application" />'); }, setIn: function() { this.core.editor().addClass('redactor-in'); }, setId: function() { var id = (this.opts.type === 'textarea') ? 'redactor-uuid-' + this.uuid : this.$element.attr('id'); this.core.editor().attr('id', (typeof id === 'undefined') ? 'redactor-uuid-' + this.uuid : id); }, getName: function() { var name = this.$element.attr('name'); return (typeof name === 'undefined') ? 'content-' + this.uuid : name; }, buildTextarea: function() { this.$textarea = $('<textarea>'); this.$textarea.attr('name', this.build.getName()); this.$textarea.hide(); this.$element.after(this.$textarea); this.build.setStartAttrs(); }, loadFromTextarea: function() { this.$editor = $('<div />'); // textarea this.$textarea = this.$element; this.$element.attr('name', this.build.getName()); // place this.$box.insertAfter(this.$element).append(this.$editor).append(this.$element); this.build.setStartAttrs(); // styles this.$editor.addClass('redactor-layer'); if (this.opts.overrideStyles) this.$editor.addClass('redactor-styles'); this.$element.hide(); this.$box.prepend('<span class="redactor-voice-label" id="redactor-voice-' + this.uuid +'" aria-hidden="false">' + this.lang.get('accessibility-help-label') + '</span>'); }, setStartAttrs: function() { this.$editor.attr({ 'aria-labelledby': 'redactor-voice-' + this.uuid, 'role': 'presentation' }); }, startTextarea: function() { this.build.createContainerBox(); // load this.build.loadFromTextarea(); // set code this.code.start(this.core.textarea().val()); // set value this.core.textarea().val(this.clean.onSync(this.$editor.html())); }, isTag: function(tag) { return (this.$element[0].tagName === tag); }, isInline: function() { return (!this.build.isTag('TEXTAREA') && !this.build.isTag('DIV') && !this.build.isTag('PRE')); }, enableEditor: function() { this.core.editor().attr({ 'contenteditable': true }); }, setOptions: function() { // inline if (this.opts.type === 'inline') { this.opts.enterKey = false; } // inline & pre if (this.opts.type === 'inline' || this.opts.type === 'pre') { this.opts.toolbarMobile = false; this.opts.toolbar = false; this.opts.air = false; this.opts.linkify = false; } // spellcheck this.core.editor().attr('spellcheck', this.opts.spellcheck); // structure if (this.opts.structure) { this.core.editor().addClass('redactor-structure'); } // styles class if (this.opts.stylesClass) { this.core.editor().addClass(this.opts.stylesClass); } // options sets only in textarea mode if (this.opts.type !== 'textarea') { return; } // direction this.core.box().attr('dir', this.opts.direction); this.core.editor().attr('dir', this.opts.direction); // tabindex if (this.opts.tabindex) { this.core.editor().attr('tabindex', this.opts.tabindex); } // min height if (this.opts.minHeight) { this.core.editor().css('min-height', this.opts.minHeight); } else { this.core.editor().css('min-height', '40px'); } // max height if (this.opts.maxHeight) { this.core.editor().css('max-height', this.opts.maxHeight); } // max width if (this.opts.maxWidth) { this.core.editor().css({ 'max-width': this.opts.maxWidth, 'margin': 'auto' }); } }, callEditor: function() { this.build.disableBrowsersEditing(); this.events.init(); this.build.setHelpers(); // init buttons if (this.opts.toolbar || this.opts.air) { this.toolbarsButtons = this.button.init(); } // load toolbar if (this.opts.air) { this.air.build(); } else if (this.opts.toolbar) { this.toolbar.build(); } if (this.detect.isMobile() && this.opts.toolbarMobile && this.opts.air) { this.opts.toolbar = true; this.toolbar.build(); } // observe dropdowns if (this.opts.air || this.opts.toolbar) { this.core.editor().on('mouseup.redactor-observe.' + this.uuid + ' keyup.redactor-observe.' + this.uuid + ' focus.redactor-observe.' + this.uuid + ' touchstart.redactor-observe.' + this.uuid, $.proxy(this.observe.toolbar, this)); this.core.element().on('blur.callback.redactor', $.proxy(function() { this.button.setInactiveAll(); }, this)); } // modal templates init this.modal.templates(); // plugins this.build.plugins(); // autosave this.autosave.init(); // sync code this.code.html = this.code.cleaned(this.core.editor().html()); // init callback this.core.callback('init'); this.core.callback('initToEdit'); // get images & files list this.storage.observe(); // started this.start = false; }, setHelpers: function() { // linkify if (this.opts.linkify) { this.linkify.format(); } // placeholder this.placeholder.init(); // focus if (this.opts.focus) { setTimeout(this.focus.start, 100); } else if (this.opts.focusEnd) { setTimeout(this.focus.end, 100); } }, disableBrowsersEditing: function() { try { // FF fix document.execCommand('enableObjectResizing', false, false); document.execCommand('enableInlineTableEditing', false, false); // IE prevent converting links document.execCommand("AutoUrlDetect", false, false); } catch (e) {} }, plugins: function() { if (!this.opts.plugins) { return; } $.each(this.opts.plugins, $.proxy(function(i, s) { var func = (typeof RedactorPlugins !== 'undefined' && typeof RedactorPlugins[s] !== 'undefined') ? RedactorPlugins : Redactor.fn; if (!$.isFunction(func[s])) { return; } this[s] = func[s](); // get methods 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); } // append lang if (typeof this[s].langs !== 'undefined') { var lang = {}; if (typeof this[s].langs[this.opts.lang] !== 'undefined') { lang = this[s].langs[this.opts.lang]; } else if (typeof this[s].langs[this.opts.lang] === 'undefined' && typeof this[s].langs.en !== 'undefined') { lang = this[s].langs.en; } // extend var self = this; $.each(lang, function(i,s) { if (typeof self.opts.curLang[i] === 'undefined') { self.opts.curLang[i] = s; } }); } // init if ($.isFunction(this[s].init)) { this[s].init(); } }, this)); } }; }, // =button button: function() { return { toolbar: function() { return (typeof this.button.$toolbar === 'undefined' || !this.button.$toolbar) ? this.$toolbar : this.button.$toolbar; }, init: function() { return { format: { title: this.lang.get('format'), icon: true, 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('heading1'), func: 'block.format' }, h2: { title: this.lang.get('heading2'), func: 'block.format' }, h3: { title: this.lang.get('heading3'), func: 'block.format' }, h4: { title: this.lang.get('heading4'), func: 'block.format' }, h5: { title: this.lang.get('heading5'), func: 'block.format' }, h6: { title: this.lang.get('heading6'), func: 'block.format' } } }, bold: { title: this.lang.get('bold-abbr'), icon: true, label: this.lang.get('bold'), func: 'inline.format' }, italic: { title: this.lang.get('italic-abbr'), icon: true, label: this.lang.get('italic'), func: 'inline.format' }, deleted: { title: this.lang.get('deleted-abbr'), icon: true, label: this.lang.get('deleted'), func: 'inline.format' }, underline: { title: this.lang.get('underline-abbr'), icon: true, label: this.lang.get('underline'), func: 'inline.format' }, lists: { title: this.lang.get('lists'), icon: true, dropdown: { 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', observe: { element: 'li', out: { attr: { 'class': 'redactor-dropdown-link-inactive', 'aria-disabled': true } } } }, indent: { title: '> ' + this.lang.get('indent'), func: 'indent.increase', observe: { element: 'li', out: { attr: { 'class': 'redactor-dropdown-link-inactive', 'aria-disabled': true } } } } } }, ul: { title: '• ' + this.lang.get('bulletslist'), icon: true, func: 'list.toggle' }, ol: { title: '1. ' + this.lang.get('numberslist'), icon: true, func: 'list.toggle' }, outdent: { title: this.lang.get('outdent'), icon: true, func: 'indent.decrease' }, indent: { title: this.lang.get('indent'), icon: true, func: 'indent.increase' }, image: { title: this.lang.get('image'), icon: true, func: 'image.show' }, file: { title: this.lang.get('file'), icon: true, func: 'file.show' }, link: { title: this.lang.get('link'), icon: true, dropdown: { link: { title: this.lang.get('link-insert'), func: 'link.show', observe: { element: 'a', 'in': { title: this.lang.get('link-edit') }, out: { title: this.lang.get('link-insert') } } }, unlink: { title: this.lang.get('unlink'), func: 'link.unlink', observe: { element: 'a', out: { attr: { 'class': 'redactor-dropdown-link-inactive', 'aria-disabled': true } } } } } }, horizontalrule: { title: this.lang.get('horizontalrule'), icon: true, func: 'line.insert' } }; }, setFormatting: function() { $.each(this.toolbarsButtons.format.dropdown, $.proxy(function (i, s) { if ($.inArray(i, this.opts.formatting) === -1) { delete this.toolbarsButtons.format.dropdown[i]; } }, this)); }, hideButtons: function() { if (this.opts.buttonsHide.length !== 0) { this.button.hideButtonsSlicer(this.opts.buttonsHide); } }, hideButtonsOnMobile: function() { if (this.detect.isMobile() && this.opts.buttonsHideOnMobile.length !== 0) { this.button.hideButtonsSlicer(this.opts.buttonsHideOnMobile); } }, hideButtonsSlicer: function(buttons) { $.each(buttons, $.proxy(function(i, s) { var index = this.opts.buttons.indexOf(s); if (index !== -1) { this.opts.buttons.splice(index, 1); } }, this)); }, load: function($toolbar) { this.button.buttons = []; $.each(this.opts.buttons, $.proxy(function(i, btnName) { if (!this.toolbarsButtons[btnName] || (btnName === 'file' && !this.file.is()) || (btnName === 'image' && !this.image.is())) { return; } $toolbar.append($('<li>').append(this.button.build(btnName, this.toolbarsButtons[btnName]))); }, this)); }, buildButtonTooltip: function($btn, title) { if (this.opts.air || this.detect.isMobile()) { return; } var $tooltip = $('<span>'); $tooltip.addClass('re-button-tooltip'); $tooltip.html(title); var self = this; var $toolbar = this.button.toolbar(); var $box = $toolbar.closest('.redactor-toolbar-box'); $box = ($box.length === 0) ? $toolbar : $box; $box.prepend($tooltip); $btn.on('mouseover', function() { if ($(this).hasClass('redactor-button-disabled')) { return; } var pos = ($toolbar.hasClass('toolbar-fixed-box')) ? $btn.offset() : $btn.position(); pos = (self.opts.toolbarFixedTarget !== document) ? $btn.position() : pos; var top = ($toolbar.hasClass('toolbar-fixed-box')) ? $btn.position().top : pos.top; var height = $btn.innerHeight(); var width = $btn.innerWidth(); var posBox = ($toolbar.hasClass('toolbar-fixed-box')) ? 'fixed' : 'absolute'; posBox = (self.opts.toolbarFixedTarget !== document) ? 'absolute' : posBox; var scrollFix = (self.opts.toolbarFixedTarget !== document) ? $toolbar.position().top : 0; $tooltip.show(); $tooltip.css({ top: (top + height + scrollFix) + 'px', left: (pos.left + width/2 - $tooltip.innerWidth()/2) + 'px', position: posBox }); }).on('mouseout', function() { $tooltip.hide(); }); }, build: function(btnName, btnObject) { if (this.opts.toolbar === false) { return; } var title = (typeof btnObject.label !== 'undefined') ? btnObject.label : btnObject.title; var $button = $('<a href="javascript:void(null);" alt="' + title + '" rel="' + btnName + '" />') $button.addClass('re-button re-' + btnName); $button.attr({ 'role': 'button', 'aria-label': title, 'tabindex': '-1' }); if (typeof btnObject.icon !== 'undefined' && !this.opts.buttonsTextLabeled) { var $icon = $('<i>'); $icon.addClass('re-icon-' + btnName); $button.append($icon); $button.addClass('re-button-icon'); this.button.buildButtonTooltip($button, title); } else { $button.html(btnObject.title); } // click if (btnObject.func || btnObject.command || btnObject.dropdown) { this.button.setEvent($button, btnName, btnObject); } // dropdown if (btnObject.dropdown) { $button.addClass('redactor-toolbar-link-dropdown').attr('aria-haspopup', true); var $dropdown = $('<ul class="redactor-dropdown redactor-dropdown-' + this.uuid + ' redactor-dropdown-box-' + btnName + '" style="display: none;">'); $button.data('dropdown', $dropdown); this.dropdown.build(btnName, $dropdown, btnObject.dropdown); } this.button.buttons.push($button); return $button; }, getButtons: function() { return this.button.toolbar().find('a.re-button'); }, getButtonsKeys: function() { return this.button.buttons; }, setEvent: function($button, btnName, btnObject) { $button.on('mousedown', $.proxy(function(e) { e.preventDefault(); if ($button.hasClass('redactor-button-disabled')) { return false; } var type = 'func'; var callback = btnObject.func; if (btnObject.command) { type = 'command'; callback = btnObject.command; } else if (btnObject.dropdown) { type = 'dropdown'; callback = false; } this.button.toggle(e, btnName, type, callback); return false; }, this)); }, toggle: function(e, btnName, type, callback, args) { if (this.detect.isIe() || !this.detect.isDesktop()) { this.utils.freezeScroll(); e.returnValue = false; } if (type === 'command') { this.inline.format(callback); } else if (type === 'dropdown') { this.dropdown.show(e, btnName); } else { this.button.clickCallback(e, callback, btnName, args); } if (type !== 'dropdown') { this.dropdown.hideAll(false); } if (this.opts.air && type !== 'dropdown') { this.air.hide(e); } if (this.detect.isIe() || !this.detect.isDesktop()) { this.utils.unfreezeScroll(); } }, clickCallback: function(e, callback, btnName, args) { var func; args = (typeof args === 'undefined') ? btnName : args; if ($.isFunction(callback)) { callback.call(this, btnName); } else if (callback.search(/\./) !== '-1') { func = callback.split('.'); if (typeof this[func[0]] === 'undefined') { return; } if (typeof args === 'object') { this[func[0]][func[1]].apply(this, args); } else { this[func[0]][func[1]].call(this, args); } } else { if (typeof args === 'object') { this[callback].apply(this, args); } else { this[callback].call(this, args); } } this.observe.buttons(e, btnName); }, all: function() { return this.button.buttons; }, get: function(key) { if (this.opts.toolbar === false) { return; } return this.button.toolbar().find('a.re-' + key); }, set: function(key, title) { if (this.opts.toolbar === false) { return; } var $btn = this.button.toolbar().find('a.re-' + key); $btn.html(title).attr('aria-label', title); return $btn; }, add: function(key, title) { if (this.button.isAdded(key) !== true) { return $(); } var btn = this.button.build(key, { title: title }); this.button.toolbar().append($('<li>').append(btn)); return btn; }, addFirst: function(key, title) { if (this.button.isAdded(key) !== true) { return $(); } var btn = this.button.build(key, { title: title }); this.button.toolbar().prepend($('<li>').append(btn)); return btn; }, addAfter: function(afterkey, key, title) { if (this.button.isAdded(key) !== true) { return $(); } var btn = this.button.build(key, { title: title }); var $btn = this.button.get(afterkey); if ($btn.length !== 0) { $btn.parent().after($('<li>').append(btn)); } else { this.button.toolbar().append($('<li>').append(btn)); } return btn; }, addBefore: function(beforekey, key, title) { if (this.button.isAdded(key) !== true) { return $(); } var btn = this.button.build(key, { title: title }); var $btn = this.button.get(beforekey); if ($btn.length !== 0) { $btn.parent().before($('<li>').append(btn)); } else { this.button.toolbar().append($('<li>').append(btn)); } return btn; }, isAdded: function(key) { var index = this.opts.buttonsHideOnMobile.indexOf(key); if (this.opts.toolbar === false || (index !== -1 && this.detect.isMobile())) { return false; } return true; }, setIcon: function($btn, icon) { if (!this.opts.buttonsTextLabeled) { $btn.html(icon).addClass('re-button-icon'); this.button.buildButtonTooltip($btn, $btn.attr('alt')); } }, changeIcon: function(key, newIconClass) { var $btn = this.button.get(key); if ($btn.length !== 0) { $btn.find('i').removeAttr('class').addClass('re-icon-' + newIconClass); } }, addCallback: function($btn, callback) { if (typeof $btn === 'undefined' || this.opts.toolbar === false) { return; } var type = (callback === 'dropdown') ? 'dropdown' : 'func'; var key = $btn.attr('rel'); $btn.on('mousedown', $.proxy(function(e) { if ($btn.hasClass('redactor-button-disabled')) { return false; } this.button.toggle(e, key, type, callback); }, this)); }, addDropdown: function($btn, dropdown) { if (this.opts.toolbar === false) { return; } $btn.addClass('redactor-toolbar-link-dropdown').attr('aria-haspopup', true); var key = $btn.attr('rel'); this.button.addCallback($btn, 'dropdown'); var $dropdown = $('<div class="redactor-dropdown redactor-dropdown-' + this.uuid + ' redactor-dropdown-box-' + key + '" style="display: none;">'); $btn.data('dropdown', $dropdown); // build dropdown if (dropdown) { this.dropdown.build(key, $dropdown, dropdown); } return $dropdown; }, setActive: function(key) { this.button.get(key).addClass('redactor-act'); }, setInactive: function(key) { this.button.get(key).removeClass('redactor-act'); }, setInactiveAll: function(key) { var $btns = this.button.toolbar().find('a.re-button'); if (typeof key !== 'undefined') { $btns = $btns.not('.re-' + key); } $btns.removeClass('redactor-act'); }, disable: function(key) { this.button.get(key).addClass('redactor-button-disabled'); }, enable: function(key) { this.button.get(key).removeClass('redactor-button-disabled'); }, disableAll: function(key) { var $btns = this.button.toolbar().find('a.re-button'); if (typeof key !== 'undefined') { $btns = $btns.not('.re-' + key); } $btns.addClass('redactor-button-disabled'); }, enableAll: function() { this.button.toolbar().find('a.re-button').removeClass('redactor-button-disabled'); }, remove: function(key) { this.button.get(key).remove(); } }; }, // =caret caret: function() { return { set: function(node1, node2, end) { var cs = this.core.editor().scrollTop(); this.core.editor().focus(); this.core.editor().scrollTop(cs); end = (typeof end === 'undefined') ? 0 : 1; node1 = node1[0] || node1; node2 = node2[0] || node2; var sel = this.selection.get(); var range = this.selection.range(sel); try { range.setStart(node1, 0); range.setEnd(node2, end); } catch (e) {} this.selection.update(sel, range); }, prepare: function(node) { // firefox focus if (this.detect.isFirefox() && typeof this.start !== 'undefined') { this.core.editor().focus(); } return node[0] || node; }, start: function(node) { var sel, range; node = this.caret.prepare(node); if (!node) { return; } if (node.tagName === 'BR') { return this.caret.before(node); } var $first = $(node).children().first(); // empty or inline tag var inline = this.utils.isInlineTag(node.tagName); if (node.innerHTML === '' || inline) { this.caret.setStartEmptyOrInline(node, inline); } // empty inline inside else if ($first && $first.length !== 0 && this.utils.isInlineTag($first[0].tagName) && $first.text() === '') { this.caret.setStartEmptyOrInline($first[0], true); } // block tag else { sel = window.getSelection(); sel.removeAllRanges(); range = document.createRange(); range.selectNodeContents(node); range.collapse(true); sel.addRange(range); } }, setStartEmptyOrInline: function(node, inline) { var sel = window.getSelection(); var range = document.createRange(); var textNode = document.createTextNode('\u200B'); range.setStart(node, 0); range.insertNode(textNode); range.setStartAfter(textNode); range.collapse(true); sel.removeAllRanges(); sel.addRange(range); // remove invisible text node if (!inline) { this.core.editor().on('keydown.redactor-remove-textnode', function() { $(textNode).remove(); $(this).off('keydown.redactor-remove-textnode'); }); } }, end: function(node) { var sel, range; node = this.caret.prepare(node); if (!node) { return; } // empty node if (node.tagName !== 'BR' && node.innerHTML === '') { return this.caret.start(node); } // br if (node.tagName === 'BR') { var space = document.createElement('span'); space.className = 'redactor-invisible-space'; space.innerHTML = '​'; $(node).after(space); sel = window.getSelection(); sel.removeAllRanges(); range = document.createRange(); range.setStartBefore(space); range.setEndBefore(space); sel.addRange(range); $(space).replaceWith(function() { return $(this).contents(); }); return; } if (node.lastChild && node.lastChild.nodeType === 1) { return this.caret.after(node.lastChild); } var sel = window.getSelection(); if (sel.getRangeAt || sel.rangeCount) { try { var range = sel.getRangeAt(0); range.selectNodeContents(node); range.collapse(false); sel.removeAllRanges(); sel.addRange(range); } catch(e) {} } }, after: function(node) { var sel, range; node = this.caret.prepare(node); if (!node) { return; } if (node.tagName === 'BR') { return this.caret.end(node); } // block tag if (this.utils.isBlockTag(node.tagName)) { var next = this.caret.next(node); if (typeof next === 'undefined') { this.caret.end(node); } else { // table if (next.tagName === 'TABLE') { next = $(next).find('th, td').first()[0]; } // list else if (next.tagName === 'UL' || next.tagName === 'OL') { next = $(next).find('li').first()[0]; } this.caret.start(next); } return; } // inline tag var textNode = document.createTextNode('\u200B'); sel = window.getSelection(); sel.removeAllRanges(); range = document.createRange(); range.setStartAfter(node); range.insertNode(textNode); range.setStartAfter(textNode); range.collapse(true); sel.addRange(range); }, before: function(node) { var sel, range; node = this.caret.prepare(node); if (!node) { return; } // block tag if (this.utils.isBlockTag(node.tagName)) { var prev = this.caret.prev(node); if (typeof prev === 'undefined') { this.caret.start(node); } else { // table if (prev.tagName === 'TABLE') { prev = $(prev).find('th, td').last()[0]; } // list else if (prev.tagName === 'UL' || prev.tagName === 'OL') { prev = $(prev).find('li').last()[0]; } this.caret.end(prev); } return; } // inline tag sel = window.getSelection(); sel.removeAllRanges(); range = document.createRange(); range.setStartBefore(node); range.collapse(true); sel.addRange(range); }, next: function(node) { var $next = $(node).next(); if ($next.hasClass('redactor-script-tag, redactor-selection-marker')) { return $next.next()[0]; } else { return $next[0]; } }, prev: function(node) { var $prev = $(node).prev(); if ($prev.hasClass('redactor-script-tag, redactor-selection-marker')) { return $prev.prev()[0]; } else { return $prev[0]; } }, // #backward offset: function(node) { return this.offset.get(node); } }; }, // =clean clean: function() { return { onSet: function(html) { html = this.clean.savePreCode(html); html = this.clean.saveFormTags(html); // convert script tag if (this.opts.script) { html = html.replace(/<script(.*?[^>]?)>([\w\W]*?)<\/script>/gi, '<pre class="redactor-script-tag" $1>$2</pre>'); } // converting entity html = html.replace(/\$/g, '$'); html = html.replace(/&/g, '&'); // replace special characters in links html = html.replace(/<a href="(.*?[^>]?)®(.*?[^>]?)">/gi, '<a href="$1®$2">'); // save markers html = html.replace(/<span id="selection-marker-1"(.*?[^>]?)><\/span>/gi, '###marker1###'); html = html.replace(/<span id="selection-marker-2"(.*?[^>]?)><\/span>/gi, '###marker2###'); // replace tags var self = this; var $div = $("<div/>").html($.parseHTML(html, document, true)); var replacement = this.opts.replaceTags; if (replacement) { var keys = Object.keys(this.opts.replaceTags); $div.find(keys.join(',')).each(function(i,s) { self.utils.replaceToTag(s, replacement[s.tagName.toLowerCase()]); }); } // add span marker $div.find('span, a').attr('data-redactor-span', true); // add style cache $div.find(this.opts.inlineTags.join(',')).each(function() { // add style cache var $el = $(this); if ($el.attr('style')) { $el.attr('data-redactor-style-cache', $el.attr('style')); } }); html = $div.html(); // remove tags var tags = ['font', 'html', 'head', 'link', 'body', 'meta', 'applet']; if (!this.opts.script) { tags.push('script'); } html = this.clean.stripTags(html, tags); // remove html comments if (this.opts.removeComments) { html = html.replace(/<!--[\s\S]*?-->/gi, ''); } // paragraphize html = this.paragraphize.load(html); // restore markers html = html.replace('###marker1###', '<span id="selection-marker-1" class="redactor-selection-marker"></span>'); html = html.replace('###marker2###', '<span id="selection-marker-2" class="redactor-selection-marker"></span>'); // empty if (html.search(/^(||\s||<br\s?\/?>|| )$/i) !== -1) { return this.opts.emptyHtml; } return html; }, onGet: function(html) { return this.clean.onSync(html); }, onSync: function(html) { // remove invisible spaces html = html.replace(/\u200B/g, ''); html = html.replace(/​/gi, ''); //html = html.replace(/ /gi, ' '); if (html.search(/^<p>(||\s||<br\s?\/?>|| )<\/p>$/i) !== -1) { return ''; } // remove image resize html = html.replace(/<span(.*?)id="redactor-image-box"(.*?[^>])>([\w\W]*?)<img(.*?)><\/span>/gi, '$3<img$4>'); html = html.replace(/<span(.*?)id="redactor-image-resizer"(.*?[^>])>(.*?)<\/span>/gi, ''); html = html.replace(/<span(.*?)id="redactor-image-editter"(.*?[^>])>(.*?)<\/span>/gi, ''); html = html.replace(/<img(.*?)style="(.*?)opacity: 0\.5;(.*?)"(.*?)>/gi, '<img$1style="$2$3"$4>'); var $div = $("<div/>").html($.parseHTML(html, document, true)); // remove empty atributes $div.find('*[style=""]').removeAttr('style'); $div.find('*[class=""]').removeAttr('class'); $div.find('*[rel=""]').removeAttr('rel'); $div.find('*[data-image=""]').removeAttr('data-image'); $div.find('*[alt=""]').removeAttr('alt'); $div.find('*[title=""]').removeAttr('title'); $div.find('*[data-redactor-style-cache]').removeAttr('data-redactor-style-cache'); // remove markers $div.find('.redactor-invisible-space, .redactor-unlink').each(function() { $(this).contents().unwrap(); }); // remove span without attributes & span marker $div.find('span, a').removeAttr('data-redactor-span data-redactor-style-cache').each(function() { if (this.attributes.length === 0) { $(this).contents().unwrap(); } }); // remove rel attribute from img $div.find('img').removeAttr('rel'); $div.find('.redactor-selection-marker, #redactor-insert-marker').remove(); html = $div.html(); // reconvert script tag if (this.opts.script) { html = html.replace(/<pre class="redactor-script-tag"(.*?[^>]?)>([\w\W]*?)<\/pre>/gi, '<script$1>$2</script>'); } // restore form tag html = this.clean.restoreFormTags(html); // remove br in|of li/header tags html = html.replace(new RegExp('<br\\s?/?></h', 'gi'), '</h'); html = html.replace(new RegExp('<br\\s?/?></li>', 'gi'), '</li>'); html = html.replace(new RegExp('</li><br\\s?/?>', 'gi'), '</li>'); // pre class html = html.replace(/<pre>/gi, "<pre>\n"); if (this.opts.preClass) { html = html.replace(/<pre>/gi, '<pre class="' + this.opts.preClass + '">'); } // link nofollow if (this.opts.linkNofollow) { html = html.replace(/<a(.*?)rel="nofollow"(.*?[^>])>/gi, '<a$1$2>'); html = html.replace(/<a(.*?[^>])>/gi, '<a$1 rel="nofollow">'); } // replace special characters var chars = { '\u2122': '™', '\u00a9': '©', '\u2026': '…', '\u2014': '—', '\u2010': '‐' }; $.each(chars, function(i,s) { html = html.replace(new RegExp(i, 'g'), s); }); html = html.replace(/&/g, '&'); // remove empty paragpraphs //html = html.replace(/<p><\/p>/gi, ""); // remove new lines html = html.replace(/\n{2,}/g, "\n"); // remove all newlines if (this.opts.removeNewlines) { html = html.replace(/\r?\n/g, ""); } return html; }, onPaste: function(html, data, insert) { // if paste event if (insert !== true) { // remove google docs markers html = html.replace(/<b\sid="internal-source-marker(.*?)">([\w\W]*?)<\/b>/gi, "$2"); html = html.replace(/<b(.*?)id="docs-internal-guid(.*?)">([\w\W]*?)<\/b>/gi, "$3"); // google docs styles html = html.replace(/<span[^>]*(font-style: italic; font-weight: bold|font-weight: bold; font-style: italic)[^>]*>([\w\W]*?)<\/span>/gi, '<b><i>$2</i></b>'); html = html.replace(/<span[^>]*(font-style: italic; font-weight: 700|font-weight: 700; font-style: italic)[^>]*>([\w\W]*?)<\/span>/gi, '<b><i>$2</i></b>'); html = html.replace(/<span[^>]*font-style: italic[^>]*>([\w\W]*?)<\/span>/gi, '<i>$1</i>'); html = html.replace(/<span[^>]*font-weight: bold[^>]*>([\w\W]*?)<\/span>/gi, '<b>$1</b>'); html = html.replace(/<span[^>]*font-weight: 700[^>]*>([\w\W]*?)<\/span>/gi, '<b>$1</b>'); // op tag html = html.replace(/<o:p[^>]*>/gi, ''); html = html.replace(/<\/o:p>/gi, ''); var msword = this.clean.isHtmlMsWord(html); if (msword) { html = this.clean.cleanMsWord(html); } } html = $.trim(html); if (data.pre) { if (this.opts.preSpaces) { html = html.replace(/\t/g, new Array(this.opts.preSpaces + 1).join(' ')); } } else { html = this.clean.replaceBrToNl(html); html = this.clean.removeTagsInsidePre(html); } // if paste event if (insert !== true) { html = this.clean.removeEmptyInlineTags(html); if (data.encode === false) { html = html.replace(/&/g, '&'); html = this.clean.convertTags(html, data); html = this.clean.getPlainText(html); html = this.clean.reconvertTags(html, data); } } if (data.text) { html = this.clean.replaceNbspToSpaces(html); html = this.clean.getPlainText(html); } if (data.lists) { html = html.replace("\n", '<br>'); } if (data.encode) { html = this.clean.encodeHtml(html); } if (data.paragraphize) { // ff bugfix html = html.replace(/ \n/g, ' '); html = html.replace(/\n /g, ' '); html = this.paragraphize.load(html); // remove empty p html = html.replace(/<p><\/p>/g, ''); } // remove paragraphs form lists (google docs bug) html = html.replace(/<li><p>/g, '<li>'); html = html.replace(/<\/p><\/li>/g, '</li>'); return html; }, getCurrentType: function(html, insert) { var blocks = this.selection.blocks(); var data = { text: false, encode: false, paragraphize: true, line: this.clean.isHtmlLine(html), blocks: this.clean.isHtmlBlocked(html), pre: false, lists: false, block: true, inline: true, links: true, images: true }; if (blocks.length === 1 && this.utils.isCurrentOrParent(['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'a', 'figcaption'])) { data.text = true; data.paragraphize = false; data.inline = false; data.images = false; data.links = false; data.line = true; } else if (this.opts.type === 'inline' || this.opts.enterKey === false) { data.paragraphize = false; data.block = false; data.line = true; } else if (blocks.length === 1 && this.utils.isCurrentOrParent(['li'])) { data.lists = true; data.block = false; data.paragraphize = false; data.images = false; } else if (blocks.length === 1 && this.utils.isCurrentOrParent(['th', 'td', 'blockquote'])) { data.block = false; data.paragraphize = false; } else if (this.opts.type === 'pre' || (blocks.length === 1 && this.utils.isCurrentOrParent('pre'))) { data.inline = false; data.block = false; data.encode = true; data.pre = true; data.paragraphize = false; data.images = false; data.links = false; } if (data.line === true) { data.paragraphize = false; } if (insert === true) { data.text = false; } return data; }, isHtmlBlocked: function(html) { var match1 = html.match(new RegExp('</(' + this.opts.blockTags.join('|' ).toUpperCase() + ')>', 'gi')); var match2 = html.match(new RegExp('<hr(.*?[^>])>', 'gi')); return (match1 === null && match2 === null) ? false : true; }, isHtmlLine: function(html) { if (this.clean.isHtmlBlocked(html)) { return false; } var matchBR = html.match(/<br\s?\/?>/gi); var matchNL = html.match(/\n/gi); return (!matchBR && !matchNL) ? true : false; }, isHtmlMsWord: function(html) { return html.match(/class="?Mso|style="[^"]*\bmso-|style='[^'']*\bmso-|w:WordDocument/i); }, removeEmptyInlineTags: function(html) { var tags = this.opts.inlineTags; var $div = $("<div/>").html($.parseHTML(html, document, true)); var self = this; var $spans = $div.find('span'); var $tags = $div.find(tags.join(',')); $tags.removeAttr('style'); $tags.each(function() { var tagHtml = $(this).html(); if (this.attributes.length === 0 && self.utils.isEmpty(tagHtml)) { $(this).replaceWith(function() { return $(this).contents(); }); } }); $spans.each(function() { var tagHtml = $(this).html(); if (this.attributes.length === 0) { $(this).replaceWith(function() { return $(this).contents(); }); } }); html = $div.html(); // convert php tags html = html.replace('<!--?php', '<?php'); html = html.replace('<!--?', '<?'); html = html.replace('?-->', '?>'); $div.remove(); return html; }, cleanMsWord: function(html) { html = html.replace(/<!--[\s\S]*?-->/g, ""); html = html.replace(/<o:p>[\s\S]*?<\/o:p>/gi, ''); html = html.replace(/\n/g, " "); html = html.replace(/<br\s?\/?>|<\/p>|<\/div>|<\/li>|<\/td>/gi, '\n\n'); // lists var $div = $("<div/>").html(html); var lastList = false; var lastLevel = 1; var listsIds = []; $div.find("p[style]").each(function() { var matches = $(this).attr('style').match(/mso\-list\:l([0-9]+)\slevel([0-9]+)/); if (matches) { var currentList = parseInt(matches[1]); var currentLevel = parseInt(matches[2]); var listType = $(this).html().match(/^[\w]+\./) ? "ol" : "ul"; var $li = $("<li/>").html($(this).html()); $li.html($li.html().replace(/^([\w\.]+)</, '<')); $li.find("span:first").remove(); if (currentLevel == 1 && $.inArray(currentList, listsIds) == -1) { var $list = $("<" + listType + "/>").attr({"data-level": currentLevel, "data-list": currentList}).html($li); $(this).replaceWith($list); lastList = currentList; listsIds.push(currentList); } else { if (currentLevel > lastLevel) { var $prevList = $div.find('[data-level="' + lastLevel + '"][data-list="' + lastList + '"]'); var $lastList = $prevList; for(var i = lastLevel; i < currentLevel; i++) { $list = $("<" + listType + "/>"); $list.appendTo($lastList.find("li").last()); $lastList = $list; } $lastList.attr({"data-level": currentLevel, "data-list": currentList}).html($li); } else { var $prevList = $div.find('[data-level="' + currentLevel + '"][data-list="' + currentList + '"]').last(); $prevList.append($li); } lastLevel = currentLevel; lastList = currentList; $(this).remove(); } } }); $div.find('[data-level][data-list]').removeAttr('data-level data-list'); html = $div.html(); return html; }, replaceNbspToSpaces: function(html) { return html.replace(' ', ' '); }, replaceBrToNl: function(html) { return html.replace(/<br\s?\/?>/gi, '\n'); }, replaceNlToBr: function(html) { return html.replace(/\n/g, '<br />'); }, convertTags: function(html, data) { var $div = $('<div>').html(html); // remove iframe $div.find('iframe').remove(); // link target & attrs var $links = $div.find('a'); $links.removeAttr('style'); if (this.opts.pasteLinkTarget !== false) { $links.attr('target', this.opts.pasteLinkTarget); } // links if (data.links && this.opts.pasteLinks) { $div.find('a').each(function(i, link) { if (link.href) { var tmp = '##%%a href="' + link.href + '"'; var attr; for (var j = 0, length = link.attributes.length; j < length; j++) { attr = link.attributes.item(j); if (attr.name !== 'href') { tmp += ' ' + attr.name + '="' + attr.value + '"'; } } link.outerHTML = tmp + '%%##' + link.innerHTML + '##%%/a%##'; } }); } html = $div.html(); // images if (data.images && this.opts.pasteImages) { html = html.replace(/<img(.*?)src="(.*?)"(.*?[^>])>/gi, '##%%img$1src="$2"$3%%##'); } // plain text if (this.opts.pastePlainText) { return html; } // all tags var blockTags = (data.lists) ? ['ul', 'ol', 'li'] : this.opts.pasteBlockTags; var tags; if (data.block || data.lists) { tags = (data.inline) ? blockTags.concat(this.opts.pasteInlineTags) : blockTags; } else { tags = (data.inline) ? this.opts.pasteInlineTags : []; } var len = tags.length; for (var i = 0; i < len; i++) { html = html.replace(new RegExp('<\/' + tags[i] + '>', 'gi'), '###/' + tags[i] + '###'); if (tags[i] === 'td' || tags[i] === 'th') { html = html.replace(new RegExp('<' + tags[i] + '(.*?[^>])((colspan|rowspan)="(.*?[^>])")?(.*?[^>])>', 'gi'), '###' + tags[i] + ' $2###'); } else if (this.utils.isInlineTag(tags[i])) { html = html.replace(new RegExp('<' + tags[i] + '([^>]*)class="([^>]*)"[^>]*>', 'gi'), '###' + tags[i] + ' class="$2"###'); html = html.replace(new RegExp('<' + tags[i] + '([^>]*)data-redactor-style-cache="([^>]*)"[^>]*>', 'gi'), '###' + tags[i] + ' cache="$2"###'); html = html.replace(new RegExp('<' + tags[i] + '[^>]*>', 'gi'), '###' + tags[i] + '###'); } else { html = html.replace(new RegExp('<' + tags[i] + '[^>]*>', 'gi'), '###' + tags[i] + '###'); } } return html; }, reconvertTags: function(html, data) { // links & images if ((data.links && this.opts.pasteLinks) || (data.images && this.opts.pasteImages)) { html = html.replace(new RegExp('##%%', 'gi'), '<'); html = html.replace(new RegExp('%%##', 'gi'), '>'); } // plain text if (this.opts.pastePlainText) { return html; } var blockTags = (data.lists) ? ['ul', 'ol', 'li'] : this.opts.pasteBlockTags; var tags; if (data.block || data.lists) { tags = (data.inline) ? blockTags.concat(this.opts.pasteInlineTags) : blockTags; } else { tags = (data.inline) ? this.opts.pasteInlineTags : []; } var len = tags.length; for (var i = 0; i < len; i++) { html = html.replace(new RegExp('###\/' + tags[i] + '###', 'gi'), '</' + tags[i] + '>'); } for (var i = 0; i < len; i++) { html = html.replace(new RegExp('###' + tags[i] + '###', 'gi'), '<' + tags[i] + '>'); } for (var i = 0; i < len; i++) { if (tags[i] === 'td' || tags[i] === 'th') { html = html.replace(new RegExp('###' + tags[i] + '\s?(.*?[^#])###', 'gi'), '<' + tags[i] + '$1>'); } else if (this.utils.isInlineTag(tags[i])) { var spanMarker = (tags[i] === 'span') ? ' data-redactor-span="true"' : ''; html = html.replace(new RegExp('###' + tags[i] + ' cache="(.*?[^#])"###', 'gi'), '<' + tags[i] + ' style="$1"' + spanMarker + ' data-redactor-style-cache="$1">'); html = html.replace(new RegExp('###' + tags[i] + '\s?(.*?[^#])###', 'gi'), '<' + tags[i] + '$1>'); } } return html; }, cleanPre: function(block) { block = (typeof block === 'undefined') ? $(this.selection.block()).closest('pre', this.core.editor()[0]) : block; $(block).find('br').replaceWith(function() { return document.createTextNode('\n'); }); $(block).find('p').replaceWith(function() { return $(this).contents(); }); }, removeTagsInsidePre: function(html) { var $div = $('<div />').append(html); $div.find('pre').replaceWith(function() { var str = $(this).html(); str = str.replace(/<br\s?\/?>|<\/p>|<\/div>|<\/li>|<\/td>/gi, '\n'); str = str.replace(/(<([^>]+)>)/gi, ''); return $('<pre />').append(str); }); html = $div.html(); $div.remove(); return html; }, getPlainText: function(html) { html = html.replace(/<!--[\s\S]*?-->/gi, ''); html = html.replace(/<style[\s\S]*?style>/gi, ''); html = html.replace(/<p><\/p>/g, ''); html = html.replace(/<\/div>|<\/li>|<\/td>/gi, '\n'); html = html.replace(/<\/p>/gi, '\n\n'); html = html.replace(/<\/H[1-6]>/gi, '\n\n'); var tmp = document.createElement('div'); tmp.innerHTML = html; html = tmp.textContent || tmp.innerText; return $.trim(html); }, savePreCode: function(html) { html = this.clean.savePreFormatting(html); html = this.clean.saveCodeFormatting(html); html = this.clean.restoreSelectionMarkers(html); return html; }, savePreFormatting: function(html) { var pre = html.match(/<pre(.*?)>([\w\W]*?)<\/pre>/gi); if (pre === null) { return html; } $.each(pre, $.proxy(function(i,s) { var arr = []; var codeTag = false; var contents, attr1, attr2; if (s.match(/<pre(.*?)>(([\n\r\s]+)?)<code(.*?)>/i)) { arr = s.match(/<pre(.*?)>(([\n\r\s]+)?)<code(.*?)>([\w\W]*?)<\/code>(([\n\r\s]+)?)<\/pre>/i); codeTag = true; contents = arr[5]; attr1 = arr[1]; attr2 = arr[4]; } else { arr = s.match(/<pre(.*?)>([\w\W]*?)<\/pre>/i); contents = arr[2]; attr1 = arr[1]; } contents = contents.replace(/<br\s?\/?>/g, '\n'); contents = contents.replace(/ /g, ' '); if (this.opts.preSpaces) { contents = contents.replace(/\t/g, new Array(this.opts.preSpaces + 1).join(' ')); } contents = this.clean.encodeEntities(contents); // $ fix contents = contents.replace(/\$/g, '$'); if (codeTag) { html = html.replace(s, '<pre' + attr1 + '><code' + attr2 + '>' + contents + '</code></pre>'); } else { html = html.replace(s, '<pre' + attr1 + '>' + contents + '</pre>'); } }, this)); return html; }, saveCodeFormatting: function(html) { var code = html.match(/<code(.*?)>([\w\W]*?)<\/code>/gi); if (code === null) { return html; } $.each(code, $.proxy(function(i,s) { var arr = s.match(/<code(.*?)>([\w\W]*?)<\/code>/i); arr[2] = arr[2].replace(/ /g, ' '); arr[2] = this.clean.encodeEntities(arr[2]); arr[2] = arr[2].replace(/\$/g, '$'); html = html.replace(s, '<code' + arr[1] + '>' + arr[2] + '</code>'); }, this)); return html; }, restoreSelectionMarkers: function(html) { html = html.replace(/<span id="selection-marker-([0-9])" class="redactor-selection-marker"><\/span>/g, '<span id="selection-marker-$1" class="redactor-selection-marker"></span>'); return html; }, saveFormTags: function(html) { return html.replace(/<form(.*?)>([\w\W]*?)<\/form>/gi, '<section$1 rel="redactor-form-tag">$2</section>'); }, restoreFormTags: function(html) { return html.replace(/<section(.*?) rel="redactor-form-tag"(.*?)>([\w\W]*?)<\/section>/gi, '<form$1$2>$3</form>'); }, encodeHtml: function(html) { html = html.replace(/”/g, '"'); html = html.replace(/“/g, '"'); html = html.replace(/‘/g, '\''); html = html.replace(/’/g, '\''); html = this.clean.encodeEntities(html); return html; }, encodeEntities: function(str) { str = String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); str = str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); return str; }, stripTags: function(input, denied) { if (typeof denied === 'undefined') { return input.replace(/(<([^>]+)>)/gi, ''); } var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi; return input.replace(tags, function ($0, $1) { return denied.indexOf($1.toLowerCase()) === -1 ? $0 : ''; }); }, removeMarkers: function(html) { return html.replace(/<span(.*?[^>]?)class="redactor-selection-marker"(.*?[^>]?)>([\w\W]*?)<\/span>/gi, ''); }, removeSpaces: function(html) { html = $.trim(html); html = html.replace(/\n/g, ''); html = html.replace(/[\t]*/g, ''); html = html.replace(/\n\s*\n/g, "\n"); html = html.replace(/^[\s\n]*/g, ' '); html = html.replace(/[\s\n]*$/g, ' '); html = html.replace( />\s{2,}</g, '> <'); // between inline tags can be only one space html = html.replace(/\n\n/g, "\n"); html = html.replace(/\u200B/g, ''); return html; }, removeSpacesHard: function(html) { html = $.trim(html); html = html.replace(/\n/g, ''); html = html.replace(/[\t]*/g, ''); html = html.replace(/\n\s*\n/g, "\n"); html = html.replace(/^[\s\n]*/g, ''); html = html.replace(/[\s\n]*$/g, ''); html = html.replace( />\s{2,}</g, '><'); html = html.replace(/\n\n/g, "\n"); html = html.replace(/\u200B/g, ''); return html; }, normalizeCurrentHeading: function() { var heading = this.selection.block(); if (this.utils.isCurrentOrParentHeader() && heading) { heading.normalize(); } } }; }, // =code code: function() { return { syncFire: true, html: false, start: function(html) { html = $.trim(html); html = html.replace(/^(<span id="selection-marker-1" class="redactor-selection-marker"><\/span>)/, ''); // clean if (this.opts.type === 'textarea') { html = this.clean.onSet(html); } else if (this.opts.type === 'div' && html === '') { html = this.opts.emptyHtml; } html = html.replace(/<p><span id="selection-marker-1" class="redactor-selection-marker"><\/span><\/p>/, ''); this.events.stopDetectChanges(); this.core.editor().html(html); this.observe.load(); this.events.startDetectChanges(); }, set: function(html, options) { html = $.trim(html); options = options || {}; // start if (options.start) { this.start = options.start; } // clean if (this.opts.type === 'textarea') { html = this.clean.onSet(html); } else if (this.opts.type === 'div' && html === '') { html = this.opts.emptyHtml; } this.core.editor().html(html); if (this.opts.type === 'textarea') { this.code.sync(); } this.placeholder.enable(); }, get: function() { if (this.opts.type === 'textarea') { return this.core.textarea().val(); } else { var html = this.core.editor().html(); // clean html = this.clean.onGet(html); return html; } }, sync: function() { if (!this.code.syncFire) { return; } var html = this.core.editor().html(); var htmlCleaned = this.code.cleaned(html); // is there a need to synchronize if (this.code.isSync(htmlCleaned)) { // do not sync return; } // save code this.code.html = htmlCleaned; if (this.opts.type !== 'textarea') { this.core.callback('sync', html); this.core.callback('change', html); return; } if (this.opts.type === 'textarea') { setTimeout($.proxy(function() { this.code.startSync(html); }, this), 10); } }, startSync: function(html) { // before clean callback html = this.core.callback('syncBefore', html); // clean html = this.clean.onSync(html); // set code this.core.textarea().val(html); // after sync callback this.core.callback('sync', html); // change callback if (this.start === false) { this.core.callback('change', html); } this.start = false; }, isSync: function(htmlCleaned) { var html = (this.code.html !== false) ? this.code.html : false; return (html !== false && html === htmlCleaned); }, cleaned: function(html) { html = html.replace(/\u200B/g, ''); return this.clean.removeMarkers(html); } }; }, // =core core: function() { return { id: function() { return this.$editor.attr('id'); }, element: function() { return this.$element; }, editor: function() { return (typeof this.$editor === 'undefined') ? $() : this.$editor; }, textarea: function() { return this.$textarea; }, box: function() { return (this.opts.type === 'textarea') ? this.$box : this.$element; }, toolbar: function() { return (this.$toolbar) ? this.$toolbar : false; }, air: function() { return (this.$air) ? this.$air : false; }, object: function() { return $.extend({}, this); }, structure: function() { this.core.editor().toggleClass('redactor-structure'); }, addEvent: function(name) { this.core.event = name; }, getEvent: function() { return this.core.event; }, callback: function(type, e, data) { var eventNamespace = 'redactor'; var returnValue = false; var events = $._data(this.core.element()[0], 'events'); // on callback if (typeof events !== 'undefined' && typeof events[type] !== 'undefined') { var len = events[type].length; for (var i = 0; i < len; i++) { var namespace = events[type][i].namespace; if (namespace === 'callback.' + eventNamespace) { var handler = events[type][i].handler; var args = (typeof data === 'undefined') ? [e] : [e, data]; returnValue = (typeof args === 'undefined') ? handler.call(this, e) : handler.call(this, e, args); } } } if (returnValue) { return returnValue; } // no callback if (typeof this.opts.callbacks[type] === 'undefined') { return (typeof data === 'undefined') ? e : data; } // callback var callback = this.opts.callbacks[type]; 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.opts.destroyed = true; this.core.callback('destroy'); // placeholder this.placeholder.destroy(); // progress this.progress.destroy(); // help label $('#redactor-voice-' + this.uuid).remove(); this.core.editor().removeClass('redactor-in redactor-styles redactor-structure redactor-layer-img-edit'); // caret service this.core.editor().off('keydown.redactor-remove-textnode'); // observer this.core.editor().off('.redactor-observe.' + this.uuid); // off events and remove data this.$element.off('.redactor').removeData('redactor'); this.core.editor().off('.redactor'); $(document).off('.redactor-dropdown'); $(document).off('.redactor-air.' + this.uuid); $(document).off('mousedown.redactor-blur.' + this.uuid); $(document).off('mousedown.redactor.' + this.uuid); $(document).off('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid); $(window).off('.redactor-toolbar.' + this.uuid); $(window).off('touchmove.redactor.' + this.uuid); $("body").off('scroll.redactor.' + this.uuid); $(this.opts.toolbarFixedTarget).off('scroll.redactor.' + this.uuid); // plugins events var self = this; if (this.opts.plugins !== false) { $.each(this.opts.plugins, function(i,s) { $(window).off('.redactor-plugin-' + s); $(document).off('.redactor-plugin-' + s); $("body").off('.redactor-plugin-' + s); self.core.editor().off('.redactor-plugin-' + s); }); } // click to edit this.$element.off('click.redactor-click-to-edit'); this.$element.removeClass('redactor-click-to-edit'); // common this.core.editor().removeClass('redactor-layer'); this.core.editor().removeAttr('contenteditable'); var html = this.code.get(); if (this.opts.toolbar && this.$toolbar) { // dropdowns off this.$toolbar.find('a').each(function() { var $el = $(this); if ($el.data('dropdown')) { $el.data('dropdown').remove(); $el.data('dropdown', {}); } }); } if (this.opts.type === 'textarea') { this.$box.after(this.$element); this.$box.remove(); this.$element.val(html).show(); } // air if (this.opts.air) { this.$air.remove(); } if (this.opts.toolbar && this.$toolbar) { this.$toolbar.remove(); } // modal if (this.$modalBox) { this.$modalBox.remove(); } if (this.$modalOverlay) { this.$modalOverlay.remove(); } // hide link's tooltip $('.redactor-link-tooltip').remove(); // autosave clearInterval(this.autosaveTimeout); } }; }, // =detect detect: function() { return { // public isWebkit: function() { return /webkit/.test(this.opts.userAgent); }, isFirefox: function() { return this.opts.userAgent.indexOf('firefox') > -1; }, isIe: function(v) { if (document.documentMode || /Edge/.test(navigator.userAgent)) { return 'edge'; } var ie; ie = RegExp('msie' + (!isNaN(v)?('\\s'+v):''), 'i').test(navigator.userAgent); if (!ie) { ie = !!navigator.userAgent.match(/Trident.*rv[ :]*11\./); } return ie; }, isMobile: function() { return /(iPhone|iPod|BlackBerry|Android)/.test(navigator.userAgent); }, isDesktop: function() { return !/(iPhone|iPod|iPad|BlackBerry|Android)/.test(navigator.userAgent); }, isIpad: function() { return /iPad/.test(navigator.userAgent); } }; }, // =dropdown dropdown: function() { return { active: false, button: false, key: false, position: [], getDropdown: function() { return this.dropdown.active; }, build: function(name, $dropdown, dropdownObject) { dropdownObject = this.dropdown.buildFormatting(name, dropdownObject); $.each(dropdownObject, $.proxy(function(btnName, btnObject) { var $item = this.dropdown.buildItem(btnName, btnObject); this.observe.addDropdown($item, btnName, btnObject); $dropdown.attr('rel', name).append($item); }, this)); }, buildFormatting: function(name, dropdownObject) { if (name !== 'format' || this.opts.formattingAdd === false) { return dropdownObject; } $.each(this.opts.formattingAdd, $.proxy(function(i,s) { var type = (this.utils.isBlockTag(s.args[0])) ? 'block' : 'inline'; dropdownObject[i] = { func: (type === 'block') ? 'block.format' : 'inline.format', args: s.args, title: s.title }; }, this)); return dropdownObject; }, buildItem: function(btnName, btnObject) { var $itemContainer = $('<li />'); if (typeof btnObject.classname !== 'undefined') { $itemContainer.addClass(btnObject.classname); } if (btnName.search(/^divider/i) !== -1) { $itemContainer.addClass('redactor-dropdown-divider'); return $itemContainer; } var $item = $('<a href="#" class="redactor-dropdown-' + btnName + '" role="button" />'); var $itemSpan = $('<span />').html(btnObject.title); $item.append($itemSpan); $item.on('mousedown', $.proxy(function(e) { e.preventDefault(); this.dropdown.buildClick(e, btnName, btnObject); }, this)); $itemContainer.append($item); return $itemContainer; }, buildClick: function(e, btnName, btnObject) { if ($(e.target).hasClass('redactor-dropdown-link-inactive')) { return; } var command = this.dropdown.buildCommand(btnObject); if (typeof btnObject.args !== 'undefined') { this.button.toggle(e, btnName, command.type, command.callback, btnObject.args); } else { this.button.toggle(e, btnName, command.type, command.callback); } }, buildCommand: function(btnObject) { var command = {}; command.type = 'func'; command.callback = btnObject.func; if (btnObject.command) { command.type = 'command'; command.callback = btnObject.command; } else if (btnObject.dropdown) { command.type = 'dropdown'; command.callback = btnObject.dropdown; } return command; }, show: function(e, key) { if (this.detect.isDesktop()) { this.core.editor().focus(); } this.dropdown.hideAll(false, key); this.dropdown.key = key; this.dropdown.button = this.button.get(this.dropdown.key); if (this.dropdown.button.hasClass('dropact')) { this.dropdown.hide(); return; } // re append this.dropdown.active = this.dropdown.button.data('dropdown').appendTo(document.body); // callback this.core.callback('dropdownShow', { dropdown: this.dropdown.active, key: this.dropdown.key, button: this.dropdown.button }); // set button this.button.setActive(this.dropdown.key); this.dropdown.button.addClass('dropact'); // position this.dropdown.getButtonPosition(); // show if (this.button.toolbar().hasClass('toolbar-fixed-box') && this.detect.isDesktop()) { this.dropdown.showIsFixedToolbar(); } else { this.dropdown.showIsUnFixedToolbar(); } // disable scroll whan dropdown scroll if (this.detect.isDesktop() && !this.detect.isFirefox()) { this.dropdown.active.on('mouseover.redactor-dropdown', $.proxy(this.utils.disableBodyScroll, this)); this.dropdown.active.on('mouseout.redactor-dropdown mousedown.redactor-dropdown', $.proxy(this.utils.enableBodyScroll, this)); } e.stopPropagation(); }, showIsFixedToolbar: function() { var top = this.dropdown.button.position().top + this.dropdown.button.innerHeight() + this.opts.toolbarFixedTopOffset; var position = 'fixed'; if (this.opts.toolbarFixedTarget !== document) { top = (this.dropdown.button.innerHeight() + this.$toolbar.offset().top) + this.opts.toolbarFixedTopOffset; position = 'absolute'; } this.dropdown.active.css({ position: position, left: this.dropdown.position.left + 'px', top: top + 'px' }).show(); // animate this.dropdown.active.redactorAnimation('slideDown', { duration: 0.2 }, $.proxy(function() { this.dropdown.enableCallback(); this.dropdown.enableEvents(); }, this)); }, showIsUnFixedToolbar: function() { this.dropdown.active.css({ position: 'absolute', left: this.dropdown.position.left + 'px', top: (this.dropdown.button.innerHeight() + this.dropdown.position.top) + 'px' }).show(); // animate this.dropdown.active.redactorAnimation(((this.opts.animation) ? 'slideDown' : 'show'), { duration: 0.2 }, $.proxy(function() { this.dropdown.enableCallback(); this.dropdown.enableEvents(); }, this)); }, enableEvents: function() { $(document).on('mousedown.redactor-dropdown', $.proxy(this.dropdown.hideAll, this)); this.core.editor().on('touchstart.redactor-dropdown', $.proxy(this.dropdown.hideAll, this)); $(document).on('keyup.redactor-dropdown', $.proxy(this.dropdown.closeHandler, this)); }, enableCallback: function() { this.core.callback('dropdownShown', { dropdown: this.dropdown.active, key: this.dropdown.key, button: this.dropdown.button }); }, getButtonPosition: function() { this.dropdown.position = this.dropdown.button.offset(); // fix right placement var dropdownWidth = this.dropdown.active.width(); if ((this.dropdown.position.left + dropdownWidth) > $(document).width()) { this.dropdown.position.left = Math.max(0, this.dropdown.position.left - dropdownWidth + parseInt(this.dropdown.button.innerWidth())); } }, closeHandler: function(e) { if (e.which !== this.keyCode.ESC) { return; } this.dropdown.hideAll(e); this.core.editor().focus(); }, hideAll: function(e, key) { if (this.detect.isDesktop()) { this.utils.enableBodyScroll(); } if (e !== false && $(e.target).closest('.redactor-dropdown').length !== 0) { return; } var $buttons = (typeof key === 'undefined') ? this.button.toolbar().find('a.dropact') : this.button.toolbar().find('a.dropact').not('.re-' + key); var $elements = (typeof key === 'undefined') ? $('.redactor-dropdown-' + this.uuid) : $('.redactor-dropdown-' + this.uuid).not('.redactor-dropdown-box-' + key); if ($elements.length !== 0) { $(document).off('.redactor-dropdown'); this.core.editor().off('.redactor-dropdown'); $.each($elements, $.proxy(function(i,s) { var $el = $(s); this.core.callback('dropdownHide', $el); $el.hide(); $el.off('mouseover mouseout').off('.redactor-dropdown'); }, this)); $buttons .removeClass('redactor-act dropact'); } }, hide: function () { if (this.dropdown.active === false) { return; } if (this.detect.isDesktop()) { this.utils.enableBodyScroll(); } this.dropdown.active.redactorAnimation(((this.opts.animation) ? 'slideUp' : 'hide'), { duration: 0.2 }, $.proxy(function() { $(document).off('.redactor-dropdown'); this.core.editor().off('.redactor-dropdown'); this.dropdown.hideOut(); }, this)); }, hideOut: function() { this.core.callback('dropdownHide', this.dropdown.active); this.dropdown.button.removeClass('redactor-act dropact'); this.dropdown.active.off('mouseover mouseout').off('.redactor-dropdown'); this.dropdown.button = false; this.dropdown.key = false; this.dropdown.active = false; } }; }, // =events events: function() { return { focused: false, blured: true, dropImage: false, stopChanges: false, stopDetectChanges: function() { this.events.stopChanges = true; }, startDetectChanges: function() { var self = this; setTimeout(function() { self.events.stopChanges = false; }, 1); }, dragover: function(e) { e.preventDefault(); e.stopPropagation(); if (e.target.tagName === 'IMG') { $(e.target).addClass('redactor-image-dragover'); } }, dragleave: function(e) { // remove image dragover this.core.editor().find('img').removeClass('redactor-image-dragover'); }, drop: function(e) { e = e.originalEvent || e; // remove image dragover this.core.editor().find('img').removeClass('redactor-image-dragover'); if (this.opts.type === 'inline' || this.opts.type === 'pre') { e.preventDefault(); return false; } if (window.FormData === undefined || !e.dataTransfer) { return true; } if (e.dataTransfer.files.length === 0) { return this.events.onDrop(e); } else { this.events.onDropUpload(e); } this.core.callback('drop', e); }, click: function(e) { var event = this.core.getEvent(); var type = (event === 'click' || event === 'arrow') ? false : 'click'; this.core.addEvent(type); this.utils.disableSelectAll(); this.core.callback('click', e); }, focus: function(e) { if (this.rtePaste) { return; } if (this.events.isCallback('focus')) { this.core.callback('focus', e); } this.events.focused = true; this.events.blured = false; // tab if (this.selection.current() === false) { var sel = this.selection.get(); var range = this.selection.range(sel); range.setStart(this.core.editor()[0], 0); range.setEnd(this.core.editor()[0], 0); this.selection.update(sel, range); } }, blur: function(e) { if (this.start || this.rtePaste) { return; } if ($(e.target).closest('#' + this.core.id() + ', .redactor-toolbar, .redactor-dropdown, #redactor-modal-box').length !== 0) { return; } if (!this.events.blured && this.events.isCallback('blur')) { this.core.callback('blur', e); } this.events.focused = false; this.events.blured = true; }, touchImageEditing: function() { var scrollTimer = -1; this.events.imageEditing = false; $(window).on('touchmove.redactor.' + this.uuid, $.proxy(function() { this.events.imageEditing = true; if (scrollTimer !== -1) { clearTimeout(scrollTimer); } scrollTimer = setTimeout($.proxy(function() { this.events.imageEditing = false; }, this), 500); }, this)); }, init: function() { this.core.editor().on('dragover.redactor dragenter.redactor', $.proxy(this.events.dragover, this)); this.core.editor().on('dragleave.redactor', $.proxy(this.events.dragleave, this)); this.core.editor().on('drop.redactor', $.proxy(this.events.drop, this)); this.core.editor().on('click.redactor', $.proxy(this.events.click, this)); this.core.editor().on('paste.redactor', $.proxy(this.paste.init, this)); this.core.editor().on('keydown.redactor', $.proxy(this.keydown.init, this)); this.core.editor().on('keyup.redactor', $.proxy(this.keyup.init, this)); this.core.editor().on('focus.redactor', $.proxy(this.events.focus, this)); $(document).on('mousedown.redactor-blur.' + this.uuid, $.proxy(this.events.blur, this)); this.events.touchImageEditing(); this.events.createObserver(); this.events.setupObserver(); }, createObserver: function() { var self = this; this.events.observer = new MutationObserver(function(mutations) { mutations.forEach($.proxy(self.events.iterateObserver, self)); }); }, iterateObserver: function(mutation) { var stop = false; // target if (((this.opts.type === 'textarea' || this.opts.type === 'div') && (!this.detect.isFirefox() && mutation.target === this.core.editor()[0])) || (mutation.attributeName === 'class' && mutation.target === this.core.editor()[0]) || (mutation.attributeName == 'data-vivaldi-spatnav-clickable') ) { stop = true; } if (!stop) { this.observe.load(); this.events.changeHandler(); } }, setupObserver: function() { this.events.observer.observe(this.core.editor()[0], { attributes: true, subtree: true, childList: true, characterData: true, characterDataOldValue: true }); }, changeHandler: function() { if (this.events.stopChanges) { return; } this.code.sync(); // autosave if (this.autosave.is()) { clearTimeout(this.autosaveTimeout); this.autosaveTimeout = setTimeout($.proxy(this.autosave.send, this), 300); } }, onDropUpload: function(e) { e.preventDefault(); e.stopPropagation(); if ((!this.opts.dragImageUpload && !this.opts.dragFileUpload) || (this.opts.imageUpload === null && this.opts.fileUpload === null)) { return; } if (e.target.tagName === 'IMG') { this.events.dropImage = e.target; } var files = e.dataTransfer.files; var len = files.length; for (var i = 0; i < len; i++) { this.upload.directUpload(files[i], e); } }, onDrop: function(e) { this.core.callback('drop', e); }, isCallback: function(name) { return (typeof this.opts.callbacks[name] !== 'undefined' && $.isFunction(this.opts.callbacks[name])); }, // #backward stopDetect: function() { this.events.stopDetectChanges(); }, startDetect: function() { this.events.startDetectChanges(); } }; }, // =file file: function() { return { is: function() { return !(!this.opts.fileUpload || !this.opts.fileUpload && !this.opts.s3); }, show: function() { // build modal this.modal.load('file', this.lang.get('file'), 700); // build upload this.upload.init('#redactor-modal-file-upload', this.opts.fileUpload, this.file.insert); // set selected text $('#redactor-filename').val(this.selection.get().toString()); // show this.modal.show(); }, insert: function(json, direct, e) { // error callback if (typeof json.error !== 'undefined') { this.modal.close(); this.core.callback('fileUploadError', json); return; } this.file.release(e, direct); // prepare this.buffer.set(); this.air.collapsed(); // get var text = this.file.text(json); var $link = $('<a />').attr('href', json.url).text(text); var id = (typeof json.id === 'undefined') ? '' : json.id; var type = (typeof json.s3 === 'undefined') ? 'file' : 's3'; // set id $link.attr('data-' + type, id); // insert $link = $(this.insert.node($link)); // focus this.caret.after($link); // callback this.storage.add({ type: type, node: $link[0], url: json.url, id: id }); if (direct !== null) { this.core.callback('fileUpload', $link, json); } }, release: function(e, direct) { if (direct) { // drag and drop upload this.marker.remove(); this.insert.nodeToPoint(e, this.marker.get()); this.selection.restore(); } else { // upload from modal this.modal.close(); } }, text: function(json) { var text = $('#redactor-filename').val(); return (typeof text === 'undefined' || text === '') ? json.name : text; } }; }, // =focus focus: function() { return { start: function() { this.core.editor().focus(); if (this.opts.type === 'inline') { return; } var $first = this.focus.first(); if ($first !== false) { this.caret.start($first); } }, end: function() { this.core.editor().focus(); var last = (this.opts.inline) ? this.core.editor() : this.focus.last(); if (last.length === 0) { return; } // get inline last node var lastNode = this.focus.lastChild(last); if (!this.detect.isWebkit() && lastNode !== false) { this.caret.end(lastNode); } else { var sel = this.selection.get(); var range = this.selection.range(sel); if (range !== null) { range.selectNodeContents(last[0]); range.collapse(false); this.selection.update(sel, range); } else { this.caret.end(last); } } }, first: function() { var $first = this.core.editor().children().first(); if ($first.length === 0 && ($first[0].length === 0 || $first[0].tagName === 'BR' || $first[0].tagName === 'HR' || $first[0].nodeType === 3)) { return false; } if ($first[0].tagName === 'UL' || $first[0].tagName === 'OL') { return $first.find('li').first(); } return $first; }, last: function() { return this.core.editor().children().last(); }, lastChild: function(last) { var lastNode = last[0].lastChild; return (lastNode !== null && this.utils.isInlineTag(lastNode.tagName)) ? lastNode : false; }, is: function() { return (this.core.editor()[0] === document.activeElement); } }; }, // =image image: function() { return { is: function() { return !(!this.opts.imageUpload || !this.opts.imageUpload && !this.opts.s3); }, show: function() { // build modal this.modal.load('image', this.lang.get('image'), 700); // build upload this.upload.init('#redactor-modal-image-droparea', this.opts.imageUpload, this.image.insert); this.modal.show(); }, insert: function(json, direct, e) { var $img; // error callback if (typeof json.error !== 'undefined') { this.modal.close(); this.events.dropImage = false; this.core.callback('imageUploadError', json, e); return; } // change image if (this.events.dropImage !== false) { $img = $(this.events.dropImage); this.core.callback('imageDelete', $img[0].src, $img); $img.attr('src', json.url); this.events.dropImage = false; this.core.callback('imageUpload', $img, json); return; } this.placeholder.hide(); var $figure = $('<' + this.opts.imageTag + '>'); $img = $('<img>'); $img.attr('src', json.url); // set id var id = (typeof json.id === 'undefined') ? '' : json.id; var type = (typeof json.s3 === 'undefined') ? 'image' : 's3'; $img.attr('data-' + type, id); $figure.append($img); var pre = this.utils.isTag(this.selection.current(), 'pre'); if (direct) { this.air.collapsed(); this.marker.remove(); var node = this.insert.nodeToPoint(e, this.marker.get()); var $next = $(node).next(); this.selection.restore(); // buffer this.buffer.set(); // insert if (typeof $next !== 'undefined' && $next.length !== 0 && $next[0].tagName === 'IMG') { // delete callback this.core.callback('imageDelete', $next[0].src, $next); // replace $next.closest('figure, p', this.core.editor()[0]).replaceWith($figure); this.caret.after($figure); } else { if (pre) { $(pre).after($figure); } else { this.insert.node($figure); } this.caret.after($figure); } } else { this.modal.close(); // buffer this.buffer.set(); // insert this.air.collapsed(); if (pre) { $(pre).after($figure); } else { this.insert.node($figure); } this.caret.after($figure); } this.events.dropImage = false; this.storage.add({ type: type, node: $img[0], url: json.url, id: id }); var nextNode = $img[0].nextSibling; var $nextFigure = $figure.next(); var isNextEmpty = $(nextNode).text().replace(/\u200B/g, ''); var isNextFigureEmpty = $nextFigure.text().replace(/\u200B/g, ''); if (isNextEmpty === '') { $(nextNode).remove(); } if ($nextFigure.length === 1 && $nextFigure[0].tagName === 'FIGURE' && isNextFigureEmpty === '') { $nextFigure.remove(); } if (direct !== null) { this.core.callback('imageUpload', $img, json); } else { this.core.callback('imageInserted', $img, json); } }, setEditable: function($image) { $image.on('dragstart', function(e) { e.preventDefault(); }); if (this.opts.imageResizable) { var handler = $.proxy(function(e) { this.observe.image = $image; this.image.resizer = this.image.loadEditableControls($image); $(document).on('mousedown.redactor-image-resize-hide.' + this.uuid, $.proxy(this.image.hideResize, this)); if (this.image.resizer) { this.image.resizer.on('mousedown.redactor touchstart.redactor', $.proxy(function(e) { this.image.setResizable(e, $image); }, this)); } }, this); $image.off('mousedown.redactor').on('mousedown.redactor', $.proxy(this.image.hideResize, this)); $image.off('click.redactor touchstart.redactor').on('click.redactor touchstart.redactor', handler); } else { $image.off('click.redactor touchstart.redactor').on('click.redactor touchstart.redactor', $.proxy(function(e) { setTimeout($.proxy(function() { this.image.showEdit($image); }, this), 200); }, this)); } }, setResizable: function(e, $image) { e.preventDefault(); this.image.resizeHandle = { x : e.pageX, y : e.pageY, el : $image, ratio: $image.width() / $image.height(), h: $image.height() }; e = e.originalEvent || e; if (e.targetTouches) { this.image.resizeHandle.x = e.targetTouches[0].pageX; this.image.resizeHandle.y = e.targetTouches[0].pageY; } this.image.startResize(); }, startResize: function() { $(document).on('mousemove.redactor-image-resize touchmove.redactor-image-resize', $.proxy(this.image.moveResize, this)); $(document).on('mouseup.redactor-image-resize touchend.redactor-image-resize', $.proxy(this.image.stopResize, this)); }, moveResize: function(e) { e.preventDefault(); e = e.originalEvent || e; var height = this.image.resizeHandle.h; if (e.targetTouches) height += (e.targetTouches[0].pageY - this.image.resizeHandle.y); else height += (e.pageY - this.image.resizeHandle.y); var width = Math.round(height * this.image.resizeHandle.ratio); if (height < 50 || width < 100) return; this.image.resizeHandle.el.attr({width: width, height: height}); this.image.resizeHandle.el.width(width); this.image.resizeHandle.el.height(height); this.code.sync(); }, stopResize: function() { this.handle = false; $(document).off('.redactor-image-resize'); this.image.hideResize(); }, hideResize: function(e) { if (e && $(e.target).closest('#redactor-image-box', this.$editor[0]).length !== 0) return; if (e && e.target.tagName == 'IMG') { var $image = $(e.target); } var imageBox = this.$editor.find('#redactor-image-box'); if (imageBox.length === 0) return; $('#redactor-image-editter').remove(); $('#redactor-image-resizer').remove(); imageBox.find('img').css({ marginTop: imageBox[0].style.marginTop, marginBottom: imageBox[0].style.marginBottom, marginLeft: imageBox[0].style.marginLeft, marginRight: imageBox[0].style.marginRight }); imageBox.css('margin', ''); imageBox.find('img').css('opacity', ''); imageBox.replaceWith(function() { return $(this).contents(); }); $(document).off('mousedown.redactor-image-resize-hide.' + this.uuid); if (typeof this.image.resizeHandle !== 'undefined') { this.image.resizeHandle.el.attr('rel', this.image.resizeHandle.el.attr('style')); } }, loadResizableControls: function($image, imageBox) { if (this.opts.imageResizable && !this.detect.isMobile()) { var imageResizer = $('<span id="redactor-image-resizer" data-redactor="verified"></span>'); if (!this.detect.isDesktop()) { imageResizer.css({ width: '15px', height: '15px' }); } imageResizer.attr('contenteditable', false); imageBox.append(imageResizer); imageBox.append($image); return imageResizer; } else { imageBox.append($image); return false; } }, loadEditableControls: function($image) { if ($('#redactor-image-box').length !== 0) { return; } var imageBox = $('<span id="redactor-image-box" data-redactor="verified">'); imageBox.css('float', $image.css('float')).attr('contenteditable', false); if ($image[0].style.margin != 'auto') { imageBox.css({ marginTop: $image[0].style.marginTop, marginBottom: $image[0].style.marginBottom, marginLeft: $image[0].style.marginLeft, marginRight: $image[0].style.marginRight }); $image.css('margin', ''); } else { imageBox.css({ 'display': 'block', 'margin': 'auto' }); } $image.css('opacity', '.5').after(imageBox); if (this.opts.imageEditable) { // editter this.image.editter = $('<span id="redactor-image-editter" data-redactor="verified">' + this.lang.get('edit') + '</span>'); this.image.editter.attr('contenteditable', false); this.image.editter.on('click', $.proxy(function() { this.image.showEdit($image); }, this)); imageBox.append(this.image.editter); // position correction var editerWidth = this.image.editter.innerWidth(); this.image.editter.css('margin-left', '-' + editerWidth/2 + 'px'); } return this.image.loadResizableControls($image, imageBox); }, showEdit: function($image) { if (this.events.imageEditing) { return; } this.observe.image = $image; var $link = $image.closest('a', this.$editor[0]); var $figure = $image.closest('figure', this.$editor[0]); var $container = ($figure.length !== 0) ? $figure : $image; this.modal.load('image-edit', this.lang.get('edit'), 705); this.image.buttonDelete = this.modal.getDeleteButton().text(this.lang.get('delete')); this.image.buttonSave = this.modal.getActionButton().text(this.lang.get('save')); this.image.buttonDelete.on('click', $.proxy(this.image.remove, this)); this.image.buttonSave.on('click', $.proxy(this.image.update, this)); if (this.opts.imageCaption === false) { $('#redactor-image-caption').val('').hide().prev().hide(); } else { var $parent = $image.closest(this.opts.imageTag, this.$editor[0]); var $ficaption = $parent.find('figcaption'); if ($ficaption !== 0) { $('#redactor-image-caption').val($ficaption.text()).show(); } } if (!this.opts.imagePosition) { $('.redactor-image-position-option').hide(); } else { var isCentered = ($figure.length !== 0) ? ($container.css('text-align') === 'center') : ($container.css('display') == 'block' && $container.css('float') == 'none'); var floatValue = (isCentered) ? 'center' : $container.css('float'); $('#redactor-image-align').val(floatValue); } $('#redactor-image-preview').html($('<img src="' + $image.attr('src') + '" style="max-width: 100%;">')); $('#redactor-image-title').val($image.attr('alt')); if ($link.length !== 0) { $('#redactor-image-link').val($link.attr('href')); if ($link.attr('target') === '_blank') { $('#redactor-image-link-blank').prop('checked', true); } } // hide link's tooltip $('.redactor-link-tooltip').remove(); this.modal.show(); // focus if (this.detect.isDesktop()) { $('#redactor-image-title').focus(); } }, update: function() { var $image = this.observe.image; var $link = $image.closest('a', this.core.editor()[0]); var title = $('#redactor-image-title').val().replace(/(<([^>]+)>)/ig,""); $image.attr('alt', title).attr('title', title); this.image.setFloating($image); // as link var link = $.trim($('#redactor-image-link').val()).replace(/(<([^>]+)>)/ig,""); if (link !== '') { // test url (add protocol) var pattern = '((xn--)?[a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,}'; var re = new RegExp('^(http|ftp|https)://' + pattern, 'i'); var re2 = new RegExp('^' + pattern, 'i'); if (link.search(re) === -1 && link.search(re2) === 0 && this.opts.linkProtocol) { link = this.opts.linkProtocol + '://' + link; } var target = ($('#redactor-image-link-blank').prop('checked')) ? true : false; if ($link.length === 0) { var a = $('<a href="' + link + '" id="redactor-img-tmp">' + this.utils.getOuterHtml($image) + '</a>'); if (target) { a.attr('target', '_blank'); } $image = $image.replaceWith(a); $link = this.core.editor().find('#redactor-img-tmp'); $link.removeAttr('id'); } else { $link.attr('href', link); if (target) { $link.attr('target', '_blank'); } else { $link.removeAttr('target'); } } } else if ($link.length !== 0) { $link.replaceWith(this.utils.getOuterHtml($image)); } this.image.addCaption($image, $link); this.modal.close(); // buffer this.buffer.set(); }, setFloating: function($image) { var $figure = $image.closest('figure', this.$editor[0]); var $container = ($figure.length !== 0) ? $figure : $image; var floating = $('#redactor-image-align').val(); var imageFloat = ''; var imageDisplay = ''; var imageMargin = ''; var textAlign = ''; switch (floating) { case 'left': imageFloat = 'left'; imageMargin = '0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin + ' 0'; break; case 'right': imageFloat = 'right'; imageMargin = '0 0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin; break; case 'center': if ($figure.length !== 0) { textAlign = 'center'; } else { imageDisplay = 'block'; imageMargin = 'auto'; } break; } $container.css({ 'float': imageFloat, 'display': imageDisplay, 'margin': imageMargin, 'text-align': textAlign }); $container.attr('rel', $image.attr('style')); }, addCaption: function($image, $link) { var caption = $('#redactor-image-caption').val(); var $target = ($link.length !== 0) ? $link : $image; var $figcaption = $target.next(); if ($figcaption.length === 0 || $figcaption[0].tagName !== 'FIGCAPTION') { $figcaption = false; } if (caption !== '') { if ($figcaption === false) { $figcaption = $('<figcaption />').text(caption); $target.after($figcaption); } else { $figcaption.text(caption); } } else if ($figcaption !== false) { $figcaption.remove(); } }, remove: function(e, $image, index) { $image = (typeof $image === 'undefined') ? $(this.observe.image) : $image; // delete from modal if (typeof e !== 'boolean') { this.buffer.set(); } this.events.stopDetectChanges(); var $link = $image.closest('a', this.core.editor()[0]); var $figure = $image.closest(this.opts.imageTag, this.core.editor()[0]); var $parent = $image.parent(); // callback var imageDeleteStop = this.core.callback('imageDelete', e, $image[0]); if (imageDeleteStop === false) { if (e) e.preventDefault(); return false; } if ($('#redactor-image-box').length !== 0) { $parent = $('#redactor-image-box').parent(); } var $next, $prev; if ($figure.length !== 0) { $prev = $figure.prev(); $next = $figure.next(); $figure.remove(); } else if ($link.length !== 0) { $parent = $link.parent(); $link.remove(); } else { $image.remove(); } $('#redactor-image-box').remove(); if (e !== false) { if ($next && $next.length !== 0) { this.caret.start($next); } else if ($prev && $prev.length !== 0) { this.caret.end($prev); } } if (typeof e !== 'boolean') { this.modal.close(); } this.utils.restoreScroll(); this.observe.image = false; this.events.startDetectChanges(); this.placeholder.enable(); this.code.sync(); } }; }, // =indent indent: function() { return { increase: function() { if (!this.list.get()) { return; } var $current = $(this.selection.current()).closest('li'); var $list = $current.closest('ul, ol', this.core.editor()[0]); var $li = $current.closest('li'); var $prev = $li.prev(); if ($prev.length === 0 || $prev[0].tagName !== 'LI') { return; } this.buffer.set(); if (this.utils.isCollapsed()) { var listTag = $list[0].tagName; var $newList = $('<' + listTag + ' />'); this.selection.save(); var $ol = $prev.find('ol').first(); if ($ol.length === 1) { $ol.append($current); } else { var listTag = $list[0].tagName; var $newList = $('<' + listTag + ' />'); $newList.append($current); $prev.append($newList); } this.selection.restore(); } else { document.execCommand('indent'); // normalize this.selection.save(); this.indent.removeEmpty(); this.indent.normalize(); this.selection.restore(); } }, decrease: function() { if (!this.list.get()) { return; } var $current = $(this.selection.current()).closest('li'); var $list = $current.closest('ul, ol', this.core.editor()[0]); this.buffer.set(); document.execCommand('outdent'); var $item = $(this.selection.current()).closest('li', this.core.editor()[0]); if (this.utils.isCollapsed()) { this.indent.repositionItem($item); } if ($item.length === 0) { document.execCommand('formatblock', false, 'p'); $item = $(this.selection.current()); var $next = $item.next(); if ($next.length !== 0 && $next[0].tagName === 'BR') { $next.remove(); } } // normalize this.selection.save(); this.indent.removeEmpty(); this.indent.normalize(); this.selection.restore(); }, repositionItem: function($item) { var $next = $item.next(); if ($next.length !== 0 && ($next[0].tagName !== 'UL' || $next[0].tagName !== 'OL')) { $item.append($next); } var $prev = $item.prev(); if ($prev.length !== 0 && $prev[0].tagName !== 'LI') { this.selection.save(); var $li = $item.parents('li', this.core.editor()[0]); $li.after($item); this.selection.restore(); } }, normalize: function() { this.core.editor().find('li').each($.proxy(function(i,s) { var $el = $(s); // remove style var filter = ''; if (this.opts.keepStyleAttr.length !== 0) { filter = ',' + this.opts.keepStyleAttr.join(','); } $el.find(this.opts.inlineTags.join(',')).not('img' + filter).removeAttr('style'); var $parent = $el.parent(); if ($parent.length !== 0 && $parent[0].tagName === 'LI') { $parent.after($el); return; } var $next = $el.next(); if ($next.length !== 0 && ($next[0].tagName === 'UL' || $next[0].tagName === 'OL')) { $el.append($next); } }, this)); }, removeEmpty: function($list) { var $lists = this.core.editor().find('ul, ol'); var $items = this.core.editor().find('li'); $items.each($.proxy(function(i, s) { this.indent.removeItemEmpty(s); }, this)); $lists.each($.proxy(function(i, s) { this.indent.removeItemEmpty(s); }, this)); $items.each($.proxy(function(i, s) { this.indent.removeItemEmpty(s); }, this)); }, removeItemEmpty: function(s) { var html = s.innerHTML.replace(/[\t\s\n]/g, ''); html = html.replace(/<span><\/span>/g, ''); if (html === '') { $(s).remove(); } } }; }, // =inline inline: function() { return { format: function(tag, attr, value, type) { // Stop formatting pre/code if (this.utils.isCurrentOrParent(['PRE', 'CODE'])) return; // Get params var params = this.inline.getParams(attr, value, type); // Arrange tag tag = this.inline.arrangeTag(tag); this.placeholder.hide(); this.buffer.set(); (this.utils.isCollapsed()) ? this.inline.formatCollapsed(tag, params) : this.inline.formatUncollapsed(tag, params); }, formatCollapsed: function(tag, params) { var newInline; var inline = this.selection.inline(); if (inline) { var currentTag = inline.tagName.toLowerCase(); if (currentTag === tag) { // empty = remove if (this.utils.isEmpty(inline.innerHTML)) { this.caret.after(inline); $(inline).remove(); } // not empty = break else { var $first = this.inline.insertBreakpoint(inline, currentTag); this.caret.after($first); } } else if ($(inline).closest(tag).length === 0) { newInline = this.inline.insertInline(tag); newInline = this.inline.setParams(newInline, params); } else { var $first = this.inline.insertBreakpoint(inline, currentTag); this.caret.after($first); } } else { newInline = this.inline.insertInline(tag); newInline = this.inline.setParams(newInline, params); } }, formatUncollapsed: function(tag, params) { this.selection.save(); var nodes = this.inline.getClearedNodes(); this.inline.setNodesStriked(nodes, tag, params); this.selection.restore(); document.execCommand('strikethrough'); this.selection.saveInstant(); var self = this; this.core.editor().find('strike').each(function() { var $el = self.utils.replaceToTag(this, tag); self.inline.setParams($el[0], params); var $inside = $el.find(tag); var $parent = $el.parent(); var $parentAround = $parent.parent(); // revert formatting (safari bug) if ($parentAround.length !== 0 && $parentAround[0].tagName.toLowerCase() === tag && $parentAround.html() == $parent[0].outerHTML) { $el.replaceWith(function() { return $(this).contents(); }); $parentAround.replaceWith(function() { return $(this).contents(); }); return; } // remove inside if ($inside.length !== 0) self.inline.cleanInsideOrParent($inside, params); // same parent if ($parent.html() == $el[0].outerHTML) self.inline.cleanInsideOrParent($parent, params); // bugfix: remove empty inline tags after selection if (self.detect.isFirefox()) { self.core.editor().find(tag + ':empty').remove(); } }); this.selection.restoreInstant(); }, cleanInsideOrParent: function($el, params) { if (params) { for (var key in params.data) { this.inline.removeSpecificAttr($el, key, params.data[key]); } } }, getClearedNodes: function() { var nodes = this.selection.nodes(); var newNodes = []; var len = nodes.length; var started = 0; // find array slice for (var i = 0; i < len; i++) { if ($(nodes[i]).hasClass('redactor-selection-marker')) { started = i + 2; break; } } // find selected inline & text nodes for (var i = 0; i < len; i++) { if (i >= started && !this.utils.isBlockTag(nodes[i].tagName)) { newNodes.push(nodes[i]); } } return newNodes; }, isConvertableAttr: function(node, name, value) { var nodeAttrValue = $(node).attr(name); if (nodeAttrValue) { if (name === 'style') { value = $.trim(value).replace(/;$/, '') var rules = value.split(';'); var count = 0; for (var i = 0; i < rules.length; i++) { var arr = rules[i].split(':'); var ruleName = $.trim(arr[0]); var ruleValue = $.trim(arr[1]); if (ruleName.search(/color/) !== -1) { var val = $(node).css(ruleName); if (val && (val === ruleValue || this.utils.rgb2hex(val) === ruleValue)) { count++; } } else if ($(node).css(ruleName) === ruleValue) { count++; } } if (count === rules.length) { return 1; } } else if (nodeAttrValue === value) { return 1; } } return 0; }, isConvertable: function(node, nodeTag, tag, params) { if (nodeTag === tag) { if (params) { var count = 0; for (var key in params.data) { count += this.inline.isConvertableAttr(node, key, params.data[key]); } if (count === Object.keys(params.data).length) { return true; } } else { return true; } } return false; }, setNodesStriked: function(nodes, tag, params) { for (var i = 0; i < nodes.length; i++) { var nodeTag = (nodes[i].tagName) ? nodes[i].tagName.toLowerCase() : undefined; var parent = nodes[i].parentNode; var parentTag = (parent && parent.tagName) ? parent.tagName.toLowerCase() : undefined; var convertable = this.inline.isConvertable(parent, parentTag, tag, params); if (convertable) { var $el = $(parent).replaceWith(function() { return $('<strike>').append($(this).contents()); }); $el.attr('data-redactor-inline-converted'); } var convertable = this.inline.isConvertable(nodes[i], nodeTag, tag, params); if (convertable) { var $el = $(nodes[i]).replaceWith(function() { return $('<strike>').append($(this).contents()); }); } } }, insertBreakpoint: function(inline, currentTag) { var breakpoint = document.createElement('span'); breakpoint.id = 'redactor-inline-breakpoint'; breakpoint = this.insert.node(breakpoint); var end = this.utils.isEndOfElement(inline); var code = this.utils.getOuterHtml(inline); var endTag = (end) ? '' : '<' + currentTag + '>'; code = code.replace(/<span id="redactor-inline-breakpoint"><\/span>/i, "</" + currentTag + ">" + endTag); var $code = $(code); $(inline).replaceWith($code); if (endTag !== '') { this.utils.cloneAttributes(inline, $code.last()); } return $code.first(); }, insertInline: function(tag) { var node = document.createElement(tag); this.insert.node(node); this.caret.start(node); return node; }, arrangeTag: function(tag) { var tags = ['b', 'bold', 'i', 'italic', 'underline', 'strikethrough', 'deleted', 'superscript', 'subscript']; var replaced = ['strong', 'strong', 'em', 'em', 'u', 'del', 'del', 'sup', 'sub']; tag = tag.toLowerCase(); for (var i = 0; i < tags.length; i++) { if (tag === tags[i]) { tag = replaced[i]; } } return tag; }, getStyleParams: function(params) { var result = {}; var rules = params.trim().replace(/;$/, '').split(';'); for (var i = 0; i < rules.length; i++) { var rule = rules[i].split(':'); if (rule) { result[rule[0].trim()] = rule[1].trim(); } } return result; }, getParams: function(attr, value, type) { var data = false; var func = 'toggle'; if (typeof attr === 'object') { data = attr; func = (value !== undefined) ? value : func; } else if (attr !== undefined && value !== undefined) { data = {}; data[attr] = value; func = (type !== undefined) ? type : func; } return (data) ? { 'func': func, 'data': data } : false; }, setParams: function(node, params) { if (params) { for (var key in params.data) { var $node = $(node); if (key === 'style') { node = this.inline[params.func + 'Style'](params.data[key], node); $node.attr('data-redactor-style-cache', $node.attr('style')); } else if (key === 'class') { node = this.inline[params.func + 'Class'](params.data[key], node); } // attr else { node = (params.func === 'remove') ? this.inline[params.func + 'Attr'](key, node) : this.inline[params.func + 'Attr'](key, params.data[key], node); } if (key === 'style' && node.tagName === 'SPAN') { $node.attr('data-redactor-span', true); } } } return node; }, // Each eachInline: function(node, callback) { var lastNode; var nodes = (node === undefined) ? this.selection.inlines() : [node]; if (nodes) { for (var i = 0; i < nodes.length; i++) { lastNode = callback(nodes[i])[0]; } } return lastNode; }, // Class replaceClass: function(value, node) { return this.inline.eachInline(node, function(el) { return $(el).removeAttr('class').addClass(value); }); }, toggleClass: function(value, node) { return this.inline.eachInline(node, function(el) { return $(el).toggleClass(value); }); }, addClass: function(value, node) { return this.inline.eachInline(node, function(el) { return $(el).addClass(value); }); }, removeClass: function(value, node) { return this.inline.eachInline(node, function(el) { return $(el).removeClass(value); }); }, removeAllClass: function(node) { return this.inline.eachInline(node, function(el) { return $(el).removeAttr('class'); }); }, // Attr replaceAttr: function(name, value, node) { return this.inline.eachInline(node, function(el) { return $(el).removeAttr(name).attr(name. value); }); }, toggleAttr: function(name, value, node) { return this.inline.eachInline(node, function(el) { var attr = $(el).attr(name); return (attr) ? $(el).removeAttr(name) : $(el).attr(name. value); }); }, addAttr: function(name, value, node) { return this.inline.eachInline(node, function(el) { return $(el).attr(name, value); }); }, removeAttr: function(name, node) { return this.inline.eachInline(node, function(el) { var $el = $(el); $el.removeAttr(name); if (name === 'style') { $el.removeAttr('data-redactor-style-cache'); } return $el; }); }, removeAllAttr: function(node) { return this.inline.eachInline(node, function(el) { var $el = $(el); var len = el.attributes.length; for (var z = 0; z < len; z++) { $el.removeAttr(el.attributes[z].name); } return $el; }); }, removeSpecificAttr: function(node, key, value) { var $el = $(node); if (key === 'style') { var arr = value.split(':'); var name = arr[0].trim(); $el.css(name, ''); if (this.utils.removeEmptyAttr(node, 'style')) { $el.removeAttr('data-redactor-style-cache'); } } else { $el.removeAttr(key)[0]; } }, // Style hasParentStyle: function($el) { var $parent = $el.parent(); return ($parent.length === 1 && $parent[0].tagName === $el[0].tagName && $parent.html() === $el[0].outerHTML) ? $parent : false; }, addParentStyle: function($el) { var $parent = this.inline.hasParentStyle($el); if ($parent) { var style = this.inline.getStyleParams($el.attr('style')); $parent.css(style); $parent.attr('data-redactor-style-cache', $parent.attr('style')); $el.replaceWith(function() { return $(this).contents(); }); } else { $el.attr('data-redactor-style-cache', $el.attr('style')); } return $el; }, replaceStyle: function(params, node) { params = this.inline.getStyleParams(params); var self = this; return this.inline.eachInline(node, function(el) { var $el = $(el); $el.removeAttr('style').css(params); var style = $el.attr('style'); if (style) $el.attr('style', style.replace(/"/g, '\'')); $el = self.inline.addParentStyle($el); return $el; }); }, toggleStyle: function(params, node) { params = this.inline.getStyleParams(params); var self = this; return this.inline.eachInline(node, function(el) { var $el = $(el); for (var key in params) { var newVal = params[key]; var oldVal = $el.css(key); oldVal = (self.utils.isRgb(oldVal)) ? self.utils.rgb2hex(oldVal) : oldVal.replace(/"/g, ''); newVal = (self.utils.isRgb(newVal)) ? self.utils.rgb2hex(newVal) : newVal.replace(/"/g, ''); if (oldVal === newVal) { $el.css(key, ''); } else { $el.css(key, newVal); } } var style = $el.attr('style'); if (style) $el.attr('style', style.replace(/"/g, '\'')); if (!self.utils.removeEmptyAttr(el, 'style')) { $el = self.inline.addParentStyle($el); } else { $el.removeAttr('data-redactor-style-cache'); } return $el; }); }, addStyle: function(params, node) { params = this.inline.getStyleParams(params); var self = this; return this.inline.eachInline(node, function(el) { var $el = $(el); $el.css(params); var style = $el.attr('style'); if (style) $el.attr('style', style.replace(/"/g, '\'')); $el = self.inline.addParentStyle($el); return $el; }); }, removeStyle: function(params, node) { params = this.inline.getStyleParams(params); var self = this; return this.inline.eachInline(node, function(el) { var $el = $(el); for (var key in params) { $el.css(key, ''); } if (self.utils.removeEmptyAttr(el, 'style')) { $el.removeAttr('data-redactor-style-cache'); } else { $el.attr('data-redactor-style-cache', $el.attr('style')); } return $el; }); }, removeAllStyle: function(node) { return this.inline.eachInline(node, function(el) { return $(el).removeAttr('style').removeAttr('data-redactor-style-cache'); }); }, removeStyleRule: function(name) { var parent = this.selection.parent(); var nodes = this.selection.inlines(); this.buffer.set(); if (parent && parent.tagName === 'SPAN') { this.inline.removeStyleRuleAttr($(parent), name); } for (var i = 0; i < nodes.length; i++) { var el = nodes[i]; var $el = $(el); if ($.inArray(el.tagName.toLowerCase(), this.opts.inlineTags) != -1 && !$el.hasClass('redactor-selection-marker')) { this.inline.removeStyleRuleAttr($el, name); } } }, removeStyleRuleAttr: function($el, name) { $el.css(name, ''); if (this.utils.removeEmptyAttr($el, 'style')) { $el.removeAttr('data-redactor-style-cache'); } else { $el.attr('data-redactor-style-cache', $el.attr('style')); } }, // Update update: function(tag, attr, value, type) { tag = this.inline.arrangeTag(tag); var params = this.inline.getParams(attr, value, type); var nodes = this.selection.inlines(); var result = []; if (nodes) { for (var i = 0; i < nodes.length; i++) { var el = nodes[i]; if (tag === '*' || el.tagName.toLowerCase() === tag) { result.push(this.inline.setParams(el, params)); } } } return result; }, // All removeFormat: function() { this.selection.save(); var nodes = this.inline.getClearedNodes(); for (var i = 0; i < nodes.length; i++) { if (nodes[i].nodeType === 1) { $(nodes[i]).replaceWith(function() { return $(this).contents(); }); } } this.selection.restore(); } }; }, // =insert insert: function() { return { set: function(html) { this.placeholder.hide(); this.code.set(html); this.focus.end(); this.placeholder.enable(); }, html: function(html, data) { this.placeholder.hide(); this.core.editor().focus(); var block = this.selection.block(); var inline = this.selection.inline(); // clean if (typeof data === 'undefined') { data = this.clean.getCurrentType(html, true); html = this.clean.onPaste(html, data, true); } html = $.parseHTML(html); // end node var endNode = $(html).last(); // delete selected content var sel = this.selection.get(); var range = this.selection.range(sel); range.deleteContents(); this.selection.update(sel, range); // insert list in list if (data.lists) { var $list = $(html); if ($list.length !== 0 && ($list[0].tagName === 'UL' || $list[0].tagName === 'OL')) { this.insert.appendLists(block, $list); return; } } if (data.blocks && block) { if (this.utils.isSelectAll()) { this.core.editor().html(html); this.focus.end(); } else { var breaked = this.utils.breakBlockTag(); if (breaked === false) { this.insert.placeHtml(html); } else { var $last = $(html).children().last(); $last.append(this.marker.get()); if (breaked.type === 'start') { breaked.$block.before(html); } else { breaked.$block.after(html); } this.selection.restore(); this.core.editor().find('p').each(function() { if ($.trim(this.innerHTML) === '') { $(this).remove(); } }); } } } else { if (inline) { // remove same tag inside var $div = $("<div/>").html(html); $div.find(inline.tagName.toLowerCase()).each(function() { $(this).contents().unwrap(); }); html = $div.html(); html = $.parseHTML(html); endNode = $(html).last(); } if (this.utils.isSelectAll()) { var $node = $(this.opts.emptyHtml); this.core.editor().html('').append($node); $node.html(html); this.caret.end($node); } else { this.insert.placeHtml(html); } } this.utils.disableSelectAll(); if (data.pre) this.clean.cleanPre(); this.caret.end(endNode); this.linkify.format(); }, text: function(text) { text = text.toString(); text = $.trim(text); var tmp = document.createElement('div'); tmp.innerHTML = text; text = tmp.textContent || tmp.innerText; if (typeof text === 'undefined') { return; } this.placeholder.hide(); this.core.editor().focus(); // blocks var blocks = this.selection.blocks(); // nl to spaces text = text.replace(/\n/g, ' '); // select all if (this.utils.isSelectAll()) { var $node = $(this.opts.emptyHtml); this.core.editor().html('').append($node); $node.html(text); this.caret.end($node); } else { // insert var sel = this.selection.get(); var node = document.createTextNode(text); if (sel.getRangeAt && sel.rangeCount) { var range = sel.getRangeAt(0); range.deleteContents(); range.insertNode(node); range.setStartAfter(node); range.collapse(true); this.selection.update(sel, range); } // wrap node if selected two or more block tags if (blocks.length > 1) { $(node).wrap('<p>'); this.caret.after(node); } } this.utils.disableSelectAll(); this.linkify.format(); this.clean.normalizeCurrentHeading(); }, raw: function(html) { this.placeholder.hide(); this.core.editor().focus(); var sel = this.selection.get(); var range = this.selection.range(sel); range.deleteContents(); var el = document.createElement("div"); el.innerHTML = html; var frag = document.createDocumentFragment(), node, lastNode; while ((node = el.firstChild)) { lastNode = frag.appendChild(node); } range.insertNode(frag); if (lastNode) { range = range.cloneRange(); range.setStartAfter(lastNode); range.collapse(true); sel.removeAllRanges(); sel.addRange(range); } }, node: function(node, deleteContent) { this.placeholder.hide(); if (typeof this.start !== 'undefined') { this.core.editor().focus(); } node = node[0] || node; var block = this.selection.block(); var gap = this.utils.isBlockTag(node.tagName); var result = true; if (this.utils.isSelectAll()) { if (gap) { this.core.editor().html(node); } else { this.core.editor().html($('<p>').html(node)); } this.code.sync(); } else if (gap && block) { var breaked = this.utils.breakBlockTag(); if (breaked === false) { this.insert.placeNode(node, deleteContent); } else { if (breaked.type === 'start') { breaked.$block.before(node); } else { breaked.$block.after(node); } this.core.editor().find('p:empty').remove(); } } else { result = this.insert.placeNode(node, deleteContent); } this.utils.disableSelectAll(); if (result) { this.caret.end(node); } return node; }, appendLists: function(block, $list) { var $block = $(block); var last; var isEmpty = this.utils.isEmpty(block.innerHTML); if (isEmpty || this.utils.isEndOfElement(block)) { last = $block; $list.find('li').each(function() { last.after(this); last = $(this); }); if (isEmpty) { $block.remove(); } } else if (this.utils.isStartOfElement(block)) { $list.find('li').each(function() { $block.before(this); last = $(this); }); } else { var endOfNode = this.selection.extractEndOfNode(block); $block.after($('<li>').append(endOfNode)); $block.append($list); last = $list; } this.marker.remove(); if (last) { this.caret.end(last); } this.linkify.format(); }, placeHtml: function(html) { var marker = document.createElement('span'); marker.id = 'redactor-insert-marker'; marker = this.insert.node(marker); $(marker).before(html); this.selection.restore(); this.caret.after(marker); $(marker).remove(); }, placeNode: function(node, deleteContent) { var sel = this.selection.get(); var range = this.selection.range(sel); if (range == null) { return false; } if (deleteContent !== false) { range.deleteContents(); } range.insertNode(node); range.collapse(false); this.selection.update(sel, range); }, nodeToPoint: function(e, node) { this.placeholder.hide(); node = node[0] || node; if (this.utils.isEmpty()) { node = (this.utils.isBlock(node)) ? node : $('<p />').append(node); this.core.editor().html(node); return node; } var range; var x = e.clientX, y = e.clientY; if (document.caretPositionFromPoint) { var pos = document.caretPositionFromPoint(x, y); var sel = document.getSelection(); range = sel.getRangeAt(0); range.setStart(pos.offsetNode, pos.offset); range.collapse(true); range.insertNode(node); } else if (document.caretRangeFromPoint) { range = document.caretRangeFromPoint(x, y); range.insertNode(node); } else if (typeof document.body.createTextRange !== "undefined") { range = document.body.createTextRange(); range.moveToPoint(x, y); var endRange = range.duplicate(); endRange.moveToPoint(x, y); range.setEndPoint("EndToEnd", endRange); range.select(); } return node; }, // #backward nodeToCaretPositionFromPoint: function(e, node) { this.insert.nodeToPoint(e, node); }, marker: function() { this.marker.insert(); } }; }, // =keydown keydown: function() { return { init: function(e) { if (this.rtePaste) { return; } var key = e.which; var arrow = (key >= 37 && key <= 40); this.keydown.ctrl = e.ctrlKey || e.metaKey; this.keydown.parent = this.selection.parent(); this.keydown.current = this.selection.current(); this.keydown.block = this.selection.block(); // detect tags this.keydown.pre = this.utils.isTag(this.keydown.current, 'pre'); this.keydown.blockquote = this.utils.isTag(this.keydown.current, 'blockquote'); this.keydown.figcaption = this.utils.isTag(this.keydown.current, 'figcaption'); this.keydown.figure = this.utils.isTag(this.keydown.current, 'figure'); // callback var keydownStop = this.core.callback('keydown', e); if (keydownStop === false) { e.preventDefault(); return false; } // shortcuts setup this.shortcuts.init(e, key); // buffer this.keydown.checkEvents(arrow, key); this.keydown.setupBuffer(e, key); if (this.utils.isSelectAll() && ( key === this.keyCode.ENTER || key === this.keyCode.BACKSPACE || key === this.keyCode.DELETE)) { e.preventDefault(); this.code.set(this.opts.emptyHtml); this.events.changeHandler(); return; } this.keydown.addArrowsEvent(arrow); this.keydown.setupSelectAll(e, key); // turn off enter key if (!this.opts.enterKey && key === this.keyCode.ENTER) { e.preventDefault(); // remove selected var sel = this.selection.get(); var range = this.selection.range(sel); if (!range.collapsed) { range.deleteContents(); } return; } // down if (this.opts.enterKey && key === this.keyCode.DOWN) { this.keydown.onArrowDown(); } // up if (this.opts.enterKey && key === this.keyCode.UP) { this.keydown.onArrowUp(); } // replace to p before / after the table or into body if ((this.opts.type === 'textarea' || this.opts.type === 'div') && this.keydown.current && this.keydown.current.nodeType === 3 && $(this.keydown.parent).hasClass('redactor-in')) { this.keydown.wrapToParagraph(); } // on Shift+Space or Ctrl+Space if (!this.keyup.lastShiftKey && key === this.keyCode.SPACE && (e.ctrlKey || e.shiftKey)) { e.preventDefault(); return this.keydown.onShiftSpace(); } // on Shift+Enter or Ctrl+Enter if (key === this.keyCode.ENTER && (e.ctrlKey || e.shiftKey)) { e.preventDefault(); return this.keydown.onShiftEnter(e); } // on enter if (key === this.keyCode.ENTER && !e.shiftKey && !e.ctrlKey && !e.metaKey) { return this.keydown.onEnter(e); } // tab or cmd + [ if (key === this.keyCode.TAB || e.metaKey && key === 221 || e.metaKey && key === 219) { return this.keydown.onTab(e, key); } // firefox bugfix if (this.detect.isFirefox() && key === this.keyCode.BACKSPACE && this.keydown.block && this.keydown.block.tagName === 'P' && this.utils.isStartOfElement(this.keydown.block)) { var $prev = $(this.keydown.block).prev(); if ($prev.length !== 0) { e.preventDefault(); $prev.append(this.marker.get()); $prev.append($(this.keydown.block).html()); $(this.keydown.block).remove(); this.selection.restore(); return; } } // backspace & delete if (key === this.keyCode.BACKSPACE || key === this.keyCode.DELETE) { if (this.observe.image && typeof this.observe.image !== 'undefined' && $('#redactor-image-box').length !== 0) { e.preventDefault(); var $prev = this.observe.image.closest('figure, p').prev() this.image.remove(false); this.observe.image = false; if ($prev && $prev.length !== 0) { this.caret.end($prev); } else { this.core.editor().focus(); } return; } this.keydown.onBackspaceAndDeleteBefore(); } if (key === this.keyCode.DELETE) { var $next = $(this.keydown.block).next(); // delete figure if (this.utils.isEndOfElement(this.keydown.block) && $next.length !== 0 && $next[0].tagName === 'FIGURE') { $next.remove(); return false; } // append list (safari bug) var tagLi = (this.keydown.block && this.keydown.block.tagName === 'LI') ? this.keydown.block : false; if (tagLi) { var $list = $(this.keydown.block).parents('ul, ol').last(); var $nextList = $list.next(); if (this.utils.isRedactorParent($list) && this.utils.isEndOfElement($list) && $nextList.length !== 0 && ($nextList[0].tagName === 'UL' || $nextList[0].tagName === 'OL')) { e.preventDefault(); $list.append($nextList.contents()); $nextList.remove(); return false; } } // append pre if (this.utils.isEndOfElement(this.keydown.block) && $next.length !== 0 && $next[0].tagName === 'PRE') { $(this.keydown.block).append($next.text()); $next.remove(); return false; } } // image delete if (key === this.keyCode.DELETE && $('#redactor-image-box').length !== 0) { this.image.remove(); } // backspace if (key === this.keyCode.BACKSPACE) { if (this.detect.isFirefox()) { this.line.removeOnBackspace(e); } // combine list after and before if paragraph is empty if (this.list.combineAfterAndBefore(this.keydown.block)) { e.preventDefault(); return; } // backspace as outdent var block = this.selection.block(); if (block && block.tagName === 'LI' && this.utils.isCollapsed() && this.utils.isStartOfElement()) { this.indent.decrease(); e.preventDefault(); return; } this.keydown.removeInvisibleSpace(); this.keydown.removeEmptyListInTable(e); } if (key === this.keyCode.BACKSPACE || key === this.keyCode.DELETE) { this.keydown.onBackspaceAndDeleteAfter(e); } }, onShiftSpace: function() { this.buffer.set(); this.insert.raw(' '); return false; }, onShiftEnter: function(e) { this.buffer.set(); return (this.keydown.pre) ? this.keydown.insertNewLine(e) : this.insert.raw('<br>'); }, onBackspaceAndDeleteBefore: function() { this.utils.saveScroll(); }, onBackspaceAndDeleteAfter: function(e) { // remove style tag setTimeout($.proxy(function() { this.code.syncFire = false; this.keydown.removeEmptyLists(); var filter = ''; if (this.opts.keepStyleAttr.length !== 0) { filter = ',' + this.opts.keepStyleAttr.join(','); } var $styleTags = this.core.editor().find('*[style]'); $styleTags.not('img, figure, iframe, #redactor-image-box, #redactor-image-editter, [data-redactor-style-cache], [data-redactor-span]' + filter).removeAttr('style'); this.keydown.formatEmpty(e); this.code.syncFire = true; }, this), 1); }, onEnter: function(e) { var stop = this.core.callback('enter', e); if (stop === false) { e.preventDefault(); return false; } // blockquote exit if (this.keydown.blockquote && this.keydown.exitFromBlockquote(e) === true) { return false; } // pre if (this.keydown.pre) { return this.keydown.insertNewLine(e); } // blockquote & figcaption else if (this.keydown.blockquote || this.keydown.figcaption) { return this.keydown.insertBreakLine(e); } // figure else if (this.keydown.figure) { setTimeout($.proxy(function() { this.keydown.replaceToParagraph('FIGURE'); }, this), 1); } // paragraphs else if (this.keydown.block) { setTimeout($.proxy(function() { this.keydown.replaceToParagraph('DIV'); }, this), 1); // empty list exit if (this.keydown.block.tagName === 'LI') { var current = this.selection.current(); var $parent = $(current).closest('li', this.$editor[0]); var $list = $parent.parents('ul,ol', this.$editor[0]).last(); if ($parent.length !== 0 && this.utils.isEmpty($parent.html()) && $list.next().length === 0 && this.utils.isEmpty($list.find("li").last().html())) { $list.find("li").last().remove(); var node = $(this.opts.emptyHtml); $list.after(node); this.caret.start(node); return false; } } } // outside else if (!this.keydown.block) { return this.keydown.insertParagraph(e); } // firefox enter into inline element if (this.detect.isFirefox() && this.utils.isInline(this.keydown.parent)) { this.keydown.insertBreakLine(e); return; } // remove inline tags in new-empty paragraph if (!this.opts.keepInlineOnEnter) { setTimeout($.proxy(function() { var inline = this.selection.inline(); if (inline && this.utils.isEmpty(inline.innerHTML)) { var parent = this.selection.block(); $(inline).remove(); //this.caret.start(parent); var range = document.createRange(); range.setStart(parent, 0); var textNode = document.createTextNode('\u200B'); range.insertNode(textNode); range.setStartAfter(textNode); range.collapse(true); var sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); } }, this), 1); } // remove last br setTimeout($.proxy(function() { var block = this.selection.block(); var nodes = block.childNodes; var last = nodes[nodes.length-1]; if (last && last.nodeType !== 3 && last.tagName === 'BR') { $(last).remove(); } }, this), 1); }, checkEvents: function(arrow, key) { if (!arrow && (this.core.getEvent() === 'click' || this.core.getEvent() === 'arrow')) { this.core.addEvent(false); if (this.keydown.checkKeyEvents(key)) { this.buffer.set(); } } }, checkKeyEvents: function(key) { var k = this.keyCode; var keys = [k.BACKSPACE, k.DELETE, k.ENTER, k.ESC, k.TAB, k.CTRL, k.META, k.ALT, k.SHIFT]; return ($.inArray(key, keys) === -1) ? true : false; }, addArrowsEvent: function(arrow) { if (!arrow) { return; } if ((this.core.getEvent() === 'click' || this.core.getEvent() === 'arrow')) { this.core.addEvent(false); return; } this.core.addEvent('arrow'); }, setupBuffer: function(e, key) { if (this.keydown.ctrl && key === 90 && !e.shiftKey && !e.altKey && this.sBuffer.length) // z key { e.preventDefault(); this.buffer.undo(); return; } // redo else if (this.keydown.ctrl && key === 90 && e.shiftKey && !e.altKey && this.sRebuffer.length !== 0) { e.preventDefault(); this.buffer.redo(); return; } else if (!this.keydown.ctrl) { if (key === this.keyCode.SPACE || key === this.keyCode.BACKSPACE || key === this.keyCode.DELETE || (key === this.keyCode.ENTER && !e.ctrlKey && !e.shiftKey)) { this.buffer.set(); } } }, exitFromBlockquote: function(e) { if (!this.utils.isEndOfElement(this.keydown.blockquote)) { return; } var tmp = this.clean.removeSpacesHard($(this.keydown.blockquote).html()); if (tmp.search(/(<br\s?\/?>){1}$/i) !== -1) { e.preventDefault(); var $last = $(this.keydown.blockquote).children().last(); $last.filter('br').remove(); $(this.keydown.blockquote).children().last().filter('span').remove(); var node = $(this.opts.emptyHtml); $(this.keydown.blockquote).after(node); this.caret.start(node); return true; } return; }, onArrowDown: function() { var tags = [this.keydown.blockquote, this.keydown.pre, this.keydown.figcaption]; for (var i = 0; i < tags.length; i++) { if (tags[i]) { this.keydown.insertAfterLastElement(tags[i]); return false; } } }, onArrowUp: function() { var tags = [this.keydown.blockquote, this.keydown.pre, this.keydown.figcaption]; for (var i = 0; i < tags.length; i++) { if (tags[i]) { this.keydown.insertBeforeFirstElement(tags[i]); return false; } } }, insertAfterLastElement: function(element) { if (!this.utils.isEndOfElement(element)) { return; } var last = this.core.editor().contents().last(); var $next = (element.tagName === 'FIGCAPTION') ? $(this.keydown.block).parent().next() : $(this.keydown.block).next(); if ($next.length !== 0) { return; } else if (last.length === 0 && last[0] !== element) { this.caret.start(last); return; } else { var node = $(this.opts.emptyHtml); if (element.tagName === 'FIGCAPTION') { $(element).parent().after(node); } else { $(element).after(node); } this.caret.start(node); } }, insertBeforeFirstElement: function(element) { if (!this.utils.isStartOfElement()) { return; } if (this.core.editor().contents().length > 1 && this.core.editor().contents().first()[0] !== element) { return; } var node = $(this.opts.emptyHtml); $(element).before(node); this.caret.start(node); }, onTab: function(e, key) { if (!this.opts.tabKey) { return true; } var isList = (this.keydown.block && this.keydown.block.tagName === 'LI') if (this.utils.isEmpty(this.code.get()) || (!isList && !this.keydown.pre && this.opts.tabAsSpaces === false)) { return true; } e.preventDefault(); this.buffer.set(); var isListStart = (isList && this.utils.isStartOfElement(this.keydown.block)); var node; if (this.keydown.pre && !e.shiftKey) { node = (this.opts.preSpaces) ? document.createTextNode(Array(this.opts.preSpaces + 1).join('\u00a0')) : document.createTextNode('\t'); this.insert.node(node); } else if (this.opts.tabAsSpaces !== false && !isListStart) { node = document.createTextNode(Array(this.opts.tabAsSpaces + 1).join('\u00a0')); this.insert.node(node); } else { if (e.metaKey && key === 219) { this.indent.decrease(); } else if (e.metaKey && key === 221) { this.indent.increase(); } else if (!e.shiftKey) { this.indent.increase(); } else { this.indent.decrease(); } } return false; }, setupSelectAll: function(e, key) { if (this.keydown.ctrl && key === 65) { this.utils.enableSelectAll(); } else if (key !== this.keyCode.LEFT_WIN && !this.keydown.ctrl) { this.utils.disableSelectAll(); } }, insertNewLine: function(e) { e.preventDefault(); var node = document.createTextNode('\n'); var sel = this.selection.get(); var range = this.selection.range(sel); range.deleteContents(); range.insertNode(node); this.caret.after(node); return false; }, insertParagraph: function(e) { e.preventDefault(); var p = document.createElement('p'); //p.innerHTML = this.opts.invisibleSpace; p.innerHTML = '<br>'; var sel = this.selection.get(); var range = this.selection.range(sel); range.deleteContents(); range.insertNode(p); this.caret.start(p); return false; }, insertBreakLine: function(e) { return this.keydown.insertBreakLineProcessing(e); }, insertDblBreakLine: function(e) { return this.keydown.insertBreakLineProcessing(e, true); }, insertBreakLineProcessing: function(e, dbl) { e.stopPropagation(); var br1 = document.createElement('br'); this.insert.node(br1); if (dbl === true) { var br2 = document.createElement('br'); this.insert.node(br2); this.caret.after(br2); } else { this.caret.after(br1); } return false; }, wrapToParagraph: function() { var $current = $(this.keydown.current); var node = $('<p>').append($current.clone()); $current.replaceWith(node); var next = $(node).next(); if (typeof(next[0]) !== 'undefined' && next[0].tagName === 'BR') { next.remove(); } this.caret.end(node); }, replaceToParagraph: function(tag) { var blockElem = this.selection.block(); var $prev = $(blockElem).prev(); var blockHtml = blockElem.innerHTML.replace(/<br\s?\/?>/gi, ''); if (blockElem.tagName === tag && this.utils.isEmpty(blockHtml) && !$(blockElem).hasClass('redactor-in')) { var p = document.createElement('p'); $(blockElem).replaceWith(p); this.keydown.setCaretToParagraph(p); return false; } else if (blockElem.tagName === 'P') { $(blockElem).removeAttr('class').removeAttr('style'); // fix #227 if (this.detect.isIe() && this.utils.isEmpty(blockHtml) && this.utils.isInline(this.keydown.parent)) { $(blockElem).on('input', $.proxy(function() { var parent = this.selection.parent(); if (this.utils.isInline(parent)) { var html = $(parent).html(); $(blockElem).html(html); this.caret.end(blockElem); } $(blockElem).off('keyup'); }, this)); } return false; } else if ($prev.hasClass(this.opts.videoContainerClass)) { $prev.removeAttr('class'); var p = document.createElement('p'); $prev.replaceWith(p); this.keydown.setCaretToParagraph(p); return false; } }, setCaretToParagraph: function(p) { var range = document.createRange(); range.setStart(p, 0); var textNode = document.createTextNode('\u200B'); range.insertNode(textNode); range.setStartAfter(textNode); range.collapse(true); var sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); }, removeInvisibleSpace: function() { var $current = $(this.keydown.current); if ($current.text().search(/^\u200B$/g) === 0) { $current.remove(); } }, removeEmptyListInTable: function(e) { var $current = $(this.keydown.current); var $parent = $(this.keydown.parent); var td = $current.closest('td', this.$editor[0]); if (td.length !== 0 && $current.closest('li', this.$editor[0]) && $parent.children('li').length === 1) { if (!this.utils.isEmpty($current.text())) { return; } e.preventDefault(); $current.remove(); $parent.remove(); this.caret.start(td); } }, removeEmptyLists: function() { var removeIt = function() { var html = $.trim(this.innerHTML).replace(/\/t\/n/g, ''); if (html === '') { $(this).remove(); } }; this.core.editor().find('li').each(removeIt); this.core.editor().find('ul, ol').each(removeIt); }, formatEmpty: function(e) { var html = $.trim(this.core.editor().html()); if (!this.utils.isEmpty(html)) { return; } e.preventDefault(); if (this.opts.type === 'inline' || this.opts.type === 'pre') { this.core.editor().html(this.marker.html()); this.selection.restore(); } else { this.core.editor().html(this.opts.emptyHtml); this.focus.start(); } return false; } }; }, // =keyup keyup: function() { return { init: function(e) { if (this.rtePaste) { return; } var key = e.which; this.keyup.block = this.selection.block(); this.keyup.current = this.selection.current(); this.keyup.parent = this.selection.parent(); this.keyup.lastShiftKey = e.shiftKey; // callback var stop = this.core.callback('keyup', e); if (stop === false) { e.preventDefault(); return false; } // replace a prev figure to paragraph if caret is before image if (key === this.keyCode.ENTER) { if (this.keyup.block && this.keyup.block.tagName === 'FIGURE') { var $prev = $(this.keyup.block).prev(); if ($prev.length !== 0 && $prev[0].tagName === 'FIGURE') { var $newTag = this.utils.replaceToTag($prev, 'p'); this.caret.start($newTag); return; } } } // replace figure to paragraph if (key === this.keyCode.BACKSPACE || key === this.keyCode.DELETE) { if (this.utils.isSelectAll()) { this.focus.start(); this.toolbar.setUnfixed(); return; } // if caret before figure - delete image if (this.keyup.block && this.keydown.block && this.keyup.block.tagName === 'FIGURE' && this.utils.isStartOfElement(this.keydown.block)) { e.preventDefault(); this.selection.save(); $(this.keyup.block).find('figcaption').remove(); $(this.keyup.block).find('img').first().remove(); this.utils.replaceToTag(this.keyup.block, 'p'); var $marker = this.marker.find(); $('html, body').animate({ scrollTop: $marker.position().top + 20 }, 500); this.selection.restore(); return; } // if paragraph does contain only image replace to figure if (this.keyup.block && this.keyup.block.tagName === 'P') { var isContainImage = $(this.keyup.block).find('img').length; var text = $(this.keyup.block).text().replace(/\u200B/g, ''); if (text === '' && isContainImage !== 0) { this.utils.replaceToTag(this.keyup.block, 'figure'); } } // if figure does not contain image - replace to paragraph if (this.keyup.block && this.keyup.block.tagName === 'FIGURE' && $(this.keyup.block).find('img').length === 0) { this.selection.save(); this.utils.replaceToTag(this.keyup.block, 'p'); this.selection.restore(); } } // linkify if (this.linkify.isKey(key)) { this.selection.save(); this.linkify.format(); this.selection.restore(); } } }; }, // =lang 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] : ''; } }; }, // =line line: function() { return { insert: function() { this.buffer.set(); // insert this.insert.html(this.line.getLineHtml()); // find var $hr = this.core.editor().find('#redactor-hr-tmp-id'); $hr.removeAttr('id'); this.core.callback('insertedLine', $hr); return $hr; }, getLineHtml: function() { var html = '<hr id="redactor-hr-tmp-id" />'; if (!this.detect.isFirefox() && this.utils.isEmpty()) { html += '<p>' + this.opts.emptyHtml + '</p>'; } return html; }, // ff only removeOnBackspace: function(e) { if (!this.utils.isCollapsed()) { return; } var $block = $(this.selection.block()); if ($block.length === 0 || !this.utils.isStartOfElement($block)) { return; } // if hr is previous element var $prev = $block.prev(); if ($prev && $prev.length !== 0 && $prev[0].tagName === 'HR') { e.preventDefault(); $prev.remove(); } } }; }, // =link link: function() { return { // public get: function() { return $(this.selection.inlines('a')); }, is: function() { var nodes = this.selection.nodes() ; var $link = $(this.selection.current()).closest('a', this.core.editor()[0]); return ($link.length === 0 || nodes.length > 1) ? false : $link; }, unlink: function(e) { // if call from clickable element if (typeof e !== 'undefined' && e.preventDefault) { e.preventDefault(); } // buffer this.buffer.set(); var links = this.selection.inlines('a'); if (links.length === 0) { return; } var $links = this.link.replaceLinksToText(links); this.observe.closeAllTooltip(); this.core.callback('deletedLink', $links); }, insert: function(link, cleaned) { var $el = this.link.is(); if (cleaned !== true) { link = this.link.buildLinkFromObject($el, link); if (link === false) { return false; } } // buffer this.buffer.set(); // callback link = this.core.callback('beforeInsertingLink', link); if ($el === false) { // insert $el = $('<a />'); $el = this.link.update($el, link); $el = $(this.insert.node($el)); var $parent = $el.parent(); if (this.utils.isRedactorParent($parent) === false) { $el.wrap('<p>'); } // remove unlink wrapper if ($parent.hasClass('redactor-unlink')) { $parent.replaceWith(function(){ return $(this).contents(); }); } this.caret.after($el); this.core.callback('insertedLink', $el); } else { // update $el = this.link.update($el, link); this.caret.after($el); } return $el; }, update: function($el, link) { $el.text(link.text); $el.attr('href', link.url); this.link.target($el, link.target); return $el; }, target: function($el, target) { return (target) ? $el.attr('target', '_blank') : $el.removeAttr('target'); }, show: function(e) { // if call from clickable element if (typeof e !== 'undefined' && e.preventDefault) { e.preventDefault(); } // close tooltip this.observe.closeAllTooltip(); // is link var $el = this.link.is(); // build modal this.link.buildModal($el); // build link var link = this.link.buildLinkFromElement($el); // if link cut & paste inside editor browser added self host to a link link.url = this.link.removeSelfHostFromUrl(link.url); // new tab target if (this.opts.linkNewTab && !$el) { link.target = true; } // set modal values this.link.setModalValues(link); // show modal this.modal.show(); // focus if (this.detect.isDesktop()) { $('#redactor-link-url').focus(); } }, // private setModalValues: function(link) { $('#redactor-link-blank').prop('checked', link.target); $('#redactor-link-url').val(link.url); $('#redactor-link-url-text').val(link.text); }, buildModal: function($el) { this.modal.load('link', this.lang.get(($el === false) ? 'link-insert' : 'link-edit'), 600); // button insert var $btn = this.modal.getActionButton(); $btn.text(this.lang.get(($el === false) ? 'insert' : 'save')).on('click', $.proxy(this.link.callback, this)); }, callback: function() { // build link var link = this.link.buildLinkFromModal(); if (link === false) { return false; } // close this.modal.close(); // insert or update this.link.insert(link, true); }, cleanUrl: function(url) { return (typeof url === 'undefined') ? '' : $.trim(url.replace(/[^\W\w\D\d+&\'@#/%?=~_|!:,.;\(\)]/gi, '')); }, cleanText: function(text) { return (typeof text === 'undefined') ? '' : $.trim(text.replace(/(<([^>]+)>)/gi, '')); }, getText: function(link) { return (link.text === '' && link.url !== '') ? this.link.truncateUrl(link.url.replace(/<|>/g, '')) : link.text; }, isUrl: function(url) { var pattern = '((xn--)?[\\W\\w\\D\\d]+(-[\\W\\w\\D\\d]+)*\\.)+[\\W\\w]{2,}'; var re1 = new RegExp('^(http|ftp|https)://' + pattern, 'i'); var re2 = new RegExp('^' + pattern, 'i'); var re3 = new RegExp('\.(html|php)$', 'i'); var re4 = new RegExp('^/', 'i'); var re5 = new RegExp('^tel:(.*?)', 'i'); // add protocol if (url.search(re1) === -1 && url.search(re2) !== -1 && url.search(re3) === -1 && url.substring(0, 1) !== '/') { url = 'http://' + url; } if (url.search(re1) !== -1 || url.search(re3) !== -1 || url.search(re4) !== -1 || url.search(re5) !== -1) { return url; } return false; }, isMailto: function(url) { return (url.search('@') !== -1 && /(http|ftp|https):\/\//i.test(url) === false); }, isEmpty: function(link) { return (link.url === '' || (link.text === '' && link.url === '')); }, truncateUrl: function(url) { return (url.length > this.opts.linkSize) ? url.substring(0, this.opts.linkSize) + '...' : url; }, parse: function(link) { // mailto if (this.link.isMailto(link.url)) { link.url = 'mailto:' + link.url.replace('mailto:', ''); } // url else if (link.url.search('#') !== 0) { if (this.opts.linkValidation) { link.url = this.link.isUrl(link.url); } } // empty url or text or isn't url return (this.link.isEmpty(link) || link.url === false) ? false : link; }, buildLinkFromModal: function() { var link = {}; // url link.url = this.link.cleanUrl($('#redactor-link-url').val()); // text link.text = this.link.cleanText($('#redactor-link-url-text').val()); link.text = this.link.getText(link); // target link.target = ($('#redactor-link-blank').prop('checked')) ? true : false; // parse return this.link.parse(link); }, buildLinkFromObject: function($el, link) { // url link.url = this.link.cleanUrl(link.url); // text link.text = (typeof link.text === 'undefined' && this.selection.is()) ? this.selection.text() : this.link.cleanText(link.text); link.text = this.link.getText(link); // target link.target = ($el === false) ? link.target : this.link.buildTarget($el); // parse return this.link.parse(link); }, buildLinkFromElement: function($el) { var link = { url: '', text: (this.selection.is()) ? this.selection.text() : '', target: false }; if ($el !== false) { link.url = $el.attr('href'); link.text = $el.text(); link.target = this.link.buildTarget($el); } return link; }, buildTarget: function($el) { return (typeof $el.attr('target') !== 'undefined' && $el.attr('target') === '_blank') ? true : false; }, removeSelfHostFromUrl: function(url) { var href = self.location.href.replace('#', '').replace(/\/$/i, ''); return url.replace(/^\/\#/, '#').replace(href, '').replace('mailto:', ''); }, replaceLinksToText: function(links) { var $first; var $links = $.each(links, function(i,s) { var $el = $(s); var $unlinked = $('<span class="redactor-unlink" />').append($el.contents()); $el.replaceWith($unlinked); if (i === 0) { $first = $unlinked; } return $el; }); // set caret after unlinked node if (links.length === 1 && this.selection.isCollapsed()) { this.caret.after($first); } return $links; } }; }, // =linkify linkify: function() { return { isKey: function(key) { return key === this.keyCode.ENTER || key === this.keyCode.SPACE; }, isLink: function(node) { return (node.nodeValue.match(this.opts.regexps.linkyoutube) || node.nodeValue.match(this.opts.regexps.linkvimeo) || node.nodeValue.match(this.opts.regexps.linkimage) || node.nodeValue.match(this.opts.regexps.url)); }, isFiltered: function(i, node) { return node.nodeType === 3 && $.trim(node.nodeValue) !== "" && !$(node).parent().is("pre") && (this.linkify.isLink(node)); }, handler: function(i, node) { var $el = $(node); var text = $el.text(); var html = text; if (html.match(this.opts.regexps.linkyoutube) || html.match(this.opts.regexps.linkvimeo)) { html = this.linkify.convertVideoLinks(html); } else if (html.match(this.opts.regexps.linkimage)) { html = this.linkify.convertImages(html); } else { html = this.linkify.convertLinks(html); } $el.before(text.replace(text, html)).remove(); }, format: function() { if (!this.opts.linkify || this.utils.isCurrentOrParent('pre')) { return; } this.core.editor().find(":not(iframe,img,a,pre,code,.redactor-unlink)").addBack().contents().filter($.proxy(this.linkify.isFiltered, this)).each($.proxy(this.linkify.handler, this)); // collect var $el; var $objects = this.core.editor().find('.redactor-linkify-object').each($.proxy(function(i,s) { $el = $(s); $el.removeClass('redactor-linkify-object'); if ($el.attr('class') === '') { $el.removeAttr('class'); } if (s.tagName === 'DIV') // video container { this.linkify.breakBlockTag($el, 'video'); } else if (s.tagName === 'IMG') // image { this.linkify.breakBlockTag($el, 'image'); } else if (s.tagName === 'A') { this.core.callback('insertedLink', $el); } return $el; }, this)); // callback setTimeout($.proxy(function() { this.code.sync(); this.core.callback('linkify', $objects); }, this), 100); }, breakBlockTag: function($el, type) { var breaked = this.utils.breakBlockTag(); if (breaked === false) { return; } var $newBlock = $el; if (type === 'image') { $newBlock = $('<figure />').append($el); } if (breaked.type === 'start') { breaked.$block.before($newBlock); } else { breaked.$block.after($newBlock); } if (type === 'image') { this.caret.after($newBlock); } }, convertVideoLinks: function(html) { var iframeStart = '<div class="' + this.opts.videoContainerClass + ' redactor-linkify-object"><iframe class="redactor-linkify-object" width="500" height="281" src="'; var iframeEnd = '" frameborder="0" allowfullscreen></iframe></div>'; if (html.match(this.opts.regexps.linkyoutube)) { html = html.replace(this.opts.regexps.linkyoutube, iframeStart + '//www.youtube.com/embed/$1' + iframeEnd); } if (html.match(this.opts.regexps.linkvimeo)) { html = html.replace(this.opts.regexps.linkvimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd); } return html; }, convertImages: function(html) { var matches = html.match(this.opts.regexps.linkimage); if (!matches) { return html; } return html.replace(html, '<img src="' + matches + '" class="redactor-linkify-object" />'); }, convertLinks: function(html) { var matches = html.match(this.opts.regexps.url); if (!matches) { return html; } matches = $.grep(matches, function(v, k) { return $.inArray(v, matches) === k; }); var length = matches.length; for (var i = 0; i < length; i++) { var href = matches[i], text = href; var linkProtocol = (href.match(/(https?|ftp):\/\//i) !== null) ? '' : 'http://'; if (text.length > this.opts.linkSize) { text = text.substring(0, this.opts.linkSize) + '...'; } if (text.search('%') === -1) { text = decodeURIComponent(text); } var regexB = "\\b"; if ($.inArray(href.slice(-1), ["/", "&", "="]) !== -1) { regexB = ""; } // escaping url var regexp = new RegExp('(' + href.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") + regexB + ')', 'g'); // target var target = ''; if (this.opts.pasteLinkTarget !== false) { target = ' target="' + this.opts.pasteLinkTarget + '"'; } html = html.replace(regexp, '<a href="' + linkProtocol + $.trim(href) + '"' + target + ' class="redactor-linkify-object">' + $.trim(text) + '</a>'); } return html; } }; }, // =list list: function() { return { toggle: function(cmd) { if (this.utils.inBlocks(['table', 'td', 'th', 'tr'])) { return; } var tag = (cmd === 'orderedlist' || cmd === 'ol') ? 'OL' : 'UL'; cmd = (tag === 'OL') ? 'orderedlist' : 'unorderedlist' var $list = $(this.selection.current()).parentsUntil('.redactor-in', 'ul, ol').first(); this.placeholder.hide(); this.buffer.set(); if ($list.length !== 0 && $list[0].tagName === tag && this.utils.isRedactorParent($list)) { this.selection.save(); // remove list $list.find('ul, ol').each(function() { var parent = $(this).closest('li'); $(this).find('li').each(function() { $(parent).after(this); }); }); $list.find('ul, ol').remove(); $list.find('li').each(function() { return $(this).replaceWith(function() { return $('<p />').append($(this).contents()); }); }); $list.replaceWith(function() { return $(this).contents(); }); this.selection.restore(); return; } this.selection.save(); if ($list.length !== 0 && $list[0].tagName !== tag) { $list.each($.proxy(function(i,s) { this.utils.replaceToTag(s, tag); }, this)); } else { document.execCommand('insert' + cmd); } this.selection.restore(); var $insertedList = this.list.get(); if (!$insertedList) { if (!this.selection.block()) { document.execCommand('formatblock', false, 'p'); } return; } // clear span $insertedList.find('span').replaceWith(function() { return $(this).contents(); }); // remove style $insertedList.find(this.opts.inlineTags.join(',')).each(function() { $(this).removeAttr('style'); }); // remove block-element list wrapper var $listParent = $insertedList.parent(); if (this.utils.isRedactorParent($listParent) && $listParent[0].tagName !== 'LI' && this.utils.isBlock($listParent)) { this.selection.save(); $listParent.replaceWith($listParent.contents()); this.selection.restore(); } }, get: function() { var current = this.selection.current(); var $list = $(current).closest('ul, ol', this.core.editor()[0]); return ($list.length === 0) ? false : $list; }, combineAfterAndBefore: function(block) { var $prev = $(block).prev(); var $next = $(block).next(); var isEmptyBlock = (block && block.tagName === 'P' && (block.innerHTML === '<br>' || block.innerHTML === '')); var isBlockWrapped = ($prev.closest('ol, ul', this.core.editor()[0]).length === 1 && $next.closest('ol, ul', this.core.editor()[0]).length === 1); if (isEmptyBlock && isBlockWrapped) { $prev.children('li').last().append(this.marker.get()); $prev.append($next.contents()); this.selection.restore(); return true; } return false; } }; }, // =marker marker: function() { return { // public get: function(num) { num = (typeof num === 'undefined') ? 1 : num; var marker = document.createElement('span'); marker.id = 'selection-marker-' + num; marker.className = 'redactor-selection-marker'; marker.innerHTML = this.opts.invisibleSpace; return marker; }, html: function(num) { return this.utils.getOuterHtml(this.marker.get(num)); }, find: function(num) { num = (typeof num === 'undefined') ? 1 : num; return this.core.editor().find('span#selection-marker-' + num); }, insert: function() { var sel = this.selection.get(); var range = this.selection.range(sel); this.marker.insertNode(range, this.marker.get(1), true); if (range && range.collapsed === false) { this.marker.insertNode(range, this.marker.get(2), false); } }, remove: function() { this.core.editor().find('.redactor-selection-marker').each(this.marker.iterateRemove); }, // private insertNode: function(range, node, collapse) { var parent = this.selection.parent(); if (range === null || $(parent).closest('.redactor-in').length === 0) { return; } range = range.cloneRange(); try { range.collapse(collapse); range.insertNode(node); } catch (e) { this.focus.start(); } }, iterateRemove: function(i, el) { var $el = $(el); var text = $el.text().replace(/\u200B/g, ''); var parent = $el.parent()[0]; if (text === '') $el.remove(); else $el.replaceWith(function() { return $(this).contents(); }); // if (parent && parent.normalize) parent.normalize(); } }; }, // =modal modal: function() { return { callbacks: {}, templates: function() { this.opts.modal = { 'image-edit': String() + '<div class="redactor-modal-tab redactor-group" data-title="General">' + '<div id="redactor-image-preview" class="redactor-modal-tab-side">' + '</div>' + '<div class="redactor-modal-tab-area">' + '<section>' + '<label>' + this.lang.get('title') + '</label>' + '<input type="text" id="redactor-image-title" />' + '</section>' + '<section>' + '<label>' + this.lang.get('caption') + '</label>' + '<input type="text" id="redactor-image-caption" aria-label="' + this.lang.get('caption') + '" />' + '</section>' + '<section>' + '<label>' + this.lang.get('link') + '</label>' + '<input type="text" id="redactor-image-link" aria-label="' + this.lang.get('link') + '" />' + '</section>' + '<section>' + '<label class="redactor-image-position-option">' + this.lang.get('image-position') + '</label>' + '<select class="redactor-image-position-option" id="redactor-image-align" aria-label="' + this.lang.get('image-position') + '">' + '<option value="none">' + this.lang.get('none') + '</option>' + '<option value="left">' + this.lang.get('left') + '</option>' + '<option value="center">' + this.lang.get('center') + '</option>' + '<option value="right">' + this.lang.get('right') + '</option>' + '</select>' + '</section>' + '<section>' + '<label class="checkbox"><input type="checkbox" id="redactor-image-link-blank" aria-label="' + this.lang.get('link-in-new-tab') + '"> ' + this.lang.get('link-in-new-tab') + '</label>' + '</section>' + '<section>' + '<button id="redactor-modal-button-action">' + this.lang.get('insert') + '</button>' + '<button id="redactor-modal-button-cancel">' + this.lang.get('cancel') + '</button>' + '<button id="redactor-modal-button-delete" class="redactor-modal-button-offset">' + this.lang.get('delete') + '</button>' + '</section>' + '</div>' + '</div>', 'image': String() + '<div class="redactor-modal-tab" data-title="Upload">' + '<section>' + '<div id="redactor-modal-image-droparea"></div>' + '</section>' + '</div>', 'file': String() + '<div class="redactor-modal-tab" data-title="Upload">' + '<section>' + '<label>' + this.lang.get('filename') + ' <span class="desc">(' + this.lang.get('optional') + ')</span></label>' + '<input type="text" id="redactor-filename" aria-label="' + this.lang.get('filename') + '" /><br><br>' + '</section>' + '<section>' + '<div id="redactor-modal-file-upload"></div>' + '</section>' + '</div>', 'link': String() + '<div class="redactor-modal-tab" data-title="General">' + '<section>' + '<label>URL</label>' + '<input type="url" id="redactor-link-url" aria-label="URL" />' + '</section>' + '<section>' + '<label>' + this.lang.get('text') + '</label>' + '<input type="text" id="redactor-link-url-text" aria-label="' + this.lang.get('text') + '" />' + '</section>' + '<section>' + '<label class="checkbox"><input type="checkbox" id="redactor-link-blank"> ' + this.lang.get('link-in-new-tab') + '</label>' + '</section>' + '<section>' + '<button id="redactor-modal-button-action">' + this.lang.get('insert') + '</button>' + '<button id="redactor-modal-button-cancel">' + this.lang.get('cancel') + '</button>' + '</section>' + '</div>' }; $.extend(this.opts, this.opts.modal); }, addCallback: function(name, callback) { this.modal.callbacks[name] = callback; }, addTemplate: function(name, template) { this.opts.modal[name] = template; }, getTemplate: function(name) { return this.opts.modal[name]; }, getModal: function() { return this.$modalBody; }, getActionButton: function() { return this.$modalBody.find('#redactor-modal-button-action'); }, getCancelButton: function() { return this.$modalBody.find('#redactor-modal-button-cancel'); }, getDeleteButton: function() { return this.$modalBody.find('#redactor-modal-button-delete'); }, load: function(templateName, title, width) { if (typeof this.$modalBox !== 'undefined' && this.$modalBox.hasClass('open')) { return; } this.modal.templateName = templateName; this.modal.width = width; this.modal.build(); this.modal.enableEvents(); this.modal.setTitle(title); this.modal.setDraggable(); this.modal.setContent(); // callbacks if (typeof this.modal.callbacks[templateName] !== 'undefined') { this.modal.callbacks[templateName].call(this); } }, show: function() { if (!this.detect.isDesktop()) { document.activeElement.blur(); } this.selection.save(); this.modal.buildTabber(); if (this.detect.isMobile()) { this.modal.width = '96%'; } // resize setTimeout($.proxy(this.modal.buildWidth, this), 0); $(window).on('resize.redactor-modal', $.proxy(this.modal.buildWidth, this)); this.$modalOverlay.redactorAnimation('fadeIn', { duration: 0.25 }); this.$modalBox.addClass('open').show(); this.$modal.redactorAnimation('fadeIn', { timing: 'cubic-bezier(0.175, 0.885, 0.320, 1.105)' }, $.proxy(function() { this.utils.saveScroll(); this.utils.disableBodyScroll(); // modal shown callback this.core.callback('modalOpened', this.modal.templateName, this.$modal); // fix bootstrap modal focus $(document).off('focusin.modal'); // enter var $elements = this.$modal.find('input[type=text],input[type=url],input[type=email]'); $elements.on('keydown.redactor-modal', $.proxy(this.modal.setEnter, this)); }, this) ); }, buildWidth: function() { var windowHeight = $(window).height(); var windowWidth = $(window).width(); var number = (typeof this.modal.width === 'number'); if (!number && this.modal.width.match(/%$/)) { this.$modal.css({ 'width': this.modal.width, 'margin-bottom': '16px' }); } else if (parseInt(this.modal.width) > windowWidth) { this.$modal.css({ 'width': '96%', 'margin-bottom': '2%' }); } else { if (number) { this.modal.width += 'px'; } this.$modal.css({ 'width': this.modal.width, 'margin-bottom': '16px' }); } // margin top var height = this.$modal.outerHeight(); var top = (windowHeight/2 - height/2) + 'px'; if (this.detect.isMobile()) { top = '2%'; } else if (height > windowHeight) { top = '16px'; } this.$modal.css('margin-top', top); }, buildTabber: function() { this.modal.tabs = this.$modal.find('.redactor-modal-tab'); if (this.modal.tabs.length < 2) { return; } this.modal.$tabsBox = $('<div id="redactor-modal-tabber" />'); $.each(this.modal.tabs, $.proxy(function(i,s) { var a = $('<a href="#" rel="' + i + '" />').text($(s).attr('data-title')); a.on('click', $.proxy(this.modal.showTab, this)); if (i === 0) { a.addClass('active'); } this.modal.$tabsBox.append(a); }, this)); this.$modalBody.prepend(this.modal.$tabsBox); }, showTab: function(e) { e.preventDefault(); var $el = $(e.target); var index = $el.attr('rel'); this.modal.tabs.hide(); this.modal.tabs.eq(index).show(); $('#redactor-modal-tabber').find('a').removeClass('active'); $el.addClass('active'); return false; }, setTitle: function(title) { this.$modalHeader.html(title); }, setContent: function() { this.$modalBody.html(this.modal.getTemplate(this.modal.templateName)); this.modal.getCancelButton().on('mousedown', $.proxy(this.modal.close, this)); }, setDraggable: function() { if (typeof $.fn.draggable === 'undefined') { return; } this.$modal.draggable({ handle: this.$modalHeader }); this.$modalHeader.css('cursor', 'move'); }, setEnter: function(e) { if (e.which !== 13) { return; } e.preventDefault(); this.modal.getActionButton().click(); }, build: function() { this.modal.buildOverlay(); this.$modalBox = $('<div id="redactor-modal-box"/>').hide(); this.$modal = $('<div id="redactor-modal" role="dialog" />'); this.$modalHeader = $('<div id="redactor-modal-header" />'); this.$modalClose = $('<button type="button" id="redactor-modal-close" aria-label="' + this.lang.get('close') + '" />').html('×'); this.$modalBody = $('<div id="redactor-modal-body" />'); this.$modal.append(this.$modalHeader); this.$modal.append(this.$modalBody); this.$modal.append(this.$modalClose); this.$modalBox.append(this.$modal); this.$modalBox.appendTo(document.body); }, buildOverlay: function() { this.$modalOverlay = $('<div id="redactor-modal-overlay">').hide(); $('body').prepend(this.$modalOverlay); }, enableEvents: function() { this.$modalClose.on('mousedown.redactor-modal', $.proxy(this.modal.close, this)); $(document).on('keyup.redactor-modal', $.proxy(this.modal.closeHandler, this)); this.core.editor().on('keyup.redactor-modal', $.proxy(this.modal.closeHandler, this)); this.$modalBox.on('click.redactor-modal', $.proxy(this.modal.close, this)); }, disableEvents: function() { this.$modalClose.off('mousedown.redactor-modal'); $(document).off('keyup.redactor-modal'); this.core.editor().off('keyup.redactor-modal'); this.$modalBox.off('click§.redactor-modal'); $(window).off('resize.redactor-modal'); }, closeHandler: function(e) { if (e.which !== this.keyCode.ESC) { return; } this.modal.close(false); }, close: function(e) { if (e) { if ($(e.target).attr('id') !== 'redactor-modal-button-cancel' && e.target !== this.$modalClose[0] && e.target !== this.$modalBox[0]) { return; } e.preventDefault(); } if (!this.$modalBox) { return; } // restore selection this.selection.restore(); this.modal.disableEvents(); this.utils.enableBodyScroll(); this.utils.restoreScroll(); this.$modalOverlay.redactorAnimation('fadeOut', { duration: 0.4 }, $.proxy(function() { this.$modalOverlay.remove(); }, this)); this.$modal.redactorAnimation('fadeOut', { duration: 0.3, timing: 'cubic-bezier(0.175, 0.885, 0.320, 1.175)' }, $.proxy(function() { if (typeof this.$modalBox !== 'undefined') { this.$modalBox.remove(); this.$modalBox = undefined; } this.core.callback('modalClosed', this.modal.templateName); }, this)); } }; }, // =observe observe: function() { return { load: function() { if (typeof this.opts.destroyed !== 'undefined') { return; } this.observe.links(); this.observe.images(); }, isCurrent: function($el, $current) { if (typeof $current === 'undefined') { $current = $(this.selection.current()); } return $current.is($el) || $current.parents($el).length > 0; }, toolbar: function() { this.observe.buttons(); this.observe.dropdowns(); }, buttons: function(e, btnName) { var current = this.selection.current(); var parent = this.selection.parent(); if (e !== false) this.button.setInactiveAll(); else this.button.setInactiveAll(btnName); if (e === false && btnName !== 'html') { if ($.inArray(btnName, this.opts.activeButtons) !== -1) this.button.toggleActive(btnName); return; } if (!this.utils.isRedactorParent(current)) { return; } // disable line if (this.core.editor().css('display') !== 'none') { if (this.utils.isCurrentOrParentHeader() || this.utils.isCurrentOrParent(['table', 'pre', 'blockquote', 'li'])) this.button.disable('horizontalrule'); else this.button.enable('horizontalrule'); } $.each(this.opts.activeButtonsStates, $.proxy(function(key, value) { var parentEl = $(parent).closest(key, this.$editor[0]); var currentEl = $(current).closest(key, this.$editor[0]); if (parentEl.length !== 0 && !this.utils.isRedactorParent(parentEl)) { return; } if (!this.utils.isRedactorParent(currentEl)) { return; } if (parentEl.length !== 0 || currentEl.closest(key, this.$editor[0]).length !== 0) { this.button.setActive(value); } }, this)); }, dropdowns: function() { var finded = $('<div />').html(this.selection.html()).find('a').length; var $current = $(this.selection.current()); var isRedactor = this.utils.isRedactorParent($current); $.each(this.opts.observe.dropdowns, $.proxy(function(key, value) { var observe = value.observe, element = observe.element, $item = value.item, inValues = typeof observe['in'] !== 'undefined' ? observe['in'] : false, outValues = typeof observe.out !== 'undefined' ? observe.out : false; if (($current.closest(element).length > 0 && isRedactor) || (element === 'a' && finded !== 0)) { this.observe.setDropdownProperties($item, inValues, outValues); } else { this.observe.setDropdownProperties($item, outValues, inValues); } }, this)); }, setDropdownProperties: function($item, addProperties, deleteProperties) { if (deleteProperties && typeof deleteProperties.attr !== 'undefined') { this.observe.setDropdownAttr($item, deleteProperties.attr, true); } if (typeof addProperties.attr !== 'undefined') { this.observe.setDropdownAttr($item, addProperties.attr); } if (typeof addProperties.title !== 'undefined') { $item.find('span').text(addProperties.title); } }, setDropdownAttr: function($item, properties, isDelete) { $.each(properties, function(key, value) { if (key === 'class') { if (!isDelete) { $item.addClass(value); } else { $item.removeClass(value); } } else { if (!isDelete) { $item.attr(key, value); } else { $item.removeAttr(key); } } }); }, addDropdown: function($item, btnName, btnObject) { if (typeof btnObject.observe === "undefined") { return; } btnObject.item = $item; this.opts.observe.dropdowns.push(btnObject); }, images: function() { if (this.opts.imageEditable) { this.core.editor().addClass('redactor-layer-img-edit'); this.core.editor().find('img').each($.proxy(function(i, img) { var $img = $(img); // IE fix (when we clicked on an image and then press backspace IE does goes to image's url) $img.closest('a', this.$editor[0]).on('click', function(e) { e.preventDefault(); }); this.image.setEditable($img); }, this)); } }, links: function() { if (this.opts.linkTooltip) { this.core.editor().find('a').each($.proxy(function(i, s) { var $link = $(s); if ($link.data('cached') !== true) { $link.data('cached', true); $link.on('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid, $.proxy(this.observe.showTooltip, this)); } }, this)); } }, getTooltipPosition: function($link) { return $link.offset(); }, showTooltip: function(e) { var $el = $(e.target); if ($el[0].tagName === 'IMG') { return; } if ($el[0].tagName !== 'A') { $el = $el.closest('a', this.$editor[0]); } if ($el[0].tagName !== 'A') { return; } var $link = $el; var pos = this.observe.getTooltipPosition($link); var tooltip = $('<span class="redactor-link-tooltip"></span>'); var href = $link.attr('href'); if (href === undefined) { href = ''; } if (href.length > 24) { href = href.substring(0, 24) + '...'; } var aLink = $('<a href="' + $link.attr('href') + '" target="_blank" />').html(href).addClass('redactor-link-tooltip-action'); var aEdit = $('<a href="#" />').html(this.lang.get('edit')).on('click', $.proxy(this.link.show, this)).addClass('redactor-link-tooltip-action'); var aUnlink = $('<a href="#" />').html(this.lang.get('unlink')).on('click', $.proxy(this.link.unlink, this)).addClass('redactor-link-tooltip-action'); tooltip.append(aLink).append(' | ').append(aEdit).append(' | ').append(aUnlink); var lineHeight = parseInt($link.css('line-height'), 10); var lineClicked = Math.ceil((e.pageY - pos.top)/lineHeight); var top = pos.top + lineClicked * lineHeight; tooltip.css({ top: top + 'px', left: pos.left + 'px' }); $('.redactor-link-tooltip').remove(); $('body').append(tooltip); this.core.editor().on('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid, $.proxy(this.observe.closeTooltip, this)); $(document).on('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid, $.proxy(this.observe.closeTooltip, this)); }, closeAllTooltip: function() { $('.redactor-link-tooltip').remove(); }, closeTooltip: function(e) { e = e.originalEvent || e; var target = e.target; var $parent = $(target).closest('a', this.$editor[0]); if ($parent.length !== 0 && $parent[0].tagName === 'A' && target.tagName !== 'A') { return; } else if ((target.tagName === 'A' && this.utils.isRedactorParent(target)) || $(target).hasClass('redactor-link-tooltip-action')) { return; } this.observe.closeAllTooltip(); this.core.editor().off('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid, $.proxy(this.observe.closeTooltip, this)); $(document).off('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid, $.proxy(this.observe.closeTooltip, this)); } }; }, // =offset offset: function() { return { get: function(node) { var cloned = this.offset.clone(node); if (cloned === false) { return 0; } var div = document.createElement('div'); div.appendChild(cloned.cloneContents()); div.innerHTML = div.innerHTML.replace(/<img(.*?[^>])>$/gi, 'i'); var text = $.trim($(div).text()).replace(/[\t\n\r\n]/g, '').replace(/\u200B/g, ''); return text.length; }, clone: function(node) { var sel = this.selection.get(); var range = this.selection.range(sel); if (range === null && typeof node === 'undefined') { return false; } node = (typeof node === 'undefined') ? this.$editor : node; if (node === false) { return false; } node = node[0] || node; var cloned = range.cloneRange(); cloned.selectNodeContents(node); cloned.setEnd(range.endContainer, range.endOffset); return cloned; }, set: function(start, end) { end = (typeof end === 'undefined') ? start : end; if (!this.focus.is()) { this.focus.start(); } var sel = this.selection.get(); var range = this.selection.range(sel); var node, offset = 0; var walker = document.createTreeWalker(this.$editor[0], NodeFilter.SHOW_TEXT, null, null); while ((node = walker.nextNode()) !== null) { offset += node.nodeValue.length; if (offset > start) { range.setStart(node, node.nodeValue.length + start - offset); start = Infinity; } if (offset >= end) { range.setEnd(node, node.nodeValue.length + end - offset); break; } } range.collapse(false); this.selection.update(sel, range); } }; }, // =paragraphize paragraphize: function() { return { load: function(html) { if (this.opts.paragraphize === false || this.opts.type === 'inline' || this.opts.type === 'pre') { return html; } if (html === '' || html === '<p></p>') { return this.opts.emptyHtml; } html = html + "\n"; this.paragraphize.safes = []; this.paragraphize.z = 0; // before html = html.replace(/(<br\s?\/?>){1,}\n?<\/blockquote>/gi, '</blockquote>'); html = html.replace(/<\/pre>/gi, "</pre>\n\n"); html = html.replace(/<p>\s<br><\/p>/gi, '<p></p>'); html = this.paragraphize.getSafes(html); html = html.replace('<br>', "\n"); html = this.paragraphize.convert(html); html = this.paragraphize.clear(html); html = this.paragraphize.restoreSafes(html); // after html = html.replace(new RegExp('<br\\s?/?>\n?<(' + this.opts.paragraphizeBlocks.join('|') + ')(.*?[^>])>', 'gi'), '<p><br /></p>\n<$1$2>'); return $.trim(html); }, getSafes: function(html) { var $div = $('<div />').append(html); // remove paragraphs in blockquotes $div.find('blockquote p').replaceWith(function() { return $(this).append('<br />').contents(); }); $div.find(this.opts.paragraphizeBlocks.join(', ')).each($.proxy(function(i,s) { this.paragraphize.z++; this.paragraphize.safes[this.paragraphize.z] = s.outerHTML; return $(s).replaceWith("\n#####replace" + this.paragraphize.z + "#####\n\n"); }, this)); // deal with redactor selection markers $div.find('span.redactor-selection-marker').each($.proxy(function(i,s) { this.paragraphize.z++; this.paragraphize.safes[this.paragraphize.z] = s.outerHTML; return $(s).replaceWith("\n#####replace" + this.paragraphize.z + "#####\n\n"); }, this)); return $div.html(); }, restoreSafes: function(html) { $.each(this.paragraphize.safes, function(i,s) { s = (typeof s !== 'undefined') ? s.replace(/\$/g, '$') : s; html = html.replace('#####replace' + i + '#####', s); }); return html; }, convert: function(html) { html = html.replace(/\r\n/g, "xparagraphmarkerz"); html = html.replace(/\n/g, "xparagraphmarkerz"); html = html.replace(/\r/g, "xparagraphmarkerz"); var re1 = /\s+/g; html = html.replace(re1, " "); html = $.trim(html); var re2 = /xparagraphmarkerzxparagraphmarkerz/gi; html = html.replace(re2, "</p><p>"); var re3 = /xparagraphmarkerz/gi; html = html.replace(re3, "<br>"); html = '<p>' + html + '</p>'; html = html.replace("<p></p>", ""); html = html.replace("\r\n\r\n", ""); html = html.replace(/<\/p><p>/g, "</p>\r\n\r\n<p>"); html = html.replace(new RegExp("<br\\s?/?></p>", "g"), "</p>"); html = html.replace(new RegExp("<p><br\\s?/?>", "g"), "<p>"); html = html.replace(new RegExp("<p><br\\s?/?>", "g"), "<p>"); html = html.replace(new RegExp("<br\\s?/?></p>", "g"), "</p>"); html = html.replace(/<p> <\/p>/gi, ""); html = html.replace(/<p>\s?<br> <\/p>/gi, ''); html = html.replace(/<p>\s?<br>/gi, '<p>'); return html; }, clear: function(html) { html = html.replace(/<p>(.*?)#####replace(.*?)#####\s?<\/p>/gi, '<p>$1</p>#####replace$2#####'); html = html.replace(/(<br\s?\/?>){2,}<\/p>/gi, '</p>'); html = html.replace(new RegExp('</blockquote></p>', 'gi'), '</blockquote>'); html = html.replace(new RegExp('<p></blockquote>', 'gi'), '</blockquote>'); html = html.replace(new RegExp('<p><blockquote>', 'gi'), '<blockquote>'); html = html.replace(new RegExp('<blockquote></p>', 'gi'), '<blockquote>'); html = html.replace(new RegExp('<p><p ', 'gi'), '<p '); html = html.replace(new RegExp('<p><p>', 'gi'), '<p>'); html = html.replace(new RegExp('</p></p>', 'gi'), '</p>'); html = html.replace(new RegExp('<p>\\s?</p>', 'gi'), ''); html = html.replace(new RegExp("\n</p>", 'gi'), '</p>'); html = html.replace(new RegExp('<p>\t?\t?\n?<p>', 'gi'), '<p>'); html = html.replace(new RegExp('<p>\t*</p>', 'gi'), ''); return html; } }; }, // =paste paste: function() { return { init: function(e) { this.rtePaste = true; var pre = (this.opts.type === 'pre' || this.utils.isCurrentOrParent('pre')) ? true : false; // clipboard event if (this.detect.isDesktop()) { if (!this.paste.pre && this.opts.clipboardImageUpload && this.opts.imageUpload && this.paste.detectClipboardUpload(e)) { if (this.detect.isIe()) { setTimeout($.proxy(this.paste.clipboardUpload, this), 100); } return; } } this.utils.saveScroll(); this.selection.save(); this.paste.createPasteBox(pre); $(window).on('scroll.redactor-freeze', $.proxy(function() { $(window).scrollTop(this.saveBodyScroll); }, this)); setTimeout($.proxy(function() { var html = this.paste.getPasteBoxCode(pre); // buffer this.buffer.set(); this.selection.restore(); this.utils.restoreScroll(); // paste info var data = this.clean.getCurrentType(html); // clean html = this.clean.onPaste(html, data); // callback var returned = this.core.callback('paste', html); html = (typeof returned === 'undefined') ? html : returned; this.paste.insert(html, data); this.rtePaste = false; // clean pre breaklines if (pre) { this.clean.cleanPre(); } $(window).off('scroll.redactor-freeze'); }, this), 1); }, getPasteBoxCode: function(pre) { var html = (pre) ? this.$pasteBox.val() : this.$pasteBox.html(); this.$pasteBox.remove(); return html; }, createPasteBox: function(pre) { var css = { position: 'fixed', width: '1px', top: 0, left: '-9999px' }; this.$pasteBox = (pre) ? $('<textarea>').css(css) : $('<div>').attr('contenteditable', 'true').css(css); this.paste.appendPasteBox(); this.$pasteBox.focus(); }, appendPasteBox: function() { if (this.detect.isIe()) { this.core.box().append(this.$pasteBox); } else { // bootstrap modal var $visibleModals = $('.modal-body:visible'); if ($visibleModals.length > 0) { $visibleModals.append(this.$pasteBox); } else { $('body').prepend(this.$pasteBox); } } }, detectClipboardUpload: function(e) { e = e.originalEvent || e; var clipboard = e.clipboardData; if (this.detect.isIe() || this.detect.isFirefox()) { return false; } // prevent safari fake url var types = clipboard.types; if (types.indexOf('public.tiff') !== -1) { e.preventDefault(); return false; } if (!clipboard.items || !clipboard.items.length) { return; } var file = clipboard.items[0].getAsFile(); if (file === null) { return false; } var reader = new FileReader(); reader.readAsDataURL(file); reader.onload = $.proxy(this.paste.insertFromClipboard, this); return true; }, clipboardUpload: function() { var imgs = this.$editor.find('img'); $.each(imgs, $.proxy(function(i,s) { if (s.src.search(/^data\:image/i) === -1) { return; } var formData = !!window.FormData ? new FormData() : null; if (!window.FormData) { return; } this.upload.direct = true; this.upload.type = 'image'; this.upload.url = this.opts.imageUpload; this.upload.callback = $.proxy(function(data) { if (this.detect.isIe()) { $(s).wrap($('<figure />')); } else { var $parent = $(s).parent(); this.utils.replaceToTag($parent, 'figure'); } s.src = data.url; this.core.callback('imageUpload', $(s), data); }, this); var blob = this.utils.dataURItoBlob(s.src); formData.append('clipboard', 1); formData.append(this.opts.imageUploadParam, blob); this.progress.show(); this.upload.send(formData, false); this.code.sync(); this.rtePaste = false; }, this)); }, insertFromClipboard: function(e) { var formData = !!window.FormData ? new FormData() : null; if (!window.FormData) { return; } this.upload.direct = true; this.upload.type = 'image'; this.upload.url = this.opts.imageUpload; this.upload.callback = this.image.insert; var blob = this.utils.dataURItoBlob(e.target.result); formData.append('clipboard', 1); formData.append(this.opts.imageUploadParam, blob); this.progress.show(); this.upload.send(formData, e); this.rtePaste = false; }, insert: function(html, data) { if (data.pre) { this.insert.raw(html); } else if (data.text) { this.insert.text(html); } else { this.insert.html(html, data); } // Firefox Clipboard Observe if (this.detect.isFirefox() && this.opts.imageUpload && this.opts.clipboardImageUpload) { setTimeout($.proxy(this.paste.clipboardUpload, this), 100); } } }; }, // =placeholder placeholder: function() { return { // public enable: function() { setTimeout($.proxy(function() { return (this.placeholder.isEditorEmpty()) ? this.placeholder.show() : this.placeholder.hide(); }, this), 5); }, show: function() { this.core.editor().addClass('redactor-placeholder'); }, update: function(text) { this.opts.placeholder = text; this.core.editor().attr('placeholder', text); }, hide: function() { this.core.editor().removeClass('redactor-placeholder'); }, is: function() { return this.core.editor().hasClass('redactor-placeholder'); }, // private init: function() { if (!this.placeholder.enabled()) { return; } if (!this.utils.isEditorRelative()) { this.utils.setEditorRelative(); } this.placeholder.build(); this.placeholder.buildPosition(); this.placeholder.enable(); this.placeholder.enableEvents(); }, enabled: function() { return (this.opts.placeholder) ? this.core.element().attr('placeholder', this.opts.placeholder) : this.placeholder.isAttr(); }, enableEvents: function() { this.core.editor().on('keydown.redactor-placeholder.' + this.uuid, $.proxy(this.placeholder.enable, this)); }, disableEvents: function() { this.core.editor().off('.redactor-placeholder.' + this.uuid); }, build: function() { this.core.editor().attr('placeholder', this.core.element().attr('placeholder')); }, buildPosition: function() { var $style = $('<style />'); $style.addClass('redactor-placeholder-style-tag'); $style.html('#' + this.core.id() + '.redactor-placeholder::after ' + this.placeholder.getPosition()); $('head').append($style); }, getPosition: function() { return '{ top: ' + this.core.editor().css('padding-top') + '; left: ' + this.core.editor().css('padding-left') + '; }'; }, isEditorEmpty: function() { var html = $.trim(this.core.editor().html()).replace(/[\t\n]/g, ''); var states = ['', '<p></p>', '<p><br></p>', this.opts.emptyHtmlRendered]; return ($.inArray(html, states) !== -1); }, isAttr: function() { return (typeof this.core.element().attr('placeholder') !== 'undefined' && this.core.element().attr('placeholder') !== ''); }, destroy: function() { this.core.editor().removeAttr('placeholder'); this.placeholder.hide(); this.placeholder.disableEvents(); $('.redactor-placeholder-style-tag').remove(); } }; }, // =progress progress: function() { return { $box: null, $bar: null, target: document.body, // or id selector // public show: function() { if (!this.progress.is()) { this.progress.build(); this.progress.$box.redactorAnimation('fadeIn'); } else { this.progress.$box.show(); } }, hide: function() { if (this.progress.is()) { this.progress.$box.redactorAnimation('fadeOut', { duration: 0.35 }, $.proxy(this.progress.destroy, this)); } }, update: function(value) { this.progress.show(); this.progress.$bar.css('width', value + '%'); }, is: function() { return (this.progress.$box === null) ? false : true; }, // private build: function() { this.progress.$bar = $('<span />'); this.progress.$box = $('<div id="redactor-progress" />'); this.progress.$box.append(this.progress.$bar); $(this.progress.target).append(this.progress.$box); }, destroy: function() { if (this.progress.is()) { this.progress.$box.remove(); } this.progress.$box = null; this.progress.$bar = null; } }; }, // =selection selection: function() { return { get: function() { if (window.getSelection) { return window.getSelection(); } else if (document.selection && document.selection.type !== "Control") { return document.selection; } return null; }, range: function(sel) { if (typeof sel === 'undefined') { sel = this.selection.get(); } if (sel.getRangeAt && sel.rangeCount) { return sel.getRangeAt(0); } return null; }, is: function() { return (this.selection.isCollapsed()) ? false : true; }, isRedactor: function() { var range = this.selection.range(); if (range !== null) { var el = range.startContainer.parentNode; if ($(el).hasClass('redactor-in') || $(el).parents('.redactor-in').length !== 0) { return true; } } return false; }, isCollapsed: function() { var sel = this.selection.get(); return (sel === null) ? false : sel.isCollapsed; }, update: function(sel, range) { if (range === null) { return; } sel.removeAllRanges(); sel.addRange(range); }, current: function() { var sel = this.selection.get(); return (sel === null) ? false : sel.anchorNode; }, parent: function() { var current = this.selection.current(); return (current === null) ? false : current.parentNode; }, block: function(node) { node = node || this.selection.current(); while (node) { if (this.utils.isBlockTag(node.tagName)) { return ($(node).hasClass('redactor-in')) ? false : node; } node = node.parentNode; } return false; }, inline: function(node) { node = node || this.selection.current(); while (node) { if (this.utils.isInlineTag(node.tagName)) { return ($(node).hasClass('redactor-in')) ? false : node; } node = node.parentNode; } return false; }, element: function(node) { if (!node) { node = this.selection.current(); } while (node) { if (node.nodeType === 1) { if ($(node).hasClass('redactor-in')) { return false; } return node; } node = node.parentNode; } return false; }, prev: function() { var current = this.selection.current(); return (current === null) ? false : this.selection.current().previousSibling; }, next: function() { var current = this.selection.current(); return (current === null) ? false : this.selection.current().nextSibling; }, blocks: function(tag) { var blocks = []; var nodes = this.selection.nodes(tag); $.each(nodes, $.proxy(function(i,node) { if (this.utils.isBlock(node)) { blocks.push(node); } }, this)); var block = this.selection.block(); if (blocks.length === 0 && block === false) { return []; } else if (blocks.length === 0 && block !== false) { return [block]; } else { return blocks; } }, inlines: function(tag) { var inlines = []; var nodes = this.selection.nodes(tag); $.each(nodes, $.proxy(function(i,node) { if (this.utils.isInline(node)) { inlines.push(node); } }, this)); var inline = this.selection.inline(); if (inlines.length === 0 && inline === false) { return []; } else if (inlines.length === 0 && inline !== false) { return [inline]; } else { return inlines; } }, nodes: function(tag) { var filter = (typeof tag === 'undefined') ? [] : (($.isArray(tag)) ? tag : [tag]); var sel = this.selection.get(); var range = this.selection.range(sel); var nodes = []; var resultNodes = []; if (this.utils.isCollapsed()) { nodes = [this.selection.current()]; } else { var node = range.startContainer; var endNode = range.endContainer; // single node if (node === endNode) { return [this.selection.parent()]; } // iterate while (node && node !== endNode) { nodes.push(node = this.selection.nextNode(node)); } // partially selected nodes node = range.startContainer; while (node && node !== range.commonAncestorContainer) { nodes.unshift(node); node = node.parentNode; } } // remove service nodes $.each(nodes, function(i,s) { if (s) { var tagName = (s.nodeType !== 1) ? false : s.tagName.toLowerCase(); if ($(s).hasClass('redactor-script-tag') || $(s).hasClass('redactor-selection-marker')) { return; } else if (tagName && filter.length !== 0 && $.inArray(tagName, filter) === -1) { return; } else { resultNodes.push(s); } } }); return (resultNodes.length === 0) ? [] : resultNodes; }, nextNode: function(node) { if (node.hasChildNodes()) { return node.firstChild; } else { while (node && !node.nextSibling) { node = node.parentNode; } if (!node) { return null; } return node.nextSibling; } }, save: function() { this.marker.insert(); this.savedSel = this.core.editor().html(); }, restore: function(removeMarkers) { var node1 = this.marker.find(1); var node2 = this.marker.find(2); if (this.detect.isFirefox()) { this.core.editor().focus(); } if (node1.length !== 0 && node2.length !== 0) { this.caret.set(node1, node2); } else if (node1.length !== 0) { this.caret.start(node1); } else { this.core.editor().focus(); } if (removeMarkers !== false) { this.marker.remove(); this.savedSel = false; } }, saveInstant: function() { var el = this.core.editor()[0]; var doc = el.ownerDocument, win = doc.defaultView; var sel = win.getSelection(); if (!sel.getRangeAt || !sel.rangeCount) { return; } var range = sel.getRangeAt(0); var selectionRange = range.cloneRange(); selectionRange.selectNodeContents(el); selectionRange.setEnd(range.startContainer, range.startOffset); var start = selectionRange.toString().length; this.saved = { start: start, end: start + range.toString().length, node: range.startContainer }; return this.saved; }, restoreInstant: function(saved) { if (typeof saved === 'undefined' && !this.saved) { return; } this.saved = (typeof saved !== 'undefined') ? saved : this.saved; var $node = this.core.editor().find(this.saved.node); if ($node.length !== 0 && $node.text().trim().replace(/\u200B/g, '').length === 0) { try { var range = document.createRange(); range.setStart($node[0], 0); var sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); } catch(e) {} return; } var el = this.core.editor()[0]; var doc = el.ownerDocument, win = doc.defaultView; var charIndex = 0, range = doc.createRange(); range.setStart(el, 0); range.collapse(true); var nodeStack = [el], node, foundStart = false, stop = false; while (!stop && (node = nodeStack.pop())) { if (node.nodeType == 3) { var nextCharIndex = charIndex + node.length; if (!foundStart && this.saved.start >= charIndex && this.saved.start <= nextCharIndex) { range.setStart(node, this.saved.start - charIndex); foundStart = true; } if (foundStart && this.saved.end >= charIndex && this.saved.end <= nextCharIndex) { range.setEnd(node, this.saved.end - charIndex); stop = true; } charIndex = nextCharIndex; } else { var i = node.childNodes.length; while (i--) { nodeStack.push(node.childNodes[i]); } } } var sel = win.getSelection(); sel.removeAllRanges(); sel.addRange(range); }, node: function(node) { $(node).prepend(this.marker.get(1)); $(node).append(this.marker.get(2)); this.selection.restore(); }, all: function() { this.core.editor().focus(); var sel = this.selection.get(); var range = this.selection.range(sel); range.selectNodeContents(this.core.editor()[0]); this.selection.update(sel, range); }, remove: function() { this.selection.get().removeAllRanges(); }, replace: function(html) { this.insert.html(html); }, text: function() { return this.selection.get().toString(); }, html: function() { var html = ''; var sel = this.selection.get(); if (sel.rangeCount) { var container = document.createElement('div'); var len = sel.rangeCount; for (var i = 0; i < len; ++i) { container.appendChild(sel.getRangeAt(i).cloneContents()); } html = this.clean.onGet(container.innerHTML); } return html; }, extractEndOfNode: function(node) { var sel = this.selection.get(); var range = this.selection.range(sel); var clonedRange = range.cloneRange(); clonedRange.selectNodeContents(node); clonedRange.setStart(range.endContainer, range.endOffset); return clonedRange.extractContents(); }, // #backward removeMarkers: function() { this.marker.remove(); }, marker: function(num) { return this.marker.get(num); }, markerHtml: function(num) { return this.marker.html(num); } }; }, // =shortcuts shortcuts: function() { return { // based on https://github.com/jeresig/jquery.hotkeys 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: "'" }, hotkeysShiftNums: { "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<", ".": ">", "/": "?", "\\": "|" }, init: function(e, key) { // disable browser's hot keys for bold and italic if shortcuts off if (this.opts.shortcuts === false) { if ((e.ctrlKey || e.metaKey) && (key === 66 || key === 73)) { e.preventDefault(); } return false; } else { // build $.each(this.opts.shortcuts, $.proxy(function(str, command) { this.shortcuts.build(e, str, command); }, this)); } }, build: function(e, str, command) { var handler = $.proxy(function() { this.shortcuts.buildHandler(command); }, this); var keys = str.split(','); var len = keys.length; for (var i = 0; i < len; i++) { if (typeof keys[i] === 'string') { this.shortcuts.handler(e, $.trim(keys[i]), handler); } } }, buildHandler: function(command) { var func; if (command.func.search(/\./) !== '-1') { func = command.func.split('.'); if (typeof this[func[0]] !== 'undefined') { this[func[0]][func[1]].apply(this, command.params); } } else { this[command.func].apply(this, command.params); } }, handler: function(e, keys, origHandler) { keys = keys.toLowerCase().split(" "); var special = this.shortcuts.hotkeysSpecialKeys[e.keyCode]; var character = String.fromCharCode(e.which).toLowerCase(); var 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 + this.shortcuts.hotkeysShiftNums[character]] = true; // "$" can be triggered as "Shift+4" or "Shift+$" or just "$" if (modif === "shift+") { possible[this.shortcuts.hotkeysShiftNums[character]] = true; } } var len = keys.length; for (var i = 0; i < len; i++) { if (possible[keys[i]]) { e.preventDefault(); return origHandler.apply(this, arguments); } } } }; }, // =storage storage: function() { return { data: [], add: function(data) { // type, node, url, id data.status = true; data.url = decodeURI(data.url); this.storage.data[data.url] = data; }, status: function(url, status) { this.storage.data[decodeURI(url)].status = status; }, observe: function() { var _this = this; var $images = this.core.editor().find('[data-image]'); $images.each(function(i, s) { _this.storage.add({ type: 'image', node: s, url: s.src, id: $(s).attr('data-image') }); }); var $files = this.core.editor().find('[data-file]'); $files.each(function(i, s) { _this.storage.add({ type: 'file', node: s, url: s.href, id: $(s).attr('data-file') }); }); var $s3 = this.core.editor().find('[data-s3]'); $s3.each(function(i, s) { var url = (s.tagName === 'IMG') ? s.src : s.href; _this.storage.add({ type: 's3', node: s, url: url, id: $(s).attr('data-s3') }); }); }, changes: function() { for (var key in this.storage.data) { var data = this.storage.data[key]; var attr = (data.node.tagName === 'IMG') ? 'src' : 'href'; var $el = this.core.editor().find('[data-' + data.type + '][' + attr + '="' + data.url + '"]'); if ($el.length === 0) { this.storage.status(data.url, false); } else { this.storage.status(data.url, true); } } return this.storage.data; } }; }, // =toolbar toolbar: function() { return { build: function() { this.button.hideButtons(); this.button.hideButtonsOnMobile(); this.$toolbarBox = $('<div />').addClass('redactor-toolbar-box'); this.$toolbar = this.toolbar.createContainer(); this.$toolbarBox.append(this.$toolbar); this.toolbar.append(); this.button.$toolbar = this.$toolbar; this.button.setFormatting(); this.button.load(this.$toolbar); this.toolbar.setOverflow(); this.toolbar.setFixed(); }, createContainer: function() { return $('<ul>').addClass('redactor-toolbar').attr({ 'id': 'redactor-toolbar-' + this.uuid, 'role': 'toolbar' }); }, append: function() { if (this.opts.toolbarExternal) { this.$toolbar.addClass('redactor-toolbar-external'); $(this.opts.toolbarExternal).html(this.$toolbarBox); } else { if (this.opts.type === 'textarea') { this.$box.prepend(this.$toolbarBox); } else { this.$element.before(this.$toolbarBox); } } }, setOverflow: function() { if (this.opts.toolbarOverflow) { this.$toolbar.addClass('redactor-toolbar-overflow'); } }, setFixed: function() { if (!this.opts.toolbarFixed || this.opts.toolbarExternal) { return; } if (this.opts.toolbarFixedTarget !== document) { var $el = $(this.opts.toolbarFixedTarget); this.toolbar.toolbarOffsetTop = ($el.length === 0) ? 0 : this.core.box().offset().top - $el.offset().top; } // bootstrap modal fix var late = (this.core.box().closest('.modal-body').length !== 0) ? 1000 : 0; setTimeout($.proxy(function() { var self = this; this.toolbar.observeScroll(false); if (this.detect.isDesktop()) { $(this.opts.toolbarFixedTarget).on('scroll.redactor.' + this.uuid, function() { if (self.core.editor().height() < 100 || self.placeholder.isEditorEmpty()) { return; } self.toolbar.observeScroll(); }); } else { $(this.opts.toolbarFixedTarget).on('scroll.redactor.' + this.uuid, function() { if (self.core.editor().height() < 100 || self.placeholder.isEditorEmpty()) { return; } self.core.toolbar().hide(); clearTimeout($.data(this, "scrollCheck")); $.data(this, "scrollCheck", setTimeout(function() { self.core.toolbar().show(); self.toolbar.observeScroll(); }, 250) ); }); } }, this), late); }, setUnfixed: function() { this.toolbar.observeScrollDisable(); }, getBoxTop: function() { return (this.opts.toolbarFixedTarget === document) ? this.core.box().offset().top : this.toolbar.toolbarOffsetTop; }, observeScroll: function(start) { // tolerance 0 if redactor in the hidden layer var tolerance = 0; if (start !== false) { tolerance = (this.opts.toolbarFixedTarget === document) ? 20 : 0; } var scrollTop = $(this.opts.toolbarFixedTarget).scrollTop(); var boxTop = this.toolbar.getBoxTop(); if (scrollTop === boxTop) { return; } if ((scrollTop + this.opts.toolbarFixedTopOffset + tolerance) > boxTop) { this.toolbar.observeScrollEnable(scrollTop, boxTop); } else { this.toolbar.observeScrollDisable(); } }, observeScrollResize: function() { this.$toolbar.css({ width: this.core.box().innerWidth(), left: this.core.box().offset().left }); }, observeScrollEnable: function(scrollTop, boxTop) { if (typeof this.fullscreen !== 'undefined' && this.fullscreen.isOpened === false) { this.toolbar.observeScrollDisable(); return; } var end = boxTop + this.core.box().outerHeight() - 32; var width = this.core.box().innerWidth(); var position = (this.detect.isDesktop()) ? 'fixed' : 'absolute'; var top = (this.detect.isDesktop()) ? this.opts.toolbarFixedTopOffset : ($(this.opts.toolbarFixedTarget).scrollTop() - boxTop + this.opts.toolbarFixedTopOffset); var left = (this.detect.isDesktop()) ? this.core.box().offset().left : 0; if (this.opts.toolbarFixedTarget !== document) { position = 'absolute'; top = this.opts.toolbarFixedTopOffset + $(this.opts.toolbarFixedTarget).scrollTop() - boxTop; left = 0; } this.$toolbar.addClass('toolbar-fixed-box'); this.$toolbar.css({ position: position, width: width, top: top, left: left }); if (scrollTop > end) { $('.redactor-dropdown-' + this.uuid + ':visible').hide(); } this.toolbar.setDropdownsFixed(); this.$toolbar.css('visibility', (scrollTop < end) ? 'visible' : 'hidden'); $(window).on('resize.redactor-toolbar.' + this.uuid, $.proxy(this.toolbar.observeScrollResize, this)); }, observeScrollDisable: function() { this.$toolbar.css({ position: 'relative', width: 'auto', top: 0, left: 0, visibility: 'visible' }); this.toolbar.unsetDropdownsFixed(); this.$toolbar.removeClass('toolbar-fixed-box'); $(window).off('resize.redactor-toolbar.' + this.uuid); }, setDropdownsFixed: function() { var position = (this.opts.toolbarFixedTarget === document && this.detect.isDesktop()) ? 'fixed' : 'absolute'; this.toolbar.setDropdownPosition(position); }, unsetDropdownsFixed: function() { this.toolbar.setDropdownPosition('absolute'); }, setDropdownPosition: function(position) { var self = this; $('.redactor-dropdown-' + this.uuid).each(function() { var $el = $(this); var $button = self.button.get($el.attr('rel')); var top = (position === 'fixed') ? self.opts.toolbarFixedTopOffset : $button.offset().top; $el.css({ position: position, top: ($button.innerHeight() + top) + 'px' }); }); } }; }, // =upload upload: function() { return { init: function(id, url, callback) { this.upload.direct = false; this.upload.callback = callback; this.upload.url = url; this.upload.$el = $(id); this.upload.$droparea = $('<div id="redactor-droparea" />'); this.upload.$placeholdler = $('<div id="redactor-droparea-placeholder" />').text(this.lang.get('upload-label')); this.upload.$input = $('<input type="file" name="file" multiple />'); this.upload.$placeholdler.append(this.upload.$input); this.upload.$droparea.append(this.upload.$placeholdler); this.upload.$el.append(this.upload.$droparea); this.upload.$droparea.off('redactor.upload'); this.upload.$input.off('redactor.upload'); this.upload.$droparea.on('dragover.redactor.upload', $.proxy(this.upload.onDrag, this)); this.upload.$droparea.on('dragleave.redactor.upload', $.proxy(this.upload.onDragLeave, this)); // change this.upload.$input.on('change.redactor.upload', $.proxy(function(e) { e = e.originalEvent || e; var len = this.upload.$input[0].files.length; for (var i = 0; i < len; i++) { var index = (len - 1) - i; this.upload.traverseFile(this.upload.$input[0].files[index], e); } }, this)); // drop this.upload.$droparea.on('drop.redactor.upload', $.proxy(function(e) { e.preventDefault(); this.upload.$droparea.removeClass('drag-hover').addClass('drag-drop'); this.upload.onDrop(e); }, this)); }, directUpload: function(file, e) { this.upload.direct = true; this.upload.traverseFile(file, e); }, onDrop: function(e) { e = e.originalEvent || e; var files = e.dataTransfer.files; if (this.opts.multipleImageUpload) { var len = files.length; for (var i = 0; i < len; i++) { this.upload.traverseFile(files[i], e); } } else { this.upload.traverseFile(files[0], e); } }, traverseFile: function(file, e) { if (this.opts.s3) { this.upload.setConfig(file); this.uploads3.send(file, e); return; } var formData = !!window.FormData ? new FormData() : null; if (window.FormData) { this.upload.setConfig(file); var name = (this.upload.type === 'image') ? this.opts.imageUploadParam : this.opts.fileUploadParam; formData.append(name, file); } this.progress.show(); var stop = this.core.callback('uploadStart', e, formData); if (stop !== false) { this.upload.send(formData, e); } }, setConfig: function(file) { this.upload.getType(file); if (this.upload.direct) { this.upload.url = (this.upload.type === 'image') ? this.opts.imageUpload : this.opts.fileUpload; this.upload.callback = (this.upload.type === 'image') ? this.image.insert : this.file.insert; } }, getType: function(file) { this.upload.type = (this.opts.imageTypes.indexOf(file.type) === -1) ? 'file' : 'image'; if (this.opts.imageUpload === null && this.opts.fileUpload !== null) { this.upload.type = 'file'; } }, getHiddenFields: function(obj, fd) { if (obj === false || typeof obj !== 'object') { return fd; } $.each(obj, $.proxy(function(k, v) { if (v !== null && v.toString().indexOf('#') === 0) { v = $(v).val(); } fd.append(k, v); }, this)); return fd; }, send: function(formData, e) { // append hidden fields if (this.upload.type === 'image') { formData = this.utils.appendFields(this.opts.imageUploadFields, formData); formData = this.utils.appendForms(this.opts.imageUploadForms, formData); formData = this.upload.getHiddenFields(this.upload.imageFields, formData); } else { formData = this.utils.appendFields(this.opts.fileUploadFields, formData); formData = this.utils.appendForms(this.opts.fileUploadForms, formData); formData = this.upload.getHiddenFields(this.upload.fileFields, formData); } var xhr = new XMLHttpRequest(); xhr.open('POST', this.upload.url); xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); // complete xhr.onreadystatechange = $.proxy(function() { if (xhr.readyState === 4) { var data = xhr.responseText; data = data.replace(/^\[/, ''); data = data.replace(/\]$/, ''); var json; try { json = (typeof data === 'string' ? JSON.parse(data) : data); } catch(err) { json = { error: true }; } this.progress.hide(); if (!this.upload.direct) { this.upload.$droparea.removeClass('drag-drop'); } this.upload.callback(json, this.upload.direct, e); } }, this); // before send var stop = this.core.callback('uploadBeforeSend', xhr); if (stop !== false) { xhr.send(formData); } }, onDrag: function(e) { e.preventDefault(); this.upload.$droparea.addClass('drag-hover'); }, onDragLeave: function(e) { e.preventDefault(); this.upload.$droparea.removeClass('drag-hover'); }, clearImageFields: function() { this.upload.imageFields = {}; }, addImageFields: function(name, value) { this.upload.imageFields[name] = value; }, removeImageFields: function(name) { delete this.upload.imageFields[name]; }, clearFileFields: function() { this.upload.fileFields = {}; }, addFileFields: function(name, value) { this.upload.fileFields[name] = value; }, removeFileFields: function(name) { delete this.upload.fileFields[name]; } }; }, // =s3 uploads3: function() { return { send: function(file, e) { this.uploads3.executeOnSignedUrl(file, $.proxy(function(signedURL) { this.uploads3.sendToS3(file, signedURL, e); }, this)); }, executeOnSignedUrl: function(file, callback) { var xhr = new XMLHttpRequest(); var mark = (this.opts.s3.search(/\?/) === -1) ? '?' : '&'; xhr.open('GET', this.opts.s3 + mark + 'name=' + file.name + '&type=' + file.type, true); // hack to pass bytes through unprocessed. if (xhr.overrideMimeType) { xhr.overrideMimeType('text/plain; charset=x-user-defined'); } var that = this; xhr.onreadystatechange = function(e) { if (this.readyState === 4 && this.status === 200) { that.progress.show(); callback(decodeURIComponent(this.responseText)); } }; xhr.send(); }, createCORSRequest: function(method, url) { var xhr = new XMLHttpRequest(); if ("withCredentials" in xhr) { xhr.open(method, url, true); } else if (typeof XDomainRequest !== "undefined") { xhr = new XDomainRequest(); xhr.open(method, url); } else { xhr = null; } return xhr; }, sendToS3: function(file, url, e) { var xhr = this.uploads3.createCORSRequest('PUT', url); if (!xhr) { return; } xhr.onload = $.proxy(function() { var json; this.progress.hide(); if (xhr.status !== 200) { // error json = { error: true }; this.upload.callback(json, this.upload.direct, xhr); return; } var s3file = url.split('?'); if (!s3file[0]) { // url parsing is fail return false; } if (!this.upload.direct) { this.upload.$droparea.removeClass('drag-drop'); } json = { url: s3file[0], id: s3file[0], s3: true }; if (this.upload.type === 'file') { var arr = s3file[0].split('/'); json.name = arr[arr.length-1]; } this.upload.callback(json, this.upload.direct, e); }, this); xhr.onerror = function() {}; xhr.upload.onprogress = function(e) {}; xhr.setRequestHeader('Content-Type', file.type); xhr.setRequestHeader('x-amz-acl', 'public-read'); xhr.send(file); } }; }, // =utils utils: function() { return { isEmpty: function(html) { html = (typeof html === 'undefined') ? this.core.editor().html() : html; html = html.replace(/[\u200B-\u200D\uFEFF]/g, ''); html = html.replace(/ /gi, ''); html = html.replace(/<\/?br\s?\/?>/g, ''); html = html.replace(/\s/g, ''); html = html.replace(/^<p>[^\W\w\D\d]*?<\/p>$/i, ''); html = html.replace(/<iframe(.*?[^>])>$/i, 'iframe'); html = html.replace(/<source(.*?[^>])>$/i, 'source'); // remove empty tags html = html.replace(/<[^\/>][^>]*><\/[^>]+>/gi, ''); html = html.replace(/<[^\/>][^>]*><\/[^>]+>/gi, ''); html = $.trim(html); return html === ''; }, isElement: function(obj) { try { // Using W3 DOM2 (works for FF, Opera and Chrome) return obj instanceof HTMLElement; } catch(e) { return (typeof obj === "object") && (obj.nodeType === 1) && (typeof obj.style === "object") && (typeof obj.ownerDocument === "object"); } }, strpos: function(haystack, needle, offset) { var i = haystack.indexOf(needle, offset); return i >= 0 ? i : false; }, dataURItoBlob: function(dataURI) { var byteString; if (dataURI.split(',')[0].indexOf('base64') >= 0) { byteString = atob(dataURI.split(',')[1]); } else { byteString = unescape(dataURI.split(',')[1]); } var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; var ia = new Uint8Array(byteString.length); for (var i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); } return new Blob([ia], { type:mimeString }); }, getOuterHtml: function(el) { return $('<div>').append($(el).eq(0).clone()).html(); }, cloneAttributes: function(from, to) { from = from[0] || from; to = $(to); var attrs = from.attributes; var len = attrs.length; while (len--) { var attr = attrs[len]; to.attr(attr.name, attr.value); } return to; }, breakBlockTag: function() { var block = this.selection.block(); if (!block) { return false; } var isEmpty = this.utils.isEmpty(block.innerHTML); var tag = block.tagName.toLowerCase(); if (tag === 'pre' || tag === 'li' || tag === 'td' || tag === 'th') { return false; } if (!isEmpty && this.utils.isStartOfElement(block)) { return { $block: $(block), $next: $(block).next(), type: 'start' }; } else if (!isEmpty && this.utils.isEndOfElement(block)) { return { $block: $(block), $next: $(block).next(), type: 'end' }; } else { var endOfNode = this.selection.extractEndOfNode(block); var $nextPart = $('<' + tag + ' />').append(endOfNode); $nextPart = this.utils.cloneAttributes(block, $nextPart); $(block).after($nextPart); return { $block: $(block), $next: $nextPart, type: 'break' }; } }, // tag detection inBlocks: function(tags) { tags = ($.isArray(tags)) ? tags : [tags]; var blocks = this.selection.blocks(); var len = blocks.length; var contains = false; for (var i = 0; i < len; i++) { if (blocks[i] !== false) { var tag = blocks[i].tagName.toLowerCase(); if ($.inArray(tag, tags) !== -1) { contains = true; } } } return contains; }, inInlines: function(tags) { tags = ($.isArray(tags)) ? tags : [tags]; var inlines = this.selection.inlines(); var len = inlines.length; var contains = false; for (var i = 0; i < len; i++) { var tag = inlines[i].tagName.toLowerCase(); if ($.inArray(tag, tags) !== -1) { contains = true; } } return contains; }, isTag: function(current, tag) { var element = $(current).closest(tag, this.core.editor()[0]); if (element.length === 1) { return element[0]; } return false; }, isBlock: function(block) { if (block === null) { return false; } block = block[0] || block; return block && this.utils.isBlockTag(block.tagName); }, isBlockTag: function(tag) { return (typeof tag === 'undefined') ? false : this.reIsBlock.test(tag); }, isInline: function(inline) { inline = inline[0] || inline; return inline && this.utils.isInlineTag(inline.tagName); }, isInlineTag: function(tag) { return (typeof tag === 'undefined') ? false : this.reIsInline.test(tag); }, // parents detection isRedactorParent: function(el) { if (!el) { return false; } if ($(el).parents('.redactor-in').length === 0 || $(el).hasClass('redactor-in')) { return false; } return el; }, isCurrentOrParentHeader: function() { return this.utils.isCurrentOrParent(['H1', 'H2', 'H3', 'H4', 'H5', 'H6']); }, isCurrentOrParent: function(tagName) { var parent = this.selection.parent(); var current = this.selection.current(); if ($.isArray(tagName)) { var matched = 0; $.each(tagName, $.proxy(function(i, s) { if (this.utils.isCurrentOrParentOne(current, parent, s)) { matched++; } }, this)); return (matched === 0) ? false : true; } else { return this.utils.isCurrentOrParentOne(current, parent, tagName); } }, isCurrentOrParentOne: function(current, parent, tagName) { tagName = tagName.toUpperCase(); return parent && parent.tagName === tagName ? parent : current && current.tagName === tagName ? current : false; }, isEditorRelative: function() { var position = this.core.editor().css('position'); var arr = ['absolute', 'fixed', 'relative']; return ($.inArray(arr, position) !== -1); }, setEditorRelative: function() { this.core.editor().addClass('redactor-relative'); }, // scroll getScrollTarget: function() { var $scrollTarget = $(this.opts.scrollTarget); return ($scrollTarget.length !== 0) ? $scrollTarget : $(document); }, freezeScroll: function() { this.freezeScrollTop = this.utils.getScrollTarget().scrollTop(); this.utils.getScrollTarget().scrollTop(this.freezeScrollTop); }, unfreezeScroll: function() { if (typeof this.freezeScrollTop === 'undefined') { return; } this.utils.getScrollTarget().scrollTop(this.freezeScrollTop); }, saveScroll: function() { this.tmpScrollTop = this.utils.getScrollTarget().scrollTop(); }, restoreScroll: function() { if (typeof this.tmpScrollTop === 'undefined') { return; } this.utils.getScrollTarget().scrollTop(this.tmpScrollTop); }, isStartOfElement: function(element) { if (typeof element === 'undefined') { element = this.selection.block(); if (!element) { return false; } } return (this.offset.get(element) === 0) ? true : false; }, isEndOfElement: function(element) { if (typeof element === 'undefined') { element = this.selection.block(); if (!element) { return false; } } var text = $.trim($(element).text()).replace(/[\t\n\r\n]/g, '').replace(/\u200B/g, ''); var offset = this.offset.get(element); return (offset === text.length) ? true : false; }, removeEmptyAttr: function(el, attr) { var $el = $(el); if (typeof $el.attr(attr) === 'undefined') { return true; } if ($el.attr(attr) === '') { $el.removeAttr(attr); return true; } return false; }, replaceToTag: function(node, tag) { var replacement; $(node).replaceWith(function() { replacement = $('<' + tag + ' />').append($(this).contents()); for (var i = 0; i < this.attributes.length; i++) { replacement.attr(this.attributes[i].name, this.attributes[i].value); } return replacement; }); return replacement; }, // select all isSelectAll: function() { return this.selectAll; }, enableSelectAll: function() { this.selectAll = true; }, disableSelectAll: function() { this.selectAll = false; }, disableBodyScroll: function() { var $body = $('html'); var windowWidth = window.innerWidth; if (!windowWidth) { var documentElementRect = document.documentElement.getBoundingClientRect(); windowWidth = documentElementRect.right - Math.abs(documentElementRect.left); } var isOverflowing = document.body.clientWidth < windowWidth; var scrollbarWidth = this.utils.measureScrollbar(); $body.css('overflow', 'hidden'); if (isOverflowing) { $body.css('padding-right', scrollbarWidth); } }, measureScrollbar: function() { var $body = $('body'); var scrollDiv = document.createElement('div'); scrollDiv.className = 'redactor-scrollbar-measure'; $body.append(scrollDiv); var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth; $body[0].removeChild(scrollDiv); return scrollbarWidth; }, enableBodyScroll: function() { $('html').css({ 'overflow': '', 'padding-right': '' }); $('body').remove('redactor-scrollbar-measure'); }, appendFields: function(appendFields, data) { if (!appendFields) { return data; } else if (typeof appendFields === 'object') { $.each(appendFields, function(k, v) { if (v !== null && v.toString().indexOf('#') === 0) { v = $(v).val(); } data.append(k, v); }); return data; } var $fields = $(appendFields); if ($fields.length === 0) { return data; } else { var str = ''; $fields.each(function() { data.append($(this).attr('name'), $(this).val()); }); return data; } }, appendForms: function(appendForms, data) { if (!appendForms) { return data; } var $forms = $(appendForms); if ($forms.length === 0) { return data; } else { var formData = $forms.serializeArray(); $.each(formData, function(z,f) { data.append(f.name, f.value); }); return data; } }, isRgb: function(str) { return (str.search(/^rgb/i) === 0); }, rgb2hex: function(rgb) { rgb = rgb.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i); return (rgb && rgb.length === 4) ? "#" + ("0" + parseInt(rgb[1],10).toString(16)).slice(-2) + ("0" + parseInt(rgb[2],10).toString(16)).slice(-2) + ("0" + parseInt(rgb[3],10).toString(16)).slice(-2) : ''; }, // #backward isCollapsed: function() { return this.selection.isCollapsed(); }, isMobile: function() { return this.detect.isMobile(); }, isDesktop: function() { return this.detect.isDesktop(); }, isPad: function() { return this.detect.isIpad(); } }; }, // #backward browser: function() { return { webkit: function() { return this.detect.isWebkit(); }, ff: function() { return this.detect.isFirefox(); }, ie: function() { return this.detect.isIe(); } }; } }; $(window).on('load.tools.redactor', function() { $('[data-tools="redactor"]').redactor(); }); // constructor Redactor.prototype.init.prototype = Redactor.prototype; })(jQuery); (function($) { $.fn.redactorAnimation = function(animation, options, callback) { return this.each(function() { new redactorAnimation(this, animation, options, callback); }); }; function redactorAnimation(element, animation, options, callback) { // default var opts = { duration: 0.5, iterate: 1, delay: 0, prefix: 'redactor-', timing: 'linear' }; this.animation = animation; this.slide = (this.animation === 'slideDown' || this.animation === 'slideUp'); this.$element = $(element); this.prefixes = ['', '-moz-', '-o-animation-', '-webkit-']; this.queue = []; // options or callback if (typeof options === 'function') { callback = options; this.opts = opts; } else { this.opts = $.extend(opts, options); } // slide if (this.slide) { this.$element.height(this.$element.height()); } // init this.init(callback); } redactorAnimation.prototype = { init: function(callback) { this.queue.push(this.animation); this.clean(); if (this.animation === 'show') { this.opts.timing = 'linear'; this.$element.removeClass('hide').show(); if (typeof callback === 'function') { callback(this); } } else if (this.animation === 'hide') { this.opts.timing = 'linear'; this.$element.hide(); if (typeof callback === 'function') { callback(this); } } else { this.animate(callback); } }, animate: function(callback) { this.$element.addClass('redactor-animated').css('display', '').removeClass('hide'); this.$element.addClass(this.opts.prefix + this.queue[0]); this.set(this.opts.duration + 's', this.opts.delay + 's', this.opts.iterate, this.opts.timing); var _callback = (this.queue.length > 1) ? null : callback; this.complete('AnimationEnd', $.proxy(function() { if (this.$element.hasClass(this.opts.prefix + this.queue[0])) { this.clean(); this.queue.shift(); if (this.queue.length) { this.animate(callback); } } }, this), _callback); }, set: function(duration, delay, iterate, timing) { var len = this.prefixes.length; while (len--) { this.$element.css(this.prefixes[len] + 'animation-duration', duration); this.$element.css(this.prefixes[len] + 'animation-delay', delay); this.$element.css(this.prefixes[len] + 'animation-iteration-count', iterate); this.$element.css(this.prefixes[len] + 'animation-timing-function', timing); } }, clean: function() { this.$element.removeClass('redactor-animated'); this.$element.removeClass(this.opts.prefix + this.queue[0]); this.set('', '', '', ''); }, complete: function(type, make, callback) { this.$element.one(type.toLowerCase() + ' webkit' + type + ' o' + type + ' MS' + type, $.proxy(function() { if (typeof make === 'function') { make(); } if (typeof callback === 'function') { callback(this); } // hide var effects = ['fadeOut', 'slideUp', 'zoomOut', 'slideOutUp', 'slideOutRight', 'slideOutLeft']; if ($.inArray(this.animation, effects) !== -1) { this.$element.css('display', 'none'); } // slide if (this.slide) { this.$element.css('height', ''); } }, this)); } }; })(jQuery);