vendor/assets/javascripts/redactor.js in biola_wcms_components-0.8.0 vs vendor/assets/javascripts/redactor.js in biola_wcms_components-0.9.0

- old
+ new

@@ -1,8 +1,8 @@ /* - Redactor v10.0.7 - Updated: January 31, 2015 + Redactor v10.1.1 + Updated: April 28, 2015 http://imperavi.com/redactor/ Copyright (c) 2009-2015, Imperavi LLC. License: http://imperavi.com/redactor/license/ @@ -26,13 +26,10 @@ }; } var uuid = 0; - var reUrlYoutube = /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w.\-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig; - var reUrlVimeo = /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/; - // Plugin $.fn.redactor = function(options) { var val = []; var args = Array.prototype.slice.call(arguments, 1); @@ -92,17 +89,17 @@ return new Redactor.prototype.init(el, options); } // Functionality $.Redactor = Redactor; - $.Redactor.VERSION = '10.0.7'; + $.Redactor.VERSION = '10.1.1'; $.Redactor.modules = ['alignment', 'autosave', 'block', 'buffer', 'build', 'button', 'caret', 'clean', 'code', 'core', 'dropdown', 'file', 'focus', 'image', 'indent', 'inline', 'insert', 'keydown', 'keyup', 'lang', 'line', 'link', 'list', 'modal', 'observe', 'paragraphize', 'paste', 'placeholder', 'progress', 'selection', 'shortcuts', - 'tabifier', 'tidy', 'toolbar', 'upload', 'utils']; + 'tabifier', 'tidy', 'toolbar', 'upload', 'utils', 'linkify']; $.Redactor.opts = { // settings lang: 'en', @@ -133,10 +130,11 @@ autosave: false, // false or url autosaveName: false, autosaveInterval: 60, // seconds autosaveOnChange: false, + autosaveFields: false, linkTooltip: true, linkProtocol: 'http', linkNofollow: false, linkSize: 50, @@ -189,13 +187,17 @@ formatting: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'], formattingAdd: false, tabifier: true, - deniedTags: ['html', 'head', 'link', 'body', 'meta', 'script', 'style', 'applet'], + deniedTags: ['script', 'style'], allowedTags: false, // or array + 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'], + removeComments: false, replaceTags: [ ['strike', 'del'] ], replaceStyles: [ @@ -321,13 +323,26 @@ anchor: 'Anchor', link_new_tab: 'Open link in new tab', underline: 'Underline', alignment: 'Alignment', filename: 'Name (optional)', - edit: 'Edit' + edit: 'Edit', + upload_label: 'Drop file here or ' + } - } + }, + + linkify: { + regexps: { + youtube: /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w.\-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig, + vimeo: /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/, + image: /((https?|www)[^\s]+\.)(jpe?g|png|gif)(\?[^\s-]+)?/ig, + url: /(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/ig, + } + }, + + codemirror: false }; // Functionality Redactor.fn = $.Redactor.prototype = { @@ -369,10 +384,20 @@ this.reIsBlock = new RegExp('^(' + this.opts.blockLevelElements.join('|' ) + ')$', 'i'); // setup allowed and denied tags this.tidy.setupAllowed(); + // setup denied tags + if (this.opts.deniedTags !== false) + { + var tags = ['html', 'head', 'link', 'body', 'meta', 'applet']; + for (var i = 0; i < tags.length; i++) + { + this.opts.deniedTags.push(tags[i]); + } + } + // load lang this.lang.load(); // extend shortcuts $.extend(this.opts.shortcuts, this.opts.shortcutsAdd); @@ -514,15 +539,15 @@ }; }, autosave: function() { return { + html: false, enable: function() { if (!this.opts.autosave) return; - this.autosave.html = false; this.autosave.name = (this.opts.autosaveName) ? this.opts.autosaveName : this.$textarea.attr('name'); if (this.opts.autosaveOnChange) return; this.autosaveInterval = setInterval(this.autosave.load, this.opts.autosaveInterval * 1000); }, @@ -534,26 +559,44 @@ load: function() { this.autosave.source = this.code.get(); if (this.autosave.html === this.autosave.source) return; - if (this.utils.isEmpty(this.autosave.source)) return; + //if (this.utils.isEmpty(this.autosave.source)) return; // data var data = {}; data['name'] = this.autosave.name; - data[this.autosave.name] = escape(encodeURIComponent(this.autosave.source)); + 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 { @@ -584,11 +627,11 @@ this.block.clearStyle = false; var type, value; if (typeof this.formatting[name].data != 'undefined') type = 'data'; else if (typeof this.formatting[name].attr != 'undefined') type = 'attr'; - else if (typeof this.formatting[name].class != 'undefined') type = 'class'; + else if (typeof this.formatting[name]['class'] != 'undefined') type = 'class'; if (typeof this.formatting[name].clear != 'undefined') { this.block.clearStyle = true; } @@ -699,10 +742,15 @@ else if (block.tagName.toLowerCase() == tag) { this.block.toggle($(block)); } + if (typeof this.block.type == 'undefined' && typeof this.block.value == 'undefined') + { + $(block).removeAttr('class').removeAttr('style'); + } + }, setMultiple: function(tag) { var block = this.block.blocks[0]; var isContainerTable = (this.block.containerTag == 'TD' || this.block.containerTag == 'TH'); @@ -775,20 +823,26 @@ // only blockquote selected if (count == this.block.blocksSize) { $.each(this.block.blocks, $.proxy(function(i,s) { + var $formatted = false; if (this.opts.linebreaks) { $(s).prepend('<br>').append('<br>'); - this.utils.replaceWithContents(s); + $formatted = this.utils.replaceWithContents(s); } else { - this.utils.replaceToTag(s, 'p'); + $formatted = this.utils.replaceToTag(s, 'p'); } + if ($formatted && typeof this.block.type == 'undefined' && typeof this.block.value == 'undefined') + { + $formatted.removeAttr('class').removeAttr('style'); + } + }, this)); return; } @@ -829,11 +883,16 @@ if (tag != 'p' && tag != 'blockquote') $formatted.find('img').remove(); if (this.block.isRemoveInline) this.utils.removeInlineTags($formatted); if (tag == 'p' || this.block.headTag) $formatted.find('p').contents().unwrap(); + if (typeof this.block.type == 'undefined' && typeof this.block.value == 'undefined') + { + $formatted.removeAttr('class').removeAttr('style'); + } + }, this)); } } }, setForce: function($el) @@ -891,11 +950,11 @@ { $el.removeClass(this.block.value); }, formatListToBlockquote: function() { - var block = $(this.block.blocks[0]).closest('ul, ol'); + var block = $(this.block.blocks[0]).closest('ul, ol', this.$editor[0]); $(block).find('ul, ol').contents().unwrap(); $(block).find('li').append($('<br>')).contents().unwrap(); var $el = this.utils.replaceToTag(block, 'blockquote'); @@ -984,14 +1043,14 @@ } }, formatTableWrapping: function($formatted) { - if ($formatted.closest('table').length === 0) return; + if ($formatted.closest('table', this.$editor[0]).length === 0) return; - if ($formatted.closest('tr').length === 0) $formatted.wrap('<tr>'); - if ($formatted.closest('td').length === 0 && $formatted.closest('th').length === 0) + if ($formatted.closest('tr', this.$editor[0]).length === 0) $formatted.wrap('<tr>'); + if ($formatted.closest('td', this.$editor[0]).length === 0 && $formatted.closest('th').length === 0) { $formatted.wrap('<td>'); } }, removeData: function(name, value) @@ -1300,10 +1359,13 @@ }, this)); // paste this.$editor.on('paste.redactor', $.proxy(this.paste.init, this)); + // cut + this.$editor.on('cut.redactor', $.proxy(this.code.sync, this)); + // keydown this.$editor.on('keydown.redactor', $.proxy(this.keydown.init, this)); // keyup this.$editor.on('keyup.redactor', $.proxy(this.keyup.init, this)); @@ -1346,12 +1408,15 @@ return (!$el.hasClass('redactor-toolbar, redactor-dropdown') && !$el.is('#redactor-modal') && $el.parents('.redactor-toolbar, .redactor-dropdown, #redactor-modal').length === 0); }, setHelpers: function() { - // autosave - this.autosave.enable(); + // linkify + if (this.linkify.isEnabled()) + { + this.linkify.format(); + } // placeholder this.placeholder.enable(); // focus @@ -1420,11 +1485,11 @@ } // dropdown if (btnObject.dropdown) { - var $dropdown = $('<div class="redactor-dropdown redactor-dropdown-box-' + btnName + '" style="display: none;">'); + var $dropdown = $('<div 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); } // tooltip @@ -1523,11 +1588,11 @@ { this.button.get(key).removeClass('redactor-act'); }, setInactiveAll: function(key) { - if (typeof key == 'undefined') + if (typeof key === 'undefined') { this.$toolbar.find('a.re-icon').removeClass('redactor-act'); } else { @@ -1570,11 +1635,11 @@ addDropdown: function($btn, dropdown) { var key = $btn.attr('rel'); this.button.addCallback($btn, 'dropdown'); - var $dropdown = $('<div class="redactor-dropdown redactor-dropdown-box-' + key + '" style="display: none;">'); + 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); @@ -1594,19 +1659,21 @@ addFirst: function(key, title) { if (!this.opts.toolbar) return; var btn = this.button.build(key, { title: title }); + btn.addClass('redactor-btn-image'); this.$toolbar.prepend($('<li>').append(btn)); return btn; }, addAfter: function(afterkey, key, title) { if (!this.opts.toolbar) return; var btn = this.button.build(key, { title: title }); + btn.addClass('redactor-btn-image'); var $btn = this.button.get(afterkey); if ($btn.length !== 0) $btn.parent().after($('<li>').append(btn)); else this.$toolbar.append($('<li>').append(btn)); @@ -1615,10 +1682,11 @@ addBefore: function(beforekey, key, title) { if (!this.opts.toolbar) return; var btn = this.button.build(key, { title: title }); + btn.addClass('redactor-btn-image'); var $btn = this.button.get(beforekey); if ($btn.length !== 0) $btn.parent().before($('<li>').append(btn)); else this.$toolbar.append($('<li>').append(btn)); @@ -1838,14 +1906,14 @@ // convert script tag html = html.replace(/<script(.*?[^>]?)>([\w\W]*?)<\/script>/gi, '<pre class="redactor-script-tag" style="display: none;" $1>$2</pre>'); // replace dollar sign to entity html = html.replace(/\$/g, '&#36;'); - html = html.replace(/”/g, '"'); - html = html.replace(/‘/g, '\''); - html = html.replace(/’/g, '\''); + // replace special characters in links + html = html.replace(/<a href="(.*?[^>]?)®(.*?[^>]?)">/gi, '<a href="$1&reg$2">'); + if (this.opts.replaceDivs) html = this.clean.replaceDivs(html); if (this.opts.linebreaks) html = this.clean.replaceParagraphsToBr(html); // save form tag html = this.clean.saveFormTags(html); @@ -1922,17 +1990,17 @@ // remove br in the of li html = html.replace(new RegExp('<br\\s?/?></li>', 'gi'), '</li>'); html = html.replace(new RegExp('</li><br\\s?/?>', 'gi'), '</li>'); // remove verified - html = html.replace(new RegExp('<div(.*?[^>]) data-tagblock="redactor"(.*?[^>])>', 'gi'), '<div$1$2>'); - html = html.replace(new RegExp('<(.*?) data-verified="redactor"(.*?[^>])>', 'gi'), '<$1$2>'); - html = html.replace(new RegExp('<span(.*?[^>])\srel="(.*?[^>])"(.*?[^>])>', 'gi'), '<span$1$3>'); - html = html.replace(new RegExp('<img(.*?[^>])\srel="(.*?[^>])"(.*?[^>])>', 'gi'), '<img$1$3>'); - html = html.replace(new RegExp('<img(.*?[^>])\sstyle="" (.*?[^>])>', 'gi'), '<img$1 $2>'); - html = html.replace(new RegExp('<img(.*?[^>])\sstyle (.*?[^>])>', 'gi'), '<img$1 $2>'); - html = html.replace(new RegExp('<span class="redactor-invisible-space">(.*?)</span>', 'gi'), '$1'); + html = html.replace(/<div(.*?[^>]) data-tagblock="redactor"(.*?[^>])>/gi, '<div$1$2>'); + html = html.replace(/<(.*?) data-verified="redactor"(.*?[^>])>/gi, '<$1$2>'); + html = html.replace(/<span(.*?[^>])\srel="(.*?[^>])"(.*?[^>])>/gi, '<span$1$3>'); + html = html.replace(/<img(.*?[^>])\srel="(.*?[^>])"(.*?[^>])>/gi, '<img$1$3>'); + html = html.replace(/<img(.*?[^>])\sstyle="" (.*?[^>])>'/gi, '<img$1 $2>'); + html = html.replace(/<img(.*?[^>])\sstyle (.*?[^>])>'/gi, '<img$1 $2>'); + html = html.replace(/<span class="redactor-invisible-space">(.*?)<\/span>/gi, '$1'); html = html.replace(/ data-save-url="(.*?[^>])"/gi, ''); // 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, ''); @@ -1962,12 +2030,10 @@ onPaste: function(html, setMode) { html = $.trim(html); html = html.replace(/\$/g, '&#36;'); - html = html.replace(/‘/g, '\''); - html = html.replace(/’/g, '\''); // convert dirty spaces html = html.replace(/<span class="s1">/gi, '<span>'); html = html.replace(/<span class="Apple-converted-space">&nbsp;<\/span>/gi, ' '); html = html.replace(/<span class="Apple-tab-span"[^>]*>\t<\/span>/gi, '\t'); @@ -1987,10 +2053,12 @@ if (this.utils.isCurrentOrParent('PRE')) { html = html.replace(/”/g, '"'); html = html.replace(/“/g, '"'); + html = html.replace(/‘/g, '\''); + html = html.replace(/’/g, '\''); return this.clean.getPreCode(html); } if (this.utils.isCurrentOrParent(['BLOCKQUOTE', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6'])) @@ -2053,10 +2121,11 @@ html = this.clean.removeDirtyStyles(html); html = this.clean.onPasteRemoveSpans(html); html = this.clean.onPasteRemoveEmpty(html); + html = this.clean.convertInline(html); return html; }, onPasteWord: function(html) @@ -2197,27 +2266,20 @@ tags = ['ul', 'ol', 'li', 'span', 'a', 'small', 'em', 'strong', 'code', 'kbd', 'mark', 'cite', 'var', 'samp', 'dfn', 'sup', 'sub', 'b', 'i', 'u', 'del', 'br', 'iframe', 'video', 'audio', 'embed', 'param', 'object', 'img']; } var options = { - deniedTags: false, - allowedTags: tags, + deniedTags: (this.opts.deniedTags) ? this.opts.deniedTags : false, + allowedTags: (this.opts.allowedTags) ? this.opts.allowedTags : tags, removeComments: true, removePhp: true, - removeAttr: false, - allowedAttr: attrAllowed, + removeAttr: (this.opts.removeAttr) ? this.opts.removeAttr : false, + allowedAttr: (this.opts.allowedAttr) ? this.opts.allowedAttr : attrAllowed, removeEmpty: tagsEmpty }; - // denied tags - if (this.opts.deniedTags) - { - options.deniedTags = this.opts.deniedTags; - } - return this.tidy.load(html, options); - }, onPasteRemoveEmpty: function(html) { html = html.replace(/<(p|h[1-6])>(|\s|\n|\t|<br\s?\/?>)<\/(p|h[1-6])>/gi, ''); @@ -2279,37 +2341,66 @@ return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : ''; }); }, savePreCode: function(html) { - var pre = html.match(/<(pre|code)(.*?)>([\w\W]*?)<\/(pre|code)>/gi); + html = this.clean.savePreFormatting(html); + html = this.clean.saveCodeFormatting(html); + + return html; + }, + savePreFormatting: function(html) + { + var pre = html.match(/<pre(.*?)>([\w\W]*?)<\/pre>/gi); if (pre !== null) { $.each(pre, $.proxy(function(i,s) { - var arr = s.match(/<(pre|code)(.*?)>([\w\W]*?)<\/(pre|code)>/i); + var arr = s.match(/<pre(.*?)>([\w\W]*?)<\/pre>/i); - arr[3] = arr[3].replace(/<br\s?\/?>/g, '\n'); - arr[3] = arr[3].replace(/&nbsp;/g, ' '); + arr[2] = arr[2].replace(/<br\s?\/?>/g, '\n'); + arr[2] = arr[2].replace(/&nbsp;/g, ' '); if (this.opts.preSpaces) { - arr[3] = arr[3].replace(/\t/g, Array(this.opts.preSpaces + 1).join(' ')); + arr[2] = arr[2].replace(/\t/g, Array(this.opts.preSpaces + 1).join(' ')); } - arr[3] = this.clean.encodeEntities(arr[3]); + arr[2] = this.clean.encodeEntities(arr[2]); // $ fix - arr[3] = arr[3].replace(/\$/g, '&#36;'); + arr[2] = arr[2].replace(/\$/g, '&#36;'); - html = html.replace(s, '<' + arr[1] + arr[2] + '>' + arr[3] + '</' + arr[1] + '>'); + html = html.replace(s, '<pre' + arr[1] + '>' + arr[2] + '</pre>'); }, this)); } return html; }, + saveCodeFormatting: function(html) + { + var code = html.match(/<code(.*?[^>])>(.*?)<\/code>/gi); + if (code !== null) + { + $.each(code, $.proxy(function(i,s) + { + var arr = s.match(/<code(.*?[^>])>(.*?)<\/code>/i); + + arr[2] = arr[2].replace(/&nbsp;/g, ' '); + arr[2] = this.clean.encodeEntities(arr[2]); + + // $ fix + arr[2] = arr[2].replace(/\$/g, '&#36;'); + + html = html.replace(s, '<code' + arr[1] + '>' + arr[2] + '</code>'); + + }, this)); + } + + return html; + }, getTextFromHtml: function(html) { html = html.replace(/<br\s?\/?>|<\/H[1-6]>|<\/p>|<\/div>|<\/li>|<\/td>/gi, '\n'); var tmp = document.createElement('div'); @@ -2321,11 +2412,11 @@ getPlainText: function(html, paragraphize) { html = this.clean.getTextFromHtml(html); html = html.replace(/\n/g, '<br />'); - if (this.opts.paragraphize && typeof paragraphize == 'undefined') + if (this.opts.paragraphize && typeof paragraphize == 'undefined' && !this.utils.browser('mozilla')) { html = this.paragraphize.load(html); } return html; @@ -2410,26 +2501,38 @@ var $s = $(s); $s.attr('style', $s.attr('rel')); }); }, + cleanEmptyParagraph: function() + { + var p = this.$editor.find("p").first(); + + if (this.utils.isEmpty(p.html())) + { + p.remove(); + } + }, setVerified: function(html) { if (this.utils.browser('msie')) return html; html = html.replace(new RegExp('<img(.*?[^>])>', 'gi'), '<img$1 data-verified="redactor">'); - html = html.replace(new RegExp('<span(.*?)>', 'gi'), '<span$1 data-verified="redactor">'); + html = html.replace(new RegExp('<span(.*?[^>])>', 'gi'), '<span$1 data-verified="redactor">'); var matches = html.match(new RegExp('<(span|img)(.*?)style="(.*?)"(.*?[^>])>', 'gi')); + if (matches) { var len = matches.length; for (var i = 0; i < len; i++) { try { + var newTag = matches[i].replace(/style="(.*?)"/i, 'style="$1" rel="$1"'); - html = html.replace(new RegExp(matches[i], 'gi'), newTag); + html = html.replace(matches[i], newTag); + } catch (e) {} } } @@ -2496,10 +2599,13 @@ else { html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '<p$1>$2</p>'); } + html = html.replace(/<div(.*?[^>])>/gi, ''); + html = html.replace(/<\/div>/gi, ''); + return html; }, replaceDivsToBr: function(html) { html = html.replace(/<div\s(.*?)>/gi, '<p>'); @@ -2538,10 +2644,12 @@ html = this.clean.onSet(html); this.$editor.html(html); this.code.sync(); + if (html !== '') this.placeholder.remove(); + setTimeout($.proxy(this.buffer.add, this), 15); if (this.start === false) this.observe.load(); }, get: function() @@ -2588,12 +2696,26 @@ this.core.setCallback('change', html); } this.start = false; - // autosave on change + if (this.autosave.html == false) + { + this.autosave.html = this.code.get(); + } + + if (this.opts.codemirror) + { + this.$textarea.next('.CodeMirror').each(function(i, el) + { + el.CodeMirror.setValue(html); + }); + } + + //autosave this.autosave.onChange(); + this.autosave.enable(); }, toggle: function() { if (this.opts.visual) { @@ -2607,49 +2729,83 @@ showCode: function() { this.code.offset = this.caret.getOffset(); var scroll = $(window).scrollTop(); - var height = this.$editor.innerHeight(); + var width = this.$editor.innerWidth(), + height = this.$editor.innerHeight(); this.$editor.hide(); var html = this.$textarea.val(); this.modified = this.clean.removeSpaces(html); // indent code html = this.tabifier.get(html); - this.$textarea.val(html).height(height).show().focus(); - this.$textarea.on('keydown.redactor-textarea-indenting', this.code.textareaIndenting); + this.$textarea.val(html); - $(window).scrollTop(scroll); - - if (this.$textarea[0].setSelectionRange) + if (this.opts.codemirror) { - this.$textarea[0].setSelectionRange(0, 0); + this.$textarea.next('.CodeMirror').each(function(i, el) + { + $(el).show(); + el.CodeMirror.setValue(html); + el.CodeMirror.setSize(width, height); + el.CodeMirror.refresh(); + el.CodeMirror.focus(); + }); } + else + { + this.$textarea.height(height).show().focus(); + this.$textarea.on('keydown.redactor-textarea-indenting', this.code.textareaIndenting); - this.$textarea[0].scrollTop = 0; + $(window).scrollTop(scroll); + if (this.$textarea[0].setSelectionRange) + { + this.$textarea[0].setSelectionRange(0, 0); + } + + this.$textarea[0].scrollTop = 0; + } + this.opts.visual = false; this.button.setInactiveInCode(); this.button.setActive('html'); this.core.setCallback('source', html); }, showVisual: function() { + var html; + if (this.opts.visual) return; - var html = this.$textarea.hide().val(); + if (this.opts.codemirror) + { + this.$textarea.next('.CodeMirror').each(function(i, el) + { + html = el.CodeMirror.getValue(); + }); + } + else + { + html = this.$textarea.hide().val(); + } if (this.modified !== this.clean.removeSpaces(html)) { this.code.set(html); } + if (this.opts.codemirror) + { + this.$textarea.next('.CodeMirror').hide(); + } + this.$editor.show(); if (!this.utils.isEmpty(html)) { this.placeholder.remove(); @@ -2662,10 +2818,11 @@ this.button.setActiveInVisual(); this.button.setInactive('html'); this.observe.load(); this.opts.visual = true; + this.core.setCallback('visual', html); }, textareaIndenting: function(e) { if (e.keyCode !== 9) return true; @@ -2731,16 +2888,34 @@ // off events and remove data this.$element.off('.redactor').removeData('redactor'); this.$editor.off('.redactor'); + $(document).off('click.redactor-image-delete.' + this.uuid); + $(document).off('click.redactor-image-resize-hide.' + this.uuid); + $(document).off('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid); + $("body").off('scroll.redactor.' + this.uuid); + $(this.opts.toolbarFixedTarget).off('scroll.redactor.' + this.uuid); + // common this.$editor.removeClass('redactor-editor redactor-linebreaks redactor-placeholder'); this.$editor.removeAttr('contenteditable'); var html = this.code.get(); + // dropdowns off + this.$toolbar.find('a').each(function() + { + var $el = $(this); + if ($el.data('dropdown')) + { + $el.data('dropdown').remove(); + $el.data('dropdown', {}); + } + }); + + if (this.build.isTextarea()) { this.$box.after(this.$element); this.$box.remove(); this.$element.val(html).show(); @@ -2775,25 +2950,35 @@ { if (name == 'formatting' && this.opts.formattingAdd) { $.each(this.opts.formattingAdd, $.proxy(function(i,s) { - var name = s.tag; - if (typeof s.class != 'undefined') + var name = s.tag, + func; + + if (typeof s['class'] != 'undefined') { - name = name + '-' + s.class; + name = name + '-' + s['class']; } s.type = (this.utils.isBlockTag(s.tag)) ? 'block' : 'inline'; - var func = (s.type == 'inline') ? 'inline.formatting' : 'block.formatting'; + if (typeof s.func !== "undefined") + { + func = s.func; + } + else + { + func = (s.type == 'inline') ? 'inline.formatting' : 'block.formatting'; + } + if (this.opts.linebreaks && s.type == 'block' && s.tag == 'p') return; this.formatting[name] = { tag: s.tag, style: s.style, - 'class': s.class, + 'class': s['class'], attr: s.attr, data: s.data, clear: s.clear }; @@ -2896,11 +3081,10 @@ var top = ($button.innerHeight() + keyPosition.top) + 'px'; $dropdown.css({ position: 'absolute', left: left, top: top }).show(); } - this.core.setCallback('dropdownShown', { dropdown: $dropdown, key: key, button: $button }); } $(document).one('click', $.proxy(this.dropdown.hide, this)); this.$editor.one('click', $.proxy(this.dropdown.hide, this)); @@ -2928,11 +3112,11 @@ hideAll: function() { this.$toolbar.find('a.dropact').removeClass('redactor-act').removeClass('dropact'); $(document.body).removeClass('body-redactor-hidden').css('margin-right', 0); - $('.redactor-dropdown').hide(); + $('.redactor-dropdown-' + this.uuid).hide(); this.core.setCallback('dropdownHide'); }, hide: function (e) { var $dropdown = $(e.target); @@ -3030,12 +3214,11 @@ return; } if (first[0].tagName == 'UL' || first[0].tagName == 'OL') { - first = first.find('li').first(); - var child = first.children().first(); + var child = first.find('li').first(); if (!this.utils.isBlock(child) && child.text() === '') { // empty inline tag in li this.caret.setStart(child); return; @@ -3058,10 +3241,12 @@ setEnd: function() { if (this.utils.browser('mozilla') || this.utils.browser('msie')) { var last = this.$editor.children().last(); + + this.$editor.focus(); this.caret.setEnd(last); } else { this.selection.get(); @@ -3100,11 +3285,11 @@ this.modal.show(); }, showEdit: function($image) { - var $link = $image.closest('a'); + var $link = $image.closest('a', this.$editor[0]); this.modal.load('imageEdit', this.lang.get('edit'), 705); this.modal.createCancelButton(); this.image.buttonDelete = this.modal.createDeleteButton(this.lang.get('_delete')); @@ -3177,11 +3362,11 @@ update: function($image) { this.image.hideResize(); this.buffer.set(); - var $link = $image.closest('a'); + var $link = $image.closest('a', this.$editor[0]); $image.attr('alt', $('#redactor-image-title').val()); this.image.setFloating($image); @@ -3239,20 +3424,20 @@ { $image.on('dragstart', $.proxy(this.image.onDrag, this)); } $image.on('mousedown', $.proxy(this.image.hideResize, this)); - $image.on('click touchstart', $.proxy(function(e) + $image.on('click.redactor touchstart', $.proxy(function(e) { this.observe.image = $image; if (this.$editor.find('#redactor-image-box').length !== 0) return false; this.image.resizer = this.image.loadEditableControls($image); - $(document).on('click.redactor-image-resize-hide', $.proxy(this.image.hideResize, this)); - this.$editor.on('click.redactor-image-resize-hide', $.proxy(this.image.hideResize, this)); + $(document).on('click.redactor-image-resize-hide.' + this.uuid, $.proxy(this.image.hideResize, this)); + this.$editor.on('click.redactor-image-resize-hide.' + this.uuid, $.proxy(this.image.hideResize, this)); // resize if (!this.opts.imageResizable) return; this.image.resizer.on('mousedown.redactor touchstart.redactor', $.proxy(function(e) @@ -3305,12 +3490,15 @@ var width = Math.round(height * this.image.resizeHandle.ratio); if (height < 50 || width < 100) return; + var height = Math.round(this.image.resizeHandle.el.width() / this.image.resizeHandle.ratio); + + this.image.resizeHandle.el.attr({width: width, height: height}); this.image.resizeHandle.el.width(width); - this.image.resizeHandle.el.height(this.image.resizeHandle.el.width()/this.image.resizeHandle.ratio); + this.image.resizeHandle.el.height(height); this.code.sync(); }, stopResize: function() { @@ -3350,11 +3538,11 @@ $el.removeAttr('data-save-url'); }); }, hideResize: function(e) { - if (e && $(e.target).closest('#redactor-image-box').length !== 0) return; + if (e && $(e.target).closest('#redactor-image-box', this.$editor[0]).length !== 0) return; if (e && e.target.tagName == 'IMG') { var $image = $(e.target); $image.attr('data-save-url', $image.attr('src')); } @@ -3381,12 +3569,12 @@ imageBox.replaceWith(function() { return $(this).contents(); }); - $(document).off('click.redactor-image-resize-hide'); - this.$editor.off('click.redactor-image-resize-hide'); + $(document).off('click.redactor-image-resize-hide.' + this.uuid); + this.$editor.off('click.redactor-image-resize-hide.' + this.uuid); if (typeof this.image.resizeHandle !== 'undefined') { this.image.resizeHandle.el.attr('rel', this.image.resizeHandle.el.attr('style')); } @@ -3462,12 +3650,12 @@ }, remove: function(image) { var $image = $(image); - var $link = $image.closest('a'); - var $figure = $image.closest('figure'); + var $link = $image.closest('a', this.$editor[0]); + var $figure = $image.closest('figure', this.$editor[0]); var $parent = $image.parent(); if ($('#redactor-image-box').length !== 0) { $parent = $('#redactor-image-box').parent(); } @@ -3648,11 +3836,11 @@ { document.execCommand('outdent'); var current = this.selection.getCurrent(); - var $item = $(current).closest('li'); + var $item = $(current).closest('li', this.$editor[0]); var $parent = $item.parent(); if ($item.length !== 0 && $parent.length !== 0 && $parent[0].tagName == 'LI') { $parent.after($item); } @@ -3712,11 +3900,11 @@ formatting: function(name) { var type, value; if (typeof this.formatting[name].style != 'undefined') type = 'style'; - else if (typeof this.formatting[name].class != 'undefined') type = 'class'; + else if (typeof this.formatting[name]['class'] != 'undefined') type = 'class'; if (type) value = this.formatting[name][type]; this.inline.format(this.formatting[name].tag, type, value); @@ -3732,16 +3920,29 @@ for (var i = 0; i < tags.length; i++) { if (tag == tags[i]) tag = replaced[i]; } + if (this.opts.allowedTags) + { + if ($.inArray(tag, this.opts.allowedTags) == -1) return; + } + else + { + if ($.inArray(tag, this.opts.deniedTags) !== -1) return; + } + this.inline.type = type || false; this.inline.value = value || false; this.buffer.set(); - this.$editor.focus(); + if (!this.utils.browser('msie')) + { + this.$editor.focus(); + } + this.selection.get(); if (this.range.collapsed) { this.inline.formatCollapsed(tag); @@ -3752,23 +3953,27 @@ } }, formatCollapsed: function(tag) { var current = this.selection.getCurrent(); - var $parent = $(current).closest(tag + '[data-redactor-tag=' + tag + ']'); + var $parent = $(current).closest(tag + '[data-redactor-tag=' + tag + ']', this.$editor[0]); // inline there is if ($parent.length !== 0 && (this.inline.type != 'style' && $parent[0].tagName != 'SPAN')) { - this.caret.setAfter($parent[0]); - // remove empty - if ( this.utils.isEmpty($parent.text())) + if (this.utils.isEmpty($parent.text())) { + this.caret.setAfter($parent[0]); + $parent.remove(); this.code.sync(); } + else if (this.utils.isEndOfElement($parent)) + { + this.caret.setAfter($parent[0]); + } return; } // create empty inline @@ -4356,10 +4561,12 @@ { lastNode = frag.appendChild(node); } this.range.insertNode(frag); + this.range.collapse(false); + this.selection.addRange(); } }; }, keydown: function() { @@ -4402,11 +4609,11 @@ { var isEndOfTable = false; var $table = false; if (this.keydown.block && this.keydown.block.tagName === 'TD') { - $table = $(this.keydown.block).closest('table'); + $table = $(this.keydown.block).closest('table', this.$editor[0]); } if ($table && $table.find('td').last()[0] === this.keydown.block) { isEndOfTable = true; @@ -4476,45 +4683,68 @@ else if (this.opts.linebreaks && !this.keydown.block) { current = this.selection.getCurrent(); $next = $(this.keydown.current).next(); - if (current !== false && $(current).hasClass('redactor-invisible-space')) + if ($next.length !== 0 && $next[0].tagName == 'BR') { + return this.keydown.insertBreakLine(e); + } + else if (current !== false && $(current).hasClass('redactor-invisible-space')) + { this.caret.setAfter(current); $(current).contents().unwrap(); + return this.keydown.insertDblBreakLine(e); } else { - if ($next.length === 0 && current === false && typeof $next.context != 'undefined') + if (this.utils.isEndOfEditor()) { return this.keydown.insertDblBreakLine(e); } - else if (this.utils.isEndOfEditor()) + else if ($next.length === 0 && current === false && typeof $next.context != 'undefined') { - return this.keydown.insertDblBreakLine(e); + return this.keydown.insertBreakLine(e); } return this.keydown.insertBreakLine(e); } } else if (this.opts.linebreaks && this.keydown.block) { setTimeout($.proxy(this.keydown.replaceDivToBreakLine, this), 1); } // paragraphs - else if (!this.opts.linebreaks && this.keydown.block && this.keydown.block.tagName !== 'LI') + else if (!this.opts.linebreaks && this.keydown.block) { - setTimeout($.proxy(this.keydown.replaceDivToParagraph, this), 1); + if (this.keydown.block.tagName !== 'LI') + { + setTimeout($.proxy(this.keydown.replaceDivToParagraph, this), 1); + } + else + { + current = this.selection.getCurrent(); + var $parent = $(current).closest('li', this.$editor[0]); + var $list = $parent.closest('ul,ol', this.$editor[0]); + + 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.setStart(node); + + return false; + } + } } else if (!this.opts.linebreaks && !this.keydown.block) { return this.keydown.insertParagraph(e); } - - } // Shift+Enter or Ctrl+Enter if (key === this.keyCode.ENTER && (e.ctrlKey || e.shiftKey)) { @@ -4529,10 +4759,16 @@ } // image delete and backspace if (key === this.keyCode.BACKSPACE || key === this.keyCode.DELETE) { + if (this.utils.browser('mozilla') && this.keydown.current && this.keydown.current.tagName === 'TD') + { + e.preventDefault(); + return false; + } + var nodes = this.selection.getNodes(); if (nodes) { var len = nodes.length; var last; @@ -4839,30 +5075,70 @@ insertBreakLineProcessing: function(e, dbl) { e.stopPropagation(); this.selection.get(); + var br1 = document.createElement('br'); - this.range.deleteContents(); + if (this.utils.browser('msie')) + { + this.range.collapse(false); + this.range.setEnd(this.range.endContainer, this.range.endOffset); + } + else + { + this.range.deleteContents(); + } + this.range.insertNode(br1); + // move br outside A tag + var $parentA = $(br1).parent("a"); + + if ($parentA.length > 0) + { + $parentA.find(br1) + .remove(); + + $parentA.after(br1); + } + if (dbl === true) { + var $next = $(br1).next(); + if ($next.length !== 0 && $next[0].tagName === 'BR' && this.utils.isEndOfEditor()) + { + this.caret.setAfter(br1); + this.code.sync(); + return false; + } + var br2 = document.createElement('br'); + this.range.insertNode(br2); this.caret.setAfter(br2); } else { - this.caret.setAfter(br1); + this.keydown.insertBreakLineProcessingAfter(br1); } this.code.sync(); - return false; }, + insertBreakLineProcessingAfter: function(node) + { + var space = this.utils.createSpaceElement(); + $(node).after(space); + this.selection.selectElement(space); + + $(space).replaceWith(function() + { + return $(this).contents(); + }); + }, removeInvisibleSpace: function() { var $current = $(this.keydown.current); if ($current.text().search(/^\u200B$/g) === 0) { @@ -4871,13 +5147,13 @@ }, removeEmptyListInTable: function(e) { var $current = $(this.keydown.current); var $parent = $(this.keydown.parent); - var td = $current.closest('td'); + var td = $current.closest('td', this.$editor[0]); - if (td.length !== 0 && $current.closest('li') && $parent.children('li').length === 1) + if (td.length !== 0 && $current.closest('li', this.$editor[0]) && $parent.children('li').length === 1) { if (!this.utils.isEmpty($current.text())) return; e.preventDefault(); @@ -4900,11 +5176,10 @@ this.keyup.current = this.selection.getCurrent(); this.keyup.parent = this.selection.getParent(); var $parent = this.utils.isRedactorParent($(this.keyup.parent).parent()); - // callback var keyupStop = this.core.setCallback('keyup', e); if (keyupStop === false) { e.preventDefault(); @@ -4929,18 +5204,13 @@ $(this.keyup.parent).contents().unwrap(); this.keyup.replaceToParagraph(); } // linkify - if (this.keyup.isLinkify(key)) - { - this.formatLinkify(this.opts.linkProtocol, this.opts.convertLinks, this.opts.convertUrlLinks, this.opts.convertImageLinks, this.opts.convertVideoLinks, this.opts.linkSize); + if (this.linkify.isEnabled() && this.linkify.isKey(key)) + this.linkify.format(); - this.observe.load(); - this.code.sync(); - } - if (key === this.keyCode.DELETE || key === this.keyCode.BACKSPACE) { // clear unverified this.clean.clearUnverified(); @@ -4956,11 +5226,14 @@ return false; } // remove empty paragraphs - this.$editor.find('p').each($.proxy(this.utils.removeEmpty, this)); + this.$editor.find('p').each($.proxy(function(i, s) + { + this.utils.removeEmpty(i, $(s).html()); + }, this)); // remove invisible space if (this.opts.linebreaks && this.keyup.current && this.keyup.current.tagName == 'DIV' && this.utils.isEmpty(this.keyup.current.innerHTML)) { $(this.keyup.current).after(this.selection.getMarkerAsHtml()); @@ -4970,14 +5243,10 @@ // if empty return this.keyup.formatEmpty(e); } }, - isLinkify: function(key) - { - return this.opts.convertLinks && (this.opts.convertUrlLinks || this.opts.convertImageLinks || this.opts.convertVideoLinks) && key === this.keyCode.ENTER && !this.utils.isCurrentOrParent('PRE'); - }, replaceToParagraph: function(clone) { var $current = $(this.keyup.current); var node; @@ -5154,27 +5423,30 @@ this.link.$inputUrl.focus(); }, cleanUrl: function() { var thref = self.location.href.replace(/\/$/i, ''); - this.link.url = this.link.url.replace(thref, ''); - this.link.url = this.link.url.replace(/^\/#/, '#'); - this.link.url = this.link.url.replace('mailto:', ''); - // remove host from href - if (!this.opts.linkProtocol) + if (typeof this.link.url !== "undefined") { - var re = new RegExp('^(http|ftp|https)://' + self.location.host, 'i'); - this.link.url = this.link.url.replace(re, ''); - } + this.link.url = this.link.url.replace(thref, ''); + this.link.url = this.link.url.replace(/^\/#/, '#'); + this.link.url = this.link.url.replace('mailto:', ''); + // remove host from href + if (!this.opts.linkProtocol) + { + var re = new RegExp('^(http|ftp|https)://' + self.location.host, 'i'); + this.link.url = this.link.url.replace(re, ''); + } + } }, getData: function() { this.link.$node = false; - var $el = $(this.selection.getCurrent()).closest('a'); + var $el = $(this.selection.getCurrent()).closest('a', this.$editor[0]); if ($el.length !== 0 && $el[0].tagName === 'A') { this.link.$node = $el; this.link.url = $el.attr('href'); @@ -5189,10 +5461,12 @@ } }, insert: function() { + this.placeholder.remove(); + var target = ''; var link = this.link.$inputUrl.val(); var text = this.link.$inputText.val(); if ($.trim(link) === '') @@ -5245,20 +5519,41 @@ if (this.link.$node) { this.buffer.set(); - this.link.$node.text(text).attr('href', link); + var $link = this.link.$node, + $el = $link.children(); + + if ($el.length > 0) + { + while ($el.length) + { + $el = $el.children(); + } + + $el = $el.end(); + } + else + { + $el = $link; + } + + $link.attr('href', link); + $el.text(text); + if (target !== '') { - this.link.$node.attr('target', target); + $link.attr('target', target); } else { - this.link.$node.removeAttr('target'); + $link.removeAttr('target'); } + this.selection.selectElement($link); + this.code.sync(); } else { if (this.utils.browser('mozilla') && this.link.text === '') @@ -5276,24 +5571,40 @@ { $a = $('<a href="' + link + '">').text(text); if (target !== '') $a.attr('target', target); $a = $(this.insert.node($a)); + + if (this.selection.getText().match(/\s$/)) + { + $a.after(" "); + } + this.selection.selectElement($a); } else { document.execCommand('createLink', false, link); - $a = $(this.selection.getCurrent()).closest('a'); + $a = $(this.selection.getCurrent()).closest('a', this.$editor[0]); + if (this.utils.browser('mozilla')) + { + $a = $('a[_moz_dirty=""]'); + } if (target !== '') $a.attr('target', target); - $a.removeAttr('style'); + $a.removeAttr('style').removeAttr('_moz_dirty'); - if (this.link.text === '' || this.link.text != text) + if (this.selection.getText().match(/\s$/)) { + $a.after(" "); + } + + if (this.link.text !== '' || this.link.text != text) + { $a.text(text); + this.selection.selectElement($a); } } } @@ -5309,32 +5620,54 @@ }, this), 5); }, unlink: function(e) { - if (typeof e != 'undefined' && e.preventDefault) e.preventDefault(); + if (typeof e != 'undefined' && e.preventDefault) + { + e.preventDefault(); + } var nodes = this.selection.getNodes(); if (!nodes) return; this.buffer.set(); var len = nodes.length; for (var i = 0; i < len; i++) { - if (nodes[i].tagName == 'A') - { - var $node = $(nodes[i]); - $node.replaceWith($node.contents()); - } + var $node = $(nodes[i]).closest('a', this.$editor[0]); + $node.replaceWith($node.contents()); } // hide link's tooltip $('.redactor-link-tooltip').remove(); this.code.sync(); + }, + toggleClass: function(className) + { + this.link.setClass(className, 'toggleClass'); + }, + addClass: function(className) + { + this.link.setClass(className, 'addClass'); + }, + removeClass: function(className) + { + this.link.setClass(className, 'removeClass'); + }, + setClass: function(className, func) + { + var links = this.selection.getInlinesTags(['a']); + if (links === false) return; + + $.each(links, function() + { + $(this)[func](className); + }); } }; }, list: function() { @@ -5346,11 +5679,11 @@ this.buffer.set(); this.selection.save(); var parent = this.selection.getParent(); - var $list = $(parent).closest('ol, ul'); + var $list = $(parent).closest('ol, ul', this.$editor[0]); if (!this.utils.isRedactorParent($list) && $list.length !== 0) { $list = false; } @@ -5384,30 +5717,29 @@ { this.list.insert(cmd); } } - this.selection.restore(); this.code.sync(); }, insert: function(cmd) { var parent = this.selection.getParent(); var current = this.selection.getCurrent(); - var $td = $(current).closest('td, th'); + var $td = $(current).closest('td, th', this.$editor[0]); if (this.utils.browser('msie') && this.opts.linebreaks) { this.list.insertInIe(cmd); } else { document.execCommand('insert' + cmd); } - var $list = $(this.selection.getParent()).closest('ol, ul'); + var $list = $(this.selection.getParent()).closest('ol, ul', this.$editor[0]); if ($td.length !== 0) { var prev = $td.prev(); var html = $td.html(); @@ -5490,17 +5822,17 @@ var $current = $(this.selection.getCurrent()); this.indent.fixEmptyIndent(); - if (!this.opts.linebreaks && $current.closest('li, th, td').length === 0) + if (!this.opts.linebreaks && $current.closest('li, th, td', this.$editor[0]).length === 0) { document.execCommand('formatblock', false, 'p'); this.$editor.find('ul, ol, blockquote').each($.proxy(this.utils.removeEmpty, this)); } - var $table = $(this.selection.getCurrent()).closest('table'); + var $table = $(this.selection.getCurrent()).closest('table', this.$editor[0]); var $prev = $table.prev(); if (!this.opts.linebreaks && $table.length !== 0 && $prev.length !== 0 && $prev[0].tagName == 'BR') { $prev.remove(); } @@ -5857,11 +6189,18 @@ buttons: function(e, btnName) { var current = this.selection.getCurrent(); var parent = this.selection.getParent(); - this.button.setInactiveAll(btnName); + 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; @@ -5870,23 +6209,23 @@ //var linkButtonName = (this.utils.isCurrentOrParent('A')) ? this.lang.get('link_edit') : this.lang.get('link_insert'); //$('body').find('a.redactor-dropdown-link').text(linkButtonName); $.each(this.opts.activeButtonsStates, $.proxy(function(key, value) { - var parentEl = $(parent).closest(key); - var currentEl = $(current).closest(key); + 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).length !== 0) + if (parentEl.length !== 0 || currentEl.closest(key, this.$editor[0]).length !== 0) { this.button.setActive(value); } }, this)); - var $parent = $(parent).closest(this.opts.alignmentTags.toString().toLowerCase()); + var $parent = $(parent).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]); if (this.utils.isRedactorParent(parent) && $parent.length) { var align = ($parent.css('text-align') === '') ? 'left' : $parent.css('text-align'); this.button.setActive('align' + align); } @@ -5901,19 +6240,19 @@ this.$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').on('click', function(e) { e.preventDefault(); }); + $img.closest('a', this.$editor[0]).on('click', function(e) { e.preventDefault(); }); if (this.utils.browser('msie')) $img.attr('unselectable', 'on'); this.image.setEditable($img); }, this)); - $(document).on('click.redactor-image-delete', $.proxy(function(e) + $(document).on('click.redactor-image-delete.' + this.uuid, $.proxy(function(e) { this.observe.image = false; if (e.target.tagName == 'IMG' && this.utils.isRedactorParent(e.target)) { this.observe.image = (this.observe.image && this.observe.image == e.target) ? false : e.target; @@ -5924,35 +6263,33 @@ }, links: function() { if (!this.opts.linkTooltip) return; - this.$editor.find('a').on('touchstart click', $.proxy(this.observe.showTooltip, this)); - this.$editor.on('touchstart click.redactor', $.proxy(this.observe.closeTooltip, this)); - $(document).on('touchstart click.redactor', $.proxy(this.observe.closeTooltip, this)); + this.$editor.find('a').on('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid, $.proxy(this.observe.showTooltip, this)); + this.$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)); }, getTooltipPosition: function($link) { return $link.offset(); }, showTooltip: function(e) { - var $link = $(e.target); - var $parent = $link.closest('a'); - var tag = ($link.length !== 0) ? $link[0].tagName : false; + var $el = $(e.target); - if ($parent[0].tagName === 'A') - { - if (tag === 'IMG') return; - else if (tag !== 'A') $link = $parent; - } + if ($el[0].tagName == 'IMG') + return; - if (tag !== 'A') - { + 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) @@ -5978,11 +6315,11 @@ closeTooltip: function(e) { e = e.originalEvent || e; var target = e.target; - var $parent = $(target).closest('a'); + 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')) @@ -6001,14 +6338,10 @@ load: function(html) { if (this.opts.linebreaks) return html; if (html === '' || html === '<p></p>') return this.opts.emptyHtml; - this.paragraphize.blocks = ['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']; - html = html + "\n"; this.paragraphize.safes = []; this.paragraphize.z = 0; @@ -6019,11 +6352,11 @@ html = this.paragraphize.replaceBreaksToNewLines(html); html = this.paragraphize.replaceBreaksToParagraphs(html); html = this.paragraphize.clear(html); html = this.paragraphize.restoreSafes(html); - html = html.replace(new RegExp('<br\\s?/?>\n?<(' + this.paragraphize.blocks.join('|') + ')(.*?[^>])>', 'gi'), '<p><br /></p>\n<$1$2>'); + 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) { @@ -6035,11 +6368,11 @@ return $(this).append('<br />').contents(); }); html = $div.html(); - $div.find(this.paragraphize.blocks.join(', ')).each($.proxy(function(i,s) + $div.find(this.opts.paragraphizeBlocks.join(', ')).each($.proxy(function(i,s) { this.paragraphize.z++; this.paragraphize.safes[this.paragraphize.z] = s.outerHTML; html = html.replace(s.outerHTML, '\n{replace' + this.paragraphize.z + '}'); @@ -6064,11 +6397,13 @@ }, restoreSafes: function(html) { $.each(this.paragraphize.safes, function(i,s) { + s = (typeof s !== 'undefined') ? s.replace(/\$/g, '&#36;') : s; html = html.replace('{replace' + i + '}', s); + }); return html; }, replaceBreaksToParagraphs: function(html) @@ -6132,11 +6467,15 @@ paste: function() { return { init: function(e) { - if (!this.opts.cleanOnPaste) return; + if (!this.opts.cleanOnPaste) + { + setTimeout($.proxy(this.code.sync, this), 1); + return; + } this.rtePaste = true; this.buffer.set(); this.selection.save(); @@ -6161,18 +6500,28 @@ this.paste.insert(html); $(window).off('scroll.redactor-freeze'); - }, this), 1); + if (this.linkify.isEnabled()) + this.linkify.format(); + }, this), 1); }, createPasteBox: function() { this.$pasteBox = $('<div>').html('').attr('contenteditable', 'true').css({ position: 'fixed', width: 0, top: 0, left: '-9999px' }); - this.$box.parent().append(this.$pasteBox); + if (this.utils.browser('msie')) + { + this.$box.append(this.$pasteBox); + } + else + { + $('body').append(this.$pasteBox); + } + this.$pasteBox.focus(); }, insert: function(html) { html = this.core.setCallback('pasteBefore', html); @@ -6325,23 +6674,32 @@ node = node.parentNode; } return false; }, - getInlines: function(nodes) + getInlines: function(nodes, tags) { this.selection.get(); if (this.range && this.range.collapsed) { return false; } var inlines = []; - nodes = (typeof nodes == 'undefined') ? this.selection.getNodes() : nodes; + nodes = (typeof nodes == 'undefined' || nodes === false) ? this.selection.getNodes() : nodes; var inlineTags = this.opts.inlineTags; inlineTags.push('span'); + + if (typeof tags !== 'undefined') + { + for (var i = 0; i < tags.length; i++) + { + inlineTags.push(tags[i]); + } + } + $.each(nodes, $.proxy(function(i,node) { if ($.inArray(node.tagName.toLowerCase(), inlineTags) != -1) { inlines.push(node); @@ -6349,10 +6707,32 @@ }, this)); return (inlines.length === 0) ? false : inlines; }, + getInlinesTags: function(tags) + { + this.selection.get(); + + if (this.range && this.range.collapsed) + { + return false; + } + + var inlines = []; + var nodes = this.selection.getNodes(); + $.each(nodes, $.proxy(function(i,node) + { + if ($.inArray(node.tagName.toLowerCase(), tags) != -1) + { + inlines.push(node); + } + + }, this)); + + return (inlines.length === 0) ? false : inlines; + }, getBlocks: function(nodes) { this.selection.get(); if (this.range && this.range.collapsed) @@ -6565,11 +6945,16 @@ this.savedSel = false; }, removeMarkers: function() { - this.$editor.find('span.redactor-selection-marker').remove(); + this.$editor.find('span.redactor-selection-marker').each(function(i,s) + { + var text = $(s).text().replace(/[\u200B-\u200D\uFEFF]/g, ''); + if (text === '') $(s).remove(); + else $(s).replaceWith(function() { return $(this).contents(); }); + }); }, getText: function() { this.selection.get(); @@ -6591,10 +6976,38 @@ html = container.innerHTML; } return this.clean.onSync(html); + }, + replaceWithHtml: function(html) + { + html = this.selection.getMarkerAsHtml(1) + html + this.selection.getMarkerAsHtml(2); + + this.selection.get(); + + if (window.getSelection && window.getSelection().getRangeAt) + { + this.range.deleteContents(); + var div = document.createElement("div"); + div.innerHTML = html; + + var frag = document.createDocumentFragment(), child; + while ((child = div.firstChild)) + { + frag.appendChild(child); + } + + this.range.insertNode(frag); + } + else if (document.selection && document.selection.createRange) + { + this.range.pasteHTML(html); + } + + this.selection.restore(); + this.code.sync(); } }; }, shortcuts: function() { @@ -6708,11 +7121,11 @@ if (!this.opts.tabifier) return code; // clean setup var ownLine = ['area', 'body', 'head', 'hr', 'i?frame', 'link', 'meta', 'noscript', 'style', 'script', 'table', 'tbody', 'thead', 'tfoot']; var contOwnLine = ['li', 'dt', 'dt', 'h[1-6]', 'option', 'script']; - var newLevel = ['blockquote', 'div', 'dl', 'fieldset', 'form', 'frameset', 'map', 'ol', 'p', 'pre', 'select', 'td', 'th', 'tr', 'ul']; + var newLevel = ['p', 'blockquote', 'div', 'dl', 'fieldset', 'form', 'frameset', 'map', 'ol', 'pre', 'select', 'td', 'th', 'tr', 'ul']; this.tabifier.lineBefore = new RegExp('^<(/?' + ownLine.join('|/?' ) + '|' + contOwnLine.join('|') + ')[ >]'); this.tabifier.lineAfter = new RegExp('^<(br|/?' + ownLine.join('|/?' ) + '|/' + contOwnLine.join('|/') + ')[ >]'); this.tabifier.newLevel = new RegExp('^</?(' + newLevel.join('|' ) + ')[ >]'); @@ -6873,10 +7286,11 @@ return tagout.replace(/\s*$/, '') + suffix + '>'; }, placeTag: function (tag, out) { var nl = tag.match(this.tabifier.newLevel); + if (tag.match(this.tabifier.lineBefore) || nl) { out = out.replace(/\s*$/, ''); out += '\n'; } @@ -6888,11 +7302,11 @@ out += tag; if (tag.match(this.tabifier.lineAfter) || tag.match(this.tabifier.newLevel)) { out = out.replace(/ *$/, ''); - out += '\n'; + //out += '\n'; } return out; } }; @@ -6989,28 +7403,17 @@ { rTags.push(this.tidy.settings.replaceTags[i][1]); replacement.push(this.tidy.settings.replaceTags[i][0]); } - this.tidy.$div.find(replacement.join(',')).each($.proxy(function(n,s) + $.each(replacement, $.proxy(function(key, value) { - var tag = rTags[n]; - $(s).replaceWith(function() + this.tidy.$div.find(value).replaceWith(function() { - var replaced = $('<' + tag + ' />').append($(this).contents()); - - for (var i = 0; i < this.attributes.length; i++) - { - replaced.attr(this.attributes[i].name, this.attributes[i].value); - } - - return replaced; + return $("<" + rTags[key] + " />", {html: $(this).html()}); }); - }, this)); - - return html; }, replaceStyles: function() { if (!this.tidy.settings.replaceStyles) return; @@ -7435,11 +7838,11 @@ if (!this.utils.isDesktop()) return; if (this.opts.toolbarExternal) return; if (!this.opts.toolbarFixed) return; this.toolbar.observeScroll(); - $(this.opts.toolbarFixedTarget).on('scroll.redactor', $.proxy(this.toolbar.observeScroll, this)); + $(this.opts.toolbarFixedTarget).on('scroll.redactor.' + this.uuid, $.proxy(this.toolbar.observeScroll, this)); }, setOverflow: function() { if (this.utils.isMobile() && this.opts.toolbarOverflow) @@ -7500,21 +7903,24 @@ }, observeScrollEnable: function(scrollTop, boxTop) { var top = this.opts.toolbarFixedTopOffset + scrollTop - boxTop; var left = 0; - var end = boxTop + this.$box.height() + 30; + var end = boxTop + this.$box.height() - 32; var width = this.$box.innerWidth(); this.$toolbar.addClass('toolbar-fixed-box'); this.$toolbar.css({ position: 'absolute', width: width, top: top + 'px', left: left }); + if (scrollTop > end) + $('.redactor-dropdown-' + this.uuid + ':visible').hide(); + this.toolbar.setDropdownsFixed(); this.$toolbar.css('visibility', (scrollTop < end) ? 'visible' : 'hidden'); }, observeScrollDisable: function() { @@ -7526,11 +7932,10 @@ visibility: 'visible' }); this.toolbar.unsetDropdownsFixed(); this.$toolbar.removeClass('toolbar-fixed-box'); - }, setDropdownsFixed: function() { var top = this.$toolbar.innerHeight() + this.opts.toolbarFixedTopOffset; var position = 'fixed'; @@ -7538,19 +7943,19 @@ { top = (this.$toolbar.innerHeight() + this.$toolbar.offset().top) + this.opts.toolbarFixedTopOffset; position = 'absolute'; } - $('.redactor-dropdown').each(function() + $('.redactor-dropdown-' + this.uuid).each(function() { $(this).css({ position: position, top: top + 'px' }); }); }, unsetDropdownsFixed: function() { var top = (this.$toolbar.innerHeight() + this.$toolbar.offset().top); - $('.redactor-dropdown').each(function() + $('.redactor-dropdown-' + this.uuid).each(function() { $(this).css({ position: 'absolute', top: top + 'px' }); }); } }; @@ -7564,11 +7969,11 @@ 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('Drop file here or '); + this.upload.$placeholdler = $('<div id="redactor-droparea-placeholder" />').text(this.lang.get('upload_label')); this.upload.$input = $('<input type="file" name="file" />'); this.upload.$placeholdler.append(this.upload.$input); this.upload.$droparea.append(this.upload.$placeholdler); this.upload.$el.append(this.upload.$droparea); @@ -7625,10 +8030,11 @@ var name = (this.upload.type == 'image') ? this.opts.imageUploadParam : this.opts.fileUploadParam; formData.append(name, file); } this.progress.show(); + this.core.setCallback('uploadStart', e, formData); this.upload.sendData(formData, e); }, setConfig: function(file) { this.upload.getType(file); @@ -7675,10 +8081,11 @@ 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) @@ -7912,10 +8319,11 @@ html = html.replace(/&nbsp;/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 if (removeEmptyTags !== false) { html = html.replace(/<[^\/>][^>]*><\/[^>]+>/gi, ''); @@ -7976,27 +8384,26 @@ return false; }, removeEmpty: function(i, s) { - var $s = $(s); + var $s = $($.parseHTML(s)); $s.find('.redactor-invisible-space').removeAttr('style').removeAttr('class'); - if ($s.find('hr, br, img, iframe').length !== 0) return; + if ($s.find('hr, br, img, iframe, source').length !== 0) return; var text = $.trim($s.text()); + if (this.utils.isEmpty(text, false)) { $s.remove(); } }, // save and restore scroll saveScroll: function() { - if (this.utils.isSelectAll()) return; - this.saveEditorScroll = this.$editor.scrollTop(); this.saveBodyScroll = $(window).scrollTop(); if (this.opts.scrollTarget) this.saveTargetScroll = $(this.opts.scrollTarget).scrollTop(); }, @@ -8037,10 +8444,12 @@ { if (removeInlineTags === true) self.utils.removeInlineTags(this); return $(this).contents(); }); + + return $(node); }, replaceToTag: function(node, tag, removeInlineTags) { var replacement; var self = this; @@ -8069,26 +8478,29 @@ var offset = this.caret.getOffsetOfElement(block); return (offset === 0) ? true : false; }, - isEndOfElement: function() + isEndOfElement: function(element) { - var block = this.selection.getBlock(); - if (!block) return false; + if (typeof element == 'undefined') + { + var element = this.selection.getBlock(); + if (!element) return false; + } - var offset = this.caret.getOffsetOfElement(block); - var text = $.trim($(block).text()).replace(/\n\r\n/g, ''); + var offset = this.caret.getOffsetOfElement(element); + var text = $.trim($(element).text()).replace(/\n\r\n/g, ''); return (offset == text.length) ? true : false; }, isEndOfEditor: function() { var block = this.$editor[0]; var offset = this.caret.getOffsetOfElement(block); - var text = $.trim($(block).text()).replace(/\n\r\n/g, ''); + var text = $.trim($(block).html().replace(/(<([^>]+)>)/gi,'')); return (offset == text.length) ? true : false; }, // test blocks @@ -8106,11 +8518,11 @@ }, // tag detection isTag: function(current, tag) { - var element = $(current).closest(tag); + var element = $(current).closest(tag, this.$editor[0]); if (element.length == 1) { return element[0]; } @@ -8207,106 +8619,150 @@ ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) || []; if (browser == 'safari') return (typeof match[3] != 'undefined') ? match[3] == 'safari' : false; if (browser == 'version') return match[2]; - if (browser == 'webkit') return (match[1] == 'chrome' || match[1] == 'webkit'); + if (browser == 'webkit') return (match[1] == 'chrome' || match[1] == 'opr' || match[1] == 'webkit'); if (match[1] == 'rv') return browser == 'msie'; if (match[1] == 'opr') return browser == 'webkit'; return browser == match[1]; } }; - } - }; + }, + linkify: function() + { + return { + isKey: function(key) + { + return key == this.keyCode.ENTER || key == this.keyCode.SPACE; + }, + isEnabled: function() + { + return this.opts.convertLinks && (this.opts.convertUrlLinks || this.opts.convertImageLinks || this.opts.convertVideoLinks) && !this.utils.isCurrentOrParent('PRE'); + }, + format: function() + { + var linkify = this.linkify, + opts = this.opts; - $(window).on('load.tools.redactor', function() - { - $('[data-tools="redactor"]').redactor(); - }); + this.$editor + .find(":not(iframe,img,a,pre)") + .addBack() + .contents() + .filter(function() + { + return this.nodeType === 3 && $.trim(this.nodeValue) != "" && !$(this).parent().is("pre") && (this.nodeValue.match(opts.linkify.regexps.youtube) || this.nodeValue.match(opts.linkify.regexps.vimeo) || this.nodeValue.match(opts.linkify.regexps.image) || this.nodeValue.match(opts.linkify.regexps.url)); + }) + .each(function() + { + var text = $(this).text(), + html = text; - // constructor - Redactor.prototype.init.prototype = Redactor.prototype; + if (opts.convertVideoLinks && (html.match(opts.linkify.regexps.youtube) || html.match(opts.linkify.regexps.vimeo)) ) + { + html = linkify.convertVideoLinks(html); + } + else if (opts.convertImageLinks && html.match(opts.linkify.regexps.image)) + { + html = linkify.convertImages(html); + } + else if (opts.convertUrlLinks) + { + html = linkify.convertLinks(html); + } - // LINKIFY - $.Redactor.fn.formatLinkify = function(protocol, convertLinks, convertUrlLinks, convertImageLinks, convertVideoLinks, linkSize) - { - var urlCheck = '((?:http[s]?:\\/\\/(?:www\\.)?|www\\.){1}(?:[0-9A-Za-z\\-%_]+\\.)+[a-zA-Z]{2,}(?::[0-9]+)?(?:(?:/[0-9A-Za-z\\-#\\.%\+_]*)+)?(?:\\?(?:[0-9A-Za-z\\-\\.%_]+(?:=[0-9A-Za-z\\-\\.%_\\+]*)?)?(?:&(?:[0-9A-Za-z\\-\\.%_]+(?:=[0-9A-Za-z\\-\\.%_\\+]*)?)?)*)?(?:#[0-9A-Za-z\\-\\.%_\\+=\\?&;]*)?)'; - var regex = new RegExp(urlCheck, 'gi'); - var rProtocol = /(https?|ftp):\/\//i; - var urlImage = /(https?:\/\/.*\.(?:png|jpg|jpeg|gif))/gi; + $(this).before(text.replace(text, html)) + .remove(); + }); - var childNodes = (this.$editor ? this.$editor[0] : this).childNodes, i = childNodes.length; - while (i--) - { - var n = childNodes[i]; - - if (n.nodeType === 3 && n.parentNode !== 'PRE') - { - var html = n.nodeValue; - - // youtube & vimeo - if (convertVideoLinks && html) + this.linkify.after(); + }, + convertVideoLinks: function(html) { var iframeStart = '<iframe width="500" height="281" src="', iframeEnd = '" frameborder="0" allowfullscreen></iframe>'; - if (html.match(reUrlYoutube)) + if (html.match(this.opts.linkify.regexps.youtube)) { - html = html.replace(reUrlYoutube, iframeStart + '//www.youtube.com/embed/$1' + iframeEnd); - $(n).after(html).remove(); + html = html.replace(this.opts.linkify.regexps.youtube, iframeStart + '//www.youtube.com/embed/$1' + iframeEnd); } - else if (html.match(reUrlVimeo)) + + if (html.match(this.opts.linkify.regexps.vimeo)) { - html = html.replace(reUrlVimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd); - $(n).after(html).remove(); + html = html.replace(this.opts.linkify.regexps.vimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd); } - } - // image - if (convertImageLinks && html && html.match(urlImage)) + return html; + }, + convertImages: function(html) { - html = html.replace(urlImage, '<img src="$1" />'); + var matches = html.match(this.opts.linkify.regexps.image); - $(n).after(html).remove(); - return; - } + if (matches) + { + html = html.replace(html, '<img src="' + matches + '" />'); + } - // link - if (html.search(/\$/g) != -1) html = html.replace(/\$/g, '&#36;'); - - var matches = html.match(regex); - if (convertUrlLinks && html && matches) + return html; + }, + convertLinks: function(html) { + var matches = html.match(this.opts.linkify.regexps.url); - var len = matches.length; - for (var z = 0; z < len; z++) + if (matches) { - // remove dot in the end - if (matches[z].match(/\.$/) !== null) matches[z] = matches[z].replace(/\.$/, ''); + matches = $.grep(matches, function(v, k) { return $.inArray(v, matches) === k; }); - var href = matches[z]; - var text = href; + var length = matches.length; - var space = ''; - if (href.match(/\s$/) !== null) space = ' '; + for (var i = 0; i < length; i++) + { + var href = matches[i], + text = href, + linkProtocol = this.opts.linkProtocol + '://'; - var addProtocol = protocol + '://'; - if (href.match(rProtocol) !== null) addProtocol = ''; + if (href.match(/(https?|ftp):\/\//i) !== null) + { + linkProtocol = ""; + } - if (text.length > linkSize) text = text.substring(0, linkSize) + '...'; - text = text.replace(/&#36;/g, '$').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); + if (text.length > this.opts.linkSize) + { + text = text.substring(0, this.opts.linkSize) + '...'; + } - html = html.replace(href, '<a href=\"' + addProtocol + $.trim(href) + '\">' + $.trim(text) + '</a>' + space); + text = decodeURIComponent(text); + + var regexB = "\\b"; + + if ($.inArray(href.slice(-1), ["/", "&", "="]) != -1) + { + regexB = ""; + } + + // escaping url + var regexp = new RegExp('(' + href.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") + regexB + ')', 'g'); + + html = html.replace(regexp, '<a href="' + linkProtocol + $.trim(href) + '">' + $.trim(text) + '</a>'); + } } - $(n).after(html).remove(); + return html; + }, + after: function() + { + this.observe.load(); + this.code.sync(); } } - else if (n.nodeType === 1 && !/^(pre|a|button|textarea)$/i.test(n.tagName)) - { - $.Redactor.fn.formatLinkify.call(n, protocol, convertLinks, convertUrlLinks, convertImageLinks, convertVideoLinks, linkSize); - } } }; + $(window).on('load.tools.redactor', function() + { + $('[data-tools="redactor"]').redactor(); + }); + + // constructor + Redactor.prototype.init.prototype = Redactor.prototype; })(jQuery);