app/assets/javascripts/annex/jquery.redactor.js in annex-cms-0.3.6 vs app/assets/javascripts/annex/jquery.redactor.js in annex-cms-0.3.7
- old
+ new
@@ -1,12 +1,12 @@
/*
- Redactor v10.0.2
- Updated: October 12, 2014
+ Redactor v10.0.6
+ Updated: January 7, 2015
http://imperavi.com/redactor/
- Copyright (c) 2009-2014, Imperavi LLC.
+ Copyright (c) 2009-2015, Imperavi LLC.
License: http://imperavi.com/redactor/license/
Usage: $('#content').redactor();
*/
@@ -92,15 +92,17 @@
return new Redactor.prototype.init(el, options);
}
// Functionality
$.Redactor = Redactor;
- $.Redactor.VERSION = '10.0.2';
- $.Redactor.modules = ['core', 'build', 'lang', 'toolbar', 'button', 'dropdown', 'code',
- 'clean', 'tidy', 'paragraphize', 'tabifier', 'focus', 'placeholder', 'autosave', 'buffer', 'indent', 'alignment', 'paste',
- 'keydown', 'keyup', 'shortcuts', 'line', 'list', 'block', 'inline', 'insert', 'caret', 'selection', 'observe',
- 'link', 'image', 'file', 'modal', 'progress', 'upload', 'utils'];
+ $.Redactor.VERSION = '10.0.6';
+ $.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'];
$.Redactor.opts = {
// settings
lang: 'en',
@@ -164,11 +166,11 @@
convertImageLinks: true,
convertVideoLinks: true,
preSpaces: 4, // or false
tabAsSpaces: false, // true or number of spaces
- tabFocus: true,
+ tabKey: true,
scrollTarget: false,
toolbar: true,
toolbarFixed: true,
@@ -339,10 +341,11 @@
TAB: 9,
CTRL: 17,
META: 91,
SHIFT: 16,
ALT: 18,
+ RIGHT: 39,
LEFT: 37,
LEFT_WIN: 91
},
// Initialization
@@ -421,100 +424,701 @@
{
this[module][methods[z]] = this[module][methods[z]].bind(this);
}
},
- core: function()
+ alignment: function()
{
return {
- getObject: function()
+ left: function()
{
- return $.extend({}, this);
+ this.alignment.set('');
},
- getEditor: function()
+ right: function()
{
- return this.$editor;
+ this.alignment.set('right');
},
- getBox: function()
+ center: function()
{
- return this.$box;
+ this.alignment.set('center');
},
- getElement: function()
+ justify: function()
{
- return this.$element;
+ this.alignment.set('justify');
},
- getTextarea: function()
+ set: function(type)
{
- return this.$textarea;
+ if (!this.utils.browser('msie')) this.$editor.focus();
+
+ this.buffer.set();
+ this.selection.save();
+
+ this.alignment.blocks = this.selection.getBlocks();
+ if (this.opts.linebreaks && this.alignment.blocks[0] === false)
+ {
+ this.alignment.setText(type);
+ }
+ else
+ {
+ this.alignment.setBlocks(type);
+ }
+
+ this.selection.restore();
+ this.code.sync();
},
- getToolbar: function()
+ setText: function(type)
{
- return (this.$toolbar) ? this.$toolbar : false;
+ var wrapper = this.selection.wrap('div');
+ $(wrapper).attr('data-tagblock', 'redactor');
+ $(wrapper).css('text-align', type);
},
- addEvent: function(name)
+ setBlocks: function(type)
{
- this.core.event = name;
+ $.each(this.alignment.blocks, $.proxy(function(i, el)
+ {
+ var $el = this.utils.getAlignmentElement(el);
+
+ if (!$el) return;
+
+ if (type === '' && typeof($el.data('tagblock')) !== 'undefined')
+ {
+ $el.replaceWith($el.html());
+ }
+ else
+ {
+ $el.css('text-align', type);
+ this.utils.removeEmptyAttr($el, 'style');
+ }
+
+
+ }, this));
+ }
+ };
+ },
+ autosave: function()
+ {
+ return {
+ 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)
+ {
+ this.autosaveInterval = setInterval($.proxy(this.autosave.load, this), this.opts.autosaveInterval * 1000);
+ }
},
- getEvent: function()
+ onChange: function()
{
- return this.core.event;
+ if (!this.opts.autosaveOnChange) return;
+
+ this.autosave.load();
},
- setCallback: function(type, e, data)
+ load: function()
{
- var callback = this.opts[type + 'Callback'];
- if ($.isFunction(callback))
+ var html = this.code.get();
+ if (this.autosave.html === html) return;
+ if (this.utils.isEmpty(html)) return;
+
+ $.ajax({
+ url: this.opts.autosave,
+ type: 'post',
+ data: 'name=' + this.autosave.name + '&' + this.autosave.name + '=' + escape(encodeURIComponent(html)),
+ success: $.proxy(function(data)
+ {
+ this.autosave.success(data, html);
+
+ }, this)
+ });
+ },
+ success: function(data, html)
+ {
+ var json;
+ try
{
- return (typeof data == 'undefined') ? callback.call(this, e) : callback.call(this, e, data);
+ json = $.parseJSON(data);
}
+ catch(e)
+ {
+ //data has already been parsed
+ json = data;
+ }
+
+ var callbackName = (typeof json.error == 'undefined') ? 'autosave' : 'autosaveError';
+
+ this.core.setCallback(callbackName, this.autosave.name, json);
+ this.autosave.html = html;
+ },
+ disable: function()
+ {
+ clearInterval(this.autosaveInterval);
+ }
+ };
+ },
+ block: function()
+ {
+ return {
+ formatting: function(name)
+ {
+ 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';
+
+ if (typeof this.formatting[name].clear != 'undefined')
+ {
+ this.block.clearStyle = true;
+ }
+
+ if (type) value = this.formatting[name][type];
+
+ this.block.format(this.formatting[name].tag, type, value);
+
+ },
+ format: function(tag, type, value)
+ {
+ if (tag == 'quote') tag = 'blockquote';
+
+ var formatTags = ['p', 'pre', 'blockquote', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
+ if ($.inArray(tag, formatTags) == -1) return;
+
+ this.block.isRemoveInline = (tag == 'pre' || tag.search(/h[1-6]/i) != -1);
+
+ // focus
+ if (!this.utils.browser('msie')) this.$editor.focus();
+
+ this.block.blocks = this.selection.getBlocks();
+
+ this.block.blocksSize = this.block.blocks.length;
+ this.block.type = type;
+ this.block.value = value;
+
+ this.buffer.set();
+ this.selection.save();
+
+ this.block.set(tag);
+
+ this.selection.restore();
+ this.code.sync();
+
+ },
+ set: function(tag)
+ {
+ this.selection.get();
+ this.block.containerTag = this.range.commonAncestorContainer.tagName;
+
+ if (this.range.collapsed)
+ {
+ this.block.setCollapsed(tag);
+ }
else
{
- return (typeof data == 'undefined') ? e : data;
+ this.block.setMultiple(tag);
}
},
- destroy: function()
+ setCollapsed: function(tag)
{
- this.core.setCallback('destroy');
+ var block = this.block.blocks[0];
+ if (block === false) return;
- // off events and remove data
- this.$element.off('.redactor').removeData('redactor');
- this.$editor.off('.redactor');
+ if (block.tagName == 'LI')
+ {
+ if (tag != 'blockquote') return;
- // common
- this.$editor.removeClass('redactor-editor redactor-linebreaks redactor-placeholder');
- this.$editor.removeAttr('contenteditable');
+ this.block.formatListToBlockquote();
+ return;
+ }
- var html = this.code.get();
+ var isContainerTable = (this.block.containerTag == 'TD' || this.block.containerTag == 'TH');
+ if (isContainerTable && !this.opts.linebreaks)
+ {
- if (this.build.isTextarea())
+ document.execCommand('formatblock', false, '<' + tag + '>');
+
+ block = this.selection.getBlock();
+ this.block.toggle($(block));
+
+ }
+ else if (block.tagName.toLowerCase() != tag)
{
- this.$box.after(this.$element);
- this.$box.remove();
- this.$element.val(html).show();
+ if (this.opts.linebreaks && tag == 'p')
+ {
+ $(block).prepend('<br>').append('<br>');
+ this.utils.replaceWithContents(block);
+ }
+ else
+ {
+ var $formatted = this.utils.replaceToTag(block, tag);
+
+ this.block.toggle($formatted);
+
+ 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();
+
+ this.block.formatTableWrapping($formatted);
+ }
}
+ else if (tag == 'blockquote' && block.tagName.toLowerCase() == tag)
+ {
+ // blockquote off
+ if (this.opts.linebreaks)
+ {
+ $(block).prepend('<br>').append('<br>');
+ this.utils.replaceWithContents(block);
+ }
+ else
+ {
+ var $el = this.utils.replaceToTag(block, 'p');
+ this.block.toggle($el);
+ }
+ }
+ else if (block.tagName.toLowerCase() == tag)
+ {
+ this.block.toggle($(block));
+ }
+
+ },
+ setMultiple: function(tag)
+ {
+ var block = this.block.blocks[0];
+ var isContainerTable = (this.block.containerTag == 'TD' || this.block.containerTag == 'TH');
+
+ if (block !== false && this.block.blocksSize === 1)
+ {
+ if (block.tagName.toLowerCase() == tag && tag == 'blockquote')
+ {
+ // blockquote off
+ if (this.opts.linebreaks)
+ {
+ $(block).prepend('<br>').append('<br>');
+ this.utils.replaceWithContents(block);
+ }
+ else
+ {
+ var $el = this.utils.replaceToTag(block, 'p');
+ this.block.toggle($el);
+ }
+ }
+ else if (block.tagName == 'LI')
+ {
+ if (tag != 'blockquote') return;
+
+ this.block.formatListToBlockquote();
+ }
+ else if (this.block.containerTag == 'BLOCKQUOTE')
+ {
+ this.block.formatBlockquote(tag);
+ }
+ else if (this.opts.linebreaks && ((isContainerTable) || (this.range.commonAncestorContainer != block)))
+ {
+ this.block.formatWrap(tag);
+ }
+ else
+ {
+ if (this.opts.linebreaks && tag == 'p')
+ {
+ $(block).prepend('<br>').append('<br>');
+ this.utils.replaceWithContents(block);
+ }
+ else if (block.tagName === 'TD')
+ {
+ this.block.formatWrap(tag);
+ }
+ else
+ {
+ var $formatted = this.utils.replaceToTag(block, tag);
+
+ this.block.toggle($formatted);
+
+ if (this.block.isRemoveInline) this.utils.removeInlineTags($formatted);
+ if (tag == 'p' || this.block.headTag) $formatted.find('p').contents().unwrap();
+ }
+ }
+ }
else
{
- this.$box.after(this.$editor);
- this.$box.remove();
- this.$element.html(html).show();
+ if (this.opts.linebreaks || tag != 'p')
+ {
+ if (tag == 'blockquote')
+ {
+ var count = 0;
+ for (var i = 0; i < this.block.blocksSize; i++)
+ {
+ if (this.block.blocks[i].tagName == 'BLOCKQUOTE') count++;
+ }
+
+ // only blockquote selected
+ if (count == this.block.blocksSize)
+ {
+ $.each(this.block.blocks, $.proxy(function(i,s)
+ {
+ if (this.opts.linebreaks)
+ {
+ $(s).prepend('<br>').append('<br>');
+ this.utils.replaceWithContents(s);
+ }
+ else
+ {
+ this.utils.replaceToTag(s, 'p');
+ }
+
+ }, this));
+
+ return;
+ }
+
+ }
+
+ this.block.formatWrap(tag);
+ }
+ else
+ {
+ var classSize = 0;
+ var toggleType = false;
+ if (this.block.type == 'class')
+ {
+ toggleType = 'toggle';
+ classSize = $(this.block.blocks).filter('.' + this.block.value).size();
+
+ if (this.block.blocksSize == classSize) toggleType = 'toggle';
+ else if (this.block.blocksSize > classSize) toggleType = 'set';
+ else if (classSize === 0) toggleType = 'set';
+
+ }
+
+ var exceptTags = ['ul', 'ol', 'li', 'td', 'th', 'dl', 'dt', 'dd'];
+ $.each(this.block.blocks, $.proxy(function(i,s)
+ {
+ if ($.inArray(s.tagName.toLowerCase(), exceptTags) != -1) return;
+
+ var $formatted = this.utils.replaceToTag(s, tag);
+
+ if (toggleType)
+ {
+ if (toggleType == 'toggle') this.block.toggle($formatted);
+ else if (toggleType == 'remove') this.block.remove($formatted);
+ else if (toggleType == 'set') this.block.setForce($formatted);
+ }
+ else this.block.toggle($formatted);
+
+ 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();
+
+
+ }, this));
+ }
}
+ },
+ setForce: function($el)
+ {
+ // remove style and class if the specified setting
+ if (this.block.clearStyle)
+ {
+ $el.removeAttr('class').removeAttr('style');
+ }
- // paste box
- if (this.$pasteBox) this.$pasteBox.remove();
+ if (this.block.type == 'class')
+ {
+ $el.addClass(this.block.value);
+ return;
+ }
+ else if (this.block.type == 'attr' || this.block.type == 'data')
+ {
+ $el.attr(this.block.value.name, this.block.value.value);
+ return;
+ }
+ },
+ toggle: function($el)
+ {
+ // remove style and class if the specified setting
+ if (this.block.clearStyle)
+ {
+ $el.removeAttr('class').removeAttr('style');
+ }
- // modal
- if (this.$modalBox) this.$modalBox.remove();
- if (this.$modalOverlay) this.$modalOverlay.remove();
+ if (this.block.type == 'class')
+ {
+ $el.toggleClass(this.block.value);
+ return;
+ }
+ else if (this.block.type == 'attr' || this.block.type == 'data')
+ {
+ if ($el.attr(this.block.value.name) == this.block.value.value)
+ {
+ $el.removeAttr(this.block.value.name);
+ }
+ else
+ {
+ $el.attr(this.block.value.name, this.block.value.value);
+ }
- // buttons tooltip
- $('.redactor-toolbar-tooltip').remove();
+ return;
+ }
+ else
+ {
+ $el.removeAttr('style class');
+ return;
+ }
+ },
+ remove: function($el)
+ {
+ $el.removeClass(this.block.value);
+ },
+ formatListToBlockquote: function()
+ {
+ var block = $(this.block.blocks[0]).closest('ul, ol');
- // autosave
- clearInterval(this.autosaveInterval);
+ $(block).find('ul, ol').contents().unwrap();
+ $(block).find('li').append($('<br>')).contents().unwrap();
+ var $el = this.utils.replaceToTag(block, 'blockquote');
+ this.block.toggle($el);
+ },
+ formatBlockquote: function(tag)
+ {
+ document.execCommand('outdent');
+ document.execCommand('formatblock', false, tag);
+
+ this.clean.clearUnverified();
+ this.$editor.find('p:empty').remove();
+
+ var formatted = this.selection.getBlock();
+
+ if (tag != 'p')
+ {
+ $(formatted).find('img').remove();
+ }
+
+ if (!this.opts.linebreaks)
+ {
+ this.block.toggle($(formatted));
+ }
+
+ this.$editor.find('ul, ol, tr, blockquote, p').each($.proxy(this.utils.removeEmpty, this));
+
+ if (this.opts.linebreaks && tag == 'p')
+ {
+ this.utils.replaceWithContents(formatted);
+ }
+
+ },
+ formatWrap: function(tag)
+ {
+ if (this.block.containerTag == 'UL' || this.block.containerTag == 'OL')
+ {
+ if (tag == 'blockquote')
+ {
+ this.block.formatListToBlockquote();
+ }
+ else
+ {
+ return;
+ }
+ }
+
+ var formatted = this.selection.wrap(tag);
+ if (formatted === false) return;
+
+ var $formatted = $(formatted);
+
+ this.block.formatTableWrapping($formatted);
+
+ var $elements = $formatted.find(this.opts.blockLevelElements.join(',') + ', td, table, thead, tbody, tfoot, th, tr');
+
+ if ((this.opts.linebreaks && tag == 'p') || tag == 'pre' || tag == 'blockquote')
+ {
+ $elements.append('<br />');
+ }
+
+ $elements.contents().unwrap();
+
+ if (tag != 'p' && tag != 'blockquote') $formatted.find('img').remove();
+
+ $.each(this.block.blocks, $.proxy(this.utils.removeEmpty, this));
+
+ $formatted.append(this.selection.getMarker(2));
+
+ if (!this.opts.linebreaks)
+ {
+ this.block.toggle($formatted);
+ }
+
+ this.$editor.find('ul, ol, tr, blockquote, p').each($.proxy(this.utils.removeEmpty, this));
+ $formatted.find('blockquote:empty').remove();
+
+ if (this.block.isRemoveInline)
+ {
+ this.utils.removeInlineTags($formatted);
+ }
+
+ if (this.opts.linebreaks && tag == 'p')
+ {
+ this.utils.replaceWithContents($formatted);
+ }
+
+ },
+ formatTableWrapping: function($formatted)
+ {
+ if ($formatted.closest('table').size() === 0) return;
+
+ if ($formatted.closest('tr').size() === 0) $formatted.wrap('<tr>');
+ if ($formatted.closest('td').size() === 0 && $formatted.closest('th').size() === 0)
+ {
+ $formatted.wrap('<td>');
+ }
+ },
+ removeData: function(name, value)
+ {
+ var blocks = this.selection.getBlocks();
+ $(blocks).removeAttr('data-' + name);
+
+ this.code.sync();
+ },
+ setData: function(name, value)
+ {
+ var blocks = this.selection.getBlocks();
+ $(blocks).attr('data-' + name, value);
+
+ this.code.sync();
+ },
+ toggleData: function(name, value)
+ {
+ var blocks = this.selection.getBlocks();
+ $.each(blocks, function()
+ {
+ if ($(this).attr('data-' + name))
+ {
+ $(this).removeAttr('data-' + name);
+ }
+ else
+ {
+ $(this).attr('data-' + name, value);
+ }
+ });
+ },
+ removeAttr: function(attr, value)
+ {
+ var blocks = this.selection.getBlocks();
+ $(blocks).removeAttr(attr);
+
+ this.code.sync();
+ },
+ setAttr: function(attr, value)
+ {
+ var blocks = this.selection.getBlocks();
+ $(blocks).attr(attr, value);
+
+ this.code.sync();
+ },
+ toggleAttr: function(attr, value)
+ {
+ var blocks = this.selection.getBlocks();
+ $.each(blocks, function()
+ {
+ if ($(this).attr(name))
+ {
+ $(this).removeAttr(name);
+ }
+ else
+ {
+ $(this).attr(name, value);
+ }
+ });
+ },
+ removeClass: function(className)
+ {
+ var blocks = this.selection.getBlocks();
+ $(blocks).removeClass(className);
+
+ this.utils.removeEmptyAttr(blocks, 'class');
+
+ this.code.sync();
+ },
+ setClass: function(className)
+ {
+ var blocks = this.selection.getBlocks();
+ $(blocks).addClass(className);
+
+ this.code.sync();
+ },
+ toggleClass: function(className)
+ {
+ var blocks = this.selection.getBlocks();
+ $(blocks).toggleClass(className);
+
+ this.code.sync();
}
};
},
+ buffer: function()
+ {
+ return {
+ set: function(type)
+ {
+ if (typeof type == 'undefined' || type == 'undo')
+ {
+ this.buffer.setUndo();
+ }
+ else
+ {
+ this.buffer.setRedo();
+ }
+ },
+ setUndo: function()
+ {
+ this.selection.save();
+ this.opts.buffer.push(this.$editor.html());
+ this.selection.restore();
+ },
+ setRedo: function()
+ {
+ this.selection.save();
+ this.opts.rebuffer.push(this.$editor.html());
+ this.selection.restore();
+ },
+ getUndo: function()
+ {
+ this.$editor.html(this.opts.buffer.pop());
+ },
+ getRedo: function()
+ {
+ this.$editor.html(this.opts.rebuffer.pop());
+ },
+ add: function()
+ {
+ this.opts.buffer.push(this.$editor.html());
+ },
+ undo: function()
+ {
+ if (this.opts.buffer.length === 0) return;
+
+ this.buffer.set('redo');
+ this.buffer.getUndo();
+
+ this.selection.restore();
+
+ setTimeout($.proxy(this.observe.load, this), 50);
+ },
+ redo: function()
+ {
+ if (this.opts.rebuffer.length === 0) return;
+
+ this.buffer.set('undo');
+ this.buffer.getRedo();
+
+ this.selection.restore();
+
+ setTimeout($.proxy(this.observe.load, this), 50);
+ }
+ };
+ },
build: function()
{
return {
run: function()
{
@@ -639,15 +1243,22 @@
{
e = e.originalEvent || e;
if (window.FormData === undefined || !e.dataTransfer) return true;
- var length = e.dataTransfer.files.length;
- if (length === 0) return true;
+ var length = e.dataTransfer.files.length;
+ if (length === 0)
+ {
+ this.code.sync();
+ setTimeout($.proxy(this.clean.clearUnverified, this), 1);
+ this.core.setCallback('drop', e);
+
+ return true;
+ }
else
{
- e.preventDefault();
+ e.preventDefault();
if (this.opts.dragImageUpload || this.opts.dragFileUpload)
{
var files = e.dataTransfer.files;
this.upload.directUpload(files[0], e);
@@ -739,28 +1350,34 @@
if (!this.opts.plugins) return;
if (!RedactorPlugins) return;
$.each(this.opts.plugins, $.proxy(function(i, s)
{
- if (RedactorPlugins[s])
+ if (typeof RedactorPlugins[s] === 'undefined') return;
+
+ if ($.inArray(s, $.Redactor.modules) !== -1)
{
- if (!$.isFunction(RedactorPlugins[s])) return;
+ $.error('Plugin name "' + s + '" matches the name of the Redactor\'s module.');
+ return;
+ }
- this[s] = RedactorPlugins[s]();
+ if (!$.isFunction(RedactorPlugins[s])) return;
- var methods = this.getModuleMethods(this[s]);
- var len = methods.length;
+ this[s] = RedactorPlugins[s]();
- // bind methods
- for (var z = 0; z < len; z++)
- {
- this[s][methods[z]] = this[s][methods[z]].bind(this);
- }
+ var methods = this.getModuleMethods(this[s]);
+ var len = methods.length;
- if ($.isFunction(this[s].init)) this[s].init();
+ // bind methods
+ for (var z = 0; z < len; z++)
+ {
+ this[s][methods[z]] = this[s][methods[z]].bind(this);
}
+ if ($.isFunction(this[s].init)) this[s].init();
+
+
}, this));
},
disableMozillaEditing: function()
@@ -773,373 +1390,16 @@
document.execCommand('enableInlineTableEditing', false, false);
} catch (e) {}
}
};
},
- lang: function()
- {
- return {
- load: function()
- {
- this.opts.curLang = this.opts.langs[this.opts.lang];
- },
- get: function(name)
- {
- return (typeof this.opts.curLang[name] != 'undefined') ? this.opts.curLang[name] : '';
- }
- };
- },
- toolbar: function()
- {
- return {
- init: function()
- {
- return {
- html:
- {
- title: this.lang.get('html'),
- func: 'code.toggle'
- },
- formatting:
- {
- title: this.lang.get('formatting'),
- dropdown:
- {
- p:
- {
- title: this.lang.get('paragraph'),
- func: 'block.format'
- },
- blockquote:
- {
- title: this.lang.get('quote'),
- func: 'block.format'
- },
- pre:
- {
- title: this.lang.get('code'),
- func: 'block.format'
- },
- h1:
- {
- title: this.lang.get('header1'),
- func: 'block.format'
- },
- h2:
- {
- title: this.lang.get('header2'),
- func: 'block.format'
- },
- h3:
- {
- title: this.lang.get('header3'),
- func: 'block.format'
- },
- h4:
- {
- title: this.lang.get('header4'),
- func: 'block.format'
- },
- h5:
- {
- title: this.lang.get('header5'),
- func: 'block.format'
- }
- }
- },
- bold:
- {
- title: this.lang.get('bold'),
- func: 'inline.format'
- },
- italic:
- {
- title: this.lang.get('italic'),
- func: 'inline.format'
- },
- deleted:
- {
- title: this.lang.get('deleted'),
- func: 'inline.format'
- },
- underline:
- {
- title: this.lang.get('underline'),
- func: 'inline.format'
- },
- unorderedlist:
- {
- title: '• ' + this.lang.get('unorderedlist'),
- func: 'list.toggle'
- },
- orderedlist:
- {
- title: '1. ' + this.lang.get('orderedlist'),
- func: 'list.toggle'
- },
- outdent:
- {
- title: '< ' + this.lang.get('outdent'),
- func: 'indent.decrease'
- },
- indent:
- {
- title: '> ' + this.lang.get('indent'),
- func: 'indent.increase'
- },
- image:
- {
- title: this.lang.get('image'),
- func: 'image.show'
- },
- file:
- {
- title: this.lang.get('file'),
- func: 'file.show'
- },
- link:
- {
- title: this.lang.get('link'),
- dropdown:
- {
- link:
- {
- title: this.lang.get('link_insert'),
- func: 'link.show'
- },
- unlink:
- {
- title: this.lang.get('unlink'),
- func: 'link.unlink'
- }
- }
- },
- alignment:
- {
- title: this.lang.get('alignment'),
- dropdown:
- {
- left:
- {
- title: this.lang.get('align_left'),
- func: 'alignment.left'
- },
- center:
- {
- title: this.lang.get('align_center'),
- func: 'alignment.center'
- },
- right:
- {
- title: this.lang.get('align_right'),
- func: 'alignment.right'
- },
- justify:
- {
- title: this.lang.get('align_justify'),
- func: 'alignment.justify'
- }
- }
- },
- horizontalrule:
- {
- title: this.lang.get('horizontalrule'),
- func: 'line.insert'
- }
- };
- },
- build: function()
- {
- this.toolbar.hideButtons();
- this.toolbar.hideButtonsOnMobile();
- this.toolbar.isButtonSourceNeeded();
-
- if (this.opts.buttons.length === 0) return;
-
- this.$toolbar = this.toolbar.createContainer();
-
- this.toolbar.setOverflow();
- this.toolbar.append();
- this.toolbar.setFormattingTags();
- this.toolbar.loadButtons();
- this.toolbar.setTabindex();
- this.toolbar.setFixed();
-
- // buttons response
- if (this.opts.activeButtons)
- {
- this.$editor.on('mouseup.redactor keyup.redactor focus.redactor', $.proxy(this.observe.buttons, this));
- }
-
- },
- createContainer: function()
- {
- return $('<ul>').addClass('redactor-toolbar').attr('id', 'redactor-toolbar-' + this.uuid);
- },
- setFormattingTags: function()
- {
- $.each(this.opts.toolbar.formatting.dropdown, $.proxy(function (i, s)
- {
- if ($.inArray(i, this.opts.formatting) == -1) delete this.opts.toolbar.formatting.dropdown[i];
- }, this));
-
- },
- loadButtons: function()
- {
- $.each(this.opts.buttons, $.proxy(function(i, btnName)
- {
- if (!this.opts.toolbar[btnName]) return;
-
- if (this.opts.fileUpload === false && btnName === 'file') return true;
- if ((this.opts.imageUpload === false && this.opts.s3 === false) && btnName === 'image') return true;
-
- var btnObject = this.opts.toolbar[btnName];
- this.$toolbar.append($('<li>').append(this.button.build(btnName, btnObject)));
-
- }, this));
- },
- append: function()
- {
- if (this.opts.toolbarExternal)
- {
- this.$toolbar.addClass('redactor-toolbar-external');
- $(this.opts.toolbarExternal).html(this.$toolbar);
- }
- else
- {
- this.$box.prepend(this.$toolbar);
- }
- },
- setFixed: function()
- {
- if (this.utils.isMobile()) 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));
-
- },
- setTabindex: function()
- {
- this.$toolbar.find('a').attr('tabindex', '-1');
- },
- setOverflow: function()
- {
- if (this.utils.isMobile() && this.opts.toolbarOverflow)
- {
- this.$toolbar.addClass('redactor-toolbar-overflow');
- }
- },
- isButtonSourceNeeded: function()
- {
- if (this.opts.buttonSource) return;
-
- var index = this.opts.buttons.indexOf('html');
- if (index !== -1)
- {
- this.opts.buttons.splice(index, 1);
- }
- },
- hideButtons: function()
- {
- if (this.opts.buttonsHide.length === 0) return;
-
- $.each(this.opts.buttonsHide, $.proxy(function(i, s)
- {
- var index = this.opts.buttons.indexOf(s);
- this.opts.buttons.splice(index, 1);
-
- }, this));
- },
- hideButtonsOnMobile: function()
- {
- if (!this.utils.isMobile() && this.opts.buttonsHideOnMobile.length === 0) return;
-
- $.each(this.opts.buttonsHideOnMobile, $.proxy(function(i, s)
- {
- var index = this.opts.buttons.indexOf(s);
- this.opts.buttons.splice(index, 1);
-
- }, this));
- },
- observeScroll: function()
- {
- var scrollTop = $(this.opts.toolbarFixedTarget).scrollTop();
- var boxTop = 1;
-
- if (this.opts.toolbarFixedTarget === document)
- {
- boxTop = this.$box.offset().top;
- }
-
- if (scrollTop > boxTop)
- {
- this.toolbar.observeScrollEnable(scrollTop, boxTop);
- }
- else
- {
- this.toolbar.observeScrollDisable();
- }
- },
- observeScrollEnable: function(scrollTop, boxTop)
- {
- var top = this.opts.toolbarFixedTopOffset + scrollTop - boxTop;
- var left = 0;
- var end = boxTop + this.$box.height() + 30;
- var width = this.$box.innerWidth();
-
- this.$toolbar.addClass('toolbar-fixed-box');
- this.$toolbar.css({
- position: 'absolute',
- width: width,
- top: top + 'px',
- left: left
- });
-
- this.toolbar.setDropdownsFixed();
- this.$toolbar.css('visibility', (scrollTop < end) ? 'visible' : 'hidden');
- },
- observeScrollDisable: function()
- {
- this.$toolbar.css({
- position: 'relative',
- width: 'auto',
- top: 0,
- left: 0,
- visibility: 'visible'
- });
-
- this.toolbar.unsetDropdownsFixed();
- this.$toolbar.removeClass('toolbar-fixed-box');
-
- },
- setDropdownsFixed: function()
- {
- var self = this;
- $('.redactor-dropdown').each(function()
- {
- $(this).css({ position: 'fixed', top: self.$toolbar.innerHeight() + self.opts.toolbarFixedTopOffset });
- });
- },
- unsetDropdownsFixed: function()
- {
- var self = this;
- $('.redactor-dropdown').each(function()
- {
- var top = (self.$toolbar.innerHeight() + self.$toolbar.offset().top) + 'px';
- $(this).css({ position: 'absolute', top: top });
- });
- }
- };
- },
button: function()
{
return {
build: function(btnName, btnObject)
{
- var $button = $('<a href="#" class="re-icon re-' + btnName + '" rel="' + btnName + '" />');
+ var $button = $('<a href="#" class="re-icon re-' + btnName + '" rel="' + btnName + '" />').attr('tabindex', '-1');
if (btnObject.func || btnObject.command || btnObject.dropdown)
{
$button.on('touchstart click', $.proxy(function(e)
{
@@ -1205,10 +1465,12 @@
});
},
onClick: function(e, btnName, type, callback)
{
+ this.button.caretOffset = this.caret.getOffset();
+
e.preventDefault();
if (this.utils.browser('msie')) e.returnValue = false;
if (type == 'command')
@@ -1220,10 +1482,11 @@
this.dropdown.show(e, btnName);
}
else
{
var func;
+
if ($.isFunction(callback))
{
callback.call(this, btnName);
this.observe.buttons(e, btnName);
}
@@ -1240,11 +1503,10 @@
{
this[callback](btnName);
this.observe.buttons(e, btnName);
}
}
-
},
get: function(key)
{
return this.$toolbar.find('a.re-' + key);
},
@@ -1363,303 +1625,213 @@
{
this.button.get(key).remove();
}
};
},
- dropdown: function()
+ caret: function()
{
return {
- build: function(name, $dropdown, dropdownObject)
+ setStart: function(node)
{
- if (name == 'formatting' && this.opts.formattingAdd)
+ // inline tag
+ if (!this.utils.isBlock(node))
{
- $.each(this.opts.formattingAdd, $.proxy(function(i,s)
- {
- var name = s.tag;
- if (typeof s.class != 'undefined')
- {
- name = name + '-' + s.class;
- }
+ var space = this.utils.createSpaceElement();
- s.type = (this.utils.isBlockTag(s.tag)) ? 'block' : 'inline';
- var 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,
- attr: s.attr,
- data: s.data
- };
-
- dropdownObject[name] = {
- func: func,
- title: s.title
- };
-
- }, this));
-
+ $(node).prepend(space);
+ this.caret.setEnd(space);
}
-
- $.each(dropdownObject, $.proxy(function(btnName, btnObject)
+ else
{
- var $item = $('<a href="#" class="redactor-dropdown-' + btnName + '">' + btnObject.title + '</a>');
- if (name == 'formatting') $item.addClass('redactor-formatting-' + btnName);
-
- $item.on('click', $.proxy(function(e)
- {
- var type = 'func';
- var callback = btnObject.func;
- if (btnObject.command)
- {
- type = 'command';
- callback = btnObject.command;
- }
- else if (btnObject.dropdown)
- {
- type = 'dropdown';
- callback = btnObject.dropdown;
- }
-
- this.button.onClick(e, btnName, type, callback);
-
- }, this));
-
- $dropdown.append($item);
-
- }, this));
+ this.caret.set(node, 0, node, 0);
+ }
},
- show: function(e, key)
+ setEnd: function(node)
{
- if (!this.opts.visual)
- {
- e.preventDefault();
- return false;
- }
+ this.caret.set(node, 1, node, 1);
+ },
+ set: function(orgn, orgo, focn, foco)
+ {
+ // focus
+ if (!this.utils.browser('msie')) this.$editor.focus();
- var $button = this.button.get(key);
+ orgn = orgn[0] || orgn;
+ focn = focn[0] || focn;
- // 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'))
+ if (this.utils.isBlockTag(orgn.tagName) && orgn.innerHTML === '')
{
- this.dropdown.hideAll();
+ orgn.innerHTML = this.opts.invisibleSpace;
}
- else
+
+ if (orgn.tagName == 'BR' && this.opts.linebreaks === false)
{
- this.dropdown.hideAll();
- this.core.setCallback('dropdownShow', { dropdown: $dropdown, key: key, button: $button });
+ var par = $(this.opts.emptyHtml)[0];
+ $(orgn).replaceWith(par);
+ orgn = par;
+ focn = orgn;
+ }
- this.button.setActive(key);
+ this.selection.get();
- $button.addClass('dropact');
+ try {
+ this.range.setStart(orgn, orgo);
+ this.range.setEnd(focn, foco);
+ }
+ catch (e) {}
- var keyPosition = $button.offset();
+ this.selection.addRange();
+ },
+ setAfter: function(node)
+ {
+ try {
+ var tag = $(node)[0].tagName;
- // fix right placement
- var dropdownWidth = $dropdown.width();
- if ((keyPosition.left + dropdownWidth) > $(document).width())
+ // inline tag
+ if (tag != 'BR' && !this.utils.isBlock(node))
{
- keyPosition.left -= dropdownWidth;
- }
+ var space = this.utils.createSpaceElement();
- var left = keyPosition.left + 'px';
- if (this.$toolbar.hasClass('toolbar-fixed-box'))
- {
- $dropdown.css({ position: 'fixed', left: left, top: this.$toolbar.innerHeight() + this.opts.toolbarFixedTopOffset }).show();
+ $(node).after(space);
+ this.caret.setEnd(space);
}
else
{
- var top = ($button.innerHeight() + keyPosition.top) + 'px';
-
- $dropdown.css({ position: 'absolute', left: left, top: top }).show();
+ if (tag != 'BR' && this.utils.browser('msie'))
+ {
+ this.caret.setStart($(node).next());
+ }
+ else
+ {
+ this.caret.setAfterOrBefore(node, 'after');
+ }
}
-
-
- 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));
-
- $dropdown.on('mouseover', function() { $('html').css('overflow', 'hidden'); });
- $dropdown.on('mouseout', function() { $('html').css('overflow', ''); });
-
- e.stopPropagation();
+ catch (e) {
+ var space = this.utils.createSpaceElement();
+ $(node).after(space);
+ this.caret.setEnd(space);
+ }
},
- hideAll: function()
+ setBefore: function(node)
{
- this.$toolbar.find('a.dropact').removeClass('redactor-act').removeClass('dropact');
-
- $('.redactor-dropdown').hide();
- this.core.setCallback('dropdownHide');
- },
- hide: function (e)
- {
- var $dropdown = $(e.target);
- if (!$dropdown.hasClass('dropact'))
+ // block tag
+ if (this.utils.isBlock(node))
{
- $dropdown.removeClass('dropact');
- this.dropdown.hideAll();
+ this.caret.setEnd($(node).prev());
}
- }
- };
- },
- code: function()
- {
- return {
- set: function(html)
- {
- html = $.trim(html.toString());
-
- // clean
- html = this.clean.onSet(html);
-
- this.$editor.html(html);
- this.code.sync();
-
- setTimeout($.proxy(this.buffer.add, this), 15);
- if (this.start === false) this.observe.load();
-
+ else
+ {
+ this.caret.setAfterOrBefore(node, 'before');
+ }
},
- get: function()
+ setAfterOrBefore: function(node, type)
{
- var code = this.$textarea.val();
+ // focus
+ if (!this.utils.browser('msie')) this.$editor.focus();
- // indent code
- code = this.tabifier.get(code);
+ node = node[0] || node;
- return code;
- },
- sync: function()
- {
- setTimeout($.proxy(this.code.startSync, this), 10);
- },
- startSync: function()
- {
- var html = this.$editor.html();
+ this.selection.get();
- // is there a need to synchronize
- if (this.code.syncCode && this.code.syncCode == html)
+ if (type == 'after')
{
- // do not sync
- return;
- }
+ try {
- // save code
- this.code.syncCode = html;
-
- // before clean callback
- html = this.core.setCallback('syncBefore', html);
-
- // clean
- html = this.clean.onSync(html);
-
- // set code
- this.$textarea.val(html);
-
- // after sync callback
- this.core.setCallback('sync', html);
-
- if (this.start === false)
+ this.range.setStartAfter(node);
+ this.range.setEndAfter(node);
+ }
+ catch (e) {}
+ }
+ else
{
- this.core.setCallback('change', html);
+ try {
+ this.range.setStartBefore(node);
+ this.range.setEndBefore(node);
+ }
+ catch (e) {}
}
- this.start = false;
- // autosave on change
- this.autosave.onChange();
+ this.range.collapse(false);
+ this.selection.addRange();
},
- toggle: function()
+ getOffsetOfElement: function(node)
{
- if (this.opts.visual)
- {
- this.code.showCode();
- }
- else
- {
- this.code.showVisual();
- }
- },
- showCode: function()
- {
- this.code.offset = this.caret.getOffset();
- var scroll = $(window).scrollTop();
+ node = node[0] || node;
- var height = this.$editor.innerHeight();
+ this.selection.get();
- this.$editor.hide();
+ var cloned = this.range.cloneRange();
+ cloned.selectNodeContents(node);
+ cloned.setEnd(this.range.endContainer, this.range.endOffset);
- var html = this.$textarea.val();
- this.modified = this.clean.removeSpaces(html);
+ return $.trim(cloned.toString()).length;
+ },
+ getOffset: function()
+ {
+ var offset = 0;
+ var sel = window.getSelection();
- // indent code
- html = this.tabifier.get(html);
+ if (sel.rangeCount > 0)
+ {
+ var range = window.getSelection().getRangeAt(0);
+ var caretRange = range.cloneRange();
+ caretRange.selectNodeContents(this.$editor[0]);
+ caretRange.setEnd(range.endContainer, range.endOffset);
+ offset = caretRange.toString().length;
+ }
- this.$textarea.val(html).height(height).show().focus();
- this.$textarea.on('keydown.redactor-textarea-indenting', this.code.textareaIndenting);
-
- $(window).scrollTop(scroll);
-
- this.opts.visual = false;
-
- this.button.setInactiveInCode();
- this.button.setActive('html');
- this.core.setCallback('source', html);
+ return offset;
},
- showVisual: function()
+ setOffset: function(start, end)
{
- if (this.opts.visual) return;
+ if (typeof end == 'undefined') end = start;
+ if (!this.focus.isFocused()) this.focus.setStart();
- var html = this.$textarea.hide().val();
+ var sel = this.selection.get();
+ var node, offset = 0;
+ var walker = document.createTreeWalker(this.$editor[0], NodeFilter.SHOW_TEXT, null, null);
- if (this.modified !== this.clean.removeSpaces(html))
+ while (node = walker.nextNode())
{
- this.code.set(html);
- }
+ offset += node.nodeValue.length;
+ if (offset > start)
+ {
+ this.range.setStart(node, node.nodeValue.length + start - offset);
+ start = Infinity;
+ }
- this.$editor.show();
-
- if (!this.utils.isEmpty(html))
- {
- this.placeholder.remove();
+ if (offset >= end)
+ {
+ this.range.setEnd(node, node.nodeValue.length + end - offset);
+ break;
+ }
}
- this.caret.setOffset(this.code.offset);
-
- this.$textarea.off('keydown.redactor-textarea-indenting');
-
- this.button.setActiveInVisual();
- this.button.setInactive('html');
-
- this.observe.load();
- this.opts.visual = true;
+ this.range.collapse(false);
+ this.selection.addRange();
},
- textareaIndenting: function(e)
+ setToPoint: function(start, end)
{
- if (e.keyCode !== 9) return true;
-
- var $el = this.$textarea;
- var start = $el.get(0).selectionStart;
- $el.val($el.val().substring(0, start) + "\t" + $el.val().substring($el.get(0).selectionEnd));
- $el.get(0).selectionStart = $el.get(0).selectionEnd = start + 1;
-
- return false;
+ this.caret.setOffset(start, end);
+ },
+ getCoords: function()
+ {
+ return this.caret.getOffset();
}
};
},
clean: function()
{
return {
onSet: function(html)
{
html = this.clean.savePreCode(html);
+ // 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, '$');
html = html.replace(/”/g, '"');
html = html.replace(/‘/g, '\'');
html = html.replace(/’/g, '\'');
@@ -1669,12 +1841,26 @@
// save form tag
html = this.clean.saveFormTags(html);
// convert font tag to span
- html = html.replace(/<font(.*?)style="(.*?)"(.*?)>([\w\W]*?)<\/font>/gi, '<span style="$2">$4</span>');
+ var $div = $('<div>');
+ $div.html(html);
+ var fonts = $div.find('font[style]');
+ if (fonts.length !== 0)
+ {
+ fonts.replaceWith(function()
+ {
+ var $el = $(this);
+ var $span = $('<span>').attr('style', $el.attr('style'));
+ return $span.append($el.contents());
+ });
+ html = $div.html();
+ }
+ $div.remove();
+
// remove font tag
html = html.replace(/<font(.*?[^<])>/gi, '');
html = html.replace(/<\/font>/gi, '');
// tidy html
@@ -1694,17 +1880,24 @@
onSync: function(html)
{
// remove spaces
html = html.replace(/[\u200B-\u200D\uFEFF]/g, '');
html = html.replace(/​/gi, '');
- html = html.replace(/ /gi, ' ');
- if (html.search(/^<p>(||\s|| )<\/p>$/i) != -1)
+ if (this.opts.cleanSpaces)
{
+ html = html.replace(/ /gi, ' ');
+ }
+
+ if (html.search(/^<p>(||\s||<br\s?\/?>|| )<\/p>$/i) != -1)
+ {
return '';
}
+ // reconvert script tag
+ html = html.replace(/<pre class="redactor-script-tag" style="display: none;"(.*?[^>]?)>([\w\W]*?)<\/pre>/gi, '<script$1>$2</script>');
+
// restore form tag
html = this.clean.restoreFormTags(html);
var chars = {
'\u2122': '™',
@@ -1720,24 +1913,29 @@
});
// 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('<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(.*?) rel="(.*?)"(.*?[^>])>', 'gi'), '<span$1$3>');
- html = html.replace(new RegExp('<img(.*?) rel="(.*?)"(.*?[^>])>', 'gi'), '<img$1$3>');
+ 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(/ 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, '');
html = html.replace(/<span(.*?)id="redactor-image-editter"(.*?[^>])>(.*?)<\/span>/gi, '');
+ // remove font tag
+ html = html.replace(/<font(.*?[^<])>/gi, '');
+ html = html.replace(/<\/font>/gi, '');
+
// tidy html
html = this.tidy.load(html);
// link nofollow
if (this.opts.linkNofollow)
@@ -1830,16 +2028,17 @@
if (this.opts.replaceDivs) html = this.clean.replaceDivs(html);
html = this.clean.saveFormTags(html);
}
- html = this.clean.onPasteIeFixLinks(html);
+
html = this.clean.onPasteWord(html);
html = this.clean.onPasteExtra(html);
html = this.clean.onPasteTidy(html, 'all');
+
// paragraphize
if (!this.clean.singleLine && this.opts.paragraphize)
{
html = this.paragraphize.load(html);
}
@@ -1858,43 +2057,48 @@
html = html.replace(/<!--[\s\S]*?-->/gi, '');
// style
html = html.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '');
- // shapes
- html = html.replace(/<img(.*?)v:shapes=(.*?)>/gi, '');
- html = html.replace(/src="file\:\/\/(.*?)"/, 'src=""');
+ if (/(class=\"?Mso|style=\"[^\"]*\bmso\-|w:WordDocument)/.test(html))
+ {
+ html = this.clean.onPasteIeFixLinks(html);
- // list
- html = html.replace(/<p(.*?)class="MsoListParagraphCxSpFirst"([\w\W]*?)<\/p>/gi, '<ul><li$2</li>');
- html = html.replace(/<p(.*?)class="MsoListParagraphCxSpMiddle"([\w\W]*?)<\/p>/gi, '<li$2</li>');
- html = html.replace(/<p(.*?)class="MsoListParagraphCxSpLast"([\w\W]*?)<\/p>/gi, '<li$2</li></ul>');
- // one line
- html = html.replace(/<p(.*?)class="MsoListParagraph"([\w\W]*?)<\/p>/gi, '<ul><li$2</li></ul>');
- // remove ms word's bullet
- html = html.replace(/·/g, '');
- html = html.replace(/<p class="Mso(.*?)"/gi, '<p');
+ // shapes
+ html = html.replace(/<img(.*?)v:shapes=(.*?)>/gi, '');
+ html = html.replace(/src="file\:\/\/(.*?)"/, 'src=""');
- // classes
- html = html.replace(/ class=\"(mso[^\"]*)\"/gi, "");
- html = html.replace(/ class=(mso\w+)/gi, "");
+ // list
+ html = html.replace(/<p(.*?)class="MsoListParagraphCxSpFirst"([\w\W]*?)<\/p>/gi, '<ul><li$2</li>');
+ html = html.replace(/<p(.*?)class="MsoListParagraphCxSpMiddle"([\w\W]*?)<\/p>/gi, '<li$2</li>');
+ html = html.replace(/<p(.*?)class="MsoListParagraphCxSpLast"([\w\W]*?)<\/p>/gi, '<li$2</li></ul>');
+ // one line
+ html = html.replace(/<p(.*?)class="MsoListParagraph"([\w\W]*?)<\/p>/gi, '<ul><li$2</li></ul>');
+ // remove ms word's bullet
+ html = html.replace(/·/g, '');
+ html = html.replace(/<p class="Mso(.*?)"/gi, '<p');
- // remove ms word tags
- html = html.replace(/<o:p(.*?)>([\w\W]*?)<\/o:p>/gi, '$2');
+ // classes
+ html = html.replace(/ class=\"(mso[^\"]*)\"/gi, "");
+ html = html.replace(/ class=(mso\w+)/gi, "");
+ // remove ms word tags
+ html = html.replace(/<o:p(.*?)>([\w\W]*?)<\/o:p>/gi, '$2');
+
+ // ms word break lines
+ html = html.replace(/\n/g, ' ');
+
+ // ms word lists break lines
+ html = html.replace(/<p>\n?<li>/gi, '<li>');
+ }
+
// remove nbsp
if (this.opts.cleanSpaces)
{
html = html.replace(/(\s| )+/g, ' ');
}
- // ms word break lines
- html = html.replace(/\n/g, ' ');
-
- // ms word lists break lines
- html = html.replace(/<p>\n?<li>/gi, '<li>');
-
return html;
},
onPasteExtra: function(html)
{
// remove google docs markers
@@ -1936,17 +2140,18 @@
},
onPasteTidy: function(html, type)
{
// remove all tags except these
var tags = ['span', 'a', 'pre', 'blockquote', 'small', 'em', 'strong', 'code', 'kbd', 'mark', 'address', 'cite', 'var', 'samp', 'dfn', 'sup', 'sub', 'b', 'i', 'u', 'del',
- 'ol', 'ul', 'li', 'dl', 'dt', 'dd', 'p', 'br', 'video', 'audio', 'embed', 'param', 'object', 'img', 'table',
+ 'ol', 'ul', 'li', 'dl', 'dt', 'dd', 'p', 'br', 'video', 'audio', 'iframe', 'embed', 'param', 'object', 'img', 'table',
'td', 'th', 'tr', 'tbody', 'tfoot', 'thead', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
var tagsEmpty = false;
var attrAllowed = [
['a', '*'],
['img', ['src', 'alt']],
['span', ['class', 'rel', 'data-verified']],
+ ['iframe', '*'],
['video', '*'],
['audio', '*'],
['embed', '*'],
['object', '*'],
['param', '*'],
@@ -1960,10 +2165,11 @@
['table', 'class'],
['td', ['colspan', 'rowspan']],
['a', '*'],
['img', ['src', 'alt', 'data-redactor-inserted-image']],
['span', ['class', 'rel', 'data-verified']],
+ ['iframe', '*'],
['video', '*'],
['audio', '*'],
['embed', '*'],
['object', '*'],
['param', '*'],
@@ -1972,18 +2178,18 @@
}
else if (type == 'td')
{
// remove all tags except these and remove all table tags: tr, td etc
tags = ['ul', 'ol', 'li', 'span', 'a', 'small', 'em', 'strong', 'code', 'kbd', 'mark', 'cite', 'var', 'samp', 'dfn', 'sup', 'sub', 'b', 'i', 'u', 'del',
- 'ol', 'ul', 'li', 'dl', 'dt', 'dd', 'br', 'video', 'audio', 'embed', 'param', 'object', 'img', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
+ 'ol', 'ul', 'li', 'dl', 'dt', 'dd', 'br', 'iframe', 'video', 'audio', 'embed', 'param', 'object', 'img', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
}
else if (type == 'li')
{
// only inline tags and ul, ol, li
tags = ['ul', 'ol', 'li', 'span', 'a', 'small', 'em', 'strong', 'code', 'kbd', 'mark', 'cite', 'var', 'samp', 'dfn', 'sup', 'sub', 'b', 'i', 'u', 'del', 'br',
- 'video', 'audio', 'embed', 'param', 'object', 'img'];
+ 'iframe', 'video', 'audio', 'embed', 'param', 'object', 'img'];
}
var options = {
deniedTags: false,
allowedTags: tags,
@@ -2129,11 +2335,11 @@
getOnlyImages: function(html)
{
html = html.replace(/<img(.*?)>/gi, '[img$1]');
// remove all tags
- html = html.replace(/<(.*?)>/gi, '');
+ html = html.replace(/<([Ss]*?)>/gi, '');
html = html.replace(/\[img(.*?)\]/gi, '<img$1>');
return html;
},
@@ -2204,12 +2410,15 @@
if (matches)
{
var len = matches.length;
for (var i = 0; i < len; i++)
{
- var newTag = matches[i].replace(/style="(.*?)"/i, 'style="$1" rel="$1"');
- html = html.replace(new RegExp(matches[i], 'gi'), newTag);
+ try {
+ var newTag = matches[i].replace(/style="(.*?)"/i, 'style="$1" rel="$1"');
+ html = html.replace(new RegExp(matches[i], 'gi'), newTag);
+ }
+ catch (e) {}
}
}
return html;
},
@@ -2303,620 +2512,492 @@
{
return html.replace(/<section(.*?) rel="redactor-form-tag"(.*?)>([\w\W]*?)<\/section>/gi, '<form$1$2>$3</form>');
}
};
},
- tidy: function()
+ code: function()
{
return {
- setupAllowed: function()
+ set: function(html)
{
- if (this.opts.allowedTags) this.opts.deniedTags = false;
- if (this.opts.allowedAttr) this.opts.removeAttr = false;
+ html = $.trim(html.toString());
- if (this.opts.linebreaks) return;
+ // clean
+ html = this.clean.onSet(html);
- var tags = ['p', 'section'];
- if (this.opts.allowedTags) this.tidy.addToAllowed(tags);
- if (this.opts.deniedTags) this.tidy.removeFromDenied(tags);
+ this.$editor.html(html);
+ this.code.sync();
+ setTimeout($.proxy(this.buffer.add, this), 15);
+ if (this.start === false) this.observe.load();
+
},
- addToAllowed: function(tags)
+ get: function()
{
- var len = tags.length;
- for (var i = 0; i < len; i++)
- {
- if ($.inArray(tags[i], this.opts.allowedTags) == -1)
- {
- this.opts.allowedTags.push(tags[i]);
- }
- }
+ var code = this.$textarea.val();
+
+ // indent code
+ code = this.tabifier.get(code);
+
+ return code;
},
- removeFromDenied: function(tags)
+ sync: function()
{
- var len = tags.length;
- for (var i = 0; i < len; i++)
- {
- var pos = $.inArray(tags[i], this.opts.deniedTags);
- if (pos != -1)
- {
- this.opts.deniedTags.splice(pos, 1);
- }
- }
+ setTimeout($.proxy(this.code.startSync, this), 10);
},
- load: function(html, options)
+ startSync: function()
{
- this.tidy.settings = {
- deniedTags: this.opts.deniedTags,
- allowedTags: this.opts.allowedTags,
- removeComments: this.opts.removeComments,
- replaceTags: this.opts.replaceTags,
- replaceStyles: this.opts.replaceStyles,
- removeDataAttr: this.opts.removeDataAttr,
- removeAttr: this.opts.removeAttr,
- allowedAttr: this.opts.allowedAttr,
- removeWithoutAttr: this.opts.removeWithoutAttr,
- removeEmpty: this.opts.removeEmpty
- };
+ var html = this.$editor.html();
- $.extend(this.tidy.settings, options);
+ // is there a need to synchronize
+ if (this.code.syncCode && this.code.syncCode == html)
+ {
+ // do not sync
+ return;
+ }
- html = this.tidy.removeComments(html);
- html = this.tidy.replaceTags(html);
+ // save code
+ this.code.syncCode = html;
- // create container
- this.tidy.$div = $('<div />').append(html);
+ // before clean callback
+ html = this.core.setCallback('syncBefore', html);
// clean
- this.tidy.replaceStyles();
- this.tidy.removeTags();
+ html = this.clean.onSync(html);
- this.tidy.removeAttr();
- this.tidy.removeEmpty();
- this.tidy.removeParagraphsInLists();
- this.tidy.removeDataAttr();
- this.tidy.removeWithoutAttr();
+ // set code
+ this.$textarea.val(html);
- html = this.tidy.$div.html();
- this.tidy.$div.remove();
+ // after sync callback
+ this.core.setCallback('sync', html);
- return html;
- },
- removeComments: function(html)
- {
- if (!this.tidy.settings.removeComments) return html;
-
- return html.replace(/<!--[\s\S]*?-->/gi, '');
- },
- replaceTags: function(html)
- {
- if (!this.tidy.settings.replaceTags) return html;
-
- var len = this.tidy.settings.replaceTags.length;
- for (var i = 0; i < len; i++)
+ if (this.start === false)
{
- var re = new RegExp('<' + this.tidy.settings.replaceTags[i][0] + '(.*?[^>])>', 'gi');
- html = html.replace(re, '<' + this.tidy.settings.replaceTags[i][1] + '$1>');
-
- re = new RegExp('</' + this.tidy.settings.replaceTags[i][0] + '>', 'gi');
- html = html.replace(re, '</' + this.tidy.settings.replaceTags[i][1] + '>');
+ this.core.setCallback('change', html);
}
+ this.start = false;
- return html;
+ // autosave on change
+ this.autosave.onChange();
},
- replaceStyles: function()
+ toggle: function()
{
- if (!this.tidy.settings.replaceStyles) return;
-
- var len = this.tidy.settings.replaceStyles.length;
- this.tidy.$div.find('span').each($.proxy(function(n,s)
+ if (this.opts.visual)
{
- var $el = $(s);
- var style = $el.attr('style');
- for (var i = 0; i < len; i++)
- {
- if (style && style.match(new RegExp('^' + this.tidy.settings.replaceStyles[i][0], 'i')))
- {
- var tagName = this.tidy.settings.replaceStyles[i][1];
- $el.replaceWith(function()
- {
- var tag = document.createElement(tagName);
- return $(tag).append($(this).contents());
- });
- }
- }
-
- }, this));
-
- },
- removeTags: function()
- {
- if (!this.tidy.settings.deniedTags && this.tidy.settings.allowedTags)
- {
- this.tidy.$div.find('*').not(this.tidy.settings.allowedTags.join(',')).contents().unwrap();
+ this.code.showCode();
}
-
- if (this.tidy.settings.deniedTags)
+ else
{
- this.tidy.$div.find(this.tidy.settings.deniedTags.join(',')).contents().unwrap();
+ this.code.showVisual();
}
},
- removeAttr: function()
+ showCode: function()
{
- var len;
- if (!this.tidy.settings.removeAttr && this.tidy.settings.allowedAttr)
- {
+ this.code.offset = this.caret.getOffset();
+ var scroll = $(window).scrollTop();
- var allowedAttrTags = [], allowedAttrData = [];
- len = this.tidy.settings.allowedAttr.length;
- for (var i = 0; i < len; i++)
- {
- allowedAttrTags.push(this.tidy.settings.allowedAttr[i][0]);
- allowedAttrData.push(this.tidy.settings.allowedAttr[i][1]);
- }
+ var height = this.$editor.innerHeight();
+ this.$editor.hide();
- this.tidy.$div.find('*').each($.proxy(function(n,s)
- {
- var $el = $(s);
- var pos = $.inArray($el[0].tagName.toLowerCase(), allowedAttrTags);
- var attributesRemove = this.tidy.removeAttrGetRemoves(pos, allowedAttrData, $el);
+ var html = this.$textarea.val();
+ this.modified = this.clean.removeSpaces(html);
- if (attributesRemove)
- {
- $.each(attributesRemove, function(z,f) {
- $el.removeAttr(f);
- });
- }
- }, this));
- }
+ // indent code
+ html = this.tabifier.get(html);
- if (this.tidy.settings.removeAttr)
- {
- len = this.tidy.settings.removeAttr.length;
- for (var i = 0; i < len; i++)
- {
- var attrs = this.tidy.settings.removeAttr[i][1];
- if ($.isArray(attrs)) attrs = attrs.join(' ');
+ this.$textarea.val(html).height(height).show().focus();
+ this.$textarea.on('keydown.redactor-textarea-indenting', this.code.textareaIndenting);
- this.tidy.$div.find(this.tidy.settings.removeAttr[i][0]).removeAttr(attrs);
- }
+ $(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);
},
- removeAttrGetRemoves: function(pos, allowed, $el)
+ showVisual: function()
{
- var attributesRemove = [];
+ if (this.opts.visual) return;
- // remove all attrs
- if (pos == -1)
- {
- $.each($el[0].attributes, function(i, item)
- {
- attributesRemove.push(item.name);
- });
+ var html = this.$textarea.hide().val();
- }
- // allow all attrs
- else if (allowed[pos] == '*')
+ if (this.modified !== this.clean.removeSpaces(html))
{
- attributesRemove = [];
+ this.code.set(html);
}
- // allow specific attrs
- else
- {
- $.each($el[0].attributes, function(i, item)
- {
- if ($.isArray(allowed[pos]))
- {
- if ($.inArray(item.name, allowed[pos]) == -1)
- {
- attributesRemove.push(item.name);
- }
- }
- else if (allowed[pos] != item.name)
- {
- attributesRemove.push(item.name);
- }
- });
- }
+ this.$editor.show();
- return attributesRemove;
- },
- removeAttrs: function (el, regex)
- {
- regex = new RegExp(regex, "g");
- return el.each(function()
+ if (!this.utils.isEmpty(html))
{
- var self = $(this);
- var len = this.attributes.length - 1;
- for (var i = len; i >= 0; i--)
- {
- var item = this.attributes[i];
- if (item && item.specified && item.name.search(regex)>=0)
- {
- self.removeAttr(item.name);
- }
- }
- });
- },
- removeEmpty: function()
- {
- if (!this.tidy.settings.removeEmpty) return;
+ this.placeholder.remove();
+ }
- this.tidy.$div.find(this.tidy.settings.removeEmpty.join(',')).each(function()
- {
- var $el = $(this);
- var text = $el.text();
- text = text.replace(/[\u200B-\u200D\uFEFF]/g, '');
- text = text.replace(/ /gi, '');
- text = text.replace(/\s/g, '');
+ this.caret.setOffset(this.code.offset);
- if (text === '' && $el.children().length === 0)
- {
- $el.remove();
- }
- });
- },
- removeParagraphsInLists: function()
- {
- this.tidy.$div.find('li p').contents().unwrap();
- },
- removeDataAttr: function()
- {
- if (!this.tidy.settings.removeDataAttr) return;
+ this.$textarea.off('keydown.redactor-textarea-indenting');
- var tags = this.tidy.settings.removeDataAttr;
- if ($.isArray(this.tidy.settings.removeDataAttr)) tags = this.tidy.settings.removeDataAttr.join(',');
+ this.button.setActiveInVisual();
+ this.button.setInactive('html');
- this.tidy.removeAttrs(this.tidy.$div.find(tags), '^(data-)');
-
+ this.observe.load();
+ this.opts.visual = true;
},
- removeWithoutAttr: function()
+ textareaIndenting: function(e)
{
- if (!this.tidy.settings.removeWithoutAttr) return;
+ if (e.keyCode !== 9) return true;
- this.tidy.$div.find(this.tidy.settings.removeWithoutAttr.join(',')).each(function()
- {
- if (this.attributes.length === 0)
- {
- $(this).contents().unwrap();
- }
- });
+ var $el = this.$textarea;
+ var start = $el.get(0).selectionStart;
+ $el.val($el.val().substring(0, start) + "\t" + $el.val().substring($el.get(0).selectionEnd));
+ $el.get(0).selectionStart = $el.get(0).selectionEnd = start + 1;
+
+ return false;
}
};
},
- paragraphize: function()
+ core: function()
{
return {
- load: function(html)
+ getObject: function()
{
- 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;
-
- html = html.replace(/(<br\s?\/?>){1,}\n?<\/blockquote>/gi, '</blockquote>');
-
- html = this.paragraphize.getSafes(html);
- html = this.paragraphize.getSafesComments(html);
- 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>');
-
- return $.trim(html);
+ return $.extend({}, this);
},
- getSafes: function(html)
+ getEditor: function()
{
- var $div = $('<div />').append(html);
-
- // remove paragraphs in blockquotes
- $div.find('blockquote p').replaceWith(function()
- {
- return $(this).append('<br />').contents();
- });
-
- html = $div.html();
-
- $div.find(this.paragraphize.blocks.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 + '}');
-
- }, this));
-
- return html;
+ return this.$editor;
},
- getSafesComments: function(html)
+ getBox: function()
{
- var commentsMatches = html.match(/<!--([\w\W]*?)-->/gi);
-
- if (!commentsMatches) return html;
-
- $.each(commentsMatches, $.proxy(function(i,s)
- {
- this.paragraphize.z++;
- this.paragraphize.safes[this.paragraphize.z] = s;
- html = html.replace(s, '\n{replace' + this.paragraphize.z + '}');
- }, this));
-
- return html;
+ return this.$box;
},
- restoreSafes: function(html)
+ getElement: function()
{
- $.each(this.paragraphize.safes, function(i,s)
+ return this.$element;
+ },
+ getTextarea: function()
+ {
+ return this.$textarea;
+ },
+ getToolbar: function()
+ {
+ return (this.$toolbar) ? this.$toolbar : false;
+ },
+ addEvent: function(name)
+ {
+ this.core.event = name;
+ },
+ getEvent: function()
+ {
+ return this.core.event;
+ },
+ setCallback: function(type, e, data)
+ {
+ var callback = this.opts[type + 'Callback'];
+ if ($.isFunction(callback))
{
- html = html.replace('{replace' + i + '}', s);
- });
-
- return html;
+ return (typeof data == 'undefined') ? callback.call(this, e) : callback.call(this, e, data);
+ }
+ else
+ {
+ return (typeof data == 'undefined') ? e : data;
+ }
},
- replaceBreaksToParagraphs: function(html)
+ destroy: function()
{
- var htmls = html.split(new RegExp('\n', 'g'), -1);
+ this.core.setCallback('destroy');
- html = '';
- if (htmls)
- {
- var len = htmls.length;
- for (var i = 0; i < len; i++)
- {
- if (!htmls.hasOwnProperty(i)) return;
+ // off events and remove data
+ this.$element.off('.redactor').removeData('redactor');
+ this.$editor.off('.redactor');
- if (htmls[i].search('{replace') == -1)
- {
- htmls[i] = htmls[i].replace(/<p>\n\t?<\/p>/gi, '');
- htmls[i] = htmls[i].replace(/<p><\/p>/gi, '');
+ // common
+ this.$editor.removeClass('redactor-editor redactor-linebreaks redactor-placeholder');
+ this.$editor.removeAttr('contenteditable');
- if (htmls[i] !== '')
- {
- html += '<p>' + htmls[i].replace(/^\n+|\n+$/g, "") + "</p>";
- }
- }
- else html += htmls[i];
- }
+ var html = this.code.get();
+
+ if (this.build.isTextarea())
+ {
+ this.$box.after(this.$element);
+ this.$box.remove();
+ this.$element.val(html).show();
}
+ else
+ {
+ this.$box.after(this.$editor);
+ this.$box.remove();
+ this.$element.html(html).show();
+ }
- return html;
- },
- replaceBreaksToNewLines: function(html)
- {
- html = html.replace(/<br \/>\s*<br \/>/gi, "\n\n");
- html = html.replace(/<br\s?\/?>\n?<br\s?\/?>/gi, "\n<br /><br />");
+ // paste box
+ if (this.$pasteBox) this.$pasteBox.remove();
- html = html.replace(new RegExp("\r\n", 'g'), "\n");
- html = html.replace(new RegExp("\r", 'g'), "\n");
- html = html.replace(new RegExp("/\n\n+/"), 'g', "\n\n");
+ // modal
+ if (this.$modalBox) this.$modalBox.remove();
+ if (this.$modalOverlay) this.$modalOverlay.remove();
- return html;
- },
- clear: function(html)
- {
- html = html.replace(new RegExp('</blockquote></p>', 'gi'), '</blockquote>');
- html = html.replace(new RegExp('<p></blockquote>', 'gi'), '</blockquote>');
- html = html.replace(new RegExp('<p><blockquote>', 'gi'), '<blockquote>');
- html = html.replace(new RegExp('<blockquote></p>', 'gi'), '<blockquote>');
+ // buttons tooltip
+ $('.redactor-toolbar-tooltip').remove();
- html = html.replace(new RegExp('<p><p ', 'gi'), '<p ');
- html = html.replace(new RegExp('<p><p>', 'gi'), '<p>');
- html = html.replace(new RegExp('</p></p>', 'gi'), '</p>');
- html = html.replace(new RegExp('<p>\\s?</p>', 'gi'), '');
- html = html.replace(new RegExp("\n</p>", 'gi'), '</p>');
- html = html.replace(new RegExp('<p>\t?\t?\n?<p>', 'gi'), '<p>');
- html = html.replace(new RegExp('<p>\t*</p>', 'gi'), '');
+ // autosave
+ clearInterval(this.autosaveInterval);
- return html;
}
};
},
- tabifier: function()
+ dropdown: function()
{
return {
- get: function(code)
+ build: function(name, $dropdown, dropdownObject)
{
- if (!this.opts.tabifier) return code;
+ if (name == 'formatting' && this.opts.formattingAdd)
+ {
+ $.each(this.opts.formattingAdd, $.proxy(function(i,s)
+ {
+ var name = s.tag;
+ if (typeof s.class != 'undefined')
+ {
+ name = name + '-' + s.class;
+ }
- // 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'];
+ s.type = (this.utils.isBlockTag(s.tag)) ? 'block' : 'inline';
+ var func = (s.type == 'inline') ? 'inline.formatting' : 'block.formatting';
- 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('|' ) + ')[ >]');
+ if (this.opts.linebreaks && s.type == 'block' && s.tag == 'p') return;
- var i = 0,
- codeLength = code.length,
- point = 0,
- start = null,
- end = null,
- tag = '',
- out = '',
- cont = '';
+ this.formatting[name] = {
+ tag: s.tag,
+ style: s.style,
+ 'class': s.class,
+ attr: s.attr,
+ data: s.data,
+ clear: s.clear
+ };
- this.tabifier.cleanlevel = 0;
+ dropdownObject[name] = {
+ func: func,
+ title: s.title
+ };
- for (; i < codeLength; i++)
+ }, this));
+
+ }
+
+ $.each(dropdownObject, $.proxy(function(btnName, btnObject)
{
- point = i;
+ var $item = $('<a href="#" class="redactor-dropdown-' + btnName + '">' + btnObject.title + '</a>');
+ if (name == 'formatting') $item.addClass('redactor-formatting-' + btnName);
- // if no more tags, copy and exit
- if (-1 == code.substr(i).indexOf( '<' ))
+ $item.on('click', $.proxy(function(e)
{
- out += code.substr(i);
+ var type = 'func';
+ var callback = btnObject.func;
+ if (btnObject.command)
+ {
+ type = 'command';
+ callback = btnObject.command;
+ }
+ else if (btnObject.dropdown)
+ {
+ type = 'dropdown';
+ callback = btnObject.dropdown;
+ }
- return this.tabifier.finish(out);
- }
+ this.button.onClick(e, btnName, type, callback);
- // copy verbatim until a tag
- while (point < codeLength && code.charAt(point) != '<')
- {
- point++;
- }
+ }, this));
- if (i != point)
- {
- cont = code.substr(i, point - i);
- if (!cont.match(/^\s{2,}$/g))
- {
- if ('\n' == out.charAt(out.length - 1)) out += this.tabifier.getTabs();
- else if ('\n' == cont.charAt(0))
- {
- out += '\n' + this.tabifier.getTabs();
- cont = cont.replace(/^\s+/, '');
- }
+ $dropdown.append($item);
- out += cont;
- }
+ }, this));
+ },
+ show: function(e, key)
+ {
+ if (!this.opts.visual)
+ {
+ e.preventDefault();
+ return false;
+ }
- if (cont.match(/\n/)) out += '\n' + this.tabifier.getTabs();
- }
+ var $button = this.button.get(key);
- start = point;
+ // 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);
- // find the end of the tag
- while (point < codeLength && '>' != code.charAt(point))
- {
- point++;
- }
+ // ios keyboard hide
+ if (this.utils.isMobile() && !this.utils.browser('msie'))
+ {
+ document.activeElement.blur();
+ }
- tag = code.substr(start, point - start);
- i = point;
+ if ($button.hasClass('dropact'))
+ {
+ this.dropdown.hideAll();
+ }
+ else
+ {
+ this.dropdown.hideAll();
+ this.core.setCallback('dropdownShow', { dropdown: $dropdown, key: key, button: $button });
- var t;
+ this.button.setActive(key);
- if ('!--' == tag.substr(1, 3))
- {
- if (!tag.match(/--$/))
- {
- while ('-->' != code.substr(point, 3))
- {
- point++;
- }
- point += 2;
- tag = code.substr(start, point - start);
- i = point;
- }
+ $button.addClass('dropact');
- if ('\n' != out.charAt(out.length - 1)) out += '\n';
+ var keyPosition = $button.offset();
- out += this.tabifier.getTabs();
- out += tag + '>\n';
- }
- else if ('!' == tag[1])
+ // fix right placement
+ var dropdownWidth = $dropdown.width();
+ if ((keyPosition.left + dropdownWidth) > $(document).width())
{
- out = this.tabifier.placeTag(tag + '>', out);
+ keyPosition.left -= dropdownWidth;
}
- else if ('?' == tag[1])
- {
- out += tag + '>\n';
- }
- else if (t = tag.match(/^<(script|style|pre)/i))
- {
- t[1] = t[1].toLowerCase();
- tag = this.tabifier.cleanTag(tag);
- out = this.tabifier.placeTag(tag, out);
- end = String(code.substr(i + 1)).toLowerCase().indexOf('</' + t[1]);
- if (end)
+ var left = keyPosition.left + 'px';
+ if (this.$toolbar.hasClass('toolbar-fixed-box'))
+ {
+ var top = this.$toolbar.innerHeight() + this.opts.toolbarFixedTopOffset;
+ var position = 'fixed';
+ if (this.opts.toolbarFixedTarget !== document)
{
- cont = code.substr(i + 1, end);
- i += end;
- out += cont;
+ top = (this.$toolbar.innerHeight() + this.$toolbar.offset().top) + this.opts.toolbarFixedTopOffset;
+ position = 'absolute';
}
+
+ $dropdown.css({ position: position, left: left, top: top + 'px' }).show();
}
else
{
- tag = this.tabifier.cleanTag(tag);
- out = this.tabifier.placeTag(tag, out);
+ 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 });
}
- return this.tabifier.finish(out);
+ $(document).one('click', $.proxy(this.dropdown.hide, this));
+ this.$editor.one('click', $.proxy(this.dropdown.hide, this));
+
+ // disable scroll whan dropdown scroll
+ var $body = $(document.body);
+ var width = $body.width();
+
+ $dropdown.on('mouseover', function() {
+
+ $body.addClass('body-redactor-hidden');
+ $body.css('margin-right', ($body.width() - width) + 'px');
+
+ });
+
+ $dropdown.on('mouseout', function() {
+
+ $body.removeClass('body-redactor-hidden').css('margin-right', 0);
+
+ });
+
+
+ e.stopPropagation();
},
- getTabs: function()
+ hideAll: function()
{
- var s = '';
- for ( var j = 0; j < this.tabifier.cleanlevel; j++ )
- {
- s += '\t';
- }
+ this.$toolbar.find('a.dropact').removeClass('redactor-act').removeClass('dropact');
- return s;
+ $(document.body).removeClass('body-redactor-hidden').css('margin-right', 0);
+ $('.redactor-dropdown').hide();
+ this.core.setCallback('dropdownHide');
},
- finish: function(code)
+ hide: function (e)
{
- 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>');
+ var $dropdown = $(e.target);
+ if (!$dropdown.hasClass('dropact'))
+ {
+ $dropdown.removeClass('dropact');
+ this.dropdown.hideAll();
+ }
+ }
+ };
+ },
+ file: function()
+ {
+ return {
+ show: function()
+ {
+ this.modal.load('file', this.lang.get('file'), 700);
+ this.upload.init('#redactor-modal-file-upload', this.opts.fileUpload, this.file.insert);
- this.tabifier.cleanlevel = 0;
+ this.selection.save();
- return code;
+ this.selection.get();
+ var text = this.sel.toString();
+
+ $('#redactor-filename').val(text);
+
+ this.modal.show();
},
- cleanTag: function (tag)
+ insert: function(json, direct, e)
{
- var tagout = '';
- tag = tag.replace(/\n/g, ' ');
- tag = tag.replace(/\s{2,}/g, ' ');
- tag = tag.replace(/^\s+|\s+$/g, ' ');
-
- var suffix = '';
- if (tag.match(/\/$/))
+ // error callback
+ if (typeof json.error != 'undefined')
{
- suffix = '/';
- tag = tag.replace(/\/+$/, '');
+ this.modal.close();
+ this.selection.restore();
+ this.core.setCallback('fileUploadError', json);
+ return;
}
- var m;
- while (m = /\s*([^= ]+)(?:=((['"']).*?\3|[^ ]+))?/.exec(tag))
+ var link;
+ if (typeof json == 'string')
{
- if (m[2]) tagout += m[1].toLowerCase() + '=' + m[2];
- else if (m[1]) tagout += m[1].toLowerCase();
+ link = json;
+ }
+ else
+ {
+ var text = $('#redactor-filename').val();
+ if (typeof text == 'undefined' || text === '') text = json.filename;
- tagout += ' ';
- tag = tag.substr(m[0].length);
+ link = '<a href="' + json.filelink + '" id="filelink-marker">' + text + '</a>';
}
- return tagout.replace(/\s*$/, '') + suffix + '>';
- },
- placeTag: function (tag, out)
- {
- var nl = tag.match(this.tabifier.newLevel);
- if (tag.match(this.tabifier.lineBefore) || nl)
+ if (direct)
{
- out = out.replace(/\s*$/, '');
- out += '\n';
+ this.selection.removeMarkers();
+ var marker = this.selection.getMarker();
+ this.insert.nodeToCaretPositionFromPoint(e, marker);
}
+ else
+ {
+ this.modal.close();
+ }
- if (nl && '/' == tag.charAt(1)) this.tabifier.cleanlevel--;
- if ('\n' == out.charAt(out.length - 1)) out += this.tabifier.getTabs();
- if (nl && '/' != tag.charAt(1)) this.tabifier.cleanlevel++;
+ this.selection.restore();
+ this.buffer.set();
- out += tag;
+ this.insert.htmlWithoutClean(link);
- if (tag.match(this.tabifier.lineAfter) || tag.match(this.tabifier.newLevel))
+ if (typeof json == 'string') return;
+
+ var linkmarker = $(this.$editor.find('a#filelink-marker'));
+ if (linkmarker.size() !== 0)
{
- out = out.replace(/ *$/, '');
- out += '\n';
+ linkmarker.removeAttr('id').removeAttr('style');
}
+ else linkmarker = false;
- return out;
+ this.core.setCallback('fileUpload', linkmarker, json);
+
}
};
},
focus: function()
{
@@ -2935,11 +3016,11 @@
if (first[0].tagName == 'UL' || first[0].tagName == 'OL')
{
first = first.find('li').first();
var child = first.children().first();
- if (!this.utils.isBlock(child) && child.text() == '')
+ if (!this.utils.isBlock(child) && child.text() === '')
{
// empty inline tag in li
this.caret.setStart(child);
return;
}
@@ -2989,167 +3070,481 @@
return this.$editor.is(':focus');
}
};
},
- placeholder: function()
+ image: function()
{
return {
- enable: function()
+ show: function()
{
- if (!this.placeholder.is()) return;
+ this.modal.load('image', this.lang.get('image'), 700);
+ this.upload.init('#redactor-modal-image-droparea', this.opts.imageUpload, this.image.insert);
- this.$editor.attr('placeholder', this.$element.attr('placeholder'));
+ this.selection.save();
+ this.modal.show();
- this.placeholder.toggle();
- this.$editor.on('keyup.redactor-placeholder', $.proxy(this.placeholder.toggle, this));
-
},
- toggle: function()
+ showEdit: function($image)
{
- var func = 'removeClass';
- if (this.utils.isEmpty(this.$editor.html(), false)) func = 'addClass';
- this.$editor[func]('redactor-placeholder');
- },
- remove: function()
- {
- this.$editor.removeClass('redactor-placeholder');
- },
- is: function()
- {
- if (this.opts.placeholder)
+ var $link = $image.closest('a');
+
+ this.modal.load('imageEdit', this.lang.get('edit'), 705);
+
+ this.modal.createCancelButton();
+ this.image.buttonDelete = this.modal.createDeleteButton(this.lang.get('_delete'));
+ this.image.buttonSave = this.modal.createActionButton(this.lang.get('save'));
+
+ this.image.buttonDelete.on('click', $.proxy(function()
{
- return this.$element.attr('placeholder', this.opts.placeholder);
+ this.image.remove($image);
+
+ }, this));
+
+ this.image.buttonSave.on('click', $.proxy(function()
+ {
+ this.image.update($image);
+
+ }, this));
+
+
+ $('#redactor-image-title').val($image.attr('alt'));
+
+ if (!this.opts.imageLink) $('.redactor-image-link-option').hide();
+ else
+ {
+ var $redactorImageLink = $('#redactor-image-link');
+
+ $redactorImageLink.attr('href', $image.attr('src'));
+ if ($link.size() !== 0)
+ {
+ $redactorImageLink.val($link.attr('href'));
+ if ($link.attr('target') == '_blank') $('#redactor-image-link-blank').prop('checked', true);
+ }
}
+
+ if (!this.opts.imagePosition) $('.redactor-image-position-option').hide();
else
{
- return !(typeof this.$element.attr('placeholder') == 'undefined' || this.$element.attr('placeholder') === '');
+ var floatValue = ($image.css('display') == 'block' && $image.css('float') == 'none') ? 'center' : $image.css('float');
+ $('#redactor-image-align').val(floatValue);
}
- }
- };
- },
- autosave: function()
- {
- return {
- enable: function()
+
+ this.modal.show();
+
+ },
+ setFloating: function($image)
{
- if (!this.opts.autosave) return;
+ var floating = $('#redactor-image-align').val();
- this.autosave.html = false;
- this.autosave.name = (this.opts.autosaveName) ? this.opts.autosaveName : this.$textarea.attr('name');
+ var imageFloat = '';
+ var imageDisplay = '';
+ var imageMargin = '';
- if (!this.opts.autosaveOnChange)
+ switch (floating)
{
- this.autosaveInterval = setInterval($.proxy(this.autosave.load, this), this.opts.autosaveInterval * 1000);
+ case 'left':
+ imageFloat = 'left';
+ imageMargin = '0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin + ' 0';
+ break;
+ case 'right':
+ imageFloat = 'right';
+ imageMargin = '0 0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin;
+ break;
+ case 'center':
+ imageDisplay = 'block';
+ imageMargin = 'auto';
+ break;
}
- },
- onChange: function()
- {
- if (!this.opts.autosaveOnChange) return;
- this.autosave.load();
+ $image.css({ 'float': imageFloat, display: imageDisplay, margin: imageMargin });
+ $image.attr('rel', $image.attr('style'));
},
- load: function()
+ update: function($image)
{
- var html = this.code.get();
- if (this.autosave.html === html) return;
- if (this.utils.isEmpty(html)) return;
+ this.image.hideResize();
+ this.buffer.set();
- $.ajax({
- url: this.opts.autosave,
- type: 'post',
- data: 'name=' + this.autosave.name + '&' + this.autosave.name + '=' + escape(encodeURIComponent(html)),
- success: $.proxy(function(data)
+ var $link = $image.closest('a');
+
+ $image.attr('alt', $('#redactor-image-title').val());
+
+ this.image.setFloating($image);
+
+ // as link
+ var link = $.trim($('#redactor-image-link').val());
+ if (link !== '')
+ {
+ var target = ($('#redactor-image-link-blank').prop('checked')) ? true : false;
+
+ if ($link.size() === 0)
{
- this.autosave.success(data, html);
+ var a = $('<a href="' + link + '">' + this.utils.getOuterHtml($image) + '</a>');
+ if (target) a.attr('target', '_blank');
- }, this)
- });
+ $image.replaceWith(a);
+ }
+ else
+ {
+ $link.attr('href', link);
+ if (target)
+ {
+ $link.attr('target', '_blank');
+ }
+ else
+ {
+ $link.removeAttr('target');
+ }
+ }
+ }
+ else if ($link.size() !== 0)
+ {
+ $link.replaceWith(this.utils.getOuterHtml($image));
+
+ }
+
+ this.modal.close();
+ this.observe.images();
+ this.code.sync();
+
+
},
- success: function(data, html)
+ setEditable: function($image)
{
- var json;
- try
+ if (this.opts.imageEditable)
{
- json = $.parseJSON(data);
+ $image.on('dragstart', $.proxy(this.image.onDrag, this));
}
- catch(e)
+
+ $image.on('mousedown', $.proxy(this.image.hideResize, this));
+ $image.on('click touchstart', $.proxy(function(e)
{
- //data has already been parsed
- json = data;
- }
+ this.observe.image = $image;
- var callbackName = (typeof json.error == 'undefined') ? 'autosave' : 'autosaveError';
+ if (this.$editor.find('#redactor-image-box').size() !== 0) return false;
- this.core.setCallback(callbackName, this.autosave.name, json);
- this.autosave.html = html;
+ 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));
+
+ // resize
+ if (!this.opts.imageResizable) return;
+
+ this.image.resizer.on('mousedown.redactor touchstart.redactor', $.proxy(function(e)
+ {
+ this.image.setResizable(e, $image);
+ }, this));
+
+
+ }, this));
},
- disable: function()
+ setResizable: function(e, $image)
{
- clearInterval(this.autosaveInterval);
- }
- };
- },
- buffer: function()
- {
- return {
- set: function(type)
+ e.preventDefault();
+
+ this.image.resizeHandle = {
+ x : e.pageX,
+ y : e.pageY,
+ el : $image,
+ ratio: $image.width() / $image.height(),
+ h: $image.height()
+ };
+
+ e = e.originalEvent || e;
+
+ if (e.targetTouches)
+ {
+ this.image.resizeHandle.x = e.targetTouches[0].pageX;
+ this.image.resizeHandle.y = e.targetTouches[0].pageY;
+ }
+
+ this.image.startResize();
+
+
+ },
+ startResize: function()
{
- if (typeof type == 'undefined' || type == 'undo')
+ $(document).on('mousemove.redactor-image-resize touchmove.redactor-image-resize', $.proxy(this.image.moveResize, this));
+ $(document).on('mouseup.redactor-image-resize touchend.redactor-image-resize', $.proxy(this.image.stopResize, this));
+ },
+ moveResize: function(e)
+ {
+ e.preventDefault();
+
+ e = e.originalEvent || e;
+
+ var height = this.image.resizeHandle.h;
+
+ if (e.targetTouches) height += (e.targetTouches[0].pageY - this.image.resizeHandle.y);
+ else height += (e.pageY - this.image.resizeHandle.y);
+
+ var width = Math.round(height * this.image.resizeHandle.ratio);
+
+ if (height < 50 || width < 100) return;
+
+ this.image.resizeHandle.el.width(width);
+ this.image.resizeHandle.el.height(this.image.resizeHandle.el.width()/this.image.resizeHandle.ratio);
+
+ this.code.sync();
+ },
+ stopResize: function()
+ {
+ this.handle = false;
+ $(document).off('.redactor-image-resize');
+
+ this.image.hideResize();
+ },
+ onDrag: function(e)
+ {
+ if (this.$editor.find('#redactor-image-box').size() !== 0)
{
- this.buffer.setUndo();
+ e.preventDefault();
+ return false;
}
- else
+
+ this.$editor.on('drop.redactor-image-inside-drop', $.proxy(function()
{
- this.buffer.setRedo();
- }
+ setTimeout($.proxy(this.image.onDrop, this), 1);
+
+ }, this));
},
- setUndo: function()
+ onDrop: function()
{
- this.selection.save();
- this.opts.buffer.push(this.$editor.html());
- this.selection.restore();
+ this.image.fixImageSourceAfterDrop();
+ this.observe.images();
+ this.$editor.off('drop.redactor-image-inside-drop');
+ this.clean.clearUnverified();
+ this.code.sync();
},
- setRedo: function()
+ fixImageSourceAfterDrop: function()
{
- this.selection.save();
- this.opts.rebuffer.push(this.$editor.html());
- this.selection.restore();
+ this.$editor.find('img[data-save-url]').each(function()
+ {
+ var $el = $(this);
+ $el.attr('src', $el.attr('data-save-url'));
+ $el.removeAttr('data-save-url');
+ });
},
- getUndo: function()
+ hideResize: function(e)
{
- this.$editor.html(this.opts.buffer.pop());
+ if (e && $(e.target).closest('#redactor-image-box').length !== 0) return;
+ if (e && e.target.tagName == 'IMG')
+ {
+ var $image = $(e.target);
+ $image.attr('data-save-url', $image.attr('src'));
+ }
+
+ var imageBox = this.$editor.find('#redactor-image-box');
+ if (imageBox.size() === 0) return;
+
+ if (this.opts.imageEditable)
+ {
+ this.image.editter.remove();
+ }
+
+ $(this.image.resizer).remove();
+
+ imageBox.find('img').css({
+ marginTop: imageBox[0].style.marginTop,
+ marginBottom: imageBox[0].style.marginBottom,
+ marginLeft: imageBox[0].style.marginLeft,
+ marginRight: imageBox[0].style.marginRight
+ });
+
+ imageBox.css('margin', '');
+ imageBox.find('img').css('opacity', '');
+ imageBox.replaceWith(function()
+ {
+ return $(this).contents();
+ });
+
+ $(document).off('click.redactor-image-resize-hide');
+ this.$editor.off('click.redactor-image-resize-hide');
+
+ if (typeof this.image.resizeHandle !== 'undefined')
+ {
+ this.image.resizeHandle.el.attr('rel', this.image.resizeHandle.el.attr('style'));
+ }
+
+ this.code.sync();
+
},
- getRedo: function()
+ loadResizableControls: function($image, imageBox)
{
- this.$editor.html(this.opts.rebuffer.pop());
+ if (this.opts.imageResizable && !this.utils.isMobile())
+ {
+ var imageResizer = $('<span id="redactor-image-resizer" data-redactor="verified"></span>');
+
+ if (!this.utils.isDesktop())
+ {
+ imageResizer.css({ width: '15px', height: '15px' });
+ }
+
+ imageResizer.attr('contenteditable', false);
+ imageBox.append(imageResizer);
+ imageBox.append($image);
+
+ return imageResizer;
+ }
+ else
+ {
+ imageBox.append($image);
+ return false;
+ }
},
- add: function()
+ loadEditableControls: function($image)
{
- this.opts.buffer.push(this.$editor.html());
+ var imageBox = $('<span id="redactor-image-box" data-redactor="verified">');
+ imageBox.css('float', $image.css('float')).attr('contenteditable', false);
+
+ if ($image[0].style.margin != 'auto')
+ {
+ imageBox.css({
+ marginTop: $image[0].style.marginTop,
+ marginBottom: $image[0].style.marginBottom,
+ marginLeft: $image[0].style.marginLeft,
+ marginRight: $image[0].style.marginRight
+ });
+
+ $image.css('margin', '');
+ }
+ else
+ {
+ imageBox.css({ 'display': 'block', 'margin': 'auto' });
+ }
+
+ $image.css('opacity', '.5').after(imageBox);
+
+
+ if (this.opts.imageEditable)
+ {
+ // editter
+ this.image.editter = $('<span id="redactor-image-editter" data-redactor="verified">' + this.lang.get('edit') + '</span>');
+ this.image.editter.attr('contenteditable', false);
+ this.image.editter.on('click', $.proxy(function()
+ {
+ this.image.showEdit($image);
+ }, this));
+
+ imageBox.append(this.image.editter);
+
+ // position correction
+ var editerWidth = this.image.editter.innerWidth();
+ this.image.editter.css('margin-left', '-' + editerWidth/2 + 'px');
+ }
+
+ return this.image.loadResizableControls($image, imageBox);
+
},
- undo: function()
+ remove: function(image)
{
- if (this.opts.buffer.length === 0) return;
+ var $image = $(image);
+ var $link = $image.closest('a');
+ var $figure = $image.closest('figure');
+ var $parent = $image.parent();
+ if ($('#redactor-image-box').size() !== 0)
+ {
+ $parent = $('#redactor-image-box').parent();
+ }
- this.buffer.set('redo');
- this.buffer.getUndo();
+ var $next;
+ if ($figure.size() !== 0)
+ {
+ $next = $figure.next();
+ $figure.remove();
+ }
+ else if ($link.size() !== 0)
+ {
+ $parent = $link.parent();
+ $link.remove();
+ }
+ else
+ {
+ $image.remove();
+ }
- this.selection.restore();
+ $('#redactor-image-box').remove();
- setTimeout($.proxy(this.observe.load, this), 50);
+ if ($figure.size() !== 0)
+ {
+ this.caret.setStart($next);
+ }
+ else
+ {
+ this.caret.setStart($parent);
+ }
+
+ // delete callback
+ this.core.setCallback('imageDelete', $image[0].src, $image);
+
+ this.modal.close();
+ this.code.sync();
},
- redo: function()
+ insert: function(json, direct, e)
{
- if (this.opts.rebuffer.length === 0) return;
+ // error callback
+ if (typeof json.error != 'undefined')
+ {
+ this.modal.close();
+ this.selection.restore();
+ this.core.setCallback('imageUploadError', json);
+ return;
+ }
- this.buffer.set('undo');
- this.buffer.getRedo();
+ var $img;
+ if (typeof json == 'string')
+ {
+ $img = $(json).attr('data-redactor-inserted-image', 'true');
+ }
+ else
+ {
+ $img = $('<img>');
+ $img.attr('src', json.filelink).attr('data-redactor-inserted-image', 'true');
+ }
+
+ var node = $img;
+ var isP = this.utils.isCurrentOrParent('P');
+ if (isP)
+ {
+ // will replace
+ node = $('<blockquote />').append($img);
+ }
+
+ if (direct)
+ {
+ this.selection.removeMarkers();
+ var marker = this.selection.getMarker();
+ this.insert.nodeToCaretPositionFromPoint(e, marker);
+ }
+ else
+ {
+ this.modal.close();
+ }
+
this.selection.restore();
+ this.buffer.set();
- setTimeout($.proxy(this.observe.load, this), 50);
+
+ this.insert.html(this.utils.getOuterHtml(node), false);
+
+ var $image = this.$editor.find('img[data-redactor-inserted-image=true]').removeAttr('data-redactor-inserted-image');
+
+ if (isP)
+ {
+ $image.parent().contents().unwrap().wrap('<p />');
+ }
+ else if (this.opts.linebreaks)
+ {
+ $image.before('<br>').after('<br>');
+ }
+
+ if (typeof json == 'string') return;
+
+ this.core.setCallback('imageUpload', $image, json);
+
}
};
},
indent: function()
{
@@ -3285,145 +3680,624 @@
$block.append('<br>');
}
}
};
},
- alignment: function()
+ inline: function()
{
return {
- left: function()
+ formatting: function(name)
{
- this.alignment.set('');
+ var type, value;
+
+ if (typeof this.formatting[name].style != 'undefined') type = 'style';
+ 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);
+
},
- right: function()
+ format: function(tag, type, value)
{
- this.alignment.set('right');
+ // Stop formatting pre and headers
+ if (this.utils.isCurrentOrParent('PRE') || this.utils.isCurrentOrParentHeader()) return;
+
+ var tags = ['b', 'bold', 'i', 'italic', 'underline', 'strikethrough', 'deleted', 'superscript', 'subscript'];
+ var replaced = ['strong', 'strong', 'em', 'em', 'u', 'del', 'del', 'sup', 'sub'];
+
+ for (var i = 0; i < tags.length; i++)
+ {
+ if (tag == tags[i]) tag = replaced[i];
+ }
+
+ this.inline.type = type || false;
+ this.inline.value = value || false;
+
+ this.buffer.set();
+ this.$editor.focus();
+
+ this.selection.get();
+
+ if (this.range.collapsed)
+ {
+ this.inline.formatCollapsed(tag);
+ }
+ else
+ {
+ this.inline.formatMultiple(tag);
+ }
},
- center: function()
+ formatCollapsed: function(tag)
{
- this.alignment.set('center');
+ var current = this.selection.getCurrent();
+ var $parent = $(current).closest(tag + '[data-redactor-tag=' + tag + ']');
+
+ // inline there is
+ if ($parent.size() !== 0)
+ {
+ this.caret.setAfter($parent[0]);
+
+ // remove empty
+ if (this.utils.isEmpty($parent.text())) $parent.remove();
+
+ this.code.sync();
+
+ return;
+ }
+
+ // create empty inline
+ var node = $('<' + tag + '>').attr('data-verified', 'redactor').attr('data-redactor-tag', tag);
+ node.html(this.opts.invisibleSpace);
+
+ node = this.inline.setFormat(node);
+
+ var node = this.insert.node(node);
+ this.caret.setEnd(node);
+
+ this.code.sync();
},
- justify: function()
+ formatMultiple: function(tag)
{
- this.alignment.set('justify');
- },
- set: function(type)
- {
- if (!this.utils.browser('msie')) this.$editor.focus();
+ this.inline.formatConvert(tag);
- this.buffer.set();
this.selection.save();
+ document.execCommand('strikethrough');
- this.alignment.blocks = this.selection.getBlocks();
- if (this.opts.linebreaks && this.alignment.blocks[0] === false)
+
+ this.$editor.find('strike').each($.proxy(function(i,s)
{
- this.alignment.setText(type);
+ var $el = $(s);
+
+ this.inline.formatRemoveSameChildren($el, tag);
+
+ var $span;
+ if (this.inline.type)
+ {
+ $span = $('<span>').attr('data-redactor-tag', tag).attr('data-verified', 'redactor');
+ $span = this.inline.setFormat($span);
+ }
+ else
+ {
+ $span = $('<' + tag + '>').attr('data-redactor-tag', tag).attr('data-verified', 'redactor');
+ }
+
+ $el.replaceWith($span.html($el.contents()));
+
+ if (tag == 'span')
+ {
+ var $parent = $span.parent();
+ if ($parent && $parent[0].tagName == 'SPAN' && this.inline.type == 'style')
+ {
+ var arr = this.inline.value.split(';');
+
+ for (var z = 0; z < arr.length; z++)
+ {
+ if (arr[z] === '') return;
+ var style = arr[z].split(':');
+ $parent.css(style[0], '');
+
+ if (this.utils.removeEmptyAttr($parent, 'style'))
+ {
+ $parent.replaceWith($parent.contents());
+ }
+
+ }
+
+ }
+ }
+
+ }, this));
+
+ // clear text decoration
+ if (tag != 'span')
+ {
+ this.$editor.find(this.opts.inlineTags.join(', ')).each($.proxy(function(i,s)
+ {
+ var $el = $(s);
+ var property = $el.css('text-decoration');
+ if (property == 'line-through')
+ {
+ $el.css('text-decoration', '');
+ this.utils.removeEmptyAttr($el, 'style');
+ }
+ }, this));
}
- else
+
+ if (tag != 'del')
{
- this.alignment.setBlocks(type);
+ var _this = this;
+ this.$editor.find('inline').each(function(i,s)
+ {
+ _this.utils.replaceToTag(s, 'del');
+ });
}
this.selection.restore();
this.code.sync();
+
},
- setText: function(type)
+ formatRemoveSameChildren: function($el, tag)
{
- var wrapper = this.selection.wrap('div');
- $(wrapper).attr('data-tagblock', 'redactor');
- $(wrapper).css('text-align', type);
+ $el.children(tag).each(function()
+ {
+ var $child = $(this);
+ if (!$child.hasClass('redactor-selection-marker'))
+ {
+ $child.contents().unwrap();
+ }
+ });
},
- setBlocks: function(type)
+ formatConvert: function(tag)
{
- $.each(this.alignment.blocks, $.proxy(function(i, el)
+ this.selection.save();
+
+ var find = '';
+ if (this.inline.type == 'class') find = '[data-redactor-class=' + this.inline.value + ']';
+ else if (this.inline.type == 'style')
{
- var $el = this.utils.getAlignmentElement(el);
+ find = '[data-redactor-style="' + this.inline.value + '"]';
+ }
- if (!$el) return;
+ if (tag != 'del')
+ {
+ var self = this;
+ this.$editor.find('del').each(function(i,s)
+ {
+ self.utils.replaceToTag(s, 'inline');
+ });
+ }
- if (type === '' && typeof($el.data('tagblock')) !== 'undefined')
+ this.$editor.find('[data-redactor-tag="' + tag + '"]' + find).each(function()
+ {
+ if (find === '' && tag == 'span' && this.tagName.toLowerCase() == tag) return;
+
+ var $el = $(this);
+ $el.replaceWith($('<strike />').html($el.contents()));
+
+ });
+
+ this.selection.restore();
+ },
+ setFormat: function(node)
+ {
+ switch (this.inline.type)
+ {
+ case 'class':
+
+ if (node.hasClass(this.inline.value))
+ {
+ node.removeClass(this.inline.value);
+ node.removeAttr('data-redactor-class');
+ }
+ else
+ {
+ node.addClass(this.inline.value);
+ node.attr('data-redactor-class', this.inline.value);
+ }
+
+
+ break;
+ case 'style':
+
+ node[0].style.cssText = this.inline.value;
+ node.attr('data-redactor-style', this.inline.value);
+
+ break;
+ }
+
+ return node;
+ },
+ removeStyle: function()
+ {
+ this.buffer.set();
+ var current = this.selection.getCurrent();
+ var nodes = this.selection.getInlines();
+
+ this.selection.save();
+
+ if (current && current.tagName === 'SPAN')
+ {
+ var $s = $(current);
+
+ $s.removeAttr('style');
+ if ($s[0].attributes.length === 0)
{
- $el.replaceWith($el.html());
+ $s.replaceWith($s.contents());
}
- else
+ }
+
+ $.each(nodes, $.proxy(function(i,s)
+ {
+ var $s = $(s);
+ if ($.inArray(s.tagName.toLowerCase(), this.opts.inlineTags) != -1 && !$s.hasClass('redactor-selection-marker'))
{
- $el.css('text-align', type);
- this.utils.removeEmptyAttr($el, 'style');
+ $s.removeAttr('style');
+ if ($s[0].attributes.length === 0)
+ {
+ $s.replaceWith($s.contents());
+ }
}
+ }, this));
+ this.selection.restore();
+ this.code.sync();
+ },
+ removeStyleRule: function(name)
+ {
+ this.buffer.set();
+ var parent = this.selection.getParent();
+ var nodes = this.selection.getInlines();
+
+ this.selection.save();
+
+ if (parent && parent.tagName === 'SPAN')
+ {
+ var $s = $(parent);
+
+ $s.css(name, '');
+ this.utils.removeEmptyAttr($s, 'style');
+ if ($s[0].attributes.length === 0)
+ {
+ $s.replaceWith($s.contents());
+ }
+ }
+
+ $.each(nodes, $.proxy(function(i,s)
+ {
+ var $s = $(s);
+ if ($.inArray(s.tagName.toLowerCase(), this.opts.inlineTags) != -1 && !$s.hasClass('redactor-selection-marker'))
+ {
+ $s.css(name, '');
+ this.utils.removeEmptyAttr($s, 'style');
+ if ($s[0].attributes.length === 0)
+ {
+ $s.replaceWith($s.contents());
+ }
+ }
}, this));
+
+ this.selection.restore();
+ this.code.sync();
+ },
+ removeFormat: function()
+ {
+ this.buffer.set();
+ var current = this.selection.getCurrent();
+
+ this.selection.save();
+
+ document.execCommand('removeFormat');
+
+ if (current && current.tagName === 'SPAN')
+ {
+ $(current).replaceWith($(current).contents());
+ }
+
+
+ $.each(this.selection.getNodes(), $.proxy(function(i,s)
+ {
+ var $s = $(s);
+ if ($.inArray(s.tagName.toLowerCase(), this.opts.inlineTags) != -1 && !$s.hasClass('redactor-selection-marker'))
+ {
+ $s.replaceWith($s.contents());
+ }
+ }, this));
+
+ this.selection.restore();
+ this.code.sync();
+
+ },
+ toggleClass: function(className)
+ {
+ this.inline.format('span', 'class', className);
+ },
+ toggleStyle: function(value)
+ {
+ this.inline.format('span', 'style', value);
}
};
},
- paste: function()
+ insert: function()
{
return {
- init: function(e)
+ set: function(html, clean)
{
- if (!this.opts.cleanOnPaste) return;
+ this.placeholder.remove();
- this.rtePaste = true;
+ html = this.clean.setVerified(html);
- this.buffer.set();
- this.selection.save();
- this.utils.saveScroll();
+ if (typeof clean == 'undefined')
+ {
+ html = this.clean.onPaste(html, false);
+ }
- this.paste.createPasteBox();
+ this.$editor.html(html);
+ this.selection.remove();
+ this.focus.setEnd();
+ this.clean.normalizeLists();
+ this.code.sync();
+ this.observe.load();
- $(window).on('scroll.redactor-freeze', $.proxy(function()
+ if (typeof clean == 'undefined')
{
- $(window).scrollTop(this.saveBodyScroll);
+ setTimeout($.proxy(this.clean.clearUnverified, this), 10);
+ }
+ },
+ text: function(text)
+ {
+ this.placeholder.remove();
- }, this));
+ text = text.toString();
+ text = $.trim(text);
+ text = this.clean.getPlainText(text, false);
- setTimeout($.proxy(function()
+ this.$editor.focus();
+
+ if (this.utils.browser('msie'))
{
- var html = this.$pasteBox.html();
+ this.insert.htmlIe(text);
+ }
+ else
+ {
+ this.selection.get();
- this.$pasteBox.remove();
+ this.range.deleteContents();
+ var el = document.createElement("div");
+ el.innerHTML = text;
+ var frag = document.createDocumentFragment(), node, lastNode;
+ while ((node = el.firstChild))
+ {
+ lastNode = frag.appendChild(node);
+ }
- this.selection.restore();
- this.utils.restoreScroll();
+ this.range.insertNode(frag);
- this.paste.insert(html);
+ if (lastNode)
+ {
+ var range = this.range.cloneRange();
+ range.setStartAfter(lastNode);
+ range.collapse(true);
+ this.sel.removeAllRanges();
+ this.sel.addRange(range);
+ }
+ }
- $(window).off('scroll.redactor-freeze');
+ this.code.sync();
+ this.clean.clearUnverified();
+ },
+ htmlWithoutClean: function(html)
+ {
+ this.insert.html(html, false);
+ },
+ html: function(html, clean)
+ {
+ this.placeholder.remove();
- }, this), 1);
+ if (typeof clean == 'undefined') clean = true;
+ this.$editor.focus();
+
+ html = this.clean.setVerified(html);
+
+ if (clean)
+ {
+ html = this.clean.onPaste(html);
+ }
+
+ if (this.utils.browser('msie'))
+ {
+ this.insert.htmlIe(html);
+ }
+ else
+ {
+ if (this.clean.singleLine) this.insert.execHtml(html);
+ else document.execCommand('insertHTML', false, html);
+
+ this.insert.htmlFixMozilla();
+
+ }
+
+ this.clean.normalizeLists();
+
+ // remove empty paragraphs finaly
+ if (!this.opts.linebreaks)
+ {
+ this.$editor.find('p').each($.proxy(this.utils.removeEmpty, this));
+ }
+
+ this.code.sync();
+ this.observe.load();
+
+ if (clean)
+ {
+ this.clean.clearUnverified();
+ }
+
},
- createPasteBox: function()
+ htmlFixMozilla: function()
{
- this.$pasteBox = $('<div>').html(' ').attr('contenteditable', 'true').css({ position: 'fixed', width: 0, top: 0, left: '-9999px' });
+ // FF inserts empty p when content was selected dblclick
+ if (!this.utils.browser('mozilla')) return;
- $(document.body).append(this.$pasteBox);
- this.$pasteBox.focus();
+ var $next = $(this.selection.getBlock()).next();
+ if ($next.length > 0 && $next[0].tagName == 'P' && $next.html() === '')
+ {
+ $next.remove();
+ }
+
},
- insert: function(html)
+ htmlIe: function(html)
{
- html = this.core.setCallback('pasteBefore', html);
+ if (this.utils.isIe11())
+ {
+ var parent = this.utils.isCurrentOrParent('P');
+ var $html = $('<div>').append(html);
+ var blocksMatch = $html.contents().is('p, :header, dl, ul, ol, div, table, td, blockquote, pre, address, section, header, footer, aside, article');
- // clean
- html = (this.utils.isSelectAll()) ? this.clean.onPaste(html, false) : this.clean.onPaste(html);
+ if (parent && blocksMatch) this.insert.ie11FixInserting(parent, html);
+ else this.insert.ie11PasteFrag(html);
- html = this.core.setCallback('paste', html);
+ return;
+ }
- if (this.utils.isSelectAll())
+ document.selection.createRange().pasteHTML(html);
+
+ },
+ execHtml: function(html)
+ {
+ html = this.clean.setVerified(html);
+
+ this.selection.get();
+
+ this.range.deleteContents();
+
+ var el = document.createElement('div');
+ el.innerHTML = html;
+
+ var frag = document.createDocumentFragment(), node, lastNode;
+ while ((node = el.firstChild))
{
- this.insert.set(html, false);
+ lastNode = frag.appendChild(node);
}
- else
+
+ this.range.insertNode(frag);
+
+ this.range.collapse(true);
+ this.caret.setAfter(lastNode);
+
+ },
+ node: function(node, deleteContents)
+ {
+ node = node[0] || node;
+
+ var html = this.utils.getOuterHtml(node);
+ html = this.clean.setVerified(html);
+
+ node = $(html)[0];
+
+ this.selection.get();
+
+ if (deleteContents !== false)
{
- this.insert.html(html, false);
+ this.range.deleteContents();
}
- this.utils.disableSelectAll();
- this.rtePaste = false;
+ this.range.insertNode(node);
+ this.range.collapse(false);
+ this.selection.addRange();
- setTimeout($.proxy(this.clean.clearUnverified, this), 10);
+ return node;
+ },
+ nodeToPoint: function(node, x, y)
+ {
+ node = node[0] || node;
+ this.selection.get();
+
+ var range;
+ if (document.caretPositionFromPoint)
+ {
+ var pos = document.caretPositionFromPoint(x, y);
+
+ this.range.setStart(pos.offsetNode, pos.offset);
+ this.range.collapse(true);
+ this.range.insertNode(node);
+ }
+ else if (document.caretRangeFromPoint)
+ {
+ range = document.caretRangeFromPoint(x, y);
+ range.insertNode(node);
+ }
+ else if (typeof document.body.createTextRange != "undefined")
+ {
+ range = document.body.createTextRange();
+ range.moveToPoint(x, y);
+ var endRange = range.duplicate();
+ endRange.moveToPoint(x, y);
+ range.setEndPoint("EndToEnd", endRange);
+ range.select();
+ }
+ },
+ nodeToCaretPositionFromPoint: function(e, node)
+ {
+ node = node[0] || node;
+
+ var range;
+ var x = e.clientX, y = e.clientY;
+ if (document.caretPositionFromPoint)
+ {
+ var pos = document.caretPositionFromPoint(x, y);
+ var sel = document.getSelection();
+ range = sel.getRangeAt(0);
+ range.setStart(pos.offsetNode, pos.offset);
+ range.collapse(true);
+ range.insertNode(node);
+ }
+ else if (document.caretRangeFromPoint)
+ {
+ range = document.caretRangeFromPoint(x, y);
+ range.insertNode(node);
+ }
+ else if (typeof document.body.createTextRange != "undefined")
+ {
+ range = document.body.createTextRange();
+ range.moveToPoint(x, y);
+ var endRange = range.duplicate();
+ endRange.moveToPoint(x, y);
+ range.setEndPoint("EndToEnd", endRange);
+ range.select();
+ }
+
+ },
+ ie11FixInserting: function(parent, html)
+ {
+ var node = document.createElement('span');
+ node.className = 'redactor-ie-paste';
+ this.insert.node(node);
+
+ var parHtml = $(parent).html();
+
+ parHtml = '<p>' + parHtml.replace(/<span class="redactor-ie-paste"><\/span>/gi, '</p>' + html + '<p>') + '</p>';
+ $(parent).replaceWith(parHtml);
+ },
+ ie11PasteFrag: function(html)
+ {
+ this.selection.get();
+ this.range.deleteContents();
+
+ var el = document.createElement("div");
+ el.innerHTML = html;
+
+ var frag = document.createDocumentFragment(), node, lastNode;
+ while ((node = el.firstChild))
+ {
+ lastNode = frag.appendChild(node);
+ }
+
+ this.range.insertNode(frag);
}
};
},
keydown: function()
{
@@ -3459,10 +4333,33 @@
{
e.preventDefault();
return false;
}
+ // ie and ff exit from table
+ if (this.opts.enterKey && (this.utils.browser('msie') || this.utils.browser('mozilla')) && (key === this.keyCode.DOWN || key === this.keyCode.RIGHT))
+ {
+ var isEndOfTable = false;
+ var $table = false;
+ if (this.keydown.block && this.keydown.block.tagName === 'TD')
+ {
+ $table = $(this.keydown.block).closest('table');
+ }
+
+ if ($table && $table.find('td').last()[0] === this.keydown.block)
+ {
+ isEndOfTable = true;
+ }
+
+ if (this.utils.isEndOfElement() && isEndOfTable)
+ {
+ var node = $(this.opts.emptyHtml);
+ $table.after(node);
+ this.caret.setStart(node);
+ }
+ }
+
// down
if (this.opts.enterKey && key === this.keyCode.DOWN)
{
this.keydown.onArrowDown();
}
@@ -3545,10 +4442,12 @@
}
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))
@@ -3691,20 +4590,20 @@
},
onShiftEnter: function(e)
{
this.buffer.set();
- if (this.keydown.blockquote && this.utils.isEndOfElement())
+ if (this.utils.isEndOfElement())
{
return this.keydown.insertDblBreakLine(e);
}
return this.keydown.insertBreakLine(e);
},
onTab: function(e, key)
{
- if (!this.opts.tabFocus) return true;
+ if (!this.opts.tabKey) return true;
if (this.utils.isEmpty(this.code.get()) && this.opts.tabAsSpaces === false) return true;
e.preventDefault();
var node;
@@ -3936,10 +4835,11 @@
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();
@@ -3950,18 +4850,25 @@
if (!this.opts.linebreaks && this.keyup.current.nodeType == 3 && this.keyup.current.length <= 1 && (this.keyup.parent === false || this.keyup.parent.tagName == 'BODY'))
{
this.keyup.replaceToParagraph();
}
- if ($(this.keyup.parent).hasClass('redactor-invisible-space') && ($parent === false || $parent[0].tagName == 'BODY'))
+ // replace div after lists
+ if (!this.opts.linebreaks && this.utils.isRedactorParent(this.keyup.current) && this.keyup.current.tagName === 'DIV')
{
+ this.keyup.replaceToParagraph(false);
+ }
+
+
+ if (!this.opts.linebreaks && $(this.keyup.parent).hasClass('redactor-invisible-space') && ($parent === false || $parent[0].tagName == 'BODY'))
+ {
$(this.keyup.parent).contents().unwrap();
this.keyup.replaceToParagraph();
}
// linkify
- if (this.opts.convertLinks && (this.opts.convertUrlLinks || this.opts.convertImageLinks || this.opts.convertVideoLinks) && key === this.keyCode.ENTER)
+ 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);
this.observe.load();
this.code.sync();
@@ -4001,14 +4908,28 @@
// if empty
return this.keyup.formatEmpty(e);
}
},
- replaceToParagraph: function()
+ 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 = $('<p>').append($current.clone());
+
+ var node;
+ if (clone === false)
+ {
+ node = $('<p>').append($current.html());
+ }
+ else
+ {
+ node = $('<p>').append($current.clone());
+ }
+
$current.replaceWith(node);
var next = $(node).next();
if (typeof(next[0]) !== 'undefined' && next[0].tagName == 'BR')
{
next.remove();
@@ -4041,111 +4962,20 @@
return false;
}
};
},
- shortcuts: function()
+ lang: function()
{
return {
- init: function(e, key)
+ load: function()
{
- // disable browser's hot keys for bold and italic
- if (!this.opts.shortcuts)
- {
- if ((e.ctrlKey || e.metaKey) && (key === 66 || key === 73)) e.preventDefault();
- return false;
- }
-
- $.each(this.opts.shortcuts, $.proxy(function(str, command)
- {
- var keys = str.split(',');
- var len = keys.length;
- for (var i = 0; i < len; i++)
- {
- if (typeof keys[i] === 'string')
- {
- this.shortcuts.handler(e, $.trim(keys[i]), $.proxy(function()
- {
- var func;
- if (command.func.search(/\./) != '-1')
- {
- func = command.func.split('.');
- if (typeof this[func[0]] != 'undefined')
- {
- this[func[0]][func[1]].apply(this, command.params);
- }
- }
- else
- {
- this[command.func].apply(this, command.params);
- }
-
- }, this));
- }
-
- }
-
- }, this));
+ this.opts.curLang = this.opts.langs[this.opts.lang];
},
- handler: function(e, keys, origHandler)
+ get: function(name)
{
- // 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, len = keys.length; i < len; i++)
- {
- if (possible[keys[i]])
- {
- e.preventDefault();
- return origHandler.apply(this, arguments);
- }
- }
+ return (typeof this.opts.curLang[name] != 'undefined') ? this.opts.curLang[name] : '';
}
};
},
line: function()
{
@@ -4224,15 +5054,234 @@
node.removeAttr('id');
}
}
};
},
+ link: function()
+ {
+ return {
+ show: function(e)
+ {
+ if (typeof e != 'undefined' && e.preventDefault) e.preventDefault();
+
+ this.modal.load('link', this.lang.get('link_insert'), 600);
+
+ this.modal.createCancelButton();
+ this.link.buttonInsert = this.modal.createActionButton(this.lang.get('insert'));
+
+ this.selection.get();
+
+ this.link.getData();
+ this.link.cleanUrl();
+
+ if (this.link.target == '_blank') $('#redactor-link-blank').prop('checked', true);
+
+ this.link.$inputUrl = $('#redactor-link-url');
+ this.link.$inputText = $('#redactor-link-url-text');
+
+ this.link.$inputText.val(this.link.text);
+ this.link.$inputUrl.val(this.link.url);
+
+ this.link.buttonInsert.on('click', $.proxy(this.link.insert, this));
+
+ // hide link's tooltip
+ $('.redactor-link-tooltip').remove();
+
+ // show modal
+ this.selection.save();
+ this.modal.show();
+ 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)
+ {
+ 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');
+ if ($el.size() !== 0 && $el[0].tagName === 'A')
+ {
+ this.link.$node = $el;
+
+ this.link.url = $el.attr('href');
+ this.link.text = $el.text();
+ this.link.target = $el.attr('target');
+ }
+ else
+ {
+ this.link.text = this.sel.toString();
+ this.link.url = '';
+ this.link.target = '';
+ }
+
+ },
+ insert: function()
+ {
+ var target = '';
+ var link = this.link.$inputUrl.val();
+ var text = this.link.$inputText.val();
+
+ if ($.trim(link) === '')
+ {
+ this.link.$inputUrl.addClass('redactor-input-error').on('keyup', function()
+ {
+ $(this).removeClass('redactor-input-error');
+ $(this).off('keyup');
+
+ });
+
+ return;
+ }
+
+ // mailto
+ if (link.search('@') != -1 && /(http|ftp|https):\/\//i.test(link) === false)
+ {
+ link = 'mailto:' + link;
+ }
+ // url, not anchor
+ else if (link.search('#') !== 0)
+ {
+ if ($('#redactor-link-blank').prop('checked'))
+ {
+ target = '_blank';
+ }
+
+ // test url (add protocol)
+ var pattern = '((xn--)?[a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,}';
+ var re = new RegExp('^(http|ftp|https)://' + pattern, 'i');
+ var re2 = new RegExp('^' + pattern, 'i');
+
+ if (link.search(re) == -1 && link.search(re2) === 0 && this.opts.linkProtocol)
+ {
+ link = this.opts.linkProtocol + '://' + link;
+ }
+ }
+
+ this.link.set(text, link, target);
+ this.modal.close();
+ },
+ set: function(text, link, target)
+ {
+ text = $.trim(text.replace(/<|>/g, ''));
+
+ this.selection.restore();
+
+ if (text === '' && link === '') return;
+ if (text === '' && link !== '') text = link;
+
+ if (this.link.$node)
+ {
+ this.buffer.set();
+
+ this.link.$node.text(text).attr('href', link);
+ if (target !== '')
+ {
+ this.link.$node.attr('target', target);
+ }
+ else
+ {
+ this.link.$node.removeAttr('target');
+ }
+
+ this.code.sync();
+ }
+ else
+ {
+ if (this.utils.browser('mozilla') && this.link.text === '')
+ {
+ var $a = $('<a />').attr('href', link).text(text);
+ if (target !== '') $a.attr('target', target);
+
+ this.insert.node($a);
+ this.selection.selectElement($a);
+ }
+ else
+ {
+ var $a;
+ if (this.utils.browser('msie'))
+ {
+ $a = $('<a href="' + link + '">').text(text);
+ if (target !== '') $a.attr('target', target);
+
+ $a = $(this.insert.node($a));
+ this.selection.selectElement($a);
+ }
+ else
+ {
+ document.execCommand('createLink', false, link);
+
+ $a = $(this.selection.getCurrent()).closest('a');
+
+ if (target !== '') $a.attr('target', target);
+ $a.removeAttr('style');
+
+ if (this.link.text === '')
+ {
+ $a.text(text);
+ this.selection.selectElement($a);
+ }
+ }
+ }
+
+ this.code.sync();
+ this.core.setCallback('insertedLink', $a);
+
+ }
+
+ // link tooltip
+ setTimeout($.proxy(function()
+ {
+ this.observe.links();
+
+ }, this), 5);
+ },
+ unlink: function(e)
+ {
+ 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());
+ }
+ }
+
+ // hide link's tooltip
+ $('.redactor-link-tooltip').remove();
+
+ this.code.sync();
+
+ }
+ };
+ },
list: function()
{
return {
toggle: function(cmd)
{
+ this.placeholder.remove();
if (!this.utils.browser('msie')) this.$editor.focus();
this.buffer.set();
this.selection.save();
@@ -4279,22 +5328,42 @@
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');
+
if (this.utils.browser('msie') && this.opts.linebreaks)
{
this.list.insertInIe(cmd);
}
else
{
document.execCommand('insert' + cmd);
}
- var parent = this.selection.getParent();
- var $list = $(parent).closest('ol, ul');
+ var $list = $(this.selection.getParent()).closest('ol, ul');
+ if ($td.size() !== 0)
+ {
+ var prev = $td.prev();
+ var html = $td.html();
+ $td.html('');
+ if (prev && prev.length === 1 && (prev[0].tagName === 'TD' || prev[0].tagName === 'TH'))
+ {
+ $(prev).after($td);
+ }
+ else
+ {
+ $(parent).prepend($td);
+ }
+
+ $td.html(html);
+ }
+
if (this.utils.isEmpty($list.find('li').text()))
{
var $children = $list.children('li');
$children.find('br').remove();
$children.append(this.selection.getMarkerAsHtml());
@@ -4362,11 +5431,11 @@
this.indent.fixEmptyIndent();
if (!this.opts.linebreaks && $current.closest('li, th, td').size() === 0)
{
document.execCommand('formatblock', false, 'p');
- this.$editor.find('ul, ol, blockquote, p').each($.proxy(this.utils.removeEmpty, this));
+ this.$editor.find('ul, ol, blockquote').each($.proxy(this.utils.removeEmpty, this));
}
var $table = $(this.selection.getCurrent()).closest('table');
var $prev = $table.prev();
if (!this.opts.linebreaks && $table.size() !== 0 && $prev.size() !== 0 && $prev[0].tagName == 'BR')
@@ -4377,1243 +5446,764 @@
this.clean.clearUnverified();
}
};
},
- block: function()
+ modal: function()
{
return {
- formatting: function(name)
+ callbacks: {},
+ loadTemplates: function()
{
- var type, value;
+ this.opts.modal = {
+ imageEdit: String()
+ + '<section id="redactor-modal-image-edit">'
+ + '<label>' + this.lang.get('title') + '</label>'
+ + '<input type="text" id="redactor-image-title" />'
+ + '<label class="redactor-image-link-option">' + this.lang.get('link') + '</label>'
+ + '<input type="text" id="redactor-image-link" class="redactor-image-link-option" />'
+ + '<label class="redactor-image-link-option"><input type="checkbox" id="redactor-image-link-blank"> ' + this.lang.get('link_new_tab') + '</label>'
+ + '<label class="redactor-image-position-option">' + this.lang.get('image_position') + '</label>'
+ + '<select class="redactor-image-position-option" id="redactor-image-align">'
+ + '<option value="none">' + this.lang.get('none') + '</option>'
+ + '<option value="left">' + this.lang.get('left') + '</option>'
+ + '<option value="center">' + this.lang.get('center') + '</option>'
+ + '<option value="right">' + this.lang.get('right') + '</option>'
+ + '</select>'
+ + '</section>',
- 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';
+ image: String()
+ + '<section id="redactor-modal-image-insert">'
+ + '<div id="redactor-modal-image-droparea"></div>'
+ + '</section>',
- if (type) value = this.formatting[name][type];
+ file: String()
+ + '<section id="redactor-modal-file-insert">'
+ + '<div id="redactor-modal-file-upload-box">'
+ + '<label>' + this.lang.get('filename') + '</label>'
+ + '<input type="text" id="redactor-filename" /><br><br>'
+ + '<div id="redactor-modal-file-upload"></div>'
+ + '</div>'
+ + '</section>',
- this.block.format(this.formatting[name].tag, type, value);
+ link: String()
+ + '<section id="redactor-modal-link-insert">'
+ + '<label>URL</label>'
+ + '<input type="url" id="redactor-link-url" />'
+ + '<label>' + this.lang.get('text') + '</label>'
+ + '<input type="text" id="redactor-link-url-text" />'
+ + '<label><input type="checkbox" id="redactor-link-blank"> ' + this.lang.get('link_new_tab') + '</label>'
+ + '</section>'
+ };
- },
- format: function(tag, type, value)
- {
- if (tag == 'quote') tag = 'blockquote';
- var formatTags = ['p', 'pre', 'blockquote', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
- if ($.inArray(tag, formatTags) == -1) return;
+ $.extend(this.opts, this.opts.modal);
- this.block.isRemoveInline = (tag == 'pre' || tag.search(/h[1-6]/i) != -1);
-
- // focus
- if (!this.utils.browser('msie')) this.$editor.focus();
-
- this.block.blocks = this.selection.getBlocks();
-
- this.block.blocksSize = this.block.blocks.length;
- this.block.type = type;
- this.block.value = value;
-
- this.buffer.set();
- this.selection.save();
-
- this.block.set(tag);
-
- this.selection.restore();
- this.code.sync();
-
},
- set: function(tag)
+ addCallback: function(name, callback)
{
- this.selection.get();
- this.block.containerTag = this.range.commonAncestorContainer.tagName;
-
- if (this.range.collapsed)
- {
- this.block.setCollapsed(tag);
- }
- else
- {
- this.block.setMultiple(tag);
- }
+ this.modal.callbacks[name] = callback;
},
- setCollapsed: function(tag)
+ createTabber: function($modal)
{
- var block = this.block.blocks[0];
- if (block === false) return;
+ this.modal.$tabber = $('<div>').attr('id', 'redactor-modal-tabber');
- if (block.tagName == 'LI')
- {
- if (tag != 'blockquote') return;
-
- this.block.formatListToBlockquote();
- return;
- }
-
- var isContainerTable = (this.block.containerTag == 'TD' || this.block.containerTag == 'TH');
- if (isContainerTable)
- {
- if (!this.opts.linebreaks && tag == 'p')
- {
- document.execCommand('formatblock', false, '<' + tag + '>');
-
- block = this.selection.getBlock();
- this.block.toggle($(block));
- }
- }
- else if (block.tagName.toLowerCase() != tag)
- {
- if (this.opts.linebreaks && tag == 'p')
- {
- $(block).prepend('<br>').append('<br>');
- this.utils.replaceWithContents(block);
- }
- else
- {
- var $formatted = this.utils.replaceToTag(block, tag);
-
- this.block.toggle($formatted);
-
- 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();
- }
- }
- else if (tag == 'blockquote' && block.tagName.toLowerCase() == tag)
- {
- // blockquote off
- if (this.opts.linebreaks)
- {
- $(block).prepend('<br>').append('<br>');
- this.utils.replaceWithContents(block);
- }
- else
- {
- var $el = this.utils.replaceToTag(block, 'p');
- this.block.toggle($el);
- }
- }
- else if (block.tagName.toLowerCase() == tag)
- {
- this.block.toggle($(block));
- }
-
+ $modal.prepend(this.modal.$tabber);
},
- setMultiple: function(tag)
+ addTab: function(id, name, active)
{
- var block = this.block.blocks[0];
- var isContainerTable = (this.block.containerTag == 'TD' || this.block.containerTag == 'TH');
-
- if (block !== false && this.block.blocksSize === 1)
+ var $tab = $('<a href="#" rel="tab' + id + '">').text(name);
+ if (active)
{
- if (block.tagName.toLowerCase() == tag && tag == 'blockquote')
- {
- // blockquote off
- if (this.opts.linebreaks)
- {
- $(block).prepend('<br>').append('<br>');
- this.utils.replaceWithContents(block);
- }
- else
- {
- var $el = this.utils.replaceToTag(block, 'p');
- this.block.toggle($el);
- }
- }
- else if (block.tagName == 'LI')
- {
- if (tag != 'blockquote') return;
-
- this.block.formatListToBlockquote();
- }
- else if (this.block.containerTag == 'BLOCKQUOTE')
- {
- this.block.formatBlockquote(tag);
- }
- else if (this.opts.linebreaks && ((isContainerTable) || (this.range.commonAncestorContainer != block)))
- {
- this.block.formatWrap(tag);
- }
- else
- {
- if (this.opts.linebreaks && tag == 'p')
- {
- $(block).prepend('<br>').append('<br>');
- this.utils.replaceWithContents(block);
- }
- else
- {
-
- var $formatted = this.utils.replaceToTag(block, tag);
-
- this.block.toggle($formatted);
-
- if (this.block.isRemoveInline) this.utils.removeInlineTags($formatted);
- if (tag == 'p' || this.block.headTag) $formatted.find('p').contents().unwrap();
- }
- }
+ $tab.addClass('active');
}
- else
+
+ var self = this;
+ $tab.on('click', function(e)
{
- if (this.opts.linebreaks || tag != 'p')
- {
- if (tag == 'blockquote')
- {
- var count = 0;
- for (var i = 0; i < this.block.blocksSize; i++)
- {
- if (this.block.blocks[i].tagName == 'BLOCKQUOTE') count++;
- }
+ e.preventDefault();
+ $('.redactor-tab').hide();
+ $('.redactor-' + $(this).attr('rel')).show();
- // only blockquote selected
- if (count == this.block.blocksSize)
- {
- $.each(this.block.blocks, $.proxy(function(i,s)
- {
- if (this.opts.linebreaks)
- {
- $(s).prepend('<br>').append('<br>');
- this.utils.replaceWithContents(s);
- }
- else
- {
- this.utils.replaceToTag(s, 'p');
- }
+ self.modal.$tabber.find('a').removeClass('active');
+ $(this).addClass('active');
- }, this));
+ });
- return;
- }
-
- }
-
- this.block.formatWrap(tag);
- }
- else
- {
- var classSize = 0;
- var toggleType = false;
- if (this.block.type == 'class')
- {
- toggleType = 'toggle';
- classSize = $(this.block.blocks).filter('.' + this.block.value).size();
-
- if (this.block.blocksSize == classSize) toggleType = 'toggle';
- else if (this.block.blocksSize > classSize) toggleType = 'set';
- else if (classSize === 0) toggleType = 'set';
-
- }
-
- var exceptTags = ['ul', 'ol', 'li', 'td', 'th', 'dl', 'dt', 'dd'];
- $.each(this.block.blocks, $.proxy(function(i,s)
- {
- if ($.inArray(s.tagName.toLowerCase(), exceptTags) != -1) return;
-
- var $formatted = this.utils.replaceToTag(s, tag);
-
- if (toggleType)
- {
- if (toggleType == 'toggle') this.block.toggle($formatted);
- else if (toggleType == 'remove') this.block.remove($formatted);
- else if (toggleType == 'set') this.block.set2($formatted);
- }
- else this.block.toggle($formatted);
-
- 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();
-
-
- }, this));
- }
- }
+ this.modal.$tabber.append($tab);
},
- toggle: function($el)
+ addTemplate: function(name, template)
{
- if (this.block.type == 'class')
- {
- $el.toggleClass(this.block.value);
- return;
- }
- else if (this.block.type == 'attr' || this.block.type == 'data')
- {
- if ($el.attr(this.block.value.name) == this.block.value.value)
- {
- $el.removeAttr(this.block.value.name);
- }
- else
- {
- $el.attr(this.block.value.name, this.block.value.value);
- }
-
- return;
- }
- else
- {
- $el.removeAttr('style class');
- return;
- }
+ this.opts.modal[name] = template;
},
- remove: function($el)
+ getTemplate: function(name)
{
- $el.removeClass(this.block.value);
+ return this.opts.modal[name];
},
- formatListToBlockquote: function()
+ getModal: function()
{
- var block = $(this.block.blocks[0]).closest('ul, ol');
-
- $(block).find('ul, ol').contents().unwrap();
- $(block).find('li').append($('<br>')).contents().unwrap();
-
- var $el = this.utils.replaceToTag(block, 'blockquote');
- this.block.toggle($el);
+ return this.$modalBody.find('section');
},
- formatBlockquote: function(tag)
+ load: function(templateName, title, width)
{
- document.execCommand('outdent');
- document.execCommand('formatblock', false, tag);
+ this.modal.templateName = templateName;
+ this.modal.width = width;
- this.clean.clearUnverified();
- this.$editor.find('p:empty').remove();
+ this.modal.build();
+ this.modal.enableEvents();
+ this.modal.setTitle(title);
+ this.modal.setDraggable();
+ this.modal.setContent();
- var formatted = this.selection.getBlock();
-
- if (tag != 'p')
+ // callbacks
+ if (typeof this.modal.callbacks[templateName] != 'undefined')
{
- $(formatted).find('img').remove();
+ this.modal.callbacks[templateName].call(this);
}
- if (!this.opts.linebreaks)
+ },
+ show: function()
+ {
+ // ios keyboard hide
+ if (this.utils.isMobile() && !this.utils.browser('msie'))
{
- this.block.toggle($(formatted));
+ document.activeElement.blur();
}
- this.$editor.find('ul, ol, tr, blockquote, p').each($.proxy(this.utils.removeEmpty, this));
+ $(document.body).removeClass('body-redactor-hidden');
+ this.modal.bodyOveflow = $(document.body).css('overflow');
+ $(document.body).css('overflow', 'hidden');
- if (this.opts.linebreaks && tag == 'p')
+ if (this.utils.isMobile())
{
- this.utils.replaceWithContents(formatted);
+ this.modal.showOnMobile();
}
-
- },
- formatWrap: function(tag)
- {
- if (this.block.containerTag == 'UL' || this.block.containerTag == 'OL')
+ else
{
- if (tag == 'blockquote')
- {
- this.block.formatListToBlockquote();
- }
- else
- {
- return;
- }
+ this.modal.showOnDesktop();
}
- var formatted = this.selection.wrap(tag);
- if (formatted === false) return;
+ this.$modalOverlay.show();
+ this.$modalBox.show();
- var $formatted = $(formatted);
+ this.modal.setButtonsWidth();
- this.block.formatTableWrapping($formatted);
+ this.utils.saveScroll();
- var $elements = $formatted.find(this.opts.blockLevelElements.join(',') + ', td, table, thead, tbody, tfoot, th, tr');
-
- if ((this.opts.linebreaks && tag == 'p') || tag == 'pre' || tag == 'blockquote')
+ // resize
+ if (!this.utils.isMobile())
{
- $elements.append('<br />');
+ setTimeout($.proxy(this.modal.showOnDesktop, this), 0);
+ $(window).on('resize.redactor-modal', $.proxy(this.modal.resize, this));
}
- $elements.contents().unwrap();
+ // modal shown callback
+ this.core.setCallback('modalOpened', this.modal.templateName, this.$modal);
- if (tag != 'p' && tag != 'blockquote') $formatted.find('img').remove();
+ // fix bootstrap modal focus
+ $(document).off('focusin.modal');
- $.each(this.block.blocks, $.proxy(this.utils.removeEmpty, this));
+ // enter
+ this.$modal.find('input[type=text]').on('keydown.redactor-modal', $.proxy(this.modal.setEnter, this));
- $formatted.append(this.selection.getMarker(2));
+ },
+ showOnDesktop: function()
+ {
+ var height = this.$modal.outerHeight();
+ var windowHeight = $(window).height();
+ var windowWidth = $(window).width();
- if (!this.opts.linebreaks)
+ if (this.modal.width > windowWidth)
{
- this.block.toggle($formatted);
+ this.$modal.css({
+ width: '96%',
+ marginTop: (windowHeight/2 - height/2) + 'px'
+ });
+ return;
}
- this.$editor.find('ul, ol, tr, blockquote, p').each($.proxy(this.utils.removeEmpty, this));
- $formatted.find('blockquote:empty').remove();
-
- if (this.block.isRemoveInline)
+ if (height > windowHeight)
{
- this.utils.removeInlineTags($formatted);
+ this.$modal.css({
+ width: this.modal.width + 'px',
+ marginTop: '20px'
+ });
}
-
- if (this.opts.linebreaks && tag == 'p')
+ else
{
- this.utils.replaceWithContents($formatted);
+ this.$modal.css({
+ width: this.modal.width + 'px',
+ marginTop: (windowHeight/2 - height/2) + 'px'
+ });
}
-
},
- formatTableWrapping: function($formatted)
+ showOnMobile: function()
{
- if ($formatted.closest('table').size() === 0) return;
+ this.$modal.css({
+ width: '96%',
+ marginTop: '2%'
+ });
- if ($formatted.closest('tr').size() === 0) $formatted.wrap('<tr>');
- if ($formatted.closest('td').size() === 0) $formatted.wrap('<td>');
},
- removeData: function(name, value)
+ resize: function()
{
- var blocks = this.selection.getBlocks();
- $(blocks).removeAttr('data-' + name);
-
- this.code.sync();
+ if (this.utils.isMobile())
+ {
+ this.modal.showOnMobile();
+ }
+ else
+ {
+ this.modal.showOnDesktop();
+ }
},
- setData: function(name, value)
+ setTitle: function(title)
{
- var blocks = this.selection.getBlocks();
- $(blocks).attr('data-' + name, value);
-
- this.code.sync();
+ this.$modalHeader.html(title);
},
- toggleData: function(name, value)
+ setContent: function()
{
- var blocks = this.selection.getBlocks();
- $.each(blocks, function()
- {
- if ($(this).attr('data-' + name))
- {
- $(this).removeAttr('data-' + name);
- }
- else
- {
- $(this).attr('data-' + name, value);
- }
- });
+ this.$modalBody.html(this.modal.getTemplate(this.modal.templateName));
},
- removeAttr: function(attr, value)
+ setDraggable: function()
{
- var blocks = this.selection.getBlocks();
- $(blocks).removeAttr(attr);
+ if (typeof $.fn.draggable === 'undefined') return;
- this.code.sync();
+ this.$modal.draggable({ handle: this.$modalHeader });
+ this.$modalHeader.css('cursor', 'move');
},
- setAttr: function(attr, value)
+ setEnter: function(e)
{
- var blocks = this.selection.getBlocks();
- $(blocks).attr(attr, value);
+ if (e.which != 13) return;
- this.code.sync();
+ e.preventDefault();
+ this.$modal.find('button.redactor-modal-action-btn').click();
},
- toggleAttr: function(attr, value)
+ createCancelButton: function()
{
- var blocks = this.selection.getBlocks();
- $.each(blocks, function()
- {
- if ($(this).attr(name))
- {
- $(this).removeAttr(name);
- }
- else
- {
- $(this).attr(name, value);
- }
- });
+ var button = $('<button>').addClass('redactor-modal-btn redactor-modal-close-btn').html(this.lang.get('cancel'));
+ button.on('click', $.proxy(this.modal.close, this));
+
+ this.$modalFooter.append(button);
},
- removeClass: function(className)
+ createDeleteButton: function(label)
{
- var blocks = this.selection.getBlocks();
- $(blocks).removeClass(className);
-
- this.utils.removeEmptyAttr(blocks, 'class');
-
- this.code.sync();
+ return this.modal.createButton(label, 'delete');
},
- setClass: function(className)
+ createActionButton: function(label)
{
- var blocks = this.selection.getBlocks();
- $(blocks).addClass(className);
-
- this.code.sync();
+ return this.modal.createButton(label, 'action');
},
- toggleClass: function(className)
+ createButton: function(label, className)
{
- var blocks = this.selection.getBlocks();
- $(blocks).toggleClass(className);
+ var button = $('<button>').addClass('redactor-modal-btn').addClass('redactor-modal-' + className + '-btn').html(label);
+ this.$modalFooter.append(button);
- this.code.sync();
- }
- };
- },
- inline: function()
- {
- return {
- formatting: function(name)
+ return button;
+ },
+ setButtonsWidth: function()
{
- var type, value;
+ var buttons = this.$modalFooter.find('button');
+ var buttonsSize = buttons.size();
+ if (buttonsSize === 0) return;
- if (typeof this.formatting[name].style != 'undefined') type = 'style';
- 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);
-
+ buttons.css('width', (100/buttonsSize) + '%');
},
- format: function(tag, type, value)
+ build: function()
{
- // Stop formatting pre
- if (this.utils.isCurrentOrParent('PRE')) return;
+ this.modal.buildOverlay();
- var tags = ['b', 'bold', 'i', 'italic', 'underline', 'strikethrough', 'deleted', 'superscript', 'subscript'];
- var replaced = ['strong', 'strong', 'em', 'em', 'u', 'del', 'del', 'sup', 'sub'];
+ this.$modalBox = $('<div id="redactor-modal-box" />').hide();
+ this.$modal = $('<div id="redactor-modal" />');
+ this.$modalHeader = $('<header />');
+ this.$modalClose = $('<span id="redactor-modal-close" />').html('×');
+ this.$modalBody = $('<div id="redactor-modal-body" />');
+ this.$modalFooter = $('<footer />');
- for (var i = 0; i < tags.length; i++)
- {
- if (tag == tags[i]) tag = replaced[i];
- }
-
- this.inline.type = type || false;
- this.inline.value = value || false;
-
- this.buffer.set();
- this.$editor.focus();
-
- this.selection.get();
-
- if (this.range.collapsed)
- {
- this.inline.formatCollapsed(tag);
- }
- else
- {
- this.inline.formatMultiple(tag);
- }
+ this.$modal.append(this.$modalHeader);
+ this.$modal.append(this.$modalClose);
+ this.$modal.append(this.$modalBody);
+ this.$modal.append(this.$modalFooter);
+ this.$modalBox.append(this.$modal);
+ this.$modalBox.appendTo(document.body);
},
- formatCollapsed: function(tag)
+ buildOverlay: function()
{
- var current = this.selection.getCurrent();
- var $parent = $(current).closest(tag + '[data-redactor-tag=' + tag + ']');
-
- // inline there is
- if ($parent.size() !== 0)
- {
- this.caret.setAfter($parent[0]);
-
- // remove empty
- if (this.utils.isEmpty($parent.text())) $parent.remove();
-
- this.code.sync();
-
- return;
- }
-
- // create empty inline
- var node = $('<' + tag + '>').attr('data-verified', 'redactor').attr('data-redactor-tag', tag);
- node.html(this.opts.invisibleSpace);
-
- node = this.inline.setFormat(node);
-
- this.insert.node(node);
- this.caret.setEnd(node);
-
- this.code.sync();
-
- return;
+ this.$modalOverlay = $('<div id="redactor-modal-overlay">').hide();
+ $('body').prepend(this.$modalOverlay);
},
- formatMultiple: function(tag)
+ enableEvents: function()
{
- this.inline.formatConvert(tag);
+ this.$modalClose.on('click.redactor-modal', $.proxy(this.modal.close, this));
+ $(document).on('keyup.redactor-modal', $.proxy(this.modal.closeHandler, this));
+ this.$editor.on('keyup.redactor-modal', $.proxy(this.modal.closeHandler, this));
+ this.$modalBox.on('click.redactor-modal', $.proxy(this.modal.close, this));
+ },
+ disableEvents: function()
+ {
+ this.$modalClose.off('click.redactor-modal');
+ $(document).off('keyup.redactor-modal');
+ this.$editor.off('keyup.redactor-modal');
+ this.$modalBox.off('click.redactor-modal');
+ $(window).off('resize.redactor-modal');
+ },
+ closeHandler: function(e)
+ {
+ if (e.which != this.keyCode.ESC) return;
- this.selection.save();
- document.execCommand('strikethrough');
-
- this.$editor.find('strike').each($.proxy(function(i,s)
- {
- var $el = $(s);
-
- this.inline.formatRemoveSameChildren($el, tag);
-
- var $span;
- if (this.inline.type)
- {
- $span = $('<span>').attr('data-redactor-tag', tag).attr('data-verified', 'redactor');
- $span = this.inline.setFormat($span);
- }
- else
- {
- $span = $('<' + tag + '>').attr('data-redactor-tag', tag).attr('data-verified', 'redactor');
- }
-
- $el.replaceWith($span.html($el.contents()));
-
- if (tag == 'span')
- {
- var $parent = $span.parent();
- if ($parent && $parent[0].tagName == 'SPAN' && this.inline.type == 'style')
- {
- var arr = this.inline.value.split(';');
-
- for (var z = 0; z < arr.length; z++)
- {
- if (arr[z] === '') return;
- var style = arr[z].split(':');
- $parent.css(style[0], '');
-
- if (this.utils.removeEmptyAttr($parent, 'style'))
- {
- $parent.replaceWith($parent.contents());
- }
-
- }
-
- }
- }
-
- }, this));
-
- if (tag != 'del')
- {
- var self = this;
- this.$editor.find('inline').each(function(i,s)
- {
- self.utils.replaceToTag(s, 'del');
- });
- }
-
- this.selection.restore();
- this.code.sync();
-
+ this.modal.close(false);
},
- formatRemoveSameChildren: function($el, tag)
+ close: function(e)
{
- $el.children(tag).each(function()
+ if (e)
{
- var $child = $(this);
- if (!$child.hasClass('redactor-selection-marker'))
+ if (!$(e.target).hasClass('redactor-modal-close-btn') && e.target != this.$modalClose[0] && e.target != this.$modalBox[0])
{
- $child.contents().unwrap();
+ return;
}
- });
- },
- formatConvert: function(tag)
- {
- this.selection.save();
- var find = '';
- if (this.inline.type == 'class') find = '[data-redactor-class=' + this.inline.value + ']';
- else if (this.inline.type == 'style')
- {
- find = '[data-redactor-style="' + this.inline.value + '"]';
+ e.preventDefault();
}
- if (tag != 'del')
- {
- var self = this;
- this.$editor.find('del').each(function(i,s)
- {
- self.utils.replaceToTag(s, 'inline');
- });
- }
+ if (!this.$modalBox) return;
- this.$editor.find('[data-redactor-tag="' + tag + '"]' + find).each(function()
- {
- if (find === '' && tag == 'span' && this.tagName.toLowerCase() == tag) return;
+ this.modal.disableEvents();
- var $el = $(this);
- $el.replaceWith($('<strike />').html($el.contents()));
+ this.$modalOverlay.remove();
- });
-
- this.selection.restore();
- },
- setFormat: function(node)
- {
- switch (this.inline.type)
+ this.$modalBox.fadeOut('fast', $.proxy(function()
{
- case 'class':
+ this.$modalBox.remove();
- if (node.hasClass(this.inline.value))
- {
- node.removeClass(this.inline.value);
- node.removeAttr('data-redactor-class');
- }
- else
- {
- node.addClass(this.inline.value);
- node.attr('data-redactor-class', this.inline.value);
- }
+ setTimeout($.proxy(this.utils.restoreScroll, this), 0);
+ if (e !== undefined) this.selection.restore();
- break;
- case 'style':
+ $(document.body).css('overflow', this.modal.bodyOveflow);
+ this.core.setCallback('modalClosed', this.modal.templateName);
- node[0].style.cssText = this.inline.value;
- node.attr('data-redactor-style', this.inline.value);
+ }, this));
- break;
- }
-
- return node;
+ }
+ };
+ },
+ observe: function()
+ {
+ return {
+ load: function()
+ {
+ this.observe.images();
+ this.observe.links();
},
- removeStyle: function()
+ buttons: function(e, btnName)
{
- this.buffer.set();
var current = this.selection.getCurrent();
- var nodes = this.selection.getInlines();
+ var parent = this.selection.getParent();
- this.selection.save();
+ this.button.setInactiveAll(btnName);
- if (current && current.tagName === 'SPAN')
+ if (e === false && btnName !== 'html')
{
- var $s = $(current);
-
- $s.removeAttr('style');
- if ($s[0].attributes.length === 0)
- {
- $s.replaceWith($s.contents());
- }
+ if ($.inArray(btnName, this.opts.activeButtons) != -1) this.button.toggleActive(btnName);
+ return;
}
- $.each(nodes, $.proxy(function(i,s)
- {
- var $s = $(s);
- if ($.inArray(s.tagName.toLowerCase(), this.opts.inlineTags) != -1 && !$s.hasClass('redactor-selection-marker'))
- {
- $s.removeAttr('style');
- if ($s[0].attributes.length === 0)
- {
- $s.replaceWith($s.contents());
- }
- }
- }, this));
+ //var linkButtonName = (this.utils.isCurrentOrParent('A')) ? this.lang.get('link_edit') : this.lang.get('link_insert');
+ //$('body').find('a.redactor-dropdown-link').text(linkButtonName);
- this.selection.restore();
- this.code.sync();
-
- },
- removeStyleRule: function(name)
- {
- this.buffer.set();
- var parent = this.selection.getParent();
- var nodes = this.selection.getInlines();
-
- this.selection.save();
-
- if (parent && parent.tagName === 'SPAN')
+ $.each(this.opts.activeButtonsStates, $.proxy(function(key, value)
{
- var $s = $(parent);
+ var parentEl = $(parent).closest(key);
+ var currentEl = $(current).closest(key);
- $s.css(name, '');
- this.utils.removeEmptyAttr($s, 'style');
- if ($s[0].attributes.length === 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)
{
- $s.replaceWith($s.contents());
+ this.button.setActive(value);
}
- }
- $.each(nodes, $.proxy(function(i,s)
- {
- var $s = $(s);
- if ($.inArray(s.tagName.toLowerCase(), this.opts.inlineTags) != -1 && !$s.hasClass('redactor-selection-marker'))
- {
- $s.css(name, '');
- this.utils.removeEmptyAttr($s, 'style');
- if ($s[0].attributes.length === 0)
- {
- $s.replaceWith($s.contents());
- }
- }
}, this));
- this.selection.restore();
- this.code.sync();
+ var $parent = $(parent).closest(this.opts.alignmentTags.toString().toLowerCase());
+ if (this.utils.isRedactorParent(parent) && $parent.length)
+ {
+ var align = ($parent.css('text-align') === '') ? 'left' : $parent.css('text-align');
+ this.button.setActive('align' + align);
+ }
},
- removeFormat: function()
+ addButton: function(tag, btnName)
{
- this.buffer.set();
- var current = this.selection.getCurrent();
+ this.opts.activeButtons.push(btnName);
+ this.opts.activeButtonsStates[tag] = btnName;
+ },
+ images: function()
+ {
+ this.$editor.find('img').each($.proxy(function(i, img)
+ {
+ var $img = $(img);
- this.selection.save();
+ // 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(); });
- document.execCommand('removeFormat');
+ if (this.utils.browser('msie')) $img.attr('unselectable', 'on');
- if (current && current.tagName === 'SPAN')
- {
- $(current).replaceWith($(current).contents());
- }
+ this.image.setEditable($img);
+ }, this));
- $.each(this.selection.getNodes(), $.proxy(function(i,s)
+ $(document).on('click.redactor-image-delete', $.proxy(function(e)
{
- var $s = $(s);
- if ($.inArray(s.tagName.toLowerCase(), this.opts.inlineTags) != -1 && !$s.hasClass('redactor-selection-marker'))
+ this.observe.image = false;
+ if (e.target.tagName == 'IMG' && this.utils.isRedactorParent(e.target))
{
- $s.replaceWith($s.contents());
+ this.observe.image = (this.observe.image && this.observe.image == e.target) ? false : e.target;
}
+
}, this));
- this.selection.restore();
- this.code.sync();
+ },
+ 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));
},
- toggleClass: function(className)
+ getTooltipPosition: function($link)
{
- this.inline.format('span', 'class', className);
+ return $link.offset();
},
- toggleStyle: function(value)
+ showTooltip: function(e)
{
- this.inline.format('span', 'style', value);
- }
- };
- },
- insert: function()
- {
- return {
- set: function(html, clean)
- {
- this.placeholder.remove();
+ var $link = $(e.target);
+ var $parent = $link.closest('a');
+ var tag = ($link.size() !== 0) ? $link[0].tagName : false;
- html = this.clean.setVerified(html);
-
- if (typeof clean == 'undefined')
+ if ($parent[0].tagName === 'A')
{
- html = this.clean.onPaste(html, false);
+ if (tag === 'IMG') return;
+ else if (tag !== 'A') $link = $parent;
}
- this.$editor.html(html);
- this.selection.remove();
- this.focus.setEnd();
- this.clean.normalizeLists();
- this.code.sync();
- this.observe.load();
-
- if (typeof clean == 'undefined')
+ if (tag !== 'A')
{
- setTimeout($.proxy(this.clean.clearUnverified, this), 10);
+ return;
}
- },
- text: function(text)
- {
- this.placeholder.remove();
- text = text.toString();
- text = $.trim(text);
- text = this.clean.getPlainText(text, false);
+ var pos = this.observe.getTooltipPosition($link);
+ var tooltip = $('<span class="redactor-link-tooltip"></span>');
- this.$editor.focus();
-
- if (this.utils.browser('msie'))
+ var href = $link.attr('href');
+ if (href === undefined)
{
- this.insert.htmlIe(text);
+ href = '';
}
- else
- {
- this.selection.get();
- this.range.deleteContents();
- var el = document.createElement("div");
- el.innerHTML = text;
- var frag = document.createDocumentFragment(), node, lastNode;
- while ((node = el.firstChild))
- {
- lastNode = frag.appendChild(node);
- }
+ if (href.length > 24) href = href.substring(0, 24) + '...';
- this.range.insertNode(frag);
+ var aLink = $('<a href="' + $link.attr('href') + '" target="_blank" />').html(href).addClass('redactor-link-tooltip-action');
+ var aEdit = $('<a href="#" />').html(this.lang.get('edit')).on('click', $.proxy(this.link.show, this)).addClass('redactor-link-tooltip-action');
+ var aUnlink = $('<a href="#" />').html(this.lang.get('unlink')).on('click', $.proxy(this.link.unlink, this)).addClass('redactor-link-tooltip-action');
- if (lastNode)
- {
- var range = this.range.cloneRange();
- range.setStartAfter(lastNode);
- range.collapse(true);
- this.sel.removeAllRanges();
- this.sel.addRange(range);
- }
- }
+ tooltip.append(aLink).append(' | ').append(aEdit).append(' | ').append(aUnlink);
+ tooltip.css({
+ top: (pos.top + 20) + 'px',
+ left: pos.left + 'px'
+ });
- this.code.sync();
- this.clean.clearUnverified();
+ $('.redactor-link-tooltip').remove();
+ $('body').append(tooltip);
},
- html: function(html, clean)
+ closeTooltip: function(e)
{
- this.placeholder.remove();
+ e = e.originalEvent || e;
- if (typeof clean == 'undefined') clean = true;
-
- this.$editor.focus();
-
- html = this.clean.setVerified(html);
-
- if (clean)
+ var target = e.target;
+ var $parent = $(target).closest('a');
+ if ($parent.size() !== 0 && $parent[0].tagName === 'A' && target.tagName !== 'A')
{
- html = this.clean.onPaste(html);
+ return;
}
-
- if (this.utils.browser('msie'))
+ else if ((target.tagName === 'A' && this.utils.isRedactorParent(target)) || $(target).hasClass('redactor-link-tooltip-action'))
{
- this.insert.htmlIe(html);
+ return;
}
- else
- {
- if (this.clean.singleLine) this.insert.execHtml(html);
- else document.execCommand('insertHTML', null, html);
- this.insert.htmlFixMozilla();
- }
+ $('.redactor-link-tooltip').remove();
+ }
- this.clean.normalizeLists();
+ };
+ },
+ paragraphize: function()
+ {
+ return {
+ load: function(html)
+ {
+ if (this.opts.linebreaks) return html;
+ if (html === '' || html === '<p></p>') return this.opts.emptyHtml;
- // remove empty paragraphs finaly
- if (!this.opts.linebreaks)
- {
- this.$editor.find('p').each($.proxy(this.utils.removeEmpty, this));
- }
+ 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'];
- this.code.sync();
- this.observe.load();
+ html = html + "\n";
- if (clean)
- {
- this.clean.clearUnverified();
- }
+ this.paragraphize.safes = [];
+ this.paragraphize.z = 0;
- },
- htmlFixMozilla: function()
- {
- // FF inserts empty p when content was selected dblclick
- if (!this.utils.browser('mozilla')) return;
+ html = html.replace(/(<br\s?\/?>){1,}\n?<\/blockquote>/gi, '</blockquote>');
- var $next = $(this.selection.getBlock()).next();
- if ($next.length > 0 && $next[0].tagName == 'P' && $next.html() === '')
- {
- $next.remove();
- }
+ html = this.paragraphize.getSafes(html);
+ html = this.paragraphize.getSafesComments(html);
+ 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>');
+
+ return $.trim(html);
},
- htmlIe: function(html)
+ getSafes: function(html)
{
- if (this.utils.isIe11())
+ var $div = $('<div />').append(html);
+
+ // remove paragraphs in blockquotes
+ $div.find('blockquote p').replaceWith(function()
{
- var parent = this.utils.isCurrentOrParent('P');
- var $html = $('<div>').append(html);
- var blocksMatch = $html.contents().is('p, :header, dl, ul, ol, div, table, td, blockquote, pre, address, section, header, footer, aside, article');
+ return $(this).append('<br />').contents();
+ });
- if (parent && blocksMatch) this.insert.ie11FixInserting(parent, html);
- else this.insert.ie11PasteFrag(html);
+ html = $div.html();
- return;
- }
+ $div.find(this.paragraphize.blocks.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 + '}');
- document.selection.createRange().pasteHTML(html);
+ }, this));
+ return html;
},
- execHtml: function(html)
+ getSafesComments: function(html)
{
- html = this.clean.setVerified(html);
+ var commentsMatches = html.match(/<!--([\w\W]*?)-->/gi);
- this.selection.get();
+ if (!commentsMatches) return html;
- this.range.deleteContents();
-
- var el = document.createElement('div');
- el.innerHTML = html;
-
- var frag = document.createDocumentFragment(), node, lastNode;
- while ((node = el.firstChild))
+ $.each(commentsMatches, $.proxy(function(i,s)
{
- lastNode = frag.appendChild(node);
- }
+ this.paragraphize.z++;
+ this.paragraphize.safes[this.paragraphize.z] = s;
+ html = html.replace(s, '\n{replace' + this.paragraphize.z + '}');
+ }, this));
- this.range.insertNode(frag);
-
- this.range.collapse(true);
- this.caret.setAfter(lastNode);
-
+ return html;
},
- node: function(node)
+ restoreSafes: function(html)
{
- node = node[0] || node;
+ $.each(this.paragraphize.safes, function(i,s)
+ {
+ html = html.replace('{replace' + i + '}', s);
+ });
- this.selection.get();
- this.range.deleteContents();
- this.range.insertNode(node);
- this.range.collapse(false);
- this.selection.addRange();
-
- return node;
+ return html;
},
- nodeToPoint: function(node, x, y)
+ replaceBreaksToParagraphs: function(html)
{
- node = node[0] || node;
+ var htmls = html.split(new RegExp('\n', 'g'), -1);
- this.selection.get();
-
- var range;
- if (document.caretPositionFromPoint)
+ html = '';
+ if (htmls)
{
- var pos = document.caretPositionFromPoint(x, y);
+ var len = htmls.length;
+ for (var i = 0; i < len; i++)
+ {
+ if (!htmls.hasOwnProperty(i)) return;
- this.range.setStart(pos.offsetNode, pos.offset);
- this.range.collapse(true);
- this.range.insertNode(node);
- }
- else if (document.caretRangeFromPoint)
- {
- range = document.caretRangeFromPoint(x, y);
- range.insertNode(node);
- }
- else if (typeof document.body.createTextRange != "undefined")
- {
- range = document.body.createTextRange();
- range.moveToPoint(x, y);
- var endRange = range.duplicate();
- endRange.moveToPoint(x, y);
- range.setEndPoint("EndToEnd", endRange);
- range.select();
- }
- },
- nodeToCaretPositionFromPoint: function(e, node)
- {
- node = node[0] || node;
+ if (htmls[i].search('{replace') == -1)
+ {
+ htmls[i] = htmls[i].replace(/<p>\n\t?<\/p>/gi, '');
+ htmls[i] = htmls[i].replace(/<p><\/p>/gi, '');
- var range;
- var x = e.clientX, y = e.clientY;
- if (document.caretPositionFromPoint)
- {
- var pos = document.caretPositionFromPoint(x, y);
- var sel = document.getSelection();
- range = sel.getRangeAt(0);
- range.setStart(pos.offsetNode, pos.offset);
- range.collapse(true);
- range.insertNode(node);
+ if (htmls[i] !== '')
+ {
+ html += '<p>' + htmls[i].replace(/^\n+|\n+$/g, "") + "</p>";
+ }
+ }
+ else html += htmls[i];
+ }
}
- else if (document.caretRangeFromPoint)
- {
- range = document.caretRangeFromPoint(x, y);
- range.insertNode(node);
- }
- else if (typeof document.body.createTextRange != "undefined")
- {
- range = document.body.createTextRange();
- range.moveToPoint(x, y);
- var endRange = range.duplicate();
- endRange.moveToPoint(x, y);
- range.setEndPoint("EndToEnd", endRange);
- range.select();
- }
+ return html;
},
- ie11FixInserting: function(parent, html)
+ replaceBreaksToNewLines: function(html)
{
- var node = document.createElement('span');
- node.className = 'redactor-ie-paste';
- this.insert.node(node);
+ html = html.replace(/<br \/>\s*<br \/>/gi, "\n\n");
+ html = html.replace(/<br\s?\/?>\n?<br\s?\/?>/gi, "\n<br /><br />");
- var parHtml = $(parent).html();
+ html = html.replace(new RegExp("\r\n", 'g'), "\n");
+ html = html.replace(new RegExp("\r", 'g'), "\n");
+ html = html.replace(new RegExp("/\n\n+/"), 'g', "\n\n");
- parHtml = '<p>' + parHtml.replace(/<span class="redactor-ie-paste"><\/span>/gi, '</p>' + html + '<p>') + '</p>';
- $(parent).replaceWith(parHtml);
+ return html;
},
- ie11PasteFrag: function(html)
+ clear: function(html)
{
- this.selection.get();
- this.range.deleteContents();
+ html = html.replace(new RegExp('</blockquote></p>', 'gi'), '</blockquote>');
+ html = html.replace(new RegExp('<p></blockquote>', 'gi'), '</blockquote>');
+ html = html.replace(new RegExp('<p><blockquote>', 'gi'), '<blockquote>');
+ html = html.replace(new RegExp('<blockquote></p>', 'gi'), '<blockquote>');
- var el = document.createElement("div");
- el.innerHTML = html;
+ html = html.replace(new RegExp('<p><p ', 'gi'), '<p ');
+ html = html.replace(new RegExp('<p><p>', 'gi'), '<p>');
+ html = html.replace(new RegExp('</p></p>', 'gi'), '</p>');
+ html = html.replace(new RegExp('<p>\\s?</p>', 'gi'), '');
+ html = html.replace(new RegExp("\n</p>", 'gi'), '</p>');
+ html = html.replace(new RegExp('<p>\t?\t?\n?<p>', 'gi'), '<p>');
+ html = html.replace(new RegExp('<p>\t*</p>', 'gi'), '');
- var frag = document.createDocumentFragment(), node, lastNode;
- while ((node = el.firstChild))
- {
- lastNode = frag.appendChild(node);
- }
-
- this.range.insertNode(frag);
+ return html;
}
};
},
- caret: function()
+ paste: function()
{
return {
- setStart: function(node)
+ init: function(e)
{
- // inline tag
- if (!this.utils.isBlock(node))
- {
- var space = this.utils.createSpaceElement();
+ if (!this.opts.cleanOnPaste) return;
- $(node).prepend(space);
- this.caret.setEnd(space);
- }
- else
- {
- this.caret.set(node, 0, node, 0);
- }
- },
- setEnd: function(node)
- {
- this.caret.set(node, 1, node, 1);
- },
- set: function(orgn, orgo, focn, foco)
- {
- // focus
- if (!this.utils.browser('msie')) this.$editor.focus();
+ this.rtePaste = true;
- orgn = orgn[0] || orgn;
- focn = focn[0] || focn;
+ this.buffer.set();
+ this.selection.save();
+ this.utils.saveScroll();
- if (this.utils.isBlockTag(orgn.tagName) && orgn.innerHTML === '')
+ this.paste.createPasteBox();
+
+ $(window).on('scroll.redactor-freeze', $.proxy(function()
{
- orgn.innerHTML = this.opts.invisibleSpace;
- }
+ $(window).scrollTop(this.saveBodyScroll);
- if (orgn.tagName == 'BR' && this.opts.linebreaks === false)
+ }, this));
+
+ setTimeout($.proxy(function()
{
- var par = $(this.opts.emptyHtml)[0];
- $(orgn).replaceWith(par);
- orgn = par;
- focn = orgn;
- }
+ var html = this.$pasteBox.html();
- this.selection.get();
+ this.$pasteBox.remove();
- try {
- this.range.setStart(orgn, orgo);
- this.range.setEnd(focn, foco);
- }
- catch (e) {}
+ this.selection.restore();
+ this.utils.restoreScroll();
- this.selection.addRange();
- },
- setAfter: function(node)
- {
- var tag = $(node)[0].tagName;
+ this.paste.insert(html);
- // inline tag
- if (tag != 'BR' && !this.utils.isBlock(node))
- {
- var space = this.utils.createSpaceElement();
+ $(window).off('scroll.redactor-freeze');
- $(node).after(space);
- this.caret.setEnd(space);
- }
- else
- {
- if (tag != 'BR' && this.utils.browser('msie'))
- {
- this.caret.setStart($(node).next());
- }
- else
- {
- this.caret.setAfterOrBefore(node, 'after');
- }
- }
+ }, this), 1);
+
},
- setBefore: function(node)
+ createPasteBox: function()
{
- // block tag
- if (this.utils.isBlock(node))
- {
- this.caret.setEnd($(node).prev());
- }
- else
- {
- this.caret.setAfterOrBefore(node, 'before');
- }
+ this.$pasteBox = $('<div>').html('').attr('contenteditable', 'true').css({ position: 'fixed', width: 0, top: 0, left: '-9999px' });
+
+ this.$box.parent().append(this.$pasteBox);
+ this.$pasteBox.focus();
},
- setAfterOrBefore: function(node, type)
+ insert: function(html)
{
- // focus
- if (!this.utils.browser('msie')) this.$editor.focus();
+ html = this.core.setCallback('pasteBefore', html);
- node = node[0] || node;
+ // clean
+ html = (this.utils.isSelectAll()) ? this.clean.onPaste(html, false) : this.clean.onPaste(html);
- this.selection.get();
+ html = this.core.setCallback('paste', html);
- if (type == 'after')
+ if (this.utils.isSelectAll())
{
- try {
-
- this.range.setStartAfter(node);
- this.range.setEndAfter(node);
- }
- catch (e) {}
+ this.insert.set(html, false);
}
else
{
- try {
- this.range.setStartBefore(node);
- this.range.setEndBefore(node);
- }
- catch (e) {}
+ this.insert.html(html, false);
}
+ this.utils.disableSelectAll();
+ this.rtePaste = false;
- this.range.collapse(false);
- this.selection.addRange();
- },
- getOffsetOfElement: function(node)
+ setTimeout($.proxy(this.clean.clearUnverified, this), 10);
+
+ // clean empty spans
+ setTimeout($.proxy(function()
+ {
+ var spans = this.$editor.find('span');
+ $.each(spans, function(i,s)
+ {
+ var html = s.innerHTML.replace(/[\u200B-\u200D\uFEFF]/, '');
+ if (html === '' && s.attributes.length === 0) $(s).remove();
+
+ });
+
+ }, this), 10);
+ }
+ };
+ },
+ placeholder: function()
+ {
+ return {
+ enable: function()
{
- node = node[0] || node;
+ if (!this.placeholder.is()) return;
- this.selection.get();
+ this.$editor.attr('placeholder', this.$element.attr('placeholder'));
- var cloned = this.range.cloneRange();
- cloned.selectNodeContents(node);
- cloned.setEnd(this.range.endContainer, this.range.endOffset);
+ this.placeholder.toggle();
+ this.$editor.on('keyup.redactor-placeholder', $.proxy(this.placeholder.toggle, this));
- return $.trim(cloned.toString()).length;
},
- getOffset: function()
+ toggle: function()
{
- var offset = 0;
- var sel = window.getSelection();
- if (sel.rangeCount > 0)
- {
- var range = window.getSelection().getRangeAt(0);
- var preCaretRange = range.cloneRange();
- preCaretRange.selectNodeContents(this.$editor[0]);
- preCaretRange.setEnd(range.endContainer, range.endOffset);
- offset = preCaretRange.toString().length;
- }
-
- return offset;
+ var func = 'removeClass';
+ if (this.utils.isEmpty(this.$editor.html(), false)) func = 'addClass';
+ this.$editor[func]('redactor-placeholder');
},
- setOffset: function(start, end)
+ remove: function()
{
- if (typeof end == 'undefined') end = start;
- if (!this.focus.isFocused()) this.focus.setStart();
-
- var range = document.createRange();
- var sel = document.getSelection();
- var node, offset = 0;
- var walker = document.createTreeWalker(this.$editor[0], NodeFilter.SHOW_TEXT, null, null);
-
- while (node = walker.nextNode())
+ this.$editor.removeClass('redactor-placeholder');
+ },
+ is: function()
+ {
+ if (this.opts.placeholder)
{
- offset += node.nodeValue.length;
- if (offset > start)
- {
- range.setStart(node, node.nodeValue.length + start - offset);
- start = Infinity;
- }
-
- if (offset >= end)
- {
- range.setEnd(node, node.nodeValue.length + end - offset);
- break;
- }
+ return this.$element.attr('placeholder', this.opts.placeholder);
}
-
- sel.removeAllRanges();
- sel.addRange(range);
- },
- setToPoint: function(start, end)
+ else
+ {
+ return !(typeof this.$element.attr('placeholder') == 'undefined' || this.$element.attr('placeholder') === '');
+ }
+ }
+ };
+ },
+ progress: function()
+ {
+ return {
+ show: function()
{
- this.caret.setOffset(start, end);
+ $(document.body).append($('<div id="redactor-progress"><span></span></div>'));
+ $('#redactor-progress').fadeIn();
},
- getCoords: function()
+ hide: function()
{
- return this.caret.getOffset();
+ $('#redactor-progress').fadeOut(1500, function()
+ {
+ $(this).remove();
+ });
}
+
};
},
selection: function()
{
return {
@@ -5937,1214 +6527,958 @@
return this.clean.onSync(html);
}
};
},
- observe: function()
+ shortcuts: function()
{
return {
- load: function()
+ init: function(e, key)
{
- this.observe.images();
- this.observe.links();
- },
- buttons: function(e, btnName)
- {
- var current = this.selection.getCurrent();
- var parent = this.selection.getParent();
-
- this.button.setInactiveAll(btnName);
-
- if (e === false && btnName !== 'html')
+ // disable browser's hot keys for bold and italic
+ if (!this.opts.shortcuts)
{
- if ($.inArray(btnName, this.opts.activeButtons) != -1) this.button.toggleActive(btnName);
- return;
+ if ((e.ctrlKey || e.metaKey) && (key === 66 || key === 73)) e.preventDefault();
+ return false;
}
- 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)
+ $.each(this.opts.shortcuts, $.proxy(function(str, command)
{
- var parentEl = $(parent).closest(key);
- var currentEl = $(current).closest(key);
-
- if (!this.utils.isRedactorParent(parentEl)) return;
- if (!this.utils.isRedactorParent(currentEl)) return;
- if (parentEl.length !== 0 || currentEl.closest(key).length !== 0)
+ var keys = str.split(',');
+ var len = keys.length;
+ for (var i = 0; i < len; i++)
{
+ if (typeof keys[i] === 'string')
+ {
+ this.shortcuts.handler(e, $.trim(keys[i]), $.proxy(function()
+ {
+ var func;
+ if (command.func.search(/\./) != '-1')
+ {
+ func = command.func.split('.');
+ if (typeof this[func[0]] != 'undefined')
+ {
+ this[func[0]][func[1]].apply(this, command.params);
+ }
+ }
+ else
+ {
+ this[command.func].apply(this, command.params);
+ }
- this.button.setActive(value);
+ }, this));
+ }
+
}
}, this));
-
- var $parent = $(parent).closest(this.opts.alignmentTags.toString().toLowerCase());
- if (this.utils.isRedactorParent(parent) && $parent.length)
- {
- var align = ($parent.css('text-align') === '') ? 'left' : $parent.css('text-align');
- this.button.setActive('align' + align);
- }
},
- addButton: function(tag, btnName)
+ handler: function(e, keys, origHandler)
{
- this.opts.activeButtons.push(btnName);
- this.opts.activeButtonsStates[tag] = btnName;
- },
- images: function()
- {
- this.$editor.find('img').each($.proxy(function(i, img)
+ // based on https://github.com/jeresig/jquery.hotkeys
+ var hotkeysSpecialKeys =
{
- var $img = $(img);
+ 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: "'"
+ };
- // 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(); });
- if (this.utils.browser('msie')) $img.attr('unselectable', 'on');
+ var hotkeysShiftNums =
+ {
+ "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
+ "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
+ ".": ">", "/": "?", "\\": "|"
+ };
- this.image.setEditable($img);
+ keys = keys.toLowerCase().split(" ");
+ var special = hotkeysSpecialKeys[e.keyCode],
+ character = String.fromCharCode( e.which ).toLowerCase(),
+ modif = "", possible = {};
- }, this));
-
- $(document).on('click.redactor-image-delete', $.proxy(function(e)
+ $.each([ "alt", "ctrl", "meta", "shift"], function(index, specialKey)
{
- this.observe.image = false;
- if (e.target.tagName == 'IMG' && this.utils.isRedactorParent(e.target))
+ if (e[specialKey + 'Key'] && special !== specialKey)
{
- this.observe.image = (this.observe.image && this.observe.image == e.target) ? false : e.target;
+ modif += specialKey + '+';
}
+ });
- }, this));
- },
- 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));
- },
- getTooltipPosition: function($link)
- {
- return $link.offset();
- },
- showTooltip: function(e)
- {
- var $link = $(e.target);
- if ($link.size() === 0 || $link[0].tagName !== 'A') return;
-
- var pos = this.observe.getTooltipPosition($link);
-
- var tooltip = $('<span class="redactor-link-tooltip"></span>');
-
- var href = $link.attr('href');
- if (href === undefined)
+ if (special) possible[modif + special] = true;
+ if (character)
{
- href = '';
+ 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;
+ }
}
- if (href.length > 24) href = href.substring(0, 24) + '...';
-
- var aLink = $('<a href="' + $link.attr('href') + '" target="_blank" />').html(href).addClass('redactor-link-tooltip-action');
- var aEdit = $('<a href="#" />').html(this.lang.get('edit')).on('click', $.proxy(this.link.show, this)).addClass('redactor-link-tooltip-action');
- var aUnlink = $('<a href="#" />').html(this.lang.get('unlink')).on('click', $.proxy(this.link.unlink, this)).addClass('redactor-link-tooltip-action');
-
- tooltip.append(aLink).append(' | ').append(aEdit).append(' | ').append(aUnlink);
- tooltip.css({
- top: (pos.top + 20) + 'px',
- left: pos.left + 'px'
- });
-
- $('.redactor-link-tooltip').remove();
- $('body').append(tooltip);
- },
- closeTooltip: function(e)
- {
- e = e.originalEvent || e;
-
- if (e.target.tagName == 'A' && !$(e.target).hasClass('redactor-link-tooltip-action') && this.utils.isRedactorParent(e.target))
+ for (var i = 0, len = keys.length; i < len; i++)
{
- return;
+ if (possible[keys[i]])
+ {
+ e.preventDefault();
+ return origHandler.apply(this, arguments);
+ }
}
-
- $('.redactor-link-tooltip').remove();
}
-
};
},
- link: function()
+ tabifier: function()
{
return {
- show: function(e)
+ get: function(code)
{
- if (typeof e != 'undefined' && e.preventDefault) e.preventDefault();
+ if (!this.opts.tabifier) return code;
- this.modal.load('link', this.lang.get('link_insert'), 600);
+ // 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'];
- this.modal.createCancelButton();
- this.link.buttonInsert = this.modal.createActionButton(this.lang.get('insert'));
+ 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('|' ) + ')[ >]');
- this.selection.get();
+ var i = 0,
+ codeLength = code.length,
+ point = 0,
+ start = null,
+ end = null,
+ tag = '',
+ out = '',
+ cont = '';
- this.link.getData();
- this.link.cleanUrl();
+ this.tabifier.cleanlevel = 0;
- if (this.link.target == '_blank') $('#redactor-link-blank').prop('checked', true);
-
- this.link.$inputUrl = $('#redactor-link-url');
- this.link.$inputText = $('#redactor-link-url-text');
-
- this.link.$inputText.val(this.link.text);
- this.link.$inputUrl.val(this.link.url);
-
- this.link.buttonInsert.on('click', $.proxy(this.link.insert, this));
-
- // show modal
- this.selection.save();
- this.modal.show();
- 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)
+ for (; i < codeLength; i++)
{
- var re = new RegExp('^(http|ftp|https)://' + self.location.host, 'i');
- this.link.url = this.link.url.replace(re, '');
- }
+ point = i;
- },
- getData: function()
- {
- this.link.$node = false;
+ // if no more tags, copy and exit
+ if (-1 == code.substr(i).indexOf( '<' ))
+ {
+ out += code.substr(i);
- var $el = $(this.selection.getCurrent()).closest('a');
- if ($el.size() !== 0 && $el[0].tagName === 'A')
- {
- this.link.$node = $el;
+ return this.tabifier.finish(out);
+ }
- this.link.url = $el.attr('href');
- this.link.text = $el.text();
- this.link.target = $el.attr('target');
- }
- else
- {
- this.link.text = this.sel.toString();
- this.link.url = '';
- this.link.target = '';
- }
+ // copy verbatim until a tag
+ while (point < codeLength && code.charAt(point) != '<')
+ {
+ point++;
+ }
- },
- insert: function()
- {
- var target = '';
- var link = this.link.$inputUrl.val();
- var text = this.link.$inputText.val();
-
- if ($.trim(link) === '')
- {
- this.link.$inputUrl.addClass('redactor-input-error').on('keyup', function()
+ if (i != point)
{
- $(this).removeClass('redactor-input-error');
- $(this).off('keyup');
+ cont = code.substr(i, point - i);
+ if (!cont.match(/^\s{2,}$/g))
+ {
+ if ('\n' == out.charAt(out.length - 1)) out += this.tabifier.getTabs();
+ else if ('\n' == cont.charAt(0))
+ {
+ out += '\n' + this.tabifier.getTabs();
+ cont = cont.replace(/^\s+/, '');
+ }
- });
- return;
- }
+ out += cont;
+ }
- // mailto
- if (link.search('@') != -1 && /(http|ftp|https):\/\//i.test(link) === false)
- {
- link = 'mailto:' + link;
- }
- // url, not anchor
- else if (link.search('#') !== 0)
- {
- if ($('#redactor-link-blank').prop('checked'))
- {
- target = '_blank';
+ if (cont.match(/\n/)) out += '\n' + this.tabifier.getTabs();
}
- // test url (add protocol)
- var pattern = '((xn--)?[a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,}';
- var re = new RegExp('^(http|ftp|https)://' + pattern, 'i');
- var re2 = new RegExp('^' + pattern, 'i');
+ start = point;
- if (link.search(re) == -1 && link.search(re2) === 0 && this.opts.linkProtocol)
+ // find the end of the tag
+ while (point < codeLength && '>' != code.charAt(point))
{
- link = this.opts.linkProtocol + '://' + link;
+ point++;
}
- }
- this.link.set(text, link, target);
- this.modal.close();
- },
- set: function(text, link, target)
- {
- text = $.trim(text.replace(/<|>/g, ''));
+ tag = code.substr(start, point - start);
+ i = point;
- this.selection.restore();
+ var t;
- if (text === '' && link === '') return;
- if (text === '' && link !== '') text = link;
+ if ('!--' == tag.substr(1, 3))
+ {
+ if (!tag.match(/--$/))
+ {
+ while ('-->' != code.substr(point, 3))
+ {
+ point++;
+ }
+ point += 2;
+ tag = code.substr(start, point - start);
+ i = point;
+ }
- if (this.link.$node)
- {
- this.buffer.set();
+ if ('\n' != out.charAt(out.length - 1)) out += '\n';
- this.link.$node.text(text).attr('href', link);
- if (target !== '')
- {
- this.link.$node.attr('target', target);
+ out += this.tabifier.getTabs();
+ out += tag + '>\n';
}
- else
+ else if ('!' == tag[1])
{
- this.link.$node.removeAttr('target');
+ out = this.tabifier.placeTag(tag + '>', out);
}
-
- this.code.sync();
- }
- else
- {
- if (this.utils.browser('mozilla') && this.link.text === '')
+ else if ('?' == tag[1])
{
- var $a = $('<a />').attr('href', link).text(text);
- if (target !== '') $a.attr('target', target);
-
- this.insert.node($a);
- this.selection.selectElement($a);
+ out += tag + '>\n';
}
- else
+ else if (t = tag.match(/^<(script|style|pre)/i))
{
- document.execCommand('createLink', false, link);
+ t[1] = t[1].toLowerCase();
+ tag = this.tabifier.cleanTag(tag);
+ out = this.tabifier.placeTag(tag, out);
+ end = String(code.substr(i + 1)).toLowerCase().indexOf('</' + t[1]);
- var $a = $(this.selection.getCurrent()).closest('a');
-
- if (target !== '') $a.attr('target', target);
- $a.removeAttr('style');
-
- if (this.link.text === '')
+ if (end)
{
- $a.text(text);
- this.selection.selectElement($a);
+ cont = code.substr(i + 1, end);
+ i += end;
+ out += cont;
}
}
-
- this.code.sync();
- this.core.setCallback('insertedLink', $a);
-
+ else
+ {
+ tag = this.tabifier.cleanTag(tag);
+ out = this.tabifier.placeTag(tag, out);
+ }
}
- // link tooltip
- setTimeout($.proxy(function()
- {
- this.observe.links();
-
- }, this), 5);
+ return this.tabifier.finish(out);
},
- unlink: function(e)
+ getTabs: function()
{
- 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++)
+ var s = '';
+ for ( var j = 0; j < this.tabifier.cleanlevel; j++ )
{
- if (nodes[i].tagName == 'A')
- {
- var $node = $(nodes[i]);
- $node.replaceWith($node.contents());
- }
+ s += '\t';
}
- // remove tooltip
- $('.redactor-link-tooltip').remove();
-
- this.code.sync();
-
- }
- };
- },
- image: function()
- {
- return {
- show: function()
+ return s;
+ },
+ finish: function(code)
{
- this.modal.load('image', this.lang.get('image'), 700);
- this.upload.init('#redactor-modal-image-droparea', this.opts.imageUpload, this.image.insert);
+ 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.selection.save();
- this.modal.show();
+ this.tabifier.cleanlevel = 0;
+ return code;
},
- showEdit: function($image)
+ cleanTag: function (tag)
{
- var $link = $image.closest('a');
+ var tagout = '';
+ tag = tag.replace(/\n/g, ' ');
+ tag = tag.replace(/\s{2,}/g, ' ');
+ tag = tag.replace(/^\s+|\s+$/g, ' ');
- this.modal.load('imageEdit', this.lang.get('edit'), 705);
-
- this.modal.createCancelButton();
- this.image.buttonDelete = this.modal.createDeleteButton(this.lang.get('_delete'));
- this.image.buttonSave = this.modal.createActionButton(this.lang.get('save'));
-
- this.image.buttonDelete.on('click', $.proxy(function()
+ var suffix = '';
+ if (tag.match(/\/$/))
{
- this.image.remove($image);
+ suffix = '/';
+ tag = tag.replace(/\/+$/, '');
+ }
- }, this));
-
- this.image.buttonSave.on('click', $.proxy(function()
+ var m;
+ while (m = /\s*([^= ]+)(?:=((['"']).*?\3|[^ ]+))?/.exec(tag))
{
- this.image.update($image);
+ if (m[2]) tagout += m[1].toLowerCase() + '=' + m[2];
+ else if (m[1]) tagout += m[1].toLowerCase();
- }, this));
-
-
- $('#redactor-image-title').val($image.attr('alt'));
-
- if (!this.opts.imageLink) $('.redactor-image-link-option').hide();
- else
- {
- var $redactorImageLink = $('#redactor-image-link');
-
- $redactorImageLink.attr('href', $image.attr('src'));
- if ($link.size() !== 0)
- {
- $redactorImageLink.val($link.attr('href'));
- if ($link.attr('target') == '_blank') $('#redactor-image-link-blank').prop('checked', true);
- }
+ tagout += ' ';
+ tag = tag.substr(m[0].length);
}
- if (!this.opts.imagePosition) $('.redactor-image-position-option').hide();
- else
+ return tagout.replace(/\s*$/, '') + suffix + '>';
+ },
+ placeTag: function (tag, out)
+ {
+ var nl = tag.match(this.tabifier.newLevel);
+ if (tag.match(this.tabifier.lineBefore) || nl)
{
- var floatValue = ($image.css('display') == 'block' && $image.css('float') == 'none') ? 'center' : $image.css('float');
- $('#redactor-image-align').val(floatValue);
+ out = out.replace(/\s*$/, '');
+ out += '\n';
}
- this.modal.show();
+ if (nl && '/' == tag.charAt(1)) this.tabifier.cleanlevel--;
+ if ('\n' == out.charAt(out.length - 1)) out += this.tabifier.getTabs();
+ if (nl && '/' != tag.charAt(1)) this.tabifier.cleanlevel++;
- },
- setFloating: function($image)
- {
- var floating = $('#redactor-image-align').val();
+ out += tag;
- var imageFloat = '';
- var imageDisplay = '';
- var imageMargin = '';
-
- switch (floating)
+ if (tag.match(this.tabifier.lineAfter) || tag.match(this.tabifier.newLevel))
{
- case 'left':
- imageFloat = 'left';
- imageMargin = '0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin + ' 0';
- break;
- case 'right':
- imageFloat = 'right';
- imageMargin = '0 0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin;
- break;
- case 'center':
- imageDisplay = 'block';
- imageMargin = 'auto';
- break;
+ out = out.replace(/ *$/, '');
+ out += '\n';
}
- $image.css({ 'float': imageFloat, display: imageDisplay, margin: imageMargin });
- $image.attr('rel', $image.attr('style'));
- },
- update: function($image)
+ return out;
+ }
+ };
+ },
+ tidy: function()
+ {
+ return {
+ setupAllowed: function()
{
- this.image.hideResize();
- this.buffer.set();
+ if (this.opts.allowedTags) this.opts.deniedTags = false;
+ if (this.opts.allowedAttr) this.opts.removeAttr = false;
- var $link = $image.closest('a');
+ if (this.opts.linebreaks) return;
- $image.attr('alt', $('#redactor-image-title').val());
+ var tags = ['p', 'section'];
+ if (this.opts.allowedTags) this.tidy.addToAllowed(tags);
+ if (this.opts.deniedTags) this.tidy.removeFromDenied(tags);
- this.image.setFloating($image);
-
- // as link
- var link = $.trim($('#redactor-image-link').val());
- if (link !== '')
+ },
+ addToAllowed: function(tags)
+ {
+ var len = tags.length;
+ for (var i = 0; i < len; i++)
{
- var target = ($('#redactor-image-link-blank').prop('checked')) ? true : false;
-
- if ($link.size() === 0)
+ if ($.inArray(tags[i], this.opts.allowedTags) == -1)
{
- var a = $('<a href="' + link + '">' + this.utils.getOuterHtml($image) + '</a>');
- if (target) a.attr('target', '_blank');
-
- $image.replaceWith(a);
+ this.opts.allowedTags.push(tags[i]);
}
- else
- {
- $link.attr('href', link);
- if (target)
- {
- $link.attr('target', '_blank');
- }
- else
- {
- $link.removeAttr('target');
- }
- }
}
- else if ($link.size() !== 0)
- {
- $link.replaceWith(this.utils.getOuterHtml($image));
-
- }
-
- this.modal.close();
- this.observe.images();
- this.code.sync();
-
-
},
- setEditable: function($image)
+ removeFromDenied: function(tags)
{
- if (!this.opts.imageEditable) return;
-
- $image.on('dragstart', $.proxy(this.image.onDrag, this));
-
- $image.on('mousedown', $.proxy(this.image.hideResize, this));
- $image.on('click touchstart', $.proxy(function(e)
+ var len = tags.length;
+ for (var i = 0; i < len; i++)
{
- this.observe.image = $image;
-
- if (this.$editor.find('#redactor-image-box').size() !== 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));
-
- // resize
- if (!this.opts.imageResizable) return;
-
- this.image.resizer.on('mousedown.redactor touchstart.redactor', $.proxy(function(e)
+ var pos = $.inArray(tags[i], this.opts.deniedTags);
+ if (pos != -1)
{
- e.preventDefault();
-
- this.image.resizeHandle = {
- x : e.pageX,
- y : e.pageY,
- el : $image,
- ratio: $image.width() / $image.height(),
- h: $image.height()
- };
-
- e = e.originalEvent || e;
-
- if (e.targetTouches)
- {
- this.image.resizeHandle.x = e.targetTouches[0].pageX;
- this.image.resizeHandle.y = e.targetTouches[0].pageY;
- }
-
- this.image.startResize();
-
- }, this));
-
-
- }, this));
+ this.opts.deniedTags.splice(pos, 1);
+ }
+ }
},
- startResize: function()
+ load: function(html, options)
{
- $(document).on('mousemove.redactor-image-resize touchmove.redactor-image-resize', $.proxy(this.image.moveResize, this));
- $(document).on('mouseup.redactor-image-resize touchend.redactor-image-resize', $.proxy(this.image.stopResize, this));
- },
- moveResize: function(e)
- {
- e.preventDefault();
+ this.tidy.settings = {
+ deniedTags: this.opts.deniedTags,
+ allowedTags: this.opts.allowedTags,
+ removeComments: this.opts.removeComments,
+ replaceTags: this.opts.replaceTags,
+ replaceStyles: this.opts.replaceStyles,
+ removeDataAttr: this.opts.removeDataAttr,
+ removeAttr: this.opts.removeAttr,
+ allowedAttr: this.opts.allowedAttr,
+ removeWithoutAttr: this.opts.removeWithoutAttr,
+ removeEmpty: this.opts.removeEmpty
+ };
- e = e.originalEvent || e;
+ $.extend(this.tidy.settings, options);
- var height = this.image.resizeHandle.h;
+ html = this.tidy.removeComments(html);
- if (e.targetTouches) height += (e.targetTouches[0].pageY - this.image.resizeHandle.y);
- else height += (e.pageY - this.image.resizeHandle.y);
+ // create container
+ this.tidy.$div = $('<div />').append(html);
+ // clean
+ this.tidy.replaceTags();
+ this.tidy.replaceStyles();
+ this.tidy.removeTags();
- var width = Math.round(height * this.image.resizeHandle.ratio);
+ this.tidy.removeAttr();
+ this.tidy.removeEmpty();
+ this.tidy.removeParagraphsInLists();
+ this.tidy.removeDataAttr();
+ this.tidy.removeWithoutAttr();
- if (height < 50 || width < 100) return;
+ html = this.tidy.$div.html();
+ this.tidy.$div.remove();
- this.image.resizeHandle.el.height(height);
- this.image.resizeHandle.el.width(width);
-
- this.code.sync();
+ return html;
},
- stopResize: function()
+ removeComments: function(html)
{
- this.handle = false;
- $(document).off('.redactor-image-resize');
- this.image.hideResize();
+ if (!this.tidy.settings.removeComments) return html;
+
+ return html.replace(/<!--[\s\S]*?-->/gi, '');
},
- onDrag: function(e)
+ replaceTags: function(html)
{
- if (this.$editor.find('#redactor-image-box').size() !== 0)
+ if (!this.tidy.settings.replaceTags) return html;
+
+ var len = this.tidy.settings.replaceTags.length;
+ var replacement = [], rTags = [];
+ for (var i = 0; i < len; i++)
{
- e.preventDefault();
- return false;
+ rTags.push(this.tidy.settings.replaceTags[i][1]);
+ replacement.push(this.tidy.settings.replaceTags[i][0]);
}
- this.$editor.on('drop.redactor-image-inside-drop', $.proxy(function()
+ this.tidy.$div.find(replacement.join(',')).each($.proxy(function(n,s)
{
- setTimeout($.proxy(this.image.onDrop, this), 1);
+ var tag = rTags[n];
+ $(s).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;
+ });
+
}, this));
+
+ return html;
},
- onDrop: function()
+ replaceStyles: function()
{
- this.image.fixImageSourceAfterDrop();
- this.observe.images();
- this.$editor.off('drop.redactor-image-inside-drop');
- this.clean.clearUnverified();
- this.code.sync();
- },
- fixImageSourceAfterDrop: function()
- {
- this.$editor.find('img[data-save-url]').each(function()
- {
- var $el = $(this);
- $el.attr('src', $el.attr('data-save-url'));
- $el.removeAttr('data-save-url');
- });
- },
- hideResize: function(e)
- {
- if (e && $(e.target).closest('#redactor-image-box').length !== 0) return;
- if (e && e.target.tagName == 'IMG')
- {
- var $image = $(e.target);
- $image.attr('data-save-url', $image.attr('src'));
- }
+ if (!this.tidy.settings.replaceStyles) return;
- var imageBox = this.$editor.find('#redactor-image-box');
- if (imageBox.size() === 0) return;
-
- this.image.editter.remove();
- $(this.image.resizer).remove();
-
- imageBox.find('img').css({
- marginTop: imageBox[0].style.marginTop,
- marginBottom: imageBox[0].style.marginBottom,
- marginLeft: imageBox[0].style.marginLeft,
- marginRight: imageBox[0].style.marginRight
- });
-
- imageBox.css('margin', '');
- imageBox.find('img').css('opacity', '');
- imageBox.replaceWith(function()
+ var len = this.tidy.settings.replaceStyles.length;
+ this.tidy.$div.find('span').each($.proxy(function(n,s)
{
- return $(this).contents();
- });
+ var $el = $(s);
+ var style = $el.attr('style');
+ for (var i = 0; i < len; i++)
+ {
+ if (style && style.match(new RegExp('^' + this.tidy.settings.replaceStyles[i][0], 'i')))
+ {
+ var tagName = this.tidy.settings.replaceStyles[i][1];
+ $el.replaceWith(function()
+ {
+ var tag = document.createElement(tagName);
+ return $(tag).append($(this).contents());
+ });
+ }
+ }
- $(document).off('click.redactor-image-resize-hide');
- this.$editor.off('click.redactor-image-resize-hide');
+ }, this));
- this.code.sync();
-
},
- loadEditableControls: function($image)
+ removeTags: function()
{
- var imageBox = $('<span id="redactor-image-box" data-redactor="verified">');
- imageBox.css('float', $image.css('float')).attr('contenteditable', false);
-
- if ($image[0].style.margin != 'auto')
+ if (!this.tidy.settings.deniedTags && this.tidy.settings.allowedTags)
{
- imageBox.css({
- marginTop: $image[0].style.marginTop,
- marginBottom: $image[0].style.marginBottom,
- marginLeft: $image[0].style.marginLeft,
- marginRight: $image[0].style.marginRight
+ this.tidy.$div.find('*').not(this.tidy.settings.allowedTags.join(',')).each(function(i, s)
+ {
+ if (s.innerHTML === '') $(s).remove();
+ else $(s).contents().unwrap();
});
-
- $image.css('margin', '');
}
- else
+
+ if (this.tidy.settings.deniedTags)
{
- imageBox.css({ 'display': 'block', 'margin': 'auto' });
+ this.tidy.$div.find(this.tidy.settings.deniedTags.join(',')).each(function(i, s)
+ {
+ if (s.innerHTML === '') $(s).remove();
+ else $(s).contents().unwrap();
+ });
}
-
- $image.css('opacity', '.5').after(imageBox);
-
- // editter
- this.image.editter = $('<span id="redactor-image-editter" data-redactor="verified">' + this.lang.get('edit') + '</span>');
- this.image.editter.attr('contenteditable', false);
- this.image.editter.on('click', $.proxy(function()
+ },
+ removeAttr: function()
+ {
+ var len;
+ if (!this.tidy.settings.removeAttr && this.tidy.settings.allowedAttr)
{
- this.image.showEdit($image);
- }, this));
- imageBox.append(this.image.editter);
+ var allowedAttrTags = [], allowedAttrData = [];
+ len = this.tidy.settings.allowedAttr.length;
+ for (var i = 0; i < len; i++)
+ {
+ allowedAttrTags.push(this.tidy.settings.allowedAttr[i][0]);
+ allowedAttrData.push(this.tidy.settings.allowedAttr[i][1]);
+ }
- // position correction
- var editerWidth = this.image.editter.innerWidth();
- this.image.editter.css('margin-left', '-' + editerWidth/2 + 'px');
+ this.tidy.$div.find('*').each($.proxy(function(n,s)
+ {
+ var $el = $(s);
+ var pos = $.inArray($el[0].tagName.toLowerCase(), allowedAttrTags);
+ var attributesRemove = this.tidy.removeAttrGetRemoves(pos, allowedAttrData, $el);
- // resizer
- if (this.opts.imageResizable && !this.utils.isMobile())
- {
- var imageResizer = $('<span id="redactor-image-resizer" data-redactor="verified"></span>');
+ if (attributesRemove)
+ {
+ $.each(attributesRemove, function(z,f) {
+ $el.removeAttr(f);
+ });
+ }
+ }, this));
+ }
- if (!this.utils.isDesktop())
+ if (this.tidy.settings.removeAttr)
+ {
+ len = this.tidy.settings.removeAttr.length;
+ for (var i = 0; i < len; i++)
{
- imageResizer.css({ width: '15px', height: '15px' });
- }
+ var attrs = this.tidy.settings.removeAttr[i][1];
+ if ($.isArray(attrs)) attrs = attrs.join(' ');
- imageResizer.attr('contenteditable', false);
- imageBox.append(imageResizer);
- imageBox.append($image);
-
- return imageResizer;
+ this.tidy.$div.find(this.tidy.settings.removeAttr[i][0]).removeAttr(attrs);
+ }
}
- else
- {
- imageBox.append($image);
- return false;
- }
+
},
- remove: function(image)
+ removeAttrGetRemoves: function(pos, allowed, $el)
{
- var $image = $(image);
- var $link = $image.closest('a');
- var $figure = $image.closest('figure');
- var $parent = $image.parent();
- if ($('#redactor-image-box').size() !== 0)
- {
- $parent = $('#redactor-image-box').parent();
- }
+ var attributesRemove = [];
- var $next;
- if ($figure.size() !== 0)
+ // remove all attrs
+ if (pos == -1)
{
- $next = $figure.next();
- $figure.remove();
+ $.each($el[0].attributes, function(i, item)
+ {
+ attributesRemove.push(item.name);
+ });
+
}
- else if ($link.size() !== 0)
+ // allow all attrs
+ else if (allowed[pos] == '*')
{
- $parent = $link.parent();
- $link.remove();
+ attributesRemove = [];
}
+ // allow specific attrs
else
{
- $image.remove();
- }
+ $.each($el[0].attributes, function(i, item)
+ {
+ if ($.isArray(allowed[pos]))
+ {
+ if ($.inArray(item.name, allowed[pos]) == -1)
+ {
+ attributesRemove.push(item.name);
+ }
+ }
+ else if (allowed[pos] != item.name)
+ {
+ attributesRemove.push(item.name);
+ }
- $('#redactor-image-box').remove();
-
- if ($figure.size() !== 0)
- {
- this.caret.setStart($next);
+ });
}
- else
- {
- this.caret.setStart($parent);
- }
- // delete callback
- this.core.setCallback('imageDelete', $image[0].src, $image);
-
- this.modal.close();
- this.code.sync();
+ return attributesRemove;
},
- insert: function(json, direct, e)
+ removeAttrs: function (el, regex)
{
- // error callback
- if (typeof json.error != 'undefined')
+ regex = new RegExp(regex, "g");
+ return el.each(function()
{
- this.modal.close();
- this.selection.restore();
- this.core.setCallback('imageUploadError', json);
- return;
- }
+ var self = $(this);
+ var len = this.attributes.length - 1;
+ for (var i = len; i >= 0; i--)
+ {
+ var item = this.attributes[i];
+ if (item && item.specified && item.name.search(regex)>=0)
+ {
+ self.removeAttr(item.name);
+ }
+ }
+ });
+ },
+ removeEmpty: function()
+ {
+ if (!this.tidy.settings.removeEmpty) return;
- var $img;
- if (typeof json == 'string')
+ this.tidy.$div.find(this.tidy.settings.removeEmpty.join(',')).each(function()
{
- $img = $(json).attr('data-redactor-inserted-image', 'true');
- }
- else
- {
- $img = $('<img>');
- $img.attr('src', json.filelink).attr('data-redactor-inserted-image', 'true');
- }
+ var $el = $(this);
+ var text = $el.text();
+ text = text.replace(/[\u200B-\u200D\uFEFF]/g, '');
+ text = text.replace(/ /gi, '');
+ text = text.replace(/\s/g, '');
+ if (text === '' && $el.children().length === 0)
+ {
+ $el.remove();
+ }
+ });
+ },
+ removeParagraphsInLists: function()
+ {
+ this.tidy.$div.find('li p').contents().unwrap();
+ },
+ removeDataAttr: function()
+ {
+ if (!this.tidy.settings.removeDataAttr) return;
- var node = $img;
- var isP = this.utils.isCurrentOrParent('P');
- if (isP)
- {
- // will replace
- node = $('<blockquote />').append($img);
- }
+ var tags = this.tidy.settings.removeDataAttr;
+ if ($.isArray(this.tidy.settings.removeDataAttr)) tags = this.tidy.settings.removeDataAttr.join(',');
- if (direct)
- {
- this.selection.removeMarkers();
- var marker = this.selection.getMarker();
- this.insert.nodeToCaretPositionFromPoint(e, marker);
- }
- else
- {
- this.modal.close();
- }
+ this.tidy.removeAttrs(this.tidy.$div.find(tags), '^(data-)');
- this.selection.restore();
- this.buffer.set();
+ },
+ removeWithoutAttr: function()
+ {
+ if (!this.tidy.settings.removeWithoutAttr) return;
-
- this.insert.html(this.utils.getOuterHtml(node), false);
-
- var $image = this.$editor.find('img[data-redactor-inserted-image=true]').removeAttr('data-redactor-inserted-image');
-
- if (isP)
+ this.tidy.$div.find(this.tidy.settings.removeWithoutAttr.join(',')).each(function()
{
- $image.parent().contents().unwrap().wrap('<p />');
- }
- else if (this.opts.linebreaks)
- {
- $image.before('<br>').after('<br>');
- }
-
- if (typeof json == 'string') return;
-
- this.core.setCallback('imageUpload', $image, json);
-
+ if (this.attributes.length === 0)
+ {
+ $(this).contents().unwrap();
+ }
+ });
}
};
},
- file: function()
+ toolbar: function()
{
return {
- show: function()
+ init: function()
{
- this.modal.load('file', this.lang.get('file'), 700);
- this.upload.init('#redactor-modal-file-upload', this.opts.fileUpload, this.file.insert);
-
- this.selection.save();
-
- this.selection.get();
- var text = this.sel.toString();
-
- $('#redactor-filename').val(text);
-
- this.modal.show();
+ return {
+ html:
+ {
+ title: this.lang.get('html'),
+ func: 'code.toggle'
+ },
+ formatting:
+ {
+ title: this.lang.get('formatting'),
+ dropdown:
+ {
+ p:
+ {
+ title: this.lang.get('paragraph'),
+ func: 'block.format'
+ },
+ blockquote:
+ {
+ title: this.lang.get('quote'),
+ func: 'block.format'
+ },
+ pre:
+ {
+ title: this.lang.get('code'),
+ func: 'block.format'
+ },
+ h1:
+ {
+ title: this.lang.get('header1'),
+ func: 'block.format'
+ },
+ h2:
+ {
+ title: this.lang.get('header2'),
+ func: 'block.format'
+ },
+ h3:
+ {
+ title: this.lang.get('header3'),
+ func: 'block.format'
+ },
+ h4:
+ {
+ title: this.lang.get('header4'),
+ func: 'block.format'
+ },
+ h5:
+ {
+ title: this.lang.get('header5'),
+ func: 'block.format'
+ }
+ }
+ },
+ bold:
+ {
+ title: this.lang.get('bold'),
+ func: 'inline.format'
+ },
+ italic:
+ {
+ title: this.lang.get('italic'),
+ func: 'inline.format'
+ },
+ deleted:
+ {
+ title: this.lang.get('deleted'),
+ func: 'inline.format'
+ },
+ underline:
+ {
+ title: this.lang.get('underline'),
+ func: 'inline.format'
+ },
+ unorderedlist:
+ {
+ title: '• ' + this.lang.get('unorderedlist'),
+ func: 'list.toggle'
+ },
+ orderedlist:
+ {
+ title: '1. ' + this.lang.get('orderedlist'),
+ func: 'list.toggle'
+ },
+ outdent:
+ {
+ title: '< ' + this.lang.get('outdent'),
+ func: 'indent.decrease'
+ },
+ indent:
+ {
+ title: '> ' + this.lang.get('indent'),
+ func: 'indent.increase'
+ },
+ image:
+ {
+ title: this.lang.get('image'),
+ func: 'image.show'
+ },
+ file:
+ {
+ title: this.lang.get('file'),
+ func: 'file.show'
+ },
+ link:
+ {
+ title: this.lang.get('link'),
+ dropdown:
+ {
+ link:
+ {
+ title: this.lang.get('link_insert'),
+ func: 'link.show'
+ },
+ unlink:
+ {
+ title: this.lang.get('unlink'),
+ func: 'link.unlink'
+ }
+ }
+ },
+ alignment:
+ {
+ title: this.lang.get('alignment'),
+ dropdown:
+ {
+ left:
+ {
+ title: this.lang.get('align_left'),
+ func: 'alignment.left'
+ },
+ center:
+ {
+ title: this.lang.get('align_center'),
+ func: 'alignment.center'
+ },
+ right:
+ {
+ title: this.lang.get('align_right'),
+ func: 'alignment.right'
+ },
+ justify:
+ {
+ title: this.lang.get('align_justify'),
+ func: 'alignment.justify'
+ }
+ }
+ },
+ horizontalrule:
+ {
+ title: this.lang.get('horizontalrule'),
+ func: 'line.insert'
+ }
+ };
},
- insert: function(json, direct, e)
+ build: function()
{
- // error callback
- if (typeof json.error != 'undefined')
- {
- this.modal.close();
- this.selection.restore();
- this.core.setCallback('fileUploadError', json);
- return;
- }
+ this.toolbar.hideButtons();
+ this.toolbar.hideButtonsOnMobile();
+ this.toolbar.isButtonSourceNeeded();
- var link;
- if (typeof json == 'string')
- {
- link = json;
- }
- else
- {
- var text = $('#redactor-filename').val();
- if (typeof text == 'undefined' || text === '') text = json.filename;
+ if (this.opts.buttons.length === 0) return;
- link = '<a href="' + json.filelink + '" id="filelink-marker">' + text + '</a>';
- }
+ this.$toolbar = this.toolbar.createContainer();
- if (direct)
+ this.toolbar.setOverflow();
+ this.toolbar.append();
+ this.toolbar.setFormattingTags();
+ this.toolbar.loadButtons();
+ this.toolbar.setFixed();
+
+ // buttons response
+ if (this.opts.activeButtons)
{
- this.selection.removeMarkers();
- var marker = this.selection.getMarker();
- this.insert.nodeToCaretPositionFromPoint(e, marker);
+ this.$editor.on('mouseup.redactor keyup.redactor focus.redactor', $.proxy(this.observe.buttons, this));
}
- else
- {
- this.modal.close();
- }
- this.selection.restore();
- this.buffer.set();
-
-
- this.insert.html(link);
-
- if (typeof json == 'string') return;
-
- var linkmarker = $(this.$editor.find('a#filelink-marker'));
- if (linkmarker.size() !== 0) linkmarker.removeAttr('id');
- else linkmarker = false;
-
-
- this.core.setCallback('fileUpload', linkmarker, json);
-
- }
- };
- },
- modal: function()
- {
- return {
- callbacks: {},
- loadTemplates: function()
- {
- this.opts.modal = {
- imageEdit: String()
- + '<section id="redactor-modal-image-edit">'
- + '<label>' + this.lang.get('title') + '</label>'
- + '<input type="text" id="redactor-image-title" />'
- + '<label class="redactor-image-link-option">' + this.lang.get('link') + '</label>'
- + '<input type="text" id="redactor-image-link" class="redactor-image-link-option" />'
- + '<label class="redactor-image-link-option"><input type="checkbox" id="redactor-image-link-blank"> ' + this.lang.get('link_new_tab') + '</label>'
- + '<label class="redactor-image-position-option">' + this.lang.get('image_position') + '</label>'
- + '<select class="redactor-image-position-option" id="redactor-image-align">'
- + '<option value="none">' + this.lang.get('none') + '</option>'
- + '<option value="left">' + this.lang.get('left') + '</option>'
- + '<option value="center">' + this.lang.get('center') + '</option>'
- + '<option value="right">' + this.lang.get('right') + '</option>'
- + '</select>'
- + '</section>',
-
- image: String()
- + '<section id="redactor-modal-image-insert">'
- + '<div id="redactor-modal-image-droparea"></div>'
- + '</section>',
-
- file: String()
- + '<section id="redactor-modal-file-insert">'
- + '<div id="redactor-modal-file-upload-box">'
- + '<label>' + this.lang.get('filename') + '</label>'
- + '<input type="text" id="redactor-filename" /><br><br>'
- + '<div id="redactor-modal-file-upload"></div>'
- + '</div>'
- + '</section>',
-
- link: String()
- + '<section id="redactor-modal-link-insert">'
- + '<label>URL</label>'
- + '<input type="url" id="redactor-link-url" />'
- + '<label>' + this.lang.get('text') + '</label>'
- + '<input type="text" id="redactor-link-url-text" />'
- + '<label><input type="checkbox" id="redactor-link-blank"> ' + this.lang.get('link_new_tab') + '</label>'
- + '</section>'
- };
-
-
- $.extend(this.opts, this.opts.modal);
-
},
- addCallback: function(name, callback)
+ createContainer: function()
{
- this.modal.callbacks[name] = callback;
+ return $('<ul>').addClass('redactor-toolbar').attr('id', 'redactor-toolbar-' + this.uuid);
},
- createTabber: function($modal)
+ setFormattingTags: function()
{
- this.modal.$tabber = $('<div>').attr('id', 'redactor-modal-tabber');
+ $.each(this.opts.toolbar.formatting.dropdown, $.proxy(function (i, s)
+ {
+ if ($.inArray(i, this.opts.formatting) == -1) delete this.opts.toolbar.formatting.dropdown[i];
+ }, this));
- $modal.prepend(this.modal.$tabber);
},
- addTab: function(id, name, active)
+ loadButtons: function()
{
- var $tab = $('<a href="#" rel="tab' + id + '">').text(name);
- if (active)
+ $.each(this.opts.buttons, $.proxy(function(i, btnName)
{
- $tab.addClass('active');
- }
+ if (!this.opts.toolbar[btnName]) return;
- var self = this;
- $tab.on('click', function(e)
- {
- e.preventDefault();
- $('.redactor-tab').hide();
- $('.redactor-' + $(this).attr('rel')).show();
+ if (this.opts.fileUpload === false && btnName === 'file') return true;
+ if ((this.opts.imageUpload === false && this.opts.s3 === false) && btnName === 'image') return true;
- self.modal.$tabber.find('a').removeClass('active');
- $(this).addClass('active');
+ var btnObject = this.opts.toolbar[btnName];
+ this.$toolbar.append($('<li>').append(this.button.build(btnName, btnObject)));
- });
-
- this.modal.$tabber.append($tab);
+ }, this));
},
- addTemplate: function(name, template)
+ append: function()
{
- this.opts.modal[name] = template;
+ if (this.opts.toolbarExternal)
+ {
+ this.$toolbar.addClass('redactor-toolbar-external');
+ $(this.opts.toolbarExternal).html(this.$toolbar);
+ }
+ else
+ {
+ this.$box.prepend(this.$toolbar);
+ }
},
- getTemplate: function(name)
+ setFixed: function()
{
- return this.opts.modal[name];
+ 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));
+
},
- getModal: function()
+ setOverflow: function()
{
- return this.$modalBody.find('section');
+ if (this.utils.isMobile() && this.opts.toolbarOverflow)
+ {
+ this.$toolbar.addClass('redactor-toolbar-overflow');
+ }
},
- load: function(templateName, title, width)
+ isButtonSourceNeeded: function()
{
- this.modal.templateName = templateName;
- this.modal.width = width;
+ if (this.opts.buttonSource) return;
- this.modal.build();
- this.modal.enableEvents();
- this.modal.setTitle(title);
- this.modal.setDraggable();
- this.modal.setContent();
-
- // callbacks
- if (typeof this.modal.callbacks[templateName] != 'undefined')
+ var index = this.opts.buttons.indexOf('html');
+ if (index !== -1)
{
- this.modal.callbacks[templateName].call(this);
+ this.opts.buttons.splice(index, 1);
}
-
},
- show: function()
+ hideButtons: function()
{
- this.modal.bodyOveflow = $(document.body).css('overflow');
- $(document.body).css('overflow', 'hidden');
+ if (this.opts.buttonsHide.length === 0) return;
- if (this.utils.isMobile())
+ $.each(this.opts.buttonsHide, $.proxy(function(i, s)
{
- this.modal.showOnMobile();
- }
- else
- {
- this.modal.showOnDesktop();
- }
+ var index = this.opts.buttons.indexOf(s);
+ this.opts.buttons.splice(index, 1);
- this.$modalOverlay.show();
- this.$modalBox.show();
+ }, this));
+ },
+ hideButtonsOnMobile: function()
+ {
+ if (!this.utils.isMobile() || this.opts.buttonsHideOnMobile.length === 0) return;
- this.modal.setButtonsWidth();
-
- this.utils.saveScroll();
-
- // resize
- if (!this.utils.isMobile())
+ $.each(this.opts.buttonsHideOnMobile, $.proxy(function(i, s)
{
- setTimeout($.proxy(this.modal.showOnDesktop, this), 0);
- $(window).on('resize.redactor-modal', $.proxy(this.modal.resize, this));
- }
+ var index = this.opts.buttons.indexOf(s);
+ this.opts.buttons.splice(index, 1);
- // modal shown callback
- this.core.setCallback('modalOpened', this.modal.templateName, this.$modal);
-
- // fix bootstrap modal focus
- $(document).off('focusin.modal');
-
- // enter
- this.$modal.find('input[type=text]').on('keypress.redactor-modal', $.proxy(this.modal.setEnter, this));
-
+ }, this));
},
- showOnDesktop: function()
+ observeScroll: function()
{
- var height = this.$modal.outerHeight();
- var windowHeight = $(window).height();
- var windowWidth = $(window).width();
+ var scrollTop = $(this.opts.toolbarFixedTarget).scrollTop();
+ var boxTop = 1;
- if (this.modal.width > windowWidth)
+ if (this.opts.toolbarFixedTarget === document)
{
- this.$modal.css({
- width: '96%',
- marginTop: (windowHeight/2 - height/2) + 'px'
- });
- return;
+ boxTop = this.$box.offset().top;
}
- if (height > windowHeight)
+ if (scrollTop > boxTop)
{
- this.$modal.css({
- width: this.modal.width + 'px',
- marginTop: '20px'
- });
+ this.toolbar.observeScrollEnable(scrollTop, boxTop);
}
else
{
- this.$modal.css({
- width: this.modal.width + 'px',
- marginTop: (windowHeight/2 - height/2) + 'px'
- });
+ this.toolbar.observeScrollDisable();
}
},
- showOnMobile: function()
+ observeScrollEnable: function(scrollTop, boxTop)
{
- this.$modal.css({
- width: '96%',
- marginTop: '2%'
+ var top = this.opts.toolbarFixedTopOffset + scrollTop - boxTop;
+ var left = 0;
+ var end = boxTop + this.$box.height() + 30;
+ var width = this.$box.innerWidth();
+
+ this.$toolbar.addClass('toolbar-fixed-box');
+ this.$toolbar.css({
+ position: 'absolute',
+ width: width,
+ top: top + 'px',
+ left: left
});
+ this.toolbar.setDropdownsFixed();
+ this.$toolbar.css('visibility', (scrollTop < end) ? 'visible' : 'hidden');
},
- resize: function()
+ observeScrollDisable: function()
{
- if (this.utils.isMobile())
- {
- this.modal.showOnMobile();
- }
- else
- {
- this.modal.showOnDesktop();
- }
- },
- setTitle: function(title)
- {
- this.$modalHeader.html(title);
- },
- setContent: function()
- {
- this.$modalBody.html(this.modal.getTemplate(this.modal.templateName));
- },
- setDraggable: function()
- {
- if (typeof $.fn.draggable === 'undefined') return;
+ this.$toolbar.css({
+ position: 'relative',
+ width: 'auto',
+ top: 0,
+ left: 0,
+ visibility: 'visible'
+ });
- this.$modal.draggable({ handle: this.$modalHeader });
- this.$modalHeader.css('cursor', 'move');
- },
- setEnter: function(e)
- {
- if (e.which != 13) return;
+ this.toolbar.unsetDropdownsFixed();
+ this.$toolbar.removeClass('toolbar-fixed-box');
- e.preventDefault();
- this.$modal.find('button.redactor-modal-action-btn').click();
},
- createCancelButton: function()
+ setDropdownsFixed: function()
{
- var button = $('<button>').addClass('redactor-modal-btn redactor-modal-close-btn').html(this.lang.get('cancel'));
- button.on('click', $.proxy(this.modal.close, this));
-
- this.$modalFooter.append(button);
- },
- createDeleteButton: function(label)
- {
- return this.modal.createButton(label, 'delete');
- },
- createActionButton: function(label)
- {
- return this.modal.createButton(label, 'action');
- },
- createButton: function(label, className)
- {
- var button = $('<button>').addClass('redactor-modal-btn').addClass('redactor-modal-' + className + '-btn').html(label);
- this.$modalFooter.append(button);
-
- return button;
- },
- setButtonsWidth: function()
- {
- var buttons = this.$modalFooter.find('button');
- var buttonsSize = buttons.size();
- if (buttonsSize === 0) return;
-
- buttons.css('width', (100/buttonsSize) + '%');
- },
- build: function()
- {
- this.modal.buildOverlay();
-
- this.$modalBox = $('<div id="redactor-modal-box" />').hide();
- this.$modal = $('<div id="redactor-modal" />');
- this.$modalHeader = $('<header />');
- this.$modalClose = $('<span id="redactor-modal-close" />').html('×');
- this.$modalBody = $('<div id="redactor-modal-body" />');
- this.$modalFooter = $('<footer />');
-
- this.$modal.append(this.$modalHeader);
- this.$modal.append(this.$modalClose);
- this.$modal.append(this.$modalBody);
- this.$modal.append(this.$modalFooter);
- this.$modalBox.append(this.$modal);
- this.$modalBox.appendTo(document.body);
- },
- buildOverlay: function()
- {
- this.$modalOverlay = $('<div id="redactor-modal-overlay">').hide();
- $('body').prepend(this.$modalOverlay);
- },
- enableEvents: function()
- {
- this.$modalClose.on('click.redactor-modal', $.proxy(this.modal.close, this));
- $(document).on('keyup.redactor-modal', $.proxy(this.modal.closeHandler, this));
- this.$editor.on('keyup.redactor-modal', $.proxy(this.modal.closeHandler, this));
- this.$modalBox.on('click.redactor-modal', $.proxy(this.modal.close, this));
- },
- disableEvents: function()
- {
- this.$modalClose.off('click.redactor-modal');
- $(document).off('keyup.redactor-modal');
- this.$editor.off('keyup.redactor-modal');
- this.$modalBox.off('click.redactor-modal');
- $(window).off('resize.redactor-modal');
- },
- closeHandler: function(e)
- {
- if (e.which != this.keyCode.ESC) return;
-
- this.modal.close(false);
- },
- close: function(e)
- {
- if (e)
+ var top = this.$toolbar.innerHeight() + this.opts.toolbarFixedTopOffset;
+ var position = 'fixed';
+ if (this.opts.toolbarFixedTarget !== document)
{
- if (!$(e.target).hasClass('redactor-modal-close-btn') && e.target != this.$modalClose[0] && e.target != this.$modalBox[0])
- {
- return;
- }
-
- e.preventDefault();
+ top = (this.$toolbar.innerHeight() + this.$toolbar.offset().top) + this.opts.toolbarFixedTopOffset;
+ position = 'absolute';
}
- if (!this.$modalBox) return;
-
- this.modal.disableEvents();
-
- this.$modalOverlay.remove();
-
- this.$modalBox.fadeOut('fast', $.proxy(function()
+ $('.redactor-dropdown').each(function()
{
- this.$modalBox.remove();
-
- setTimeout($.proxy(this.utils.restoreScroll, this), 0);
-
- if (e !== undefined) this.selection.restore();
-
- $(document.body).css('overflow', this.modal.bodyOveflow);
- this.core.setCallback('modalClosed', this.modal.templateName);
-
- }, this));
-
- }
- };
- },
- progress: function()
- {
- return {
- show: function()
- {
- $(document.body).append($('<div id="redactor-progress"><span></span></div>'));
- $('#redactor-progress').fadeIn();
+ $(this).css({ position: position, top: top + 'px' });
+ });
},
- hide: function()
+ unsetDropdownsFixed: function()
{
- $('#redactor-progress').fadeOut(1500, function()
+ var top = (this.$toolbar.innerHeight() + this.$toolbar.offset().top);
+ $('.redactor-dropdown').each(function()
{
- $(this).remove();
+ $(this).css({ position: 'absolute', top: top + 'px' });
});
}
-
};
},
upload: function()
{
return {
@@ -7501,10 +7835,11 @@
html = html.replace(/[\u200B-\u200D\uFEFF]/g, '');
html = html.replace(/ /gi, '');
html = html.replace(/<\/?br\s?\/?>/g, '');
html = html.replace(/\s/g, '');
html = html.replace(/^<p>[^\W\w\D\d]*?<\/p>$/i, '');
+ html = html.replace(/<iframe(.*?[^>])>$/i, 'iframe');
// remove empty tags
if (removeEmptyTags !== false)
{
html = html.replace(/<[^\/>][^>]*><\/[^>]+>/gi, '');
@@ -7567,16 +7902,13 @@
},
removeEmpty: function(i, s)
{
var $s = $(s);
- $s.find('.redactor-invisible-space').replaceWith(function()
- {
- return $(this).contents();
- });
+ $s.find('.redactor-invisible-space').removeAttr('style').removeAttr('class');
- if ($s.find('hr, br, img').length !== 0) return;
+ if ($s.find('hr, br, img, iframe').length !== 0) return;
var text = $.trim($s.text());
if (this.utils.isEmpty(text, false))
{
$s.remove();
}
@@ -7727,10 +8059,14 @@
return false;
}
return el;
},
+ isCurrentOrParentHeader: function()
+ {
+ return this.utils.isCurrentOrParent(['H1', 'H2', 'H3', 'H4', 'H5', 'H6']);
+ },
isCurrentOrParent: function(tagName)
{
var parent = this.selection.getParent();
var current = this.selection.getCurrent();
@@ -7752,10 +8088,12 @@
return this.utils.isCurrentOrParentOne(current, parent, tagName);
}
},
isCurrentOrParentOne: function(current, parent, tagName)
{
+ tagName = tagName.toUpperCase();
+
return parent && parent.tagName === tagName ? parent : current && current.tagName === tagName ? current : false;
},
// browsers detection
@@ -7799,20 +8137,21 @@
Redactor.prototype.init.prototype = Redactor.prototype;
// 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 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;
var childNodes = (this.$editor ? this.$editor[0] : this).childNodes, i = childNodes.length;
while (i--)
{
var n = childNodes[i];
- if (n.nodeType === 3)
+
+ if (n.nodeType === 3 && n.parentNode !== 'PRE')
{
var html = n.nodeValue;
// youtube & vimeo
if (convertVideoLinks && html)
@@ -7870,10 +8209,10 @@
}
$(n).after(html).remove();
}
}
- else if (n.nodeType === 1 && !/^(a|button|textarea)$/i.test(n.tagName))
+ else if (n.nodeType === 1 && !/^(pre|a|button|textarea)$/i.test(n.tagName))
{
$.Redactor.fn.formatLinkify.call(n, protocol, convertLinks, convertUrlLinks, convertImageLinks, convertVideoLinks, linkSize);
}
}
};
\ No newline at end of file