app/assets/javascripts/redactor.js in scrivito_editors-0.0.12 vs app/assets/javascripts/redactor.js in scrivito_editors-0.0.13

- old
+ new

@@ -1,17 +1,16 @@ /* - Redactor v9.2.1 - Updated: Mar 19, 2014 + Redactor v9.2.6 + Updated: Jul 19, 2014 http://imperavi.com/redactor/ Copyright (c) 2009-2014, Imperavi LLC. License: http://imperavi.com/redactor/license/ Usage: $('#content').redactor(); */ - (function($) { var uuid = 0; "use strict"; @@ -29,10 +28,13 @@ Range.prototype.equals = function() { return this[0] === this[1]; }; + var reUrlYoutube = /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w.-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig; + var reUrlVimeo = /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/; + // Plugin $.fn.redactor = function(options) { var val = []; var args = Array.prototype.slice.call(arguments, 1); @@ -69,11 +71,11 @@ { return new Redactor.prototype.init(el, options); } $.Redactor = Redactor; - $.Redactor.VERSION = '9.2.1'; + $.Redactor.VERSION = '9.2.6'; $.Redactor.opts = { // settings rangy: false, @@ -82,11 +84,11 @@ css: false, // url lang: 'en', direction: 'ltr', // ltr or rtl - placeholder: '', + placeholder: false, typewriter: false, wym: false, mobile: true, cleanup: true, @@ -102,11 +104,21 @@ focus: false, tabindex: false, autoresize: true, minHeight: false, maxHeight: false, - shortcuts: true, + shortcuts: { + 'ctrl+m, meta+m': "this.execCommand('removeFormat', false)", + 'ctrl+b, meta+b': "this.execCommand('bold', false)", + 'ctrl+i, meta+i': "this.execCommand('italic', false)", + 'ctrl+h, meta+h': "this.execCommand('superscript', false)", + 'ctrl+l, meta+l': "this.execCommand('subscript', false)", + 'ctrl+k, meta+k': "this.linkShow()", + 'ctrl+shift+7': "this.execCommand('insertorderedlist', false)", + 'ctrl+shift+8': "this.execCommand('insertunorderedlist', false)" + }, + shortcutsAdd: false, autosave: false, // false or url autosaveInterval: 60, // seconds plugins: false, // array @@ -114,18 +126,20 @@ //linkAnchor: true, //linkEmail: true, linkProtocol: 'http://', linkNofollow: false, linkSize: 50, + predefinedLinks: false, // json url (ex. /some-url.json ) or false imageFloatMargin: '10px', - imageGetJson: false, // url (ex. /folder/images.json ) or false + imageGetJson: false, // json url (ex. /some-images.json ) or false dragUpload: true, // false imageTabLink: true, imageUpload: false, // url imageUploadParam: 'file', // input name + imageResizable: true, fileUpload: false, // url fileUploadParam: 'file', // input name clipboardUpload: true, // or false clipboardUploadUrl: false, // url @@ -210,10 +224,11 @@ newLevel: ['blockquote', 'div', 'dl', 'fieldset', 'form', 'frameset', 'map', 'ol', 'p', 'pre', 'select', 'td', 'th', 'tr', 'ul'], blockLevelElements: ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DD', 'DL', 'DT', 'DIV', 'LI', 'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'PRE', 'ADDRESS', 'SECTION', 'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE', 'TD'], + // lang langs: { en: { html: 'HTML', video: 'Insert Video', @@ -381,10 +396,16 @@ } // load lang this.opts.curLang = this.opts.langs[this.opts.lang]; + // extend shortcuts + $.extend(this.opts.shortcuts, this.opts.shortcutsAdd); + + // init placeholder + this.placeholderInit(); + // Build this.buildStart(); }, toolbarInit: function(lang) @@ -800,10 +821,16 @@ this.sync(); }, setFullpageOnInit: function(html) { + this.fullpageDoctype = html.match(/^<\!doctype[^>]*>/i); + if (this.fullpageDoctype && this.fullpageDoctype.length == 1) + { + html = html.replace(/^<\!doctype[^>]*>/i, ''); + } + html = this.cleanSavePreCode(html, true); html = this.cleanConverters(html); html = this.cleanEmpty(html); // set code @@ -812,10 +839,18 @@ // set no editable this.setNonEditable(); this.setSpansVerified(); this.sync(); }, + setFullpageDoctype: function() + { + if (this.fullpageDoctype && this.fullpageDoctype.length == 1) + { + var source = this.fullpageDoctype[0] + '\n' + this.$source.val(); + this.$source.val(source); + } + }, setSpansVerified: function() { var spans = this.$editor.find('span'); var replacementTag = 'inline'; @@ -864,12 +899,10 @@ if (source == editor) { // do not sync return false; } - - // fix second level up ul, ol html = html.replace(/<\/li><(ul|ol)>([\w\W]*?)<\/(ul|ol)>/gi, '<$1>$2</$1></li>'); if ($.trim(html) === '<br>') html = ''; @@ -886,10 +919,11 @@ // before callback html = this.callback('syncBefore', false, html); this.$source.val(html); + this.setFullpageDoctype(); // onchange & after callback this.callback('syncAfter', false, html); if (this.start === false) @@ -970,40 +1004,46 @@ html = html.replace(/<font(.*?)>([\w\W]*?)<\/font>/gi, '$2'); } // remove spans html = html.replace(/<span(.*?)>([\w\W]*?)<\/span>/gi, '$2'); + html = html.replace(/<inline>([\w\W]*?)<\/inline>/gi, '$1'); html = html.replace(/<inline>/gi, '<span>'); html = html.replace(/<inline /gi, '<span '); html = html.replace(/<\/inline>/gi, '</span>'); + + if (this.opts.removeEmptyTags) + { + html = html.replace(/<span>([\w\W]*?)<\/span>/gi, '$1'); + } + html = html.replace(/<span(.*?)class="redactor_placeholder"(.*?)>([\w\W]*?)<\/span>/gi, ''); - html = html.replace(/<span>([\w\W]*?)<\/span>/gi, '$1'); + html = html.replace(/<img(.*?)contenteditable="false"(.*?)>/gi, '<img$1$2>'); // special characters - html = html.replace(/&amp;/gi, '&'); - html = html.replace(/™/gi, '&trade;'); - html = html.replace(/©/gi, '&copy;'); - html = html.replace(/…/gi, '&hellip;'); - html = html.replace(/—/gi, '&mdash;'); - html = html.replace(/‐/gi, '&dash;'); + html = html.replace(/&/gi, '&'); + html = html.replace(/\u2122/gi, '&trade;'); + html = html.replace(/\u00a9/gi, '&copy;'); + html = html.replace(/\u2026/gi, '&hellip;'); + html = html.replace(/\u2014/gi, '&mdash;'); + html = html.replace(/\u2010/gi, '&dash;'); - html = this.cleanReConvertProtected(html); return html; }, + // BUILD buildStart: function() { // content this.content = ''; // container this.$box = $('<div class="redactor_box" />'); - this.$box.css('z-index', 100-this.uuid); // textarea test if (this.$source[0].tagName === 'TEXTAREA') this.opts.textareamode = true; // mobile @@ -1173,15 +1213,21 @@ }, buildBindKeyboard: function() { this.dblEnter = 0; - if (this.opts.dragUpload && this.opts.imageUpload !== false) + if (this.opts.dragUpload && (this.opts.imageUpload !== false || this.opts.s3 !== false)) { this.$editor.on('drop.redactor', $.proxy(this.buildEventDrop, this)); } + this.$editor.on('click.redactor', $.proxy(function() + { + this.selectall = false; + + }, this)); + this.$editor.on('input.redactor', $.proxy(this.sync, this)); this.$editor.on('paste.redactor', $.proxy(this.buildEventPaste, this)); this.$editor.on('keydown.redactor', $.proxy(this.buildEventKeydown, this)); this.$editor.on('keyup.redactor', $.proxy(this.buildEventKeyup, this)); @@ -1231,16 +1277,15 @@ return true; } this.bufferSet(); - var progress = $('<div id="redactor-progress"><span></span></div>'); - $(document.body).append(progress); + this.showProgressBar(); if (this.opts.s3 === false) { - this.dragUploadAjax(this.opts.imageUpload, file, true, progress, e, this.opts.imageUploadParam); + this.dragUploadAjax(this.opts.imageUpload, file, true, e, this.opts.imageUploadParam); } else { this.s3uploadFile(file); } @@ -1303,10 +1348,11 @@ buildEventClipboardUpload: function(e) { var event = e.originalEvent || e; this.clipboardFilePaste = false; + if (typeof(event.clipboardData) === 'undefined') return false; if (event.clipboardData.items) { var file = event.clipboardData.items[0].getAsFile(); if (file !== null) @@ -1336,17 +1382,42 @@ var block = this.getBlock(); var pre = false; this.callback('keydown', e); - // disabling cmd|ctrl + left - if (this.browser('mozilla') && ctrl && key === 37) + /* + firefox cmd+left/Cmd+right browser back/forward fix - + http://joshrhoderick.wordpress.com/2010/05/05/how-firefoxs-command-key-bug-kills-usability-on-the-mac/ + */ + if (this.browser('mozilla') && "modify" in window.getSelection()) { - e.preventDefault(); - return false; + if ((ctrl) && (e.keyCode===37 || e.keyCode===39)) + { + var selection = this.getSelection(); + var lineOrWord = (e.metaKey ? "line" : "word"); + if (e.keyCode===37) + { + selection.modify("extend","left",lineOrWord); + if (!e.shiftKey) + { + selection.collapseToStart(); + } + } + if (e.keyCode===39) + { + selection.modify("extend","right",lineOrWord); + if (!e.shiftKey) + { + selection.collapseToEnd(); + } + } + + e.preventDefault(); + } } + this.imageResizeHide(false); // pre & down if ((parent && $(parent).get(0).tagName === 'PRE') || (current && $(current).get(0).tagName === 'PRE')) { @@ -1369,11 +1440,11 @@ this.insertAfterLastElement(current, parent); } } // shortcuts setup - if (ctrl && !e.shiftKey) this.shortcuts(e, key); + this.shortcuts(e, key); // buffer setup if (ctrl && key === 90 && !e.shiftKey && !e.altKey) // z key { e.preventDefault(); @@ -1406,13 +1477,13 @@ { this.selectall = false; } // enter - if (key == this.keyCode.ENTER && !e.shiftKey && !e.ctrlKey && !e.metaKey ) + if (key == this.keyCode.ENTER && !e.shiftKey && !e.ctrlKey && !e.metaKey) { - // + // remove selected content on enter var range = this.getRange(); if (range && range.collapsed === false) { sel = this.getSelection(); if (sel.rangeCount) @@ -1471,15 +1542,47 @@ } else this.dblEnter++; } // pre - if (pre === true) return this.buildEventKeydownPre(e, current); + if (pre === true) + { + return this.buildEventKeydownPre(e, current); + } else { if (!this.opts.linebreaks) { + // lists exit + if (block && block.tagName == 'LI') + { + var listCurrent = this.getBlock(); + if (listCurrent !== false || listCurrent.tagName === 'LI') + { + var listText = $.trim($(block).text()); + var listCurrentText = $.trim($(listCurrent).text()); + if (listText == '' + && listCurrentText == '' + && $(listCurrent).next('li').size() == 0 + && $(listCurrent).parents('li').size() == 0) + { + this.bufferSet(); + + var $list = $(listCurrent).closest('ol, ul'); + $(listCurrent).remove(); + var node = $('<p>' + this.opts.invisibleSpace + '</p>'); + $list.after(node); + this.selectionStart(node); + + this.sync(); + this.callback('enter', e); + return false; + } + } + + } + // replace div to p if (block && this.opts.rBlockTest.test(block.tagName)) { // hit enter this.bufferSet(); @@ -1498,11 +1601,10 @@ } else if (block === false) { // hit enter this.bufferSet(); - var node = $('<p>' + this.opts.invisibleSpace + '</p>'); this.insertNode(node[0]); this.selectionStart(node); this.callback('enter', e); return false; @@ -1557,11 +1659,11 @@ { return this.buildEventKeydownTab(e, pre, key); } // delete zero-width space before the removing - if (key === this.keyCode.BACKSPACE) this.buildEventKeydownBackspace(current); + if (key === this.keyCode.BACKSPACE) this.buildEventKeydownBackspace(e, current, parent); }, buildEventKeydownPre: function(e, current) { e.preventDefault(); @@ -1605,29 +1707,44 @@ else this.indentingOutdent(); } return false; }, - buildEventKeydownBackspace: function(current) + buildEventKeydownBackspace: function(e, current, parent) { + // remove empty list in table + if (parent && current && parent.parentNode.tagName == 'TD' + && parent.tagName == 'UL' && current.tagName == 'LI' && $(parent).children('li').size() == 1) + { + var text = $(current).text().replace(/[\u200B-\u200D\uFEFF]/g, ''); + if (text == '') + { + var node = parent.parentNode; + $(parent).remove(); + this.selectionStart(node); + this.sync(); + return false; + } + } + if (typeof current.tagName !== 'undefined' && /^(H[1-6])$/i.test(current.tagName)) { var node; if (this.opts.linebreaks === false) node = $('<p>' + this.opts.invisibleSpace + '</p>'); else node = $('<br>' + this.opts.invisibleSpace); $(current).replaceWith(node); this.selectionStart(node); + this.sync(); } if (typeof current.nodeValue !== 'undefined' && current.nodeValue !== null) { - - //var value = $.trim(current.nodeValue.replace(/[^\u0000-\u1C7F]/g, '')); if (current.remove && current.nodeType === 3 && current.nodeValue.match(/[^\u200B]/g) == null) { - current.remove(); + $(current).prev().remove(); + this.sync(); } } }, buildEventKeydownInsertLineBreak: function(e) { @@ -1785,36 +1902,56 @@ } // iframe css this.iframeAddCss(); - if (this.opts.fullpage) this.setFullpageOnInit(this.$editor.html()); + if (this.opts.fullpage) + { + this.setFullpageOnInit(this.$source.val()); + } else this.set(this.content, true, false); this.buildOptions(); this.buildAfter(); }, // PLACEHOLDER - placeholderStart: function(html) + placeholderInit: function() { - if (this.isEmpty(html)) + if (this.opts.placeholder !== false) { - if (this.$element.attr('placeholder')) + this.placeholderText = this.opts.placeholder; + this.opts.placeholder = true; + } + else + { + if (typeof this.$element.attr('placeholder') == 'undefined' || this.$element.attr('placeholder') == '') { - this.opts.placeholder = this.$element.attr('placeholder'); + this.opts.placeholder = false; } - - if (this.opts.placeholder !== '') + else { - this.opts.focus = false; - this.placeholderOnFocus(); - this.placeholderOnBlur(); - - return this.placeholderGet(); + this.placeholderText = this.$element.attr('placeholder'); + this.opts.placeholder = true; } } + }, + placeholderStart: function(html) + { + if (this.opts.placeholder === false) + { + return false; + } + + if (this.isEmpty(html)) + { + this.opts.focus = false; + this.placeholderOnFocus(); + this.placeholderOnBlur(); + + return this.placeholderGet(); + } else { this.placeholderOnBlur(); } @@ -1828,11 +1965,18 @@ { this.$editor.on('blur.redactor_placeholder', $.proxy(this.placeholderBlur, this)); }, placeholderGet: function() { - return $('<span class="redactor_placeholder">').data('redactor', 'verified').attr('contenteditable', false).text(this.opts.placeholder); + var ph = $('<span class="redactor_placeholder">').data('redactor', 'verified') + .attr('contenteditable', false).text(this.placeholderText); + + if (this.opts.linebreaks === false) + { + return $('<p>').append(ph); + } + else return ph; }, placeholderBlur: function() { var html = this.get(); if (this.isEmpty(html)) @@ -1878,37 +2022,103 @@ // SHORTCUTS shortcuts: function(e, key) { + // disable browser's hot keys for bold and italic if (!this.opts.shortcuts) { - if (key === 66 || key === 73) e.preventDefault(); + if ((e.ctrlKey || e.metaKey) && (key === 66 || key === 73)) + { + e.preventDefault(); + } + return false; } - if (key === 77) this.shortcutsLoad(e, 'removeFormat'); // Ctrl + m - else if (key === 66) this.shortcutsLoad(e, 'bold'); // Ctrl + b - else if (key === 73) this.shortcutsLoad(e, 'italic'); // Ctrl + i + $.each(this.opts.shortcuts, $.proxy(function(str, command) + { + var keys = str.split(','); + for (var i in keys) + { + if (typeof keys[i] === 'string') + { + this.shortcutsHandler(e, $.trim(keys[i]), $.proxy(function() + { + eval(command); + }, this)); + } - else if (key === 74) this.shortcutsLoad(e, 'insertunorderedlist'); // Ctrl + j - else if (key === 75) this.shortcutsLoad(e, 'insertorderedlist'); // Ctrl + k + } - else if (key === 72) this.shortcutsLoad(e, 'superscript'); // Ctrl + h - else if (key === 76) this.shortcutsLoad(e, 'subscript'); // Ctrl + l + }, this)); + }, - shortcutsLoad: function(e, cmd) + shortcutsHandler: function(e, keys, origHandler) { - e.preventDefault(); - this.execCommand(cmd, false); + // based on https://github.com/jeresig/jquery.hotkeys + var hotkeysSpecialKeys = + { + 8: "backspace", 9: "tab", 10: "return", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", + 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", + 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 59: ";", 61: "=", + 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", + 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", + 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", + 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 173: "-", 186: ";", 187: "=", + 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'" + }; + + + var hotkeysShiftNums = + { + "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", + "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<", + ".": ">", "/": "?", "\\": "|" + }; + + keys = keys.toLowerCase().split(" "); + var special = hotkeysSpecialKeys[e.keyCode], + character = String.fromCharCode( e.which ).toLowerCase(), + modif = "", possible = {}; + + $.each([ "alt", "ctrl", "meta", "shift"], function(index, specialKey) + { + if (e[specialKey + 'Key'] && special !== specialKey) + { + modif += specialKey + '+'; + } + }); + + + if (special) + { + possible[modif + special] = true; + } + + if (character) + { + possible[modif + character] = true; + possible[modif + hotkeysShiftNums[character]] = true; + + // "$" can be triggered as "Shift+4" or "Shift+$" or just "$" + if (modif === "shift+") + { + possible[hotkeysShiftNums[character]] = true; + } + } + + for (var i = 0, l = keys.length; i < l; i++) + { + if (possible[keys[i]]) + { + e.preventDefault(); + return origHandler.apply(this, arguments); + } + } }, - shortcutsLoadFormat: function(e, cmd) - { - e.preventDefault(); - this.formatBlocks(cmd); - }, // FOCUS focus: function() { if (!this.browser('opera')) @@ -1982,25 +2192,37 @@ else this.toggleVisual(); }, toggleVisual: function() { var html = this.$source.hide().val(); - if (typeof this.modified !== 'undefined') { - this.modified = this.cleanRemoveSpaces(this.modified, false) !== this.cleanRemoveSpaces(html, false); + var modified = this.modified.replace(/\n/g, ''); + + var thtml = html.replace(/\n/g, ''); + thtml = this.cleanRemoveSpaces(thtml, false); + + this.modified = this.cleanRemoveSpaces(modified, false) !== thtml; } if (this.modified) { // don't remove the iframe even if cleared all. - if (this.opts.fullpage && html === '') this.setFullpageOnInit(html); + if (this.opts.fullpage && html === '') + { + this.setFullpageOnInit(html); + } else { this.set(html); - if (this.opts.fullpage) this.buildBindKeyboard(); + if (this.opts.fullpage) + { + this.buildBindKeyboard(); + } } + + this.callback('change', false, html); } if (this.opts.iframe) this.$frame.show(); else this.$editor.show(); @@ -2013,10 +2235,12 @@ this.observeStart(); this.buttonActiveVisual(); this.buttonInactive('html'); this.opts.visual = true; + + }, toggleCode: function(direct) { if (direct !== false) this.selectionSave(); @@ -2290,10 +2514,12 @@ }, airShow: function (e, keyboard) { if (!this.opts.air) return; + this.selectionSave(); + var left, top; $('.redactor_air').hide(); if (keyboard) { @@ -2395,20 +2621,26 @@ else { $item = $('<a href="#" class="' + btnObject.className + ' redactor_dropdown_' + btnName + '">' + btnObject.title + '</a>'); $item.on('click', $.proxy(function(e) { + if (this.opts.air) + { + this.selectionRestore(); + } + if (e.preventDefault) e.preventDefault(); if (this.browser('msie')) e.returnValue = false; if (btnObject.callback) btnObject.callback.call(this, btnName, $item, btnObject, e); if (btnObject.exec) this.execCommand(btnObject.exec, btnName); if (btnObject.func) this[btnObject.func](btnName); this.buttonActiveObserver(); if (this.opts.air) this.$air.fadeOut(100); + }, this)); } $dropdown.append($item); @@ -2420,27 +2652,25 @@ { e.preventDefault(); return false; } - var $dropdown = this.$toolbar.find('.redactor_dropdown_box_' + key); var $button = this.buttonGet(key); + // Always re-append it to the end of <body> so it always has the highest sub-z-index. + var $dropdown = $button.data('dropdown').appendTo(document.body); + if ($button.hasClass('dropact')) this.dropdownHideAll(); else { this.dropdownHideAll(); this.callback('dropdownShow', { dropdown: $dropdown, key: key, button: $button }); this.buttonActive(key); $button.addClass('dropact'); - var keyPosition = $button.position(); - if (this.toolbarFixed) - { - keyPosition = $button.offset(); - } + var keyPosition = $button.offset(); // fix right placement var dropdownWidth = $dropdown.width(); if ((keyPosition.left + dropdownWidth) > $(document).width()) { @@ -2449,14 +2679,14 @@ var left = keyPosition.left + 'px'; var btnHeight = $button.innerHeight(); var position = 'absolute'; - var top = btnHeight + 'px'; + var top = (btnHeight + this.opts.toolbarFixedTopOffset) + 'px'; if (this.opts.toolbarFixed && this.toolbarFixed) position = 'fixed'; - else if (!this.opts.air) top = keyPosition.top + btnHeight + 'px'; + else top = keyPosition.top + btnHeight + 'px'; $dropdown.css({ position: position, left: left, top: top }).show(); this.callback('dropdownShown', { dropdown: $dropdown, key: key, button: $button }); } @@ -2467,11 +2697,13 @@ }, this); $(document).one('click', hdlHideDropDown); this.$editor.one('click', hdlHideDropDown); + this.$editor.one('touchstart', hdlHideDropDown); + e.stopPropagation(); this.focusWithSaveScroll(); }, dropdownHideAll: function() { @@ -2541,11 +2773,11 @@ // dropdown if (btnObject.dropdown) { var $dropdown = $('<div class="redactor_dropdown redactor_dropdown_box_' + btnName + '" style="display: none;">'); - $dropdown.appendTo(this.$toolbar); + $button.data('dropdown', $dropdown); this.dropdownBuild($dropdown, btnObject.dropdown); } return $button; }, @@ -2679,26 +2911,16 @@ var $parent = $(parent).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]); if ($parent.length) { var align = $parent.css('text-align'); - - switch (align) + if (align == '') { - case 'right': - this.buttonActive('alignright'); - break; - case 'center': - this.buttonActive('aligncenter'); - break; - case 'justify': - this.buttonActive('alignjustify'); - break; - default: - this.buttonActive('alignleft'); - break; + align = 'left'; } + + this.buttonActive('align' + align); } }, // EXEC execPasteFrag: function(html) @@ -2871,11 +3093,12 @@ { data += this.outerHtml($('<p>').append(cloned.contents())); } else { - data += cloned.html() + '<br>'; + var clonedHtml = cloned.html().replace(/<br\s?\/?>$/i, ''); + data += clonedHtml + '<br>'; } if (i == 0) { $s.addClass('redactor-replaced').empty(); @@ -2897,15 +3120,53 @@ // insert lists else { var firstParent = $(this.getParent()).closest('td'); - this.document.execCommand(cmd); + if (this.browser('msie') && !this.isIe11() && this.opts.linebreaks) + { + var wrapper = this.selectionWrap('div'); + var wrapperHtml = $(wrapper).html(); + var tmpList = $('<ul>'); + if (cmd == 'insertorderedlist') + { + tmpList = $('<ol>'); + } + var tmpLi = $('<li>'); + + if ($.trim(wrapperHtml) == '') + { + tmpLi.append(wrapperHtml + '<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>'); + tmpList.append(tmpLi); + this.$editor.find('#selection-marker-1').replaceWith(tmpList); + } + else + { + tmpLi.append(wrapperHtml); + tmpList.append(tmpLi); + $(wrapper).replaceWith(tmpList); + } + } + else + { + this.document.execCommand(cmd); + } + var parent = this.getParent(); var $list = $(parent).closest('ol, ul'); + if (this.opts.linebreaks === false) + { + var listText = $.trim($list.text()); + if (listText == '') + { + $list.children('li').find('br').remove(); + $list.children('li').append('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>'); + } + } + if (firstParent.size() != 0) { $list.wrapAll('<td>'); } @@ -2924,11 +3185,11 @@ this.$editor.focus(); } } this.selectionRestore(); - + this.$editor.find('#selection-marker-1').removeAttr('id'); this.sync(); this.callback('execCommand', cmd, param); return; }, @@ -3052,11 +3313,11 @@ if (left <= 0) { // linebreaks if (this.opts.linebreaks === true && typeof($el.data('tagblock')) !== 'undefined') { - $el.replaceWith($el.html()); + $el.replaceWith($el.html() + '<br>'); } // all block tags else { $el.css('margin-left', ''); @@ -3175,11 +3436,10 @@ }, this)); } this.selectionRestore(); - this.sync(); }, // CLEAN cleanEmpty: function(html) @@ -3196,11 +3456,15 @@ return html; }, cleanConverters: function(html) { // convert div to p - if (this.opts.convertDivs) html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '<p$1>$2</p>'); + if (this.opts.convertDivs && !this.opts.gallery) + { + html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '<p$1>$2</p>'); + } + if (this.opts.paragraphy) html = this.cleanParagraphy(html); return html; }, cleanConvertProtected: function(html) @@ -3286,12 +3550,10 @@ return html; }, cleanRemoveEmptyTags: function(html) { - html = html.replace(/<span>([\w\W]*?)<\/span>/gi, '$1'); - // remove zero width-space html = html.replace(/[\u200B-\u200D\uFEFF]/g, ''); var etagsInline = ["<b>\\s*</b>", "<b>&nbsp;</b>", "<em>\\s*</em>"] var etags = ["<pre></pre>", "<blockquote>\\s*</blockquote>", "<dd></dd>", "<dt></dt>", "<ul></ul>", "<ol></ol>", "<li></li>", "<table></table>", "<tr></tr>", "<span>\\s*<span>", "<span>&nbsp;<span>", "<p>\\s*</p>", "<p></p>", "<p>&nbsp;</p>", "<p>\\s*<br>\\s*</p>", "<div>\\s*</div>", "<div>\\s*<br>\\s*</div>"]; @@ -3345,10 +3607,11 @@ html = html.replace(s, '{replace' + i + '}\n'); }); } html = html.replace(/<br \/>\s*<br \/>/gi, "\n\n"); + html = html.replace(/<br><br>/gi, "\n\n"); function R(str, mod, r) { return html.replace(new RegExp(str, mod), r); } @@ -3368,11 +3631,11 @@ { if (htmls.hasOwnProperty(i)) { if (htmls[i].search('{replace') == -1) { - htmls[i] = htmls[i].replace(/<p>\n\t<\/p>/gi, ''); + htmls[i] = htmls[i].replace(/<p>\n\t?<\/p>/gi, ''); htmls[i] = htmls[i].replace(/<p><\/p>/gi, ''); if (htmls[i] != '') { html += '<p>' + htmls[i].replace(/^\n+|\n+$/g, "") + "</p>"; @@ -3433,19 +3696,15 @@ else html = html.replace(/<strong>([\w\W]*?)<\/strong>/gi, '<b>$1</b>'); if (this.opts.italicTag === 'em') html = html.replace(/<i>([\w\W]*?)<\/i>/gi, '<em>$1</em>'); else html = html.replace(/<em>([\w\W]*?)<\/em>/gi, '<i>$1</i>'); - if (set !== true) - { - html = html.replace(/<strike>([\w\W]*?)<\/strike>/gi, '<del>$1</del>'); - } - else - { - html = html.replace(/<del>([\w\W]*?)<\/del>/gi, '<strike>$1</strike>'); - } + html = html.replace(/<span style="text-decoration: underline;">([\w\W]*?)<\/span>/gi, '<u>$1</u>'); + if (set !== true) html = html.replace(/<strike>([\w\W]*?)<\/strike>/gi, '<del>$1</del>'); + else html = html.replace(/<del>([\w\W]*?)<\/del>/gi, '<strike>$1</strike>'); + return html; }, cleanStripTags: function(html) { if (html == '' || typeof html == 'undefined') return html; @@ -3512,10 +3771,18 @@ $.each($elem, $.proxy(function(i,s) { this.removeEmptyAttr(s, 'style'); }, this)); + var $elem2 = this.$editor.find('b, strong, i, em, u, strike, del'); + $elem2.css('font-size', ''); + + $.each($elem2, $.proxy(function(i,s) + { + this.removeEmptyAttr(s, 'style'); + }, this)); + // When we paste text in Safari is wrapping inserted div (remove it) this.$editor.find('div[style="text-align: -webkit-auto;"]').contents().unwrap(); // Remove all styles in ul, ol, li this.$editor.find('ul, ol, li').removeAttr('style'); @@ -3630,11 +3897,11 @@ tag = this.cleanTag(tag); out = this.placeTag(tag, out); } } - return this.cleanFinish( out ); + return this.cleanFinish(out); }, cleanGetTabs: function() { var s = ''; for ( var j = 0; j < this.cleanlevel; j++ ) @@ -3644,14 +3911,14 @@ return s; }, cleanFinish: function(code) { - code = code.replace( /\n\s*\n/g, '\n' ); - code = code.replace( /^[\s\n]*/, '' ); - code = code.replace( /[\s\n]*$/, '' ); - code = code.replace( /<script(.*?)>\n<\/script>/gi, '<script$1></script>' ); + code = code.replace(/\n\s*\n/g, '\n'); + code = code.replace(/^[\s\n]*/, ''); + code = code.replace(/[\s\n]*$/, ''); + code = code.replace(/<script(.*?)>\n<\/script>/gi, '<script$1></script>'); this.cleanlevel = 0; return code; }, @@ -3736,10 +4003,15 @@ this.sync(); }, formatBlocks: function(tag) { + if (this.browser('mozilla') && this.isFocused()) + { + this.$editor.focus(); + } + this.bufferSet(); var nodes = this.getBlocks(); this.selectionSave(); @@ -3840,10 +4112,15 @@ }, // QUOTE formatQuote: function() { + if (this.browser('mozilla') && this.isFocused()) + { + this.$editor.focus(); + } + this.bufferSet(); // paragraphy if (this.opts.linebreaks === false) { @@ -4079,11 +4356,10 @@ }); this.selectionRestore(); this.sync(); }, - inlineSetClass: function(className) { var current = this.getCurrent(); if (!$(current).hasClass(className)) this.inlineMethods('addClass', className); }, @@ -4138,12 +4414,30 @@ { $(el)[type](attr, value); } else { - this.document.execCommand('fontSize', false, 4 ); + var cmd, arg = value; + switch (attr) + { + case 'font-size': + cmd = 'fontSize'; + arg = 4; + break; + case 'font-family': + cmd = 'fontName'; + break; + case 'color': + cmd = 'foreColor'; + break; + case 'background-color': + cmd = 'backColor'; + break; + } + this.document.execCommand(cmd, false, arg); + var fonts = this.$editor.find('font'); $.each(fonts, $.proxy(function(i, s) { this.inlineSetMethods(type, s, attr, value); @@ -4265,10 +4559,11 @@ inlineRemoveFormatReplace: function(el) { $(el).replaceWith($(el).contents()); }, + // INSERT insertHtml: function (html, sync) { var current = this.getCurrent(); var parent = current.parentNode; @@ -4359,13 +4654,13 @@ if (sel.getRangeAt && sel.rangeCount) { var range = sel.getRangeAt(0); range.deleteContents(); - var el = this.document.createElement('div'); + var el = document.createElement('div'); el.innerHTML = html; - var frag = this.document.createDocumentFragment(), node, lastNode; + var frag = document.createDocumentFragment(), node, lastNode; while ((node = el.firstChild)) { lastNode = frag.appendChild(node); } @@ -4413,12 +4708,25 @@ if ($html.length) html = $html.text(); this.focusWithSaveScroll(); - if (this.browser('msie') && !this.isIe11()) this.document.selection.createRange().pasteHTML(html); - else this.document.execCommand('inserthtml', false, html); + if (this.browser('msie')) + { + if (!this.isIe11()) + { + this.document.selection.createRange().pasteHTML(html); + } + else + { + this.execPasteFrag(html); + } + } + else + { + this.document.execCommand('inserthtml', false, html); + } this.sync(); }, insertNode: function(node) { @@ -4450,10 +4758,12 @@ range.setEndAfter(node); range.setStartAfter(node); sel.removeAllRanges(); sel.addRange(range); } + + return node; }, insertNodeToCaretPositionFromPoint: function(e, node) { var range; var x = e.clientX, y = e.clientY; @@ -4669,13 +4979,21 @@ // remove google docs marker 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"); + + html = html.replace(/<span[^>]*(font-style: italic; font-weight: bold|font-weight: bold; font-style: italic)[^>]*>/gi, '<span style="font-weight: bold;"><span style="font-style: italic;">'); + html = html.replace(/<span[^>]*font-style: italic[^>]*>/gi, '<span style="font-style: italic;">'); + html = html.replace(/<span[^>]*font-weight: bold[^>]*>/gi, '<span style="font-weight: bold;">'); + html = html.replace(/<span[^>]*text-decoration: underline[^>]*>/gi, '<span style="text-decoration: underline;">'); + // strip tags - html = this.cleanStripTags(html); + //html = this.cleanStripTags(html); + + // prevert html = html.replace(/<td>\u200b*<\/td>/gi, '[td]'); html = html.replace(/<td>&nbsp;<\/td>/gi, '[td]'); html = html.replace(/<td><br><\/td>/gi, '[td]'); html = html.replace(/<td(.*?)colspan="(.*?)"(.*?)>([\w\W]*?)<\/td>/gi, '[td colspan="$2"]$4[/td]'); @@ -4685,10 +5003,11 @@ html = html.replace(/<video(.*?)>([\w\W]*?)<\/video>/gi, '[video$1]$2[/video]'); html = html.replace(/<audio(.*?)>([\w\W]*?)<\/audio>/gi, '[audio$1]$2[/audio]'); html = html.replace(/<embed(.*?)>([\w\W]*?)<\/embed>/gi, '[embed$1]$2[/embed]'); html = html.replace(/<object(.*?)>([\w\W]*?)<\/object>/gi, '[object$1]$2[/object]'); html = html.replace(/<param(.*?)>/gi, '[param$1]'); + html = html.replace(/<img(.*?)>/gi, '[img$1]'); // remove classes html = html.replace(/ class="(.*?)"/gi, ''); @@ -4740,10 +5059,13 @@ else { html = html.replace(/<div><\/div>/gi, '<br />'); } + // strip tags + html = this.cleanStripTags(html); + if (this.currentOrParentIs('LI')) { html = html.replace(/<p>([\w\W]*?)<\/p>/gi, '$1<br>'); } else if (tablePaste === false) @@ -4984,23 +5306,24 @@ this.insertHtml('<img src="' + result + '" />'); } }, // BUFFER - bufferSet: function(html, selectionSave) + bufferSet: function(selectionSave) { - if (html !== undefined || html === false) this.opts.buffer.push(html); - else + if (selectionSave !== false) { - if (selectionSave !== false) - { - this.selectionSave(); - } + this.selectionSave(); + } - this.opts.buffer.push(this.$editor.html()); + this.opts.buffer.push(this.$editor.html()); + + if (selectionSave !== false) + { this.selectionRemoveMarkers('buffer'); } + }, bufferUndo: function() { if (this.opts.buffer.length === 0) { @@ -5044,15 +5367,17 @@ if (this.opts.observeLinks) this.observeLinks(); }, observeLinks: function() { this.$editor.find('a').on('click', $.proxy(this.linkObserver, this)); + this.$editor.on('click.redactor', $.proxy(function(e) { this.linkObserverTooltipClose(e); }, this)); + $(document).on('click.redactor', $.proxy(function(e) { this.linkObserverTooltipClose(e); }, this)); @@ -5062,18 +5387,33 @@ if (this.opts.observeImages === false) return false; this.$editor.find('img').each($.proxy(function(i, elem) { if (this.browser('msie')) $(elem).attr('unselectable', 'on'); - this.imageResize(elem); + var parent = $(elem).parent(); + if (!parent.hasClass('royalSlider') && !parent.hasClass('fotorama')) + { + this.imageResize(elem); + } + }, this)); + + // royalSlider and fotorama + this.$editor.find('.fotorama, .royalSlider').on('click', $.proxy(this.editGallery, this)); + }, linkObserver: function(e) { var $link = $(e.target); + var parent = $(e.target).parent(); + if (parent.hasClass('royalSlider') || parent.hasClass('fotorama')) + { + return; + } + if ($link.size() == 0 || $link[0].tagName !== 'A') return; var pos = $link.offset(); if (this.opts.iframe) { @@ -5397,10 +5737,16 @@ if (newnodes.length === 0) newnodes = [this.getBlock()]; return newnodes; }, + isInlineNode: function(node) + { + if (node.nodeType != 1) return false; + + return !this.rTestBlock.test(node.nodeName); + }, nodeTestBlocks: function(node) { return node.nodeType == 1 && this.rTestBlock.test(node.nodeName); }, tagTestBlock: function(tag) @@ -5550,11 +5896,14 @@ }, // SAVE & RESTORE selectionSave: function() { - if (!this.isFocused()) this.focusWithSaveScroll(); + if (!this.isFocused()) + { + this.focusWithSaveScroll(); + } if (!this.opts.rangy) { this.selectionCreateMarker(this.getRange()); } @@ -5587,14 +5936,23 @@ }, selectionSetMarker: function(range, node, type) { var boundaryRange = range.cloneRange(); - boundaryRange.collapse(type); + try { + boundaryRange.collapse(type); + boundaryRange.insertNode(node); + boundaryRange.detach(); + } + catch (e) + { + var html = this.opts.emptyHtml; + if (this.opts.linebreaks) html = '<br>'; - boundaryRange.insertNode(node); - boundaryRange.detach(); + this.$editor.prepend(html); + this.focus(); + } }, selectionRestore: function(replace, remove) { if (!this.opts.rangy) { @@ -5615,10 +5973,11 @@ this.focusWithSaveScroll(); } if (node1.length != 0 && node2.length != 0) { + this.selectionSet(node1[0], 0, node2[0], 0); } else if (node1.length != 0) { this.selectionSet(node1[0], 0, null, 0); @@ -5677,11 +6036,11 @@ }, this)); }, tableInsert: function() { - this.bufferSet(false, false); + this.bufferSet(false); var rows = $('#redactor_table_rows').val(), columns = $('#redactor_table_columns').val(), $table_box = $('<div></div>'), tableId = Math.floor(Math.random() * 99999), @@ -5709,10 +6068,15 @@ } $table_box.append($table); var html = $table_box.html(); + if (this.opts.linebreaks === false && this.browser('mozilla')) + { + html += '<p>' + this.opts.invisibleSpace + '</p>'; + } + this.modalClose(); this.selectionRestore(); var current = this.getBlock() || this.getCurrent(); @@ -5725,10 +6089,11 @@ $(current).after(html) } else { + this.insertHtmlAdvanced(html, false); } this.selectionRestore(); @@ -5773,10 +6138,11 @@ } } $current_tr.remove(); this.selectionRestore(); + $table.find('span#selection-marker-1').remove(); this.sync(); }, tableDeleteColumn: function() { var parent = this.getParent(); @@ -5807,10 +6173,11 @@ $(elem).find('td').eq(index).remove(); }, this)); this.selectionRestore(); + $table.find('span#selection-marker-1').remove(); this.sync(); }, tableAddHead: function() { var $table = $(this.getParent()).closest('table'); @@ -5933,10 +6300,23 @@ videoInsert: function () { var data = $('#redactor_insert_video_area').val(); data = this.cleanStripTags(data); + // parse if it is link on youtube & vimeo + var iframeStart = '<iframe width="500" height="281" src="', + iframeEnd = '" frameborder="0" allowfullscreen></iframe>'; + + if (data.match(reUrlYoutube)) + { + data = data.replace(reUrlYoutube, iframeStart + '//www.youtube.com/embed/$1' + iframeEnd); + } + else if (data.match(reUrlVimeo)) + { + data = data.replace(reUrlVimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd); + } + this.selectionRestore(); var current = this.getBlock() || this.getCurrent(); if (current) $(current).after(data) @@ -5952,10 +6332,44 @@ { this.selectionSave(); var callback = $.proxy(function() { + // Predefined links + if (this.opts.predefinedLinks !== false) + { + this.predefinedLinksStorage = {}; + var that = this; + $.getJSON(this.opts.predefinedLinks, function(data) + { + var $select = $('#redactor-predefined-links'); + $select .html(''); + $.each(data, function(key, val) + { + that.predefinedLinksStorage[key] = val; + $select.append($('<option>').val(key).html(val.name)); + }); + + $select.on('change', function() + { + var key = $(this).val(); + var name = '', url = ''; + if (key != 0) + { + name = that.predefinedLinksStorage[key].name; + url = that.predefinedLinksStorage[key].url; + } + + $('#redactor_link_url').val(url); + $('#redactor_link_url_text').val(name); + + }); + + $select.show(); + }); + } + this.insert_link_node = false; var sel = this.getSelection(); var url = '', text = '', target = ''; @@ -5997,12 +6411,13 @@ { $('#redactor_link_blank').prop('checked', true); } this.linkInsertPressed = false; - $('#redactor_insert_link_btn').click($.proxy(this.linkProcess, this)); + $('#redactor_insert_link_btn').on('click', $.proxy(this.linkProcess, this)); + setTimeout(function() { $('#redactor_link_url').focus(); }, 200); @@ -6024,11 +6439,11 @@ var link = $('#redactor_link_url').val(); var text = $('#redactor_link_url_text').val(); // mailto - if (link.search('@') != -1) + if (link.search('@') != -1 && /(http|ftp|https):\/\//i.test(link) === false) { link = 'mailto:' + link; } // url, not anchor else if (link.search('#') != 0) @@ -6198,10 +6613,11 @@ var callback = $.proxy(function() { // json if (this.opts.imageGetJson) { + $.getJSON(this.opts.imageGetJson, $.proxy(function(data) { var folders = {}, count = 0; // folders @@ -6259,11 +6675,11 @@ }, this)); } else { - $('#redactor-modal-tab-2').remove(); + $('#redactor-tab-control-2').remove(); } if (this.opts.imageUpload || this.opts.s3) { // dragupload @@ -6314,12 +6730,12 @@ $('#redactor_tabs').remove(); $('#redactor_tab3').show(); } else { - $('#redactor-modal-tab-1').remove(); - $('#redactor-modal-tab-2').addClass('redactor_tabs_act'); + $('#redactor-tab-control-1').remove(); + $('#redactor-tab-control-2').addClass('redactor_tabs_act'); $('#redactor_tab2').show(); } } if (!this.opts.imageTabLink && (this.opts.imageUpload || this.opts.imageGetJson)) @@ -6423,20 +6839,20 @@ this.modalClose(); this.sync(); }, imageSave: function(el) { + this.imageResizeHide(false); + var $el = $(el); var parent = $el.parent(); $el.attr('alt', $('#redactor_file_alt').val()); var floating = $('#redactor_form_image_align').val(); var margin = ''; - this.imageResizeHide(false); - if (floating === 'left') { margin = '0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin + ' 0'; $el.css({ 'float': 'left', 'margin': margin }); } @@ -6579,76 +6995,79 @@ var imageResizer = this.imageResizeControls($image); // resize var isResizing = false; - imageResizer.on('mousedown', function(e) + if (imageResizer !== false) { - isResizing = true; - e.preventDefault(); + imageResizer.on('mousedown', function(e) + { + isResizing = true; + e.preventDefault(); - ratio = $image.width() / $image.height(); + ratio = $image.width() / $image.height(); - start_x = Math.round(e.pageX - $image.eq(0).offset().left); - start_y = Math.round(e.pageY - $image.eq(0).offset().top); + start_x = Math.round(e.pageX - $image.eq(0).offset().left); + start_y = Math.round(e.pageY - $image.eq(0).offset().top); - }); + }); - $(this.document.body).on('mousemove', $.proxy(function(e) - { - if (isResizing) + $(this.document.body).on('mousemove', $.proxy(function(e) { - var mouse_x = Math.round(e.pageX - $image.eq(0).offset().left) - start_x; - var mouse_y = Math.round(e.pageY - $image.eq(0).offset().top) - start_y; + if (isResizing) + { + var mouse_x = Math.round(e.pageX - $image.eq(0).offset().left) - start_x; + var mouse_y = Math.round(e.pageY - $image.eq(0).offset().top) - start_y; - var div_h = $image.height(); + var div_h = $image.height(); - var new_h = parseInt(div_h, 10) + mouse_y; - var new_w = Math.round(new_h * ratio); + var new_h = parseInt(div_h, 10) + mouse_y; + var new_w = Math.round(new_h * ratio); - if (new_w > min_w) - { - $image.width(new_w); - - if (new_w < 100) + if (new_w > min_w) { - this.imageEditter.css({ - marginTop: '-7px', - marginLeft: '-13px', - fontSize: '9px', - padding: '3px 5px' - }); + $image.width(new_w); + + if (new_w < 100) + { + this.imageEditter.css({ + marginTop: '-7px', + marginLeft: '-13px', + fontSize: '9px', + padding: '3px 5px' + }); + } + else + { + this.imageEditter.css({ + marginTop: '-11px', + marginLeft: '-18px', + fontSize: '11px', + padding: '7px 10px' + }); + } } - else - { - this.imageEditter.css({ - marginTop: '-11px', - marginLeft: '-18px', - fontSize: '11px', - padding: '7px 10px' - }); - } - } - start_x = Math.round(e.pageX - $image.eq(0).offset().left); - start_y = Math.round(e.pageY - $image.eq(0).offset().top); + start_x = Math.round(e.pageX - $image.eq(0).offset().left); + start_y = Math.round(e.pageY - $image.eq(0).offset().top); - this.sync() - } - }, this)).on('mouseup', function() - { - isResizing = false; - }); + this.sync() + } + }, this)).on('mouseup', function() + { + isResizing = false; + }); + } this.$editor.on('keydown.redactor-image-delete', $.proxy(function(e) { var key = e.which; if (this.keyCode.BACKSPACE == key || this.keyCode.DELETE == key) { - this.bufferSet(false, false); + this.bufferSet(false); this.imageResizeHide(false); this.imageRemove($image); } }, this)); @@ -6711,29 +7130,38 @@ this.imageEdit($image); }, this)); imageBox.append(this.imageEditter); // resizer - var imageResizer = $('<span id="redactor-image-resizer" data-redactor="verified"></span>'); - imageResizer.css({ - position: 'absolute', - zIndex: 2, - lineHeight: 1, - cursor: 'nw-resize', - bottom: '-4px', - right: '-5px', - border: '1px solid #fff', - backgroundColor: '#000', - width: '8px', - height: '8px' - }); - imageResizer.attr('contenteditable', false); - imageBox.append(imageResizer); + if (this.opts.imageResizable) + { + var imageResizer = $('<span id="redactor-image-resizer" data-redactor="verified"></span>'); + imageResizer.css({ + position: 'absolute', + zIndex: 2, + lineHeight: 1, + cursor: 'nw-resize', + bottom: '-4px', + right: '-5px', + border: '1px solid #fff', + backgroundColor: '#000', + width: '8px', + height: '8px' + }); + imageResizer.attr('contenteditable', false); + imageBox.append(imageResizer); - imageBox.append($image); + imageBox.append($image); - return imageResizer; + return imageResizer; + } + else + { + imageBox.append($image); + + return false; + } }, imageThumbClick: function(e) { var img = '<img id="image-marker" src="' + $(e.target).attr('rel') + '" alt="' + $(e.target).attr('title') + '" />'; @@ -6797,18 +7225,35 @@ this.modalClose(); this.observeImages(); }, + // PROGRESS BAR + buildProgressBar: function() + { + if ($('#redactor-progress').size() != 0) return; + + this.$progressBar = $('<div id="redactor-progress"><span></span></div>'); + $(document.body).append(this.$progressBar); + }, + showProgressBar: function() + { + this.buildProgressBar(); + $('#redactor-progress').fadeIn(); + }, + hideProgressBar: function() + { + $('#redactor-progress').fadeOut(1500); + }, + // MODAL modalTemplatesInit: function() { $.extend( this.opts, { modal_file: String() + '<section id="redactor-modal-file-insert">' - + '<div id="redactor-progress" class="redactor-progress-inline" style="display: none;"><span></span></div>' + '<form id="redactorUploadFileForm" method="post" action="" enctype="multipart/form-data">' + '<label>' + this.opts.curLang.filename + '</label>' + '<input type="text" id="redactor_filename" class="redactor_input" />' + '<div style="margin-top: 7px;">' + '<input type="file" id="redactor_file" name="' + this.opts.fileUploadParam + '" />' @@ -6842,11 +7287,10 @@ + '<div id="redactor_tabs">' + '<a href="#" id="redactor-tab-control-1" class="redactor_tabs_act">' + this.opts.curLang.upload + '</a>' + '<a href="#" id="redactor-tab-control-2">' + this.opts.curLang.choose + '</a>' + '<a href="#" id="redactor-tab-control-3">' + this.opts.curLang.link + '</a>' + '</div>' - + '<div id="redactor-progress" class="redactor-progress-inline" style="display: none;"><span></span></div>' + '<form id="redactorInsertImageForm" method="post" action="" enctype="multipart/form-data">' + '<div id="redactor_tab1" class="redactor_tab">' + '<input type="file" id="redactor_file" name="' + this.opts.imageUploadParam + '" />' + '</div>' + '<div id="redactor_tab2" class="redactor_tab" style="display: none;">' @@ -6863,10 +7307,11 @@ + '<button class="redactor_modal_btn redactor_modal_action_btn" id="redactor_upload_btn">' + this.opts.curLang.insert + '</button>' + '</footer>', modal_link: String() + '<section id="redactor-modal-link-insert">' + + '<select id="redactor-predefined-links" style="width: 99.5%; display: none;"></select>' + '<label>URL</label>' + '<input type="text" class="redactor_input" id="redactor_link_url" />' + '<label>' + this.opts.curLang.text + '</label>' + '<input type="text" class="redactor_input" id="redactor_link_url_text" />' + '<label><input type="checkbox" id="redactor_link_blank"> ' + this.opts.curLang.link_new_tab + '</label>' @@ -6902,48 +7347,111 @@ }); }, modalInit: function(title, content, width, callback) { - var $redactorModalOverlay = $('#redactor_modal_overlay'); + this.modalSetOverlay(); - // modal overlay - if (!$redactorModalOverlay.length) + this.$redactorModalWidth = width; + this.$redactorModal = $('#redactor_modal'); + + if (!this.$redactorModal.length) { - this.$overlay = $redactorModalOverlay = $('<div id="redactor_modal_overlay" style="display: none;"></div>'); - $('body').prepend(this.$overlay); + this.$redactorModal = $('<div id="redactor_modal" style="display: none;" />'); + this.$redactorModal.append($('<div id="redactor_modal_close">&times;</div>')); + this.$redactorModal.append($('<header id="redactor_modal_header" />')); + this.$redactorModal.append($('<div id="redactor_modal_inner" />')); + this.$redactorModal.appendTo(document.body); } - if (this.opts.modalOverlay) + $('#redactor_modal_close').on('click', $.proxy(this.modalClose, this)); + $(document).on('keyup', $.proxy(this.modalCloseHandler, this)); + this.$editor.on('keyup', $.proxy(this.modalCloseHandler, this)); + + this.modalSetContent(content); + this.modalSetTitle(title); + this.modalSetDraggable(); + this.modalLoadTabs(); + this.modalOnCloseButton(); + this.modalSetButtonsWidth(); + + this.saveModalScroll = this.document.body.scrollTop; + if (this.opts.autoresize === false) { - $redactorModalOverlay.show().on('click', $.proxy(this.modalClose, this)); + this.saveModalScroll = this.$editor.scrollTop(); } - var $redactorModal = $('#redactor_modal'); + if (this.isMobile() === false) this.modalShowOnDesktop(); + else this.modalShowOnMobile(); - if (!$redactorModal.length) + // modal actions callback + if (typeof callback === 'function') { - this.$modal = $redactorModal = $('<div id="redactor_modal" style="display: none;"><div id="redactor_modal_close">&times;</div><header id="redactor_modal_header"></header><div id="redactor_modal_inner"></div></div>'); - $('body').append(this.$modal); + callback(); } - $('#redactor_modal_close').on('click', $.proxy(this.modalClose, this)); + // modal shown callback + setTimeout($.proxy(function() + { + this.callback('modalOpened', this.$redactorModal); - this.hdlModalClose = $.proxy(function(e) + }, this), 11); + + // fix bootstrap modal focus + $(document).off('focusin.modal'); + + // enter + this.$redactorModal.find('input[type=text]').on('keypress', $.proxy(function(e) { - if (e.keyCode === this.keyCode.ESC) + if (e.which === 13) { - this.modalClose(); - return false; + this.$redactorModal.find('.redactor_modal_action_btn').click(); + e.preventDefault(); } + }, this)); - }, this); + return this.$redactorModal; - $(document).keyup(this.hdlModalClose); - this.$editor.keyup(this.hdlModalClose); + }, + modalShowOnDesktop: function() + { + this.$redactorModal.css({ + position: 'fixed', + top: '-2000px', + left: '50%', + width: this.$redactorModalWidth + 'px', + marginLeft: '-' + (this.$redactorModalWidth / 2) + 'px' + }).show(); - // set content + this.modalSaveBodyOveflow = $(document.body).css('overflow'); + $(document.body).css('overflow', 'hidden'); + + setTimeout($.proxy(function() + { + var height = this.$redactorModal.outerHeight(); + this.$redactorModal.css({ + top: '50%', + height: 'auto', + minHeight: 'auto', + marginTop: '-' + (height + 10) / 2 + 'px' + }); + }, this), 15); + }, + modalShowOnMobile: function() + { + this.$redactorModal.css({ + position: 'fixed', + width: '100%', + height: '100%', + top: '0', + left: '0', + margin: '0', + minHeight: '300px' + }).show(); + }, + modalSetContent: function(content) + { this.modalcontent = false; if (content.indexOf('#') == 0) { this.modalcontent = $(content); $('#redactor_modal_inner').empty().append(this.modalcontent.html()); @@ -6952,133 +7460,85 @@ } else { $('#redactor_modal_inner').empty().append(content); } - - $redactorModal.find('#redactor_modal_header').html(title); - - // draggable - if (typeof $.fn.draggable !== 'undefined') - { - $redactorModal.draggable({ handle: '#redactor_modal_header' }); - $redactorModal.find('#redactor_modal_header').css('cursor', 'move'); - } - - var $redactor_tabs = $('#redactor_tabs'); - - // tabs - if ($redactor_tabs.length ) - { - var that = this; - $redactor_tabs.find('a').each(function(i, s) - { - i++; - $(s).on('click', function(e) - { - e.preventDefault(); - - $redactor_tabs.find('a').removeClass('redactor_tabs_act'); - $(this).addClass('redactor_tabs_act'); - $('.redactor_tab').hide(); - $('#redactor_tab' + i ).show(); - $('#redactor_tab_selected').val(i); - - if (that.isMobile() === false) - { - var height = $redactorModal.outerHeight(); - $redactorModal.css('margin-top', '-' + (height + 10) / 2 + 'px'); - } - }); - }); - } - - $redactorModal.find('.redactor_btn_modal_close').on('click', $.proxy(this.modalClose, this)); - - var buttons = $redactorModal.find('footer button'); + }, + modalSetTitle: function(title) + { + this.$redactorModal.find('#redactor_modal_header').html(title); + }, + modalSetButtonsWidth: function() + { + var buttons = this.$redactorModal.find('footer button').not('.redactor_modal_btn_hidden'); var buttonsSize = buttons.size(); if (buttonsSize > 0) { - $(buttons).css('width', (width/buttonsSize) + 'px') + $(buttons).css('width', (this.$redactorModalWidth/buttonsSize) + 'px') } - - // save scroll - if (this.opts.autoresize === true) + }, + modalOnCloseButton: function() + { + this.$redactorModal.find('.redactor_btn_modal_close').on('click', $.proxy(this.modalClose, this)); + }, + modalSetOverlay: function() + { + if (this.opts.modalOverlay) { - this.saveModalScroll = this.document.body.scrollTop; - } - else - { - this.saveModalScroll = this.$editor.scrollTop(); - } + this.$redactorModalOverlay = $('#redactor_modal_overlay'); + if (!this.$redactorModalOverlay.length) + { + this.$redactorModalOverlay = $('<div id="redactor_modal_overlay" style="display: none;"></div>'); + $('body').prepend(this.$redactorModalOverlay); + } - if (this.isMobile() === false) - { - $redactorModal.css({ - position: 'fixed', - top: '-2000px', - left: '50%', - width: width + 'px', - marginLeft: '-' + (width / 2) + 'px' - }).show(); - - this.modalSaveBodyOveflow = $(document.body).css('overflow'); - $(document.body).css('overflow', 'hidden'); - + this.$redactorModalOverlay.show().on('click', $.proxy(this.modalClose, this)); } - else + }, + modalSetDraggable: function() + { + if (typeof $.fn.draggable !== 'undefined') { - $redactorModal.css({ - position: 'fixed', - width: '100%', - height: '100%', - top: '0', - left: '0', - margin: '0', - minHeight: '300px' - }).show(); + this.$redactorModal.draggable({ handle: '#redactor_modal_header' }); + this.$redactorModal.find('#redactor_modal_header').css('cursor', 'move'); } + }, + modalLoadTabs: function() + { + var $redactor_tabs = $('#redactor_tabs'); + if (!$redactor_tabs.length) return false; - // modal actions callback - if (typeof callback === 'function') + var that = this; + $redactor_tabs.find('a').each(function(i, s) { - callback(); - } + i++; + $(s).on('click', function(e) + { + e.preventDefault(); - // modal shown callback - setTimeout($.proxy(function() - { - this.callback('modalOpened'); + $redactor_tabs.find('a').removeClass('redactor_tabs_act'); + $(this).addClass('redactor_tabs_act'); + $('.redactor_tab').hide(); + $('#redactor_tab' + i ).show(); + $('#redactor_tab_selected').val(i); - }, this), 11); + if (that.isMobile() === false) + { + var height = that.$redactorModal.outerHeight(); + that.$redactorModal.css('margin-top', '-' + (height + 10) / 2 + 'px'); + } + }); + }); - // fix bootstrap modal focus - $(document).off('focusin.modal'); - - if (this.isMobile() === false) + }, + modalCloseHandler: function(e) + { + if (e.keyCode === this.keyCode.ESC) { - setTimeout(function() - { - var height = $redactorModal.outerHeight(); - $redactorModal.css({ - top: '50%', - height: 'auto', - minHeight: 'auto', - marginTop: '-' + (height + 10) / 2 + 'px' - }); - }, 10); + this.modalClose(); + return false; } - - $redactorModal.find('input[type=text]').keypress(function(e) - { - if (e.which === 13 ) - { - $redactorModal.find('.redactor_modal_action_btn').click(); - e.preventDefault(); - } - }); - }, modalClose: function() { $('#redactor_modal_close').off('click', this.modalClose); $('#redactor_modal').fadeOut('fast', $.proxy(function() @@ -7096,12 +7556,12 @@ if (this.opts.modalOverlay) { $('#redactor_modal_overlay').hide().off('click', this.modalClose); } - $(document).unbind('keyup', this.hdlModalClose); - this.$editor.unbind('keyup', this.hdlModalClose); + $(document).off('keyup', this.modalCloseHandler); + this.$editor.off('keyup', this.modalCloseHandler); this.selectionRestore(); // restore scroll if (this.opts.autoresize && this.saveModalScroll) @@ -7159,15 +7619,16 @@ 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) { - $('#redactor-progress').fadeIn(); + that.showProgressBar(); callback(decodeURIComponent(this.responseText)); } else if(this.readyState == 4 && this.status != 200) { //setProgress(0, 'Could not contact signing script. Status = ' + this.status); @@ -7208,11 +7669,11 @@ { if (xhr.status == 200) { //setProgress(100, 'Upload completed.'); - $('#redactor-progress, #redactor-progress-drag').hide(); + this.hideProgressBar(); var s3image = url.split('?'); if (!s3image[0]) { @@ -7318,11 +7779,11 @@ $('#' + this.uploadOptions.trigger).click($.proxy(this.uploadSubmit, this)); } }, uploadSubmit: function(e) { - $('#redactor-progress').fadeIn(); + this.showProgressBar(); this.uploadForm(this.element, this.uploadFrame()); }, uploadFrame: function() { this.id = 'f' + Math.floor(Math.random() * 99999); @@ -7399,11 +7860,11 @@ else d = window.frames[this.id].document; // Success if (this.uploadOptions.success) { - $('#redactor-progress').hide(); + this.hideProgressBar(); if (typeof d !== 'undefined') { // Remove bizarre <pre> tag wrappers around our json data: var rawString = d.body.innerHTML; @@ -7475,16 +7936,16 @@ this.dropareabox.get(0).ondrop = $.proxy(function(e) { e.preventDefault(); this.dropareabox.removeClass('hover').addClass('drop'); + this.showProgressBar(); + this.dragUploadAjax(this.draguploadOptions.url, e.dataTransfer.files[0], false, e, this.draguploadOptions.uploadParam); - this.dragUploadAjax(this.draguploadOptions.url, e.dataTransfer.files[0], false, false, false, this.draguploadOptions.uploadParam); - }, this ); }, - dragUploadAjax: function(url, file, directupload, progress, e, uploadParam) + dragUploadAjax: function(url, file, directupload, e, uploadParam) { if (!directupload) { var xhr = $.ajaxSettings.xhr(); if (xhr.upload) @@ -7536,17 +7997,14 @@ data = data.replace(/^\[/, ''); data = data.replace(/\]$/, ''); var json = (typeof data === 'string' ? $.parseJSON(data) : data); + this.hideProgressBar(); + if (directupload) { - progress.fadeOut('slow', function() - { - $(this).remove(); - }); - var $img = $('<img>'); $img.attr('src', json.filelink).attr('id', 'drag-image-marker'); this.insertNodeToCaretPositionFromPoint(e, $img[0]); @@ -7629,10 +8087,25 @@ html = html.replace(/\s/g, ''); html = html.replace(/^<p>[^\W\w\D\d]*?<\/p>$/i, ''); return html == ''; }, + getInternetExplorerVersion: function() + { + var rv = false; + if (navigator.appName == 'Microsoft Internet Explorer') + { + var ua = navigator.userAgent; + var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); + if (re.exec(ua) != null) + { + rv = parseFloat(RegExp.$1); + } + } + + return rv; + }, isIe11: function() { return !!navigator.userAgent.match(/Trident\/7\./); }, browser: function(browser) @@ -7743,15 +8216,13 @@ Redactor.prototype.init.prototype = Redactor.prototype; // LINKIFY $.Redactor.fn.formatLinkify = function(protocol, convertLinks, convertImageLinks, convertVideoLinks, linkSize) { - var url1 = /(^|&lt;|\s)(www\..+?\..+?)([.),]?)(\s|\.\s+|\)|&gt;|$)/, - url2 = /(^|&lt;|\s)(((https?|ftp):\/\/|mailto:).+?)([.),]?)(\s|\.\s+|\)|&gt;|$)/, - urlImage = /(https?:\/\/.*\.(?:png|jpg|jpeg|gif))/gi, - urlYoutube = /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w.-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig, - urlVimeo = /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/; + var url = /(((https?|ftps?):\/\/)|www[.][^\s])(.+?\..+?)([.),]?)(\s|\.\s+|\)|$)/gi, + rProtocol = /(https?|ftp):\/\//i, + urlImage = /(https?:\/\/.*\.(?:png|jpg|jpeg|gif))/gi; var childNodes = (this.$editor ? this.$editor.get(0) : this).childNodes, i = childNodes.length; while (i--) { var n = childNodes[i]; @@ -7763,18 +8234,18 @@ if (convertVideoLinks && html) { var iframeStart = '<iframe width="500" height="281" src="', iframeEnd = '" frameborder="0" allowfullscreen></iframe>'; - if (html.match(urlYoutube)) + if (html.match(reUrlYoutube)) { - html = html.replace(urlYoutube, iframeStart + '//www.youtube.com/embed/$1' + iframeEnd); + html = html.replace(reUrlYoutube, iframeStart + '//www.youtube.com/embed/$1' + iframeEnd); $(n).after(html).remove(); } - else if (html.match(urlVimeo)) + else if (html.match(reUrlVimeo)) { - html = html.replace(urlVimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd); + html = html.replace(reUrlVimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd); $(n).after(html).remove(); } } // image @@ -7784,77 +8255,35 @@ $(n).after(html).remove(); } // link - if (convertLinks && html && (html.match(url1) || html.match(url2))) + if (convertLinks && html && html.match(url)) { - var found = true; - var first = true; + var matches = html.match(url); - while (found) + for (var i in matches) { - var href; - var url = url1; - var href1 = url1.exec(html); - var href2 = url2.exec(html); + var href = matches[i]; + var text = href; - if (href1 && href1[2] && href2 && href2[2]) - { - //process whichever came first sequentially *first* - var index1 = html.indexOf(href1[2]); - var index2 = html.indexOf(href2[2]); - if (index1 < index2) - { - href = href1; - url = url1; - } - else - { - href = href2; - url = url2 - } - } - else if (href1 && href1[2]) - { - href = href1; - url = url1 - } - else if (href2 && href2[2]) - { - href = href2; - url = url2 - } + var space = ''; + if (href.match(/\s$/) !== null) space = ' '; - found = (href && href.length); - if (found) - { - href = href[2]; - } + var addProtocol = protocol; + if (href.match(rProtocol) !== null) addProtocol = ''; - if (found && href && href.length > linkSize) - { - href = href.substring(0, linkSize) + '...'; - } + if (text.length > linkSize) text = text.substring(0, linkSize) + '...'; - if (first) - { - html = html.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); - } + text = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') - if (found && href) - { - if (url == url1) - { - html = html.replace(url1, '$1<a href="' + protocol + '$2">' + $.trim(href) + '</a>$3$4') - } - else - { - html = html.replace(url2, '$1<a href="$2">' + $.trim(href) + '</a>$5$6'); - } - } + /* + To handle URLs which may have $ characters in them, need to escape $ -> $$ to prevent $1 from getting treated as a backreference. + See http://gotofritz.net/blog/code-snippets/escaping-in-replace-strings-in-javascript/ + */ + var escapedBackReferences = text.replace('$', '$$$'); - first = false; + html = html.replace(href, '<a href=\"' + addProtocol + $.trim(href) + '\">' + $.trim(escapedBackReferences) + '</a>' + space); } $(n).after(html).remove(); } } \ No newline at end of file