app/assets/javascripts/annex/jquery.redactor.js in annex-cms-0.1.8 vs app/assets/javascripts/annex/jquery.redactor.js in annex-cms-0.2.0
- old
+ new
@@ -1,38 +1,38 @@
/*
- Redactor v9.1.9
- Updated: Dec 12, 2013
+ Redactor v10.0.2
+ Updated: October 12, 2014
http://imperavi.com/redactor/
- Copyright (c) 2009-2013, Imperavi LLC.
+ Copyright (c) 2009-2014, Imperavi LLC.
License: http://imperavi.com/redactor/license/
Usage: $('#content').redactor();
*/
(function($)
{
- var uuid = 0;
+ 'use strict';
- "use strict";
-
- var Range = function(range)
+ if (!Function.prototype.bind)
{
- this[0] = range.startOffset;
- this[1] = range.endOffset;
+ Function.prototype.bind = function(scope)
+ {
+ var fn = this;
+ return function()
+ {
+ return fn.apply(scope);
+ };
+ };
+ }
- this.range = range;
+ var uuid = 0;
- return this;
- };
+ var reUrlYoutube = /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w.\-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig;
+ var reUrlVimeo = /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/;
- Range.prototype.equals = function()
- {
- return this[0] === this[1];
- };
-
// Plugin
$.fn.redactor = function(options)
{
var val = [];
var args = Array.prototype.slice.call(arguments, 1);
@@ -40,23 +40,45 @@
if (typeof options === 'string')
{
this.each(function()
{
var instance = $.data(this, 'redactor');
- if (typeof instance !== 'undefined' && $.isFunction(instance[options]))
+ var func;
+
+ if (options.search(/\./) != '-1')
{
- var methodVal = instance[options].apply(instance, args);
- if (methodVal !== undefined && methodVal !== instance) val.push(methodVal);
+ func = options.split('.');
+ if (typeof instance[func[0]] != 'undefined')
+ {
+ func = instance[func[0]][func[1]];
+ }
}
- else return $.error('No such method "' + options + '" for Redactor');
+ else
+ {
+ func = instance[options];
+ }
+
+ if (typeof instance !== 'undefined' && $.isFunction(func))
+ {
+ var methodVal = func.apply(instance, args);
+ if (methodVal !== undefined && methodVal !== instance)
+ {
+ val.push(methodVal);
+ }
+ }
+ else
+ {
+ $.error('No such method "' + options + '" for Redactor');
+ }
});
}
else
{
this.each(function()
{
- if (!$.data(this, 'redactor')) $.data(this, 'redactor', Redactor(this, options));
+ $.data(this, 'redactor', {});
+ $.data(this, 'redactor', Redactor(this, options));
});
}
if (val.length === 0) return this;
else if (val.length === 1) return val[0];
@@ -68,7188 +90,7725 @@
function Redactor(el, options)
{
return new Redactor.prototype.init(el, options);
}
+ // Functionality
$.Redactor = Redactor;
- $.Redactor.VERSION = '9.1.9';
+ $.Redactor.VERSION = '10.0.2';
+ $.Redactor.modules = ['core', 'build', 'lang', 'toolbar', 'button', 'dropdown', 'code',
+ 'clean', 'tidy', 'paragraphize', 'tabifier', 'focus', 'placeholder', 'autosave', 'buffer', 'indent', 'alignment', 'paste',
+ 'keydown', 'keyup', 'shortcuts', 'line', 'list', 'block', 'inline', 'insert', 'caret', 'selection', 'observe',
+ 'link', 'image', 'file', 'modal', 'progress', 'upload', 'utils'];
+
$.Redactor.opts = {
- // settings
- rangy: false,
+ // settings
+ lang: 'en',
+ direction: 'ltr', // ltr or rtl
- iframe: false,
- fullpage: false,
- css: false, // url
+ plugins: false, // array
- lang: 'en',
- direction: 'ltr', // ltr or rtl
+ focus: false,
+ focusEnd: false,
- placeholder: false,
+ placeholder: false,
- wym: false,
- mobile: true,
- cleanup: true,
- tidyHtml: true,
- pastePlainText: false,
- removeEmptyTags: true,
- templateVars: false,
- xhtml: false,
+ visual: true,
+ tabindex: false,
- visual: true,
- focus: false,
- tabindex: false,
- autoresize: true,
- minHeight: false,
- maxHeight: false,
- shortcuts: true,
+ minHeight: false,
+ maxHeight: false,
- autosave: false, // false or url
- autosaveInterval: 60, // seconds
+ linebreaks: false,
+ replaceDivs: true,
+ paragraphize: true,
+ cleanStyleOnEnter: false,
+ enterKey: true,
- plugins: false, // array
+ cleanOnPaste: true,
+ cleanSpaces: true,
+ pastePlainText: false,
- linkAnchor: true,
- linkEmail: true,
- linkTooltip: true,
- linkProtocol: 'http://',
- linkNofollow: false,
- linkSize: 50,
+ autosave: false, // false or url
+ autosaveName: false,
+ autosaveInterval: 60, // seconds
+ autosaveOnChange: false,
- imageFloatMargin: '10px',
- imageGetJson: false, // url (ex. /folder/images.json ) or false
+ linkTooltip: true,
+ linkProtocol: 'http',
+ linkNofollow: false,
+ linkSize: 50,
- imageUpload: false, // url
- imageUploadParam: 'file', // input name
- fileUpload: false, // url
- fileUploadParam: 'file', // input name
- clipboardUpload: true, // or false
- clipboardUploadUrl: false, // url
- dragUpload: true, // false
+ imageEditable: true,
+ imageLink: true,
+ imagePosition: true,
+ imageFloatMargin: '10px',
+ imageResizable: true,
- dnbImageTypes: ['image/png', 'image/jpeg', 'image/gif'], // or false
+ imageUpload: false,
+ imageUploadParam: 'file',
- s3: false,
- uploadFields: false,
+ uploadImageField: false,
- observeImages: true,
- observeLinks: true,
+ dragImageUpload: true,
- modalOverlay: true,
+ fileUpload: false,
+ fileUploadParam: 'file',
- tabSpaces: false, // true or number of spaces
- tabFocus: true,
+ dragFileUpload: true,
- air: false,
- airButtons: ['formatting', '|', 'bold', 'italic', 'deleted', '|', 'unorderedlist', 'orderedlist', 'outdent', 'indent'],
+ s3: false,
- toolbar: true,
- toolbarFixed: false,
- toolbarFixedTarget: document,
- toolbarFixedTopOffset: 0, // pixels
- toolbarFixedBox: false,
- toolbarExternal: false, // ID selector
- buttonSource: true,
+ convertLinks: true,
+ convertUrlLinks: true,
+ convertImageLinks: true,
+ convertVideoLinks: true,
- buttonSeparator: '<li class="redactor_separator"></li>',
+ preSpaces: 4, // or false
+ tabAsSpaces: false, // true or number of spaces
+ tabFocus: true,
- buttonsCustom: {},
- buttonsAdd: [],
- buttons: [
- 'html', '|', 'formatting', '|', 'bold', 'italic', 'deleted', '|',
- 'unorderedlist', 'orderedlist', 'outdent', 'indent', '|',
- 'image', 'video', 'file', 'table', 'link', '|',
- 'alignment', '|', 'horizontalrule'
- ], // 'underline', 'alignleft', 'aligncenter', 'alignright', 'justify'
+ scrollTarget: false,
- activeButtons: ['deleted', 'italic', 'bold', 'underline', 'unorderedlist', 'orderedlist', 'alignleft', 'aligncenter', 'alignright', 'justify', 'table'],
- activeButtonsStates: {
- b: 'bold',
- strong: 'bold',
- i: 'italic',
- em: 'italic',
- del: 'deleted',
- strike: 'deleted',
- ul: 'unorderedlist',
- ol: 'orderedlist',
- u: 'underline',
- tr: 'table',
- td: 'table',
- table: 'table'
- },
- activeButtonsAdd: false, // object, ex.: { tag: 'buttonName' }
+ toolbar: true,
+ toolbarFixed: true,
+ toolbarFixedTarget: document,
+ toolbarFixedTopOffset: 0, // pixels
+ toolbarExternal: false, // ID selector
+ toolbarOverflow: false,
- formattingTags: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
+ buttonSource: false,
+ buttons: ['html', 'formatting', 'bold', 'italic', 'deleted', 'unorderedlist', 'orderedlist',
+ 'outdent', 'indent', 'image', 'file', 'link', 'alignment', 'horizontalrule'], // + 'underline'
- linebreaks: false,
- paragraphy: true,
- convertDivs: true,
- convertLinks: true,
- convertImageLinks: false,
- convertVideoLinks: false,
- formattingPre: false,
- phpTags: false,
+ buttonsHide: [],
+ buttonsHideOnMobile: [],
- allowedTags: false,
- deniedTags: ['html', 'head', 'link', 'body', 'meta', 'script', 'style', 'applet'],
+ formatting: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
+ formattingAdd: false,
- boldTag: 'strong',
- italicTag: 'em',
+ tabifier: true,
- // private
- indentValue: 20,
- buffer: [],
- rebuffer: [],
- textareamode: false,
- emptyHtml: '<p>​</p>',
- invisibleSpace: '​',
- rBlockTest: /^(P|H[1-6]|LI|ADDRESS|SECTION|HEADER|FOOTER|ASIDE|ARTICLE)$/i,
- alignmentTags: ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DD', 'DL', 'DT', 'DIV', 'TD',
- 'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'ADDRESS', 'SECTION',
- 'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE'],
- ownLine: ['area', 'body', 'head', 'hr', 'i?frame', 'link', 'meta', 'noscript', 'style', 'script', 'table', 'tbody', 'thead', 'tfoot'],
- contOwnLine: ['li', 'dt', 'dt', 'h[1-6]', 'option', 'script'],
- newLevel: ['blockquote', 'div', 'dl', 'fieldset', 'form', 'frameset', 'map', 'ol', 'p', 'pre', 'select', 'td', 'th', 'tr', 'ul'],
- blockLevelElements: ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DD', 'DL', 'DT', 'DIV', 'LI',
- 'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'PRE', 'ADDRESS', 'SECTION',
- 'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE', 'TD'],
- // lang
- langs: {
- en: {
- html: 'HTML',
- video: 'Insert Video',
- image: 'Insert Image',
- table: 'Table',
- link: 'Link',
- link_insert: 'Insert link',
- link_edit: 'Edit link',
- unlink: 'Unlink',
- formatting: 'Formatting',
- paragraph: 'Normal text',
- quote: 'Quote',
- code: 'Code',
- header1: 'Header 1',
- header2: 'Header 2',
- header3: 'Header 3',
- header4: 'Header 4',
- header5: 'Header 5',
- bold: 'Bold',
- italic: 'Italic',
- fontcolor: 'Font Color',
- backcolor: 'Back Color',
- unorderedlist: 'Unordered List',
- orderedlist: 'Ordered List',
- outdent: 'Outdent',
- indent: 'Indent',
- cancel: 'Cancel',
- insert: 'Insert',
- save: 'Save',
- _delete: 'Delete',
- insert_table: 'Insert Table',
- insert_row_above: 'Add Row Above',
- insert_row_below: 'Add Row Below',
- insert_column_left: 'Add Column Left',
- insert_column_right: 'Add Column Right',
- delete_column: 'Delete Column',
- delete_row: 'Delete Row',
- delete_table: 'Delete Table',
- rows: 'Rows',
- columns: 'Columns',
- add_head: 'Add Head',
- delete_head: 'Delete Head',
- title: 'Title',
- image_position: 'Position',
- none: 'None',
- left: 'Left',
- right: 'Right',
- image_web_link: 'Image Web Link',
- text: 'Text',
- mailto: 'Email',
- web: 'URL',
- video_html_code: 'Video Embed Code',
- file: 'Insert File',
- upload: 'Upload',
- download: 'Download',
- choose: 'Choose',
- or_choose: 'Or choose',
- drop_file_here: 'Drop file here',
- align_left: 'Align text to the left',
- align_center: 'Center text',
- align_right: 'Align text to the right',
- align_justify: 'Justify text',
- horizontalrule: 'Insert Horizontal Rule',
- deleted: 'Deleted',
- anchor: 'Anchor',
- link_new_tab: 'Open link in new tab',
- underline: 'Underline',
- alignment: 'Alignment',
- filename: 'Name (optional)',
- edit: 'Edit',
- title: 'Title'
- }
+ deniedTags: ['html', 'head', 'link', 'body', 'meta', 'script', 'style', 'applet'],
+ allowedTags: false, // or array
+
+ removeComments: false,
+ replaceTags: [
+ ['strike', 'del']
+ ],
+ replaceStyles: [
+ ['font-weight:\\s?bold', "strong"],
+ ['font-style:\\s?italic', "em"],
+ ['text-decoration:\\s?underline', "u"],
+ ['text-decoration:\\s?line-through', 'del']
+ ],
+ removeDataAttr: false,
+
+ removeAttr: false, // or multi array
+ allowedAttr: false, // or multi array
+
+ removeWithoutAttr: ['span'], // or false
+ removeEmpty: ['p'], // or false;
+
+ activeButtons: ['deleted', 'italic', 'bold', 'underline', 'unorderedlist', 'orderedlist',
+ 'alignleft', 'aligncenter', 'alignright', 'justify'],
+ activeButtonsStates: {
+ b: 'bold',
+ strong: 'bold',
+ i: 'italic',
+ em: 'italic',
+ del: 'deleted',
+ strike: 'deleted',
+ ul: 'unorderedlist',
+ ol: 'orderedlist',
+ u: 'underline'
+ },
+
+ shortcuts: {
+ 'ctrl+shift+m, meta+shift+m': { func: 'inline.removeFormat' },
+ 'ctrl+b, meta+b': { func: 'inline.format', params: ['bold'] },
+ 'ctrl+i, meta+i': { func: 'inline.format', params: ['italic'] },
+ 'ctrl+h, meta+h': { func: 'inline.format', params: ['superscript'] },
+ 'ctrl+l, meta+l': { func: 'inline.format', params: ['subscript'] },
+ 'ctrl+k, meta+k': { func: 'link.show' },
+ 'ctrl+shift+7': { func: 'list.toggle', params: ['orderedlist'] },
+ 'ctrl+shift+8': { func: 'list.toggle', params: ['unorderedlist'] }
+ },
+ shortcutsAdd: false,
+
+ // private
+ buffer: [],
+ rebuffer: [],
+ emptyHtml: '<p>​</p>',
+ invisibleSpace: '​',
+ imageTypes: ['image/png', 'image/jpeg', 'image/gif'],
+ indentValue: 20,
+ verifiedTags: ['a', 'img', 'b', 'strong', 'sub', 'sup', 'i', 'em', 'u', 'small', 'strike', 'del', 'cite', 'ul', 'ol', 'li'], // and for span tag special rule
+ inlineTags: ['strong', 'b', 'u', 'em', 'i', 'code', 'del', 'ins', 'samp', 'kbd', 'sup', 'sub', 'mark', 'var', 'cite', 'small'],
+ alignmentTags: ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DL', 'DT', 'DD', 'DIV', 'TD', 'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'ADDRESS', 'SECTION', 'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE'],
+ blockLevelElements: ['PRE', 'UL', 'OL', 'LI'],
+
+
+ // lang
+ langs: {
+ en: {
+ html: 'HTML',
+ video: 'Insert Video',
+ image: 'Insert Image',
+ table: 'Table',
+ link: 'Link',
+ link_insert: 'Insert link',
+ link_edit: 'Edit link',
+ unlink: 'Unlink',
+ formatting: 'Formatting',
+ paragraph: 'Normal text',
+ quote: 'Quote',
+ code: 'Code',
+ header1: 'Header 1',
+ header2: 'Header 2',
+ header3: 'Header 3',
+ header4: 'Header 4',
+ header5: 'Header 5',
+ bold: 'Bold',
+ italic: 'Italic',
+ fontcolor: 'Font Color',
+ backcolor: 'Back Color',
+ unorderedlist: 'Unordered List',
+ orderedlist: 'Ordered List',
+ outdent: 'Outdent',
+ indent: 'Indent',
+ cancel: 'Cancel',
+ insert: 'Insert',
+ save: 'Save',
+ _delete: 'Delete',
+ insert_table: 'Insert Table',
+ insert_row_above: 'Add Row Above',
+ insert_row_below: 'Add Row Below',
+ insert_column_left: 'Add Column Left',
+ insert_column_right: 'Add Column Right',
+ delete_column: 'Delete Column',
+ delete_row: 'Delete Row',
+ delete_table: 'Delete Table',
+ rows: 'Rows',
+ columns: 'Columns',
+ add_head: 'Add Head',
+ delete_head: 'Delete Head',
+ title: 'Title',
+ image_position: 'Position',
+ none: 'None',
+ left: 'Left',
+ right: 'Right',
+ center: 'Center',
+ image_web_link: 'Image Web Link',
+ text: 'Text',
+ mailto: 'Email',
+ web: 'URL',
+ video_html_code: 'Video Embed Code or Youtube/Vimeo Link',
+ file: 'Insert File',
+ upload: 'Upload',
+ download: 'Download',
+ choose: 'Choose',
+ or_choose: 'Or choose',
+ drop_file_here: 'Drop file here',
+ align_left: 'Align text to the left',
+ align_center: 'Center text',
+ align_right: 'Align text to the right',
+ align_justify: 'Justify text',
+ horizontalrule: 'Insert Horizontal Rule',
+ deleted: 'Deleted',
+ anchor: 'Anchor',
+ link_new_tab: 'Open link in new tab',
+ underline: 'Underline',
+ alignment: 'Alignment',
+ filename: 'Name (optional)',
+ edit: 'Edit'
}
+ }
};
// Functionality
Redactor.fn = $.Redactor.prototype = {
keyCode: {
BACKSPACE: 8,
DELETE: 46,
DOWN: 40,
ENTER: 13,
+ SPACE: 32,
ESC: 27,
TAB: 9,
CTRL: 17,
META: 91,
+ SHIFT: 16,
+ ALT: 18,
LEFT: 37,
LEFT_WIN: 91
},
// Initialization
init: function(el, options)
{
- this.rtePaste = false;
- this.$element = this.$source = $(el);
+ this.$element = $(el);
this.uuid = uuid++;
- // clonning options
- var opts = $.extend(true, {}, $.Redactor.opts);
+ // if paste event detected = true
+ this.rtePaste = false;
+ this.$pasteBox = false;
- // current settings
+ this.loadOptions(options);
+ this.loadModules();
+
+ // formatting storage
+ this.formatting = {};
+
+ // block level tags
+ $.merge(this.opts.blockLevelElements, this.opts.alignmentTags);
+ this.reIsBlock = new RegExp('^(' + this.opts.blockLevelElements.join('|' ) + ')$', 'i');
+
+ // setup allowed and denied tags
+ this.tidy.setupAllowed();
+
+ // load lang
+ this.lang.load();
+
+ // extend shortcuts
+ $.extend(this.opts.shortcuts, this.opts.shortcutsAdd);
+
+ // start callback
+ this.core.setCallback('start');
+
+ // build
+ this.start = true;
+ this.build.run();
+ },
+
+ loadOptions: function(options)
+ {
this.opts = $.extend(
{},
- opts,
+ $.extend(true, {}, $.Redactor.opts),
this.$element.data(),
options
);
+ },
+ getModuleMethods: function(object)
+ {
+ return Object.getOwnPropertyNames(object).filter(function(property)
+ {
+ return typeof object[property] == 'function';
+ });
+ },
+ loadModules: function()
+ {
+ var len = $.Redactor.modules.length;
+ for (var i = 0; i < len; i++)
+ {
+ this.bindModuleMethods($.Redactor.modules[i]);
+ }
+ },
+ bindModuleMethods: function(module)
+ {
+ if (typeof this[module] == 'undefined') return;
- this.start = true;
- this.dropdowns = [];
+ // init module
+ this[module] = this[module]();
- // get sizes
- this.sourceHeight = this.$source.css('height');
- this.sourceWidth = this.$source.css('width');
+ var methods = this.getModuleMethods(this[module]);
+ var len = methods.length;
- // dependency of the editor modes
- if (this.opts.fullpage) this.opts.iframe = true;
- if (this.opts.linebreaks) this.opts.paragraphy = false;
- if (this.opts.paragraphy) this.opts.linebreaks = false;
- if (this.opts.toolbarFixedBox) this.opts.toolbarFixed = true;
+ // bind methods
+ for (var z = 0; z < len; z++)
+ {
+ this[module][methods[z]] = this[module][methods[z]].bind(this);
+ }
+ },
- // the alias for iframe mode
- this.document = document;
- this.window = window;
+ core: function()
+ {
+ return {
+ getObject: function()
+ {
+ return $.extend({}, this);
+ },
+ getEditor: function()
+ {
+ return this.$editor;
+ },
+ getBox: function()
+ {
+ return this.$box;
+ },
+ getElement: function()
+ {
+ return this.$element;
+ },
+ getTextarea: function()
+ {
+ return this.$textarea;
+ },
+ getToolbar: function()
+ {
+ return (this.$toolbar) ? this.$toolbar : false;
+ },
+ addEvent: function(name)
+ {
+ this.core.event = name;
+ },
+ getEvent: function()
+ {
+ return this.core.event;
+ },
+ setCallback: function(type, e, data)
+ {
+ var callback = this.opts[type + 'Callback'];
+ if ($.isFunction(callback))
+ {
+ return (typeof data == 'undefined') ? callback.call(this, e) : callback.call(this, e, data);
+ }
+ else
+ {
+ return (typeof data == 'undefined') ? e : data;
+ }
+ },
+ destroy: function()
+ {
+ this.core.setCallback('destroy');
- // selection saved
- this.savedSel = false;
+ // off events and remove data
+ this.$element.off('.redactor').removeData('redactor');
+ this.$editor.off('.redactor');
- // clean setup
- this.cleanlineBefore = new RegExp('^<(/?' + this.opts.ownLine.join('|/?' ) + '|' + this.opts.contOwnLine.join('|') + ')[ >]');
- this.cleanlineAfter = new RegExp('^<(br|/?' + this.opts.ownLine.join('|/?' ) + '|/' + this.opts.contOwnLine.join('|/') + ')[ >]');
- this.cleannewLevel = new RegExp('^</?(' + this.opts.newLevel.join('|' ) + ')[ >]');
+ // common
+ this.$editor.removeClass('redactor-editor redactor-linebreaks redactor-placeholder');
+ this.$editor.removeAttr('contenteditable');
- // block level
- this.rTestBlock = new RegExp('^(' + this.opts.blockLevelElements.join('|' ) + ')$', 'i');
+ var html = this.code.get();
- // setup formatting permissions
- if (this.opts.linebreaks === false)
- {
- if (this.opts.allowedTags !== false)
- {
- var arrSearch = ['strong', 'em', 'del'];
- var arrAdd = ['b', 'i', 'strike'];
-
- if ($.inArray('p', this.opts.allowedTags) === '-1') this.opts.allowedTags.push('p');
-
- for (i in arrSearch)
+ if (this.build.isTextarea())
{
- if ($.inArray(arrSearch[i], this.opts.allowedTags) != '-1') this.opts.allowedTags.push(arrAdd[i]);
+ 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();
+ }
- if (this.opts.deniedTags !== false)
- {
- var pos = $.inArray('p', this.opts.deniedTags);
- if (pos !== '-1') this.opts.deniedTags.splice(pos, pos);
- }
- }
+ // paste box
+ if (this.$pasteBox) this.$pasteBox.remove();
- // ie & opera
- if (this.browser('msie') || this.browser('opera'))
- {
- this.opts.buttons = this.removeFromArrayByValue(this.opts.buttons, 'horizontalrule');
- }
+ // modal
+ if (this.$modalBox) this.$modalBox.remove();
+ if (this.$modalOverlay) this.$modalOverlay.remove();
- // load lang
- this.opts.curLang = this.opts.langs[this.opts.lang];
+ // buttons tooltip
+ $('.redactor-toolbar-tooltip').remove();
- // Build
- this.buildStart();
+ // autosave
+ clearInterval(this.autosaveInterval);
+ }
+ };
},
- toolbarInit: function(lang)
+ build: function()
{
return {
- html:
+ run: function()
{
- title: lang.html,
- func: 'toggle'
+
+ this.build.createContainerBox();
+ this.build.loadContent();
+ this.build.loadEditor();
+ this.build.enableEditor();
+ this.build.setCodeAndCall();
+
},
- formatting:
+ isTextarea: function()
{
- title: lang.formatting,
- func: 'show',
- dropdown:
+ return (this.$element[0].tagName === 'TEXTAREA');
+ },
+ createContainerBox: function()
+ {
+ this.$box = $('<div class="redactor-box" />');
+ },
+ createTextarea: function()
+ {
+ this.$textarea = $('<textarea />').attr('name', this.build.getTextareaName());
+ },
+ getTextareaName: function()
+ {
+ var name = this.$element.attr('id');
+ if (typeof(name) == 'undefined')
{
- p:
- {
- title: lang.paragraph,
- func: 'formatBlocks'
- },
- blockquote:
- {
- title: lang.quote,
- func: 'formatQuote',
- className: 'redactor_format_blockquote'
- },
- pre:
- {
- title: lang.code,
- func: 'formatBlocks',
- className: 'redactor_format_pre'
- },
- h1:
- {
- title: lang.header1,
- func: 'formatBlocks',
- className: 'redactor_format_h1'
- },
- h2:
- {
- title: lang.header2,
- func: 'formatBlocks',
- className: 'redactor_format_h2'
- },
- h3:
- {
- title: lang.header3,
- func: 'formatBlocks',
- className: 'redactor_format_h3'
- },
- h4:
- {
- title: lang.header4,
- func: 'formatBlocks',
- className: 'redactor_format_h4'
- },
- h5:
- {
- title: lang.header5,
- func: 'formatBlocks',
- className: 'redactor_format_h5'
- }
+ name = 'content-' + this.uuid;
}
+
+ return name;
},
- bold:
+ loadContent: function()
{
- title: lang.bold,
- exec: 'bold'
+ var func = (this.build.isTextarea()) ? 'val' : 'html';
+ this.content = $.trim(this.$element[func]());
},
- italic:
+ enableEditor: function()
{
- title: lang.italic,
- exec: 'italic'
+ this.$editor.attr({ 'contenteditable': true, 'dir': this.opts.direction });
},
- deleted:
+ loadEditor: function()
{
- title: lang.deleted,
- exec: 'strikethrough'
+ var func = (this.build.isTextarea()) ? 'fromTextarea' : 'fromElement';
+ this.build[func]();
},
- underline:
+ fromTextarea: function()
{
- title: lang.underline,
- exec: 'underline'
+ this.$editor = $('<div />');
+ this.$textarea = this.$element;
+ this.$box.insertAfter(this.$element).append(this.$editor).append(this.$element);
+ this.$editor.addClass('redactor-editor');
+
+ this.$element.hide();
},
- unorderedlist:
+ fromElement: function()
{
- title: '• ' + lang.unorderedlist,
- exec: 'insertunorderedlist'
+ this.$editor = this.$element;
+ this.build.createTextarea();
+ this.$box.insertAfter(this.$editor).append(this.$editor).append(this.$textarea);
+ this.$editor.addClass('redactor-editor');
+
+ this.$textarea.hide();
},
- orderedlist:
+ setCodeAndCall: function()
{
- title: '1. ' + lang.orderedlist,
- exec: 'insertorderedlist'
+ // set code
+ this.code.set(this.content);
+
+ this.build.setOptions();
+ this.build.callEditor();
+
+ // code mode
+ if (!this.opts.visual)
+ {
+ setTimeout($.proxy(this.code.showCode, this), 200);
+ }
},
- outdent:
+ callEditor: function()
{
- title: '< ' + lang.outdent,
- func: 'indentingOutdent'
+ this.build.disableMozillaEditing();
+ this.build.setEvents();
+ this.build.setHelpers();
+
+ // load toolbar
+ if (this.opts.toolbar)
+ {
+ this.opts.toolbar = this.toolbar.init();
+ this.toolbar.build();
+ }
+
+ // modal templates init
+ this.modal.loadTemplates();
+
+ // plugins
+ this.build.plugins();
+
+ // observers
+ setTimeout($.proxy(this.observe.load, this), 4);
+
+ // init callback
+ this.core.setCallback('init');
},
- indent:
+ setOptions: function()
{
- title: '> ' + lang.indent,
- func: 'indentingIndent'
+ // textarea direction
+ $(this.$textarea).attr('dir', this.opts.direction);
+
+ if (this.opts.linebreaks) this.$editor.addClass('redactor-linebreaks');
+
+ if (this.opts.tabindex) this.$editor.attr('tabindex', this.opts.tabindex);
+
+ if (this.opts.minHeight) this.$editor.css('minHeight', this.opts.minHeight);
+ if (this.opts.maxHeight) this.$editor.css('maxHeight', this.opts.maxHeight);
+
},
- image:
+ setEvents: function()
{
- title: lang.image,
- func: 'imageShow'
+ // drop
+ this.$editor.on('drop.redactor', $.proxy(function(e)
+ {
+ e = e.originalEvent || e;
+
+ if (window.FormData === undefined || !e.dataTransfer) return true;
+
+ var length = e.dataTransfer.files.length;
+ if (length === 0) return true;
+ else
+ {
+ e.preventDefault();
+
+ if (this.opts.dragImageUpload || this.opts.dragFileUpload)
+ {
+ var files = e.dataTransfer.files;
+ this.upload.directUpload(files[0], e);
+ }
+ }
+
+ setTimeout($.proxy(this.clean.clearUnverified, this), 1);
+
+ this.core.setCallback('drop', e);
+
+ }, this));
+
+
+ // click
+ this.$editor.on('click.redactor', $.proxy(function(e)
+ {
+ var type = 'click';
+ if ((this.core.getEvent() == 'click' || this.core.getEvent() == 'arrow'))
+ {
+ type = false;
+ }
+
+ this.core.addEvent(type);
+ this.utils.disableSelectAll();
+ this.core.setCallback('click', e);
+
+ }, this));
+
+ // paste
+ this.$editor.on('paste.redactor', $.proxy(this.paste.init, this));
+
+ // keydown
+ this.$editor.on('keydown.redactor', $.proxy(this.keydown.init, this));
+
+ // keyup
+ this.$editor.on('keyup.redactor', $.proxy(this.keyup.init, this));
+
+ // textarea keydown
+ if ($.isFunction(this.opts.codeKeydownCallback))
+ {
+ this.$textarea.on('keydown.redactor-textarea', $.proxy(this.opts.codeKeydownCallback, this));
+ }
+
+ // textarea keyup
+ if ($.isFunction(this.opts.codeKeyupCallback))
+ {
+ this.$textarea.on('keyup.redactor-textarea', $.proxy(this.opts.codeKeyupCallback, this));
+ }
+
+ // focus
+ if ($.isFunction(this.opts.focusCallback))
+ {
+ this.$editor.on('focus.redactor', $.proxy(this.opts.focusCallback, this));
+ }
+
+ var clickedElement;
+ $(document).on('mousedown', function(e) {
+ clickedElement = $(e.target);
+ });
+
+ // blur
+ this.$editor.on('blur.redactor', $.proxy(function(e)
+ {
+ if (this.rtePaste) return;
+
+ var $el = $(clickedElement);
+ if (!$el.hasClass('redactor-toolbar, redactor-dropdown') && !$el.is('#redactor-modal') && $el.parents('.redactor-toolbar, .redactor-dropdown, #redactor-modal').size() === 0)
+ {
+ this.utils.disableSelectAll();
+ if ($.isFunction(this.opts.blurCallback)) this.core.setCallback('blur', e);
+ }
+ }, this));
},
- video:
+ setHelpers: function()
{
- title: lang.video,
- func: 'videoShow'
+ // autosave
+ this.autosave.enable();
+
+ // placeholder
+ this.placeholder.enable();
+
+ // focus
+ if (this.opts.focus) setTimeout($.proxy(this.focus.setStart, this), 100);
+ if (this.opts.focusEnd) setTimeout($.proxy(this.focus.setEnd, this), 100);
+
},
- file:
+ plugins: function()
{
- title: lang.file,
- func: 'fileShow'
+ if (!this.opts.plugins) return;
+ if (!RedactorPlugins) return;
+
+ $.each(this.opts.plugins, $.proxy(function(i, s)
+ {
+ if (RedactorPlugins[s])
+ {
+ if (!$.isFunction(RedactorPlugins[s])) return;
+
+ this[s] = RedactorPlugins[s]();
+
+ var methods = this.getModuleMethods(this[s]);
+ var len = methods.length;
+
+ // bind methods
+ for (var z = 0; z < len; z++)
+ {
+ this[s][methods[z]] = this[s][methods[z]].bind(this);
+ }
+
+ if ($.isFunction(this[s].init)) this[s].init();
+ }
+
+ }, this));
+
+
},
- table:
+ disableMozillaEditing: function()
{
- title: lang.table,
- func: 'show',
- dropdown:
- {
- insert_table:
+ if (!this.utils.browser('mozilla')) return;
+
+ // FF fix
+ try {
+ document.execCommand('enableObjectResizing', false, false);
+ document.execCommand('enableInlineTableEditing', false, false);
+ } catch (e) {}
+ }
+ };
+ },
+ lang: function()
+ {
+ return {
+ load: function()
+ {
+ this.opts.curLang = this.opts.langs[this.opts.lang];
+ },
+ get: function(name)
+ {
+ return (typeof this.opts.curLang[name] != 'undefined') ? this.opts.curLang[name] : '';
+ }
+ };
+ },
+ toolbar: function()
+ {
+ return {
+ init: function()
+ {
+ return {
+ html:
{
- title: lang.insert_table,
- func: 'tableShow'
+ title: this.lang.get('html'),
+ func: 'code.toggle'
},
- separator_drop1:
+ formatting:
{
- name: 'separator'
+ 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'
+ }
+ }
},
- insert_row_above:
+ bold:
{
- title: lang.insert_row_above,
- func: 'tableAddRowAbove'
+ title: this.lang.get('bold'),
+ func: 'inline.format'
},
- insert_row_below:
+ italic:
{
- title: lang.insert_row_below,
- func: 'tableAddRowBelow'
+ title: this.lang.get('italic'),
+ func: 'inline.format'
},
- insert_column_left:
+ deleted:
{
- title: lang.insert_column_left,
- func: 'tableAddColumnLeft'
+ title: this.lang.get('deleted'),
+ func: 'inline.format'
},
- insert_column_right:
+ underline:
{
- title: lang.insert_column_right,
- func: 'tableAddColumnRight'
+ title: this.lang.get('underline'),
+ func: 'inline.format'
},
- separator_drop2:
+ unorderedlist:
{
- name: 'separator'
+ title: '• ' + this.lang.get('unorderedlist'),
+ func: 'list.toggle'
},
- add_head:
+ orderedlist:
{
- title: lang.add_head,
- func: 'tableAddHead'
+ title: '1. ' + this.lang.get('orderedlist'),
+ func: 'list.toggle'
},
- delete_head:
+ outdent:
{
- title: lang.delete_head,
- func: 'tableDeleteHead'
+ title: '< ' + this.lang.get('outdent'),
+ func: 'indent.decrease'
},
- separator_drop3:
+ indent:
{
- name: 'separator'
+ title: '> ' + this.lang.get('indent'),
+ func: 'indent.increase'
},
- delete_column:
+ image:
{
- title: lang.delete_column,
- func: 'tableDeleteColumn'
+ title: this.lang.get('image'),
+ func: 'image.show'
},
- delete_row:
+ file:
{
- title: lang.delete_row,
- func: 'tableDeleteRow'
+ title: this.lang.get('file'),
+ func: 'file.show'
},
- delete_table:
- {
- title: lang.delete_table,
- func: 'tableDeleteTable'
- }
- }
- },
- link: {
- title: lang.link,
- func: 'show',
- dropdown:
- {
link:
{
- title: lang.link_insert,
- func: 'linkShow'
+ 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'
+ }
+ }
},
- unlink:
+ alignment:
{
- title: lang.unlink,
- exec: 'unlink'
+ 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));
}
+
},
- fontcolor:
+ createContainer: function()
{
- title: lang.fontcolor,
- func: 'show'
+ return $('<ul>').addClass('redactor-toolbar').attr('id', 'redactor-toolbar-' + this.uuid);
},
- backcolor:
+ setFormattingTags: function()
{
- title: lang.backcolor,
- func: 'show'
+ $.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));
+
},
- alignment:
+ loadButtons: function()
{
- title: lang.alignment,
- func: 'show',
- dropdown:
+ $.each(this.opts.buttons, $.proxy(function(i, btnName)
{
- alignleft:
- {
- title: lang.align_left,
- func: 'alignmentLeft'
- },
- aligncenter:
- {
- title: lang.align_center,
- func: 'alignmentCenter'
- },
- alignright:
- {
- title: lang.align_right,
- func: 'alignmentRight'
- },
- justify:
- {
- title: lang.align_justify,
- func: 'alignmentJustify'
- }
+ 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);
+ }
},
- alignleft:
+ setFixed: function()
{
- title: lang.align_left,
- func: 'alignmentLeft'
+ 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));
+
},
- aligncenter:
+ setTabindex: function()
{
- title: lang.align_center,
- func: 'alignmentCenter'
+ this.$toolbar.find('a').attr('tabindex', '-1');
},
- alignright:
+ setOverflow: function()
{
- title: lang.align_right,
- func: 'alignmentRight'
+ if (this.utils.isMobile() && this.opts.toolbarOverflow)
+ {
+ this.$toolbar.addClass('redactor-toolbar-overflow');
+ }
},
- justify:
+ isButtonSourceNeeded: function()
{
- title: lang.align_justify,
- func: 'alignmentJustify'
+ if (this.opts.buttonSource) return;
+
+ var index = this.opts.buttons.indexOf('html');
+ if (index !== -1)
+ {
+ this.opts.buttons.splice(index, 1);
+ }
},
- horizontalrule:
+ hideButtons: function()
{
- exec: 'inserthorizontalrule',
- title: lang.horizontalrule
- }
+ 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);
- // CALLBACKS
- callback: function(type, event, data)
- {
- var callback = this.opts[ type + 'Callback' ];
- if ($.isFunction(callback))
- {
- if (event === false) return callback.call(this, data);
- else return callback.call(this, event, data);
- }
- else return data;
- },
+ }, 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);
- // DESTROY
- destroy: function()
- {
- clearInterval(this.autosaveInterval);
+ }, this));
+ },
+ observeScroll: function()
+ {
+ var scrollTop = $(this.opts.toolbarFixedTarget).scrollTop();
+ var boxTop = 1;
- $(window).off('.redactor');
- this.$source.off('redactor-textarea');
- this.$element.off('.redactor').removeData('redactor');
+ if (this.opts.toolbarFixedTarget === document)
+ {
+ boxTop = this.$box.offset().top;
+ }
- var html = this.get();
+ 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();
- if (this.opts.textareamode)
- {
- this.$box.after(this.$source);
- this.$box.remove();
- this.$source.val(html).show();
- }
- else
- {
- var $elem = this.$editor;
- if (this.opts.iframe) $elem = this.$element;
+ this.$toolbar.addClass('toolbar-fixed-box');
+ this.$toolbar.css({
+ position: 'absolute',
+ width: width,
+ top: top + 'px',
+ left: left
+ });
- this.$box.after($elem);
- this.$box.remove();
+ 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'
+ });
- $elem.removeClass('redactor_editor').removeClass('redactor_editor_wym').removeAttr('contenteditable').html(html).show();
- }
+ this.toolbar.unsetDropdownsFixed();
+ this.$toolbar.removeClass('toolbar-fixed-box');
- if (this.opts.air)
- {
- $('#redactor_air_' + this.uuid).remove();
- }
+ },
+ 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 });
+ });
+ }
+ };
},
-
- // API GET
- getObject: function()
+ button: function()
{
- return $.extend({}, this);
- },
- getEditor: function()
- {
- return this.$editor;
- },
- getBox: function()
- {
- return this.$box;
- },
- getIframe: function()
- {
- return (this.opts.iframe) ? this.$frame : false;
- },
- getToolbar: function()
- {
- return (this.$toolbar) ? this.$toolbar : false;
- },
+ return {
+ build: function(btnName, btnObject)
+ {
+ var $button = $('<a href="#" class="re-icon re-' + btnName + '" rel="' + btnName + '" />');
- // CODE GET & SET
- get: function()
- {
- return this.$source.val();
- },
- getCodeIframe: function()
- {
- this.$editor.removeAttr('contenteditable').removeAttr('dir');
- var html = this.outerHtml(this.$frame.contents().children());
- this.$editor.attr({ 'contenteditable': true, 'dir': this.opts.direction });
+ if (btnObject.func || btnObject.command || btnObject.dropdown)
+ {
+ $button.on('touchstart click', $.proxy(function(e)
+ {
+ if ($button.hasClass('redactor-button-disabled')) return false;
- return html;
- },
- set: function(html, strip, placeholderRemove)
- {
- html = html.toString();
- html = html.replace(/\$/g, '$');
+ var type = 'func';
+ var callback = btnObject.func;
+ if (btnObject.command)
+ {
+ type = 'command';
+ callback = btnObject.command;
+ }
+ else if (btnObject.dropdown)
+ {
+ type = 'dropdown';
+ callback = false;
+ }
- if (this.opts.fullpage) this.setCodeIframe(html);
- else this.setEditor(html, strip);
+ this.button.onClick(e, btnName, type, callback);
- if (html == '') placeholderRemove = false;
- if (placeholderRemove !== false) this.placeholderRemove();
- },
- setEditor: function(html, strip)
- {
+ }, this));
+ }
- if (strip !== false)
- {
- html = this.cleanSavePreCode(html);
+ // dropdown
+ if (btnObject.dropdown)
+ {
+ var $dropdown = $('<div class="redactor-dropdown redactor-dropdown-box-' + btnName + '" style="display: none;">');
+ $button.data('dropdown', $dropdown);
+ this.dropdown.build(btnName, $dropdown, btnObject.dropdown);
+ }
- html = this.cleanStripTags(html);
- html = this.cleanConvertProtected(html);
- html = this.cleanConvertInlineTags(html, true);
+ // tooltip
+ if (this.utils.isDesktop())
+ {
+ this.button.createTooltip($button, btnName, btnObject.title);
+ }
- if (this.opts.linebreaks === false) html = this.cleanConverters(html);
- else html = html.replace(/<p(.*?)>([\w\W]*?)<\/p>/gi, '$2<br>');
- }
+ return $button;
+ },
+ createTooltip: function($button, name, title)
+ {
+ var $tooltip = $('<span>').addClass('redactor-toolbar-tooltip redactor-toolbar-tooltip-' + name).hide().html(title);
+ $tooltip.appendTo('body');
- // $ fix
- html = html.replace(/&#36;/g, '$');
+ $button.on('mouseover', function()
+ {
+ if ($(this).hasClass('redactor-button-disabled')) return;
- html = this.cleanEmpty(html);
+ var pos = $button.offset();
+ var height = $button.innerHeight();
+ var width = $button.innerWidth();
- this.$editor.html(html);
+ $tooltip.show();
+ $tooltip.css({
+ top: (pos.top + height) + 'px',
+ left: (pos.left + width/2 - $tooltip.innerWidth()/2) + 'px'
+ });
+ });
- // set no editable
- this.setNonEditable();
- this.setSpansVerified();
- this.sync();
- },
- setCodeIframe: function(html)
- {
- var doc = this.iframePage();
- this.$frame[0].src = "about:blank";
+ $button.on('mouseout', function()
+ {
+ $tooltip.hide();
+ });
- html = this.cleanConvertProtected(html);
- html = this.cleanConvertInlineTags(html);
- html = this.cleanRemoveSpaces(html);
+ },
+ onClick: function(e, btnName, type, callback)
+ {
+ e.preventDefault();
- doc.open();
- doc.write(html);
- doc.close();
+ if (this.utils.browser('msie')) e.returnValue = false;
- // redefine editor for fullpage mode
- if (this.opts.fullpage)
- {
- this.$editor = this.$frame.contents().find('body').attr({ 'contenteditable': true, 'dir': this.opts.direction });
- }
+ if (type == 'command')
+ {
+ this.inline.format(callback);
+ }
+ else if (type == 'dropdown')
+ {
+ this.dropdown.show(e, btnName);
+ }
+ else
+ {
+ var func;
+ if ($.isFunction(callback))
+ {
+ callback.call(this, btnName);
+ this.observe.buttons(e, btnName);
+ }
+ else if (callback.search(/\./) != '-1')
+ {
+ func = callback.split('.');
+ if (typeof this[func[0]] != 'undefined')
+ {
+ this[func[0]][func[1]](btnName);
+ this.observe.buttons(e, btnName);
+ }
+ }
+ else
+ {
+ this[callback](btnName);
+ this.observe.buttons(e, btnName);
+ }
+ }
- // set no editable
- this.setNonEditable();
- this.setSpansVerified();
- this.sync();
+ },
+ get: function(key)
+ {
+ return this.$toolbar.find('a.re-' + key);
+ },
+ setActive: function(key)
+ {
+ this.button.get(key).addClass('redactor-act');
+ },
+ setInactive: function(key)
+ {
+ this.button.get(key).removeClass('redactor-act');
+ },
+ setInactiveAll: function(key)
+ {
+ if (typeof key == 'undefined')
+ {
+ this.$toolbar.find('a.re-icon').removeClass('redactor-act');
+ }
+ else
+ {
+ this.$toolbar.find('a.re-icon').not('.re-' + key).removeClass('redactor-act');
+ }
+ },
+ setActiveInVisual: function()
+ {
+ this.$toolbar.find('a.re-icon').not('a.re-html').removeClass('redactor-button-disabled');
+ },
+ setInactiveInCode: function()
+ {
+ this.$toolbar.find('a.re-icon').not('a.re-html').addClass('redactor-button-disabled');
+ },
+ changeIcon: function(key, classname)
+ {
+ this.button.get(key).addClass('re-' + classname);
+ },
+ removeIcon: function(key, classname)
+ {
+ this.button.get(key).removeClass('re-' + classname);
+ },
+ setAwesome: function(key, name)
+ {
+ var $button = this.button.get(key);
+ $button.removeClass('redactor-btn-image').addClass('fa-redactor-btn');
+ $button.html('<i class="fa ' + name + '"></i>');
+ },
+ addCallback: function($btn, callback)
+ {
+ var type = (callback == 'dropdown') ? 'dropdown' : 'func';
+ var key = $btn.attr('rel');
+ $btn.on('touchstart click', $.proxy(function(e)
+ {
+ if ($btn.hasClass('redactor-button-disabled')) return false;
+ this.button.onClick(e, key, type, callback);
- },
- setFullpageOnInit: function(html)
- {
- html = this.cleanSavePreCode(html, true);
- html = this.cleanConverters(html);
- html = this.cleanEmpty(html);
+ }, this));
+ },
+ addDropdown: function($btn, dropdown)
+ {
+ var key = $btn.attr('rel');
+ this.button.addCallback($btn, 'dropdown');
- // set code
- this.$editor.html(html);
+ var $dropdown = $('<div class="redactor-dropdown redactor-dropdown-box-' + key + '" style="display: none;">');
+ $btn.data('dropdown', $dropdown);
- // set no editable
- this.setNonEditable();
- this.setSpansVerified();
- this.sync();
- },
- setSpansVerified: function()
- {
- var spans = this.$editor.find('span');
- var replacementTag = 'inline';
+ if (dropdown)
+ {
+ this.dropdown.build(key, $dropdown, dropdown);
+ }
- $.each(spans, function() {
- var outer = this.outerHTML;
+ return $dropdown;
+ },
+ add: function(key, title)
+ {
+ if (!this.opts.toolbar) return;
- // Replace opening tag
- var regex = new RegExp('<' + this.tagName, 'gi');
- var newTag = outer.replace(regex, '<' + replacementTag);
+ var btn = this.button.build(key, { title: title });
+ btn.addClass('redactor-btn-image');
- // Replace closing tag
- regex = new RegExp('</' + this.tagName, 'gi');
- newTag = newTag.replace(regex, '</' + replacementTag);
+ this.$toolbar.append($('<li>').append(btn));
- $(this).replaceWith(newTag);
- });
+ return btn;
+ },
+ addFirst: function(key, title)
+ {
+ if (!this.opts.toolbar) return;
- },
- setSpansVerifiedHtml: function(html)
- {
- html = html.replace(/<span(.*?)>/, '<inline$1>');
- return html.replace(/<\/span>/, '</inline>');
- },
- setNonEditable: function()
- {
- this.$editor.find('.noneditable').attr('contenteditable', false);
- },
+ var btn = this.button.build(key, { title: title });
+ this.$toolbar.prepend($('<li>').append(btn));
- // SYNC
- sync: function()
- {
- var html = '';
+ return btn;
+ },
+ addAfter: function(afterkey, key, title)
+ {
+ if (!this.opts.toolbar) return;
- this.cleanUnverified();
+ var btn = this.button.build(key, { title: title });
+ var $btn = this.button.get(afterkey);
- if (this.opts.fullpage) html = this.getCodeIframe();
- else html = this.$editor.html();
+ if ($btn.size() !== 0) $btn.parent().after($('<li>').append(btn));
+ else this.$toolbar.append($('<li>').append(btn));
- html = this.syncClean(html);
- //html = this.cleanRemoveSpaces(html);
- html = this.cleanRemoveEmptyTags(html);
+ return btn;
+ },
+ addBefore: function(beforekey, key, title)
+ {
+ if (!this.opts.toolbar) return;
- // fix second level up ul, ol
- html = html.replace(/<\/li><(ul|ol)>([\w\W]*?)<\/(ul|ol)>/gi, '<$1>$2</$1></li>');
+ var btn = this.button.build(key, { title: title });
+ var $btn = this.button.get(beforekey);
- if ($.trim(html) === '<br>') html = '';
+ if ($btn.size() !== 0) $btn.parent().before($('<li>').append(btn));
+ else this.$toolbar.append($('<li>').append(btn));
- // xhtml
- if (this.opts.xhtml)
- {
- var xhtmlTags = ['br', 'hr', 'img', 'link', 'input', 'meta'];
- $.each(xhtmlTags, function(i,s)
+ return btn;
+ },
+ remove: function(key)
{
- html = html.replace(new RegExp('<' + s + '(.*?[^\/$]?)>', 'gi'), '<' + s + '$1 />');
- });
+ this.button.get(key).remove();
+ }
+ };
+ },
+ dropdown: function()
+ {
+ return {
+ build: function(name, $dropdown, dropdownObject)
+ {
+ 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;
+ }
- }
+ s.type = (this.utils.isBlockTag(s.tag)) ? 'block' : 'inline';
+ var func = (s.type == 'inline') ? 'inline.formatting' : 'block.formatting';
- // before callback
- html = this.callback('syncBefore', false, html);
+ if (this.opts.linebreaks && s.type == 'block' && s.tag == 'p') return;
- this.$source.val(html);
+ this.formatting[name] = {
+ tag: s.tag,
+ style: s.style,
+ 'class': s.class,
+ attr: s.attr,
+ data: s.data
+ };
- // onchange & after callback
- this.callback('syncAfter', false, html);
+ dropdownObject[name] = {
+ func: func,
+ title: s.title
+ };
- if (this.start === false)
- {
- this.callback('change', false, html);
- }
+ }, this));
- },
- syncClean: function(html)
- {
- if (!this.opts.fullpage) html = this.cleanStripTags(html);
+ }
- html = $.trim(html);
+ $.each(dropdownObject, $.proxy(function(btnName, btnObject)
+ {
+ var $item = $('<a href="#" class="redactor-dropdown-' + btnName + '">' + btnObject.title + '</a>');
+ if (name == 'formatting') $item.addClass('redactor-formatting-' + btnName);
- // removeplaceholder
- html = this.placeholderRemoveFromCode(html);
+ $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;
+ }
- // remove space
- html = html.replace(/​/gi, '');
- html = html.replace(/​/gi, '');
- html = html.replace(/<\/a> /gi, '<\/a> ');
+ this.button.onClick(e, btnName, type, callback);
- // link nofollow
- if (this.opts.linkNofollow)
- {
- html = html.replace(/<a(.*?)rel="nofollow"(.*?)>/gi, '<a$1$2>');
- html = html.replace(/<a(.*?)>/gi, '<a$1 rel="nofollow">');
- }
+ }, this));
- // php code fix
- html = html.replace('<!--?php', '<?php');
- html = html.replace('?-->', '?>');
+ $dropdown.append($item);
- // revert no editable
- html = html.replace(/<(.*?)class="noeditable"(.*?) contenteditable="false"(.*?)>/gi, '<$1class="noeditable"$2$3>');
+ }, this));
+ },
+ show: function(e, key)
+ {
+ if (!this.opts.visual)
+ {
+ e.preventDefault();
+ return false;
+ }
- html = html.replace(/ data-tagblock=""/gi, '');
- html = html.replace(/<br\s?\/?>\n?<\/(P|H[1-6]|LI|ADDRESS|SECTION|HEADER|FOOTER|ASIDE|ARTICLE)>/gi, '</$1>');
+ var $button = this.button.get(key);
- // remove image resize
- html = html.replace(/<span(.*?)id="redactor-image-box"(.*?)>([\w\W]*?)<img(.*?)><\/span>/i, '$3<img$4>');
- html = html.replace(/<span(.*?)id="redactor-image-resizer"(.*?)>(.*?)<\/span>/i, '');
- html = html.replace(/<span(.*?)id="redactor-image-editter"(.*?)>(.*?)<\/span>/i, '');
+ // 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);
- // remove font
- html = html.replace(/<font(.*?)>([\w\W]*?)<\/font>/gi, '$2');
+ if ($button.hasClass('dropact'))
+ {
+ this.dropdown.hideAll();
+ }
+ else
+ {
+ this.dropdown.hideAll();
+ this.core.setCallback('dropdownShow', { dropdown: $dropdown, key: key, button: $button });
- // remove spans
- html = html.replace(/<span(.*?)>([\w\W]*?)<\/span>/gi, '$2');
- html = html.replace(/<inline>/gi, '<span>');
- html = html.replace(/<inline /gi, '<span ');
- html = html.replace(/<\/inline>/gi, '</span>');
- html = html.replace(/<span(.*?)class="redactor_placeholder"(.*?)>([\w\W]*?)<\/span>/gi, '');
- html = html.replace(/<span>([\w\W]*?)<\/span>/gi, '$1');
+ this.button.setActive(key);
- // fixes
- html = html.replace(/&/gi, '&');
- html = html.replace(/™/gi, '™');
- html = html.replace(/©/gi, '©');
+ $button.addClass('dropact');
+ var keyPosition = $button.offset();
- html = this.cleanReConvertProtected(html);
+ // fix right placement
+ var dropdownWidth = $dropdown.width();
+ if ((keyPosition.left + dropdownWidth) > $(document).width())
+ {
+ keyPosition.left -= dropdownWidth;
+ }
- return html;
- },
+ 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();
+ }
+ else
+ {
+ var top = ($button.innerHeight() + keyPosition.top) + 'px';
+ $dropdown.css({ position: 'absolute', left: left, top: top }).show();
+ }
- // BUILD
- buildStart: function()
- {
- // content
- this.content = '';
- // container
- this.$box = $('<div class="redactor_box" />');
+ this.core.setCallback('dropdownShown', { dropdown: $dropdown, key: key, button: $button });
+ }
- // textarea test
- if (this.$source[0].tagName === 'TEXTAREA') this.opts.textareamode = true;
+ $(document).one('click', $.proxy(this.dropdown.hide, this));
+ this.$editor.one('click', $.proxy(this.dropdown.hide, this));
- // mobile
- if (this.opts.mobile === false && this.isMobile())
- {
- this.buildMobile();
- }
- else
- {
- // get the content at the start
- this.buildContent();
+ $dropdown.on('mouseover', function() { $('html').css('overflow', 'hidden'); });
+ $dropdown.on('mouseout', function() { $('html').css('overflow', ''); });
- if (this.opts.iframe)
+ e.stopPropagation();
+ },
+ hideAll: function()
{
- // build as iframe
- this.opts.autoresize = false;
- this.iframeStart();
- }
- else if (this.opts.textareamode) this.buildFromTextarea();
- else this.buildFromElement();
+ this.$toolbar.find('a.dropact').removeClass('redactor-act').removeClass('dropact');
- // options and final setup
- if (!this.opts.iframe)
+ $('.redactor-dropdown').hide();
+ this.core.setCallback('dropdownHide');
+ },
+ hide: function (e)
{
- this.buildOptions();
- this.buildAfter();
+ var $dropdown = $(e.target);
+ if (!$dropdown.hasClass('dropact'))
+ {
+ $dropdown.removeClass('dropact');
+ this.dropdown.hideAll();
+ }
}
- }
+ };
},
- buildMobile: function()
+ code: function()
{
- if (!this.opts.textareamode)
- {
- this.$editor = this.$source;
- this.$editor.hide();
- this.$source = this.buildCodearea(this.$editor);
- this.$source.val(this.content);
- }
+ return {
+ set: function(html)
+ {
+ html = $.trim(html.toString());
- this.$box.insertAfter(this.$source).append(this.$source);
- },
- buildContent: function()
- {
- if (this.opts.textareamode) this.content = $.trim(this.$source.val());
- else this.content = $.trim(this.$source.html());
- },
- buildFromTextarea: function()
- {
- this.$editor = $('<div />');
- this.$box.insertAfter(this.$source).append(this.$editor).append(this.$source);
+ // clean
+ html = this.clean.onSet(html);
- // enable
- this.buildAddClasses(this.$editor);
- this.buildEnable();
- },
- buildFromElement: function()
- {
- this.$editor = this.$source;
- this.$source = this.buildCodearea(this.$editor);
- this.$box.insertAfter(this.$editor).append(this.$editor).append(this.$source);
+ this.$editor.html(html);
+ this.code.sync();
- // enable
- this.buildEnable();
- },
- buildCodearea: function($source)
- {
- return $('<textarea />').attr('name', $source.attr('id')).css('height', this.sourceHeight);
- },
- buildAddClasses: function(el)
- {
- // append textarea classes to editable layer
- $.each(this.$source.get(0).className.split(/\s+/), function(i,s)
- {
- el.addClass('redactor_' + s);
- });
- },
- buildEnable: function()
- {
- this.$editor.addClass('redactor_editor').attr({ 'contenteditable': true, 'dir': this.opts.direction });
- this.$source.attr('dir', this.opts.direction).hide();
+ setTimeout($.proxy(this.buffer.add, this), 15);
+ if (this.start === false) this.observe.load();
- // set code
- this.set(this.content, true, false);
- },
- buildOptions: function()
- {
- var $source = this.$editor;
- if (this.opts.iframe) $source = this.$frame;
+ },
+ get: function()
+ {
+ var code = this.$textarea.val();
- // options
- if (this.opts.tabindex) $source.attr('tabindex', this.opts.tabindex);
- if (this.opts.minHeight) $source.css('min-height', this.opts.minHeight + 'px');
- if (this.opts.maxHeight)
- {
- this.opts.autoresize = false;
- this.sourceHeight = this.opts.maxHeight;
- }
- if (this.opts.wym) this.$editor.addClass('redactor_editor_wym');
- if (!this.opts.autoresize) $source.css('height', this.sourceHeight);
- },
- buildAfter: function()
- {
- this.start = false;
+ // indent code
+ code = this.tabifier.get(code);
- // load toolbar
- if (this.opts.toolbar)
- {
- this.opts.toolbar = this.toolbarInit(this.opts.curLang);
- this.toolbarBuild();
- }
+ return code;
+ },
+ sync: function()
+ {
+ setTimeout($.proxy(this.code.startSync, this), 10);
+ },
+ startSync: function()
+ {
+ var html = this.$editor.html();
- // modal templates
- this.modalTemplatesInit();
+ // is there a need to synchronize
+ if (this.code.syncCode && this.code.syncCode == html)
+ {
+ // do not sync
+ return;
+ }
- // plugins
- this.buildPlugins();
+ // save code
+ this.code.syncCode = html;
- // enter, tab, etc.
- this.buildBindKeyboard();
+ // before clean callback
+ html = this.core.setCallback('syncBefore', html);
- // autosave
- if (this.opts.autosave) this.autosave();
+ // clean
+ html = this.clean.onSync(html);
- // observers
- setTimeout($.proxy(this.observeStart, this), 4);
+ // set code
+ this.$textarea.val(html);
- // FF fix
- if (this.browser('mozilla'))
- {
- try {
- this.document.execCommand('enableObjectResizing', false, false);
- this.document.execCommand('enableInlineTableEditing', false, false);
- } catch (e) {}
- }
+ // after sync callback
+ this.core.setCallback('sync', html);
- // focus
- if (this.opts.focus) setTimeout($.proxy(this.focus, this), 100);
+ if (this.start === false)
+ {
+ this.core.setCallback('change', html);
+ }
- // code mode
- if (!this.opts.visual)
- {
- setTimeout($.proxy(function()
+ this.start = false;
+
+ // autosave on change
+ this.autosave.onChange();
+ },
+ toggle: function()
{
- this.opts.visual = true;
- this.toggle(false);
+ if (this.opts.visual)
+ {
+ this.code.showCode();
+ }
+ else
+ {
+ this.code.showVisual();
+ }
+ },
+ showCode: function()
+ {
+ this.code.offset = this.caret.getOffset();
+ var scroll = $(window).scrollTop();
- }, this), 200);
- }
+ var height = this.$editor.innerHeight();
- // init callback
- this.callback('init');
- },
- buildBindKeyboard: function()
- {
- this.dblEnter = 0;
+ this.$editor.hide();
- if (this.opts.dragUpload && this.opts.imageUpload !== false)
- {
- this.$editor.on('drop.redactor', $.proxy(this.buildEventDrop, this));
- }
+ var html = this.$textarea.val();
+ this.modified = this.clean.removeSpaces(html);
- this.$editor.on('paste.redactor', $.proxy(this.buildEventPaste, this));
- this.$editor.on('keydown.redactor', $.proxy(this.buildEventKeydown, this));
- this.$editor.on('keyup.redactor', $.proxy(this.buildEventKeyup, this));
+ // indent code
+ html = this.tabifier.get(html);
- // textarea callback
- if ($.isFunction(this.opts.textareaKeydownCallback))
- {
- this.$source.on('keydown.redactor-textarea', $.proxy(this.opts.textareaKeydownCallback, this));
- }
+ this.$textarea.val(html).height(height).show().focus();
+ this.$textarea.on('keydown.redactor-textarea-indenting', this.code.textareaIndenting);
- // focus callback
- if ($.isFunction(this.opts.focusCallback))
- {
- this.$editor.on('focus.redactor', $.proxy(this.opts.focusCallback, this));
- }
+ $(window).scrollTop(scroll);
- var clickedElement;
- $(document).mousedown(function(e) {
- clickedElement = $(e.target);
- });
+ this.opts.visual = false;
- // blur callback
- this.$editor.on('blur.redactor', $.proxy(function(e)
- {
- if (!$(clickedElement).hasClass('redactor_toolbar') && $(clickedElement).parents('.redactor_toolbar').size() == 0)
+ this.button.setInactiveInCode();
+ this.button.setActive('html');
+ this.core.setCallback('source', html);
+ },
+ showVisual: function()
{
- this.selectall = false;
- if ($.isFunction(this.opts.blurCallback)) this.callback('blur', e);
- }
- }, this));
+ if (this.opts.visual) return;
- },
- buildEventDrop: function(e)
- {
- e = e.originalEvent || e;
+ var html = this.$textarea.hide().val();
- if (window.FormData === undefined || !e.dataTransfer) return true;
+ if (this.modified !== this.clean.removeSpaces(html))
+ {
+ this.code.set(html);
+ }
- var length = e.dataTransfer.files.length;
- if (length == 0) return true;
+ this.$editor.show();
- e.preventDefault();
+ if (!this.utils.isEmpty(html))
+ {
+ this.placeholder.remove();
+ }
- var file = e.dataTransfer.files[0];
+ this.caret.setOffset(this.code.offset);
- if (this.opts.dnbImageTypes !== false && this.opts.dnbImageTypes.indexOf(file.type) == -1)
- {
- return true;
- }
+ this.$textarea.off('keydown.redactor-textarea-indenting');
- this.bufferSet();
+ this.button.setActiveInVisual();
+ this.button.setInactive('html');
- var progress = $('<div id="redactor-progress-drag" class="redactor-progress redactor-progress-striped"><div id="redactor-progress-bar" class="redactor-progress-bar" style="width: 100%;"></div></div>');
- $(document.body).append(progress);
+ this.observe.load();
+ this.opts.visual = true;
+ },
+ textareaIndenting: function(e)
+ {
+ if (e.keyCode !== 9) return true;
- if (this.opts.s3 === false)
- {
- this.dragUploadAjax(this.opts.imageUpload, file, true, progress, e, this.opts.imageUploadParam);
- }
- else
- {
- this.s3uploadFile(file);
- }
+ 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;
+ }
+ };
},
- buildEventPaste: function(e)
+ clean: function()
{
- var oldsafari = false;
- if (this.browser('webkit') && navigator.userAgent.indexOf('Chrome') === -1)
- {
- var arr = this.browser('version').split('.');
- if (arr[0] < 536) oldsafari = true;
- }
+ return {
+ onSet: function(html)
+ {
+ html = this.clean.savePreCode(html);
- if (oldsafari) return true;
+ // replace dollar sign to entity
+ html = html.replace(/\$/g, '$');
+ html = html.replace(/”/g, '"');
+ html = html.replace(/‘/g, '\'');
+ html = html.replace(/’/g, '\'');
- // paste except opera (not webkit)
- if (this.browser('opera')) return true;
+ if (this.opts.replaceDivs) html = this.clean.replaceDivs(html);
+ if (this.opts.linebreaks) html = this.clean.replaceParagraphsToBr(html);
- // clipboard upload
- if (this.opts.clipboardUpload && this.buildEventClipboardUpload(e)) return true;
+ // save form tag
+ html = this.clean.saveFormTags(html);
- if (this.opts.cleanup)
- {
- this.rtePaste = true;
+ // convert font tag to span
+ html = html.replace(/<font(.*?)style="(.*?)"(.*?)>([\w\W]*?)<\/font>/gi, '<span style="$2">$4</span>');
- this.selectionSave();
+ // remove font tag
+ html = html.replace(/<font(.*?[^<])>/gi, '');
+ html = html.replace(/<\/font>/gi, '');
- if (!this.selectall)
- {
- if (this.opts.autoresize === true && this.fullscreen !== true)
- {
- this.$editor.height(this.$editor.height());
- this.saveScroll = this.document.body.scrollTop;
- }
- else
- {
- this.saveScroll = this.$editor.scrollTop();
- }
- }
+ // tidy html
+ html = this.tidy.load(html);
- var frag = this.extractContent();
+ // paragraphize
+ if (this.opts.paragraphize) html = this.paragraphize.load(html);
- setTimeout($.proxy(function()
- {
- var pastedFrag = this.extractContent();
- this.$editor.append(frag);
+ // verified
+ html = this.clean.setVerified(html);
- this.selectionRestore();
+ // convert inline tags
+ html = this.clean.convertInline(html);
- var html = this.getFragmentHtml(pastedFrag);
- this.pasteClean(html);
-
- if (this.opts.autoresize === true && this.fullscreen !== true) this.$editor.css('height', 'auto');
-
- }, this), 1);
- }
- },
- buildEventClipboardUpload: function(e)
- {
- var event = e.originalEvent || e;
- this.clipboardFilePaste = false;
-
- if (typeof(event.clipboardData) === 'undefined') return false;
- if (event.clipboardData.items)
- {
- var file = event.clipboardData.items[0].getAsFile();
- if (file !== null)
+ return html;
+ },
+ onSync: function(html)
{
- this.bufferSet();
- this.clipboardFilePaste = true;
+ // remove spaces
+ html = html.replace(/[\u200B-\u200D\uFEFF]/g, '');
+ html = html.replace(/​/gi, '');
+ html = html.replace(/ /gi, ' ');
- var reader = new FileReader();
- reader.onload = $.proxy(this.pasteClipboardUpload, this);
- reader.readAsDataURL(file);
+ if (html.search(/^<p>(||\s|| )<\/p>$/i) != -1)
+ {
+ return '';
+ }
- return true;
- }
- }
+ // restore form tag
+ html = this.clean.restoreFormTags(html);
- return false;
+ var chars = {
+ '\u2122': '™',
+ '\u00a9': '©',
+ '\u2026': '…',
+ '\u2014': '—',
+ '\u2010': '‐'
+ };
+ // replace special characters
+ $.each(chars, function(i,s)
+ {
+ html = html.replace(new RegExp(i, 'g'), s);
+ });
- },
- buildEventKeydown: function(e)
- {
- if (this.rtePaste) return false;
+ // 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>');
- var key = e.which;
- var ctrl = e.ctrlKey || e.metaKey;
- var parent = this.getParent();
- var current = this.getCurrent();
- var block = this.getBlock();
- var pre = false;
+ // remove verified
+ html = html.replace(new RegExp('<div(.*?) data-tagblock="redactor"(.*?[^>])>', 'gi'), '<div$1$2>');
+ html = html.replace(new RegExp('<(.*?) data-verified="redactor"(.*?[^>])>', 'gi'), '<$1$2>');
+ html = html.replace(new RegExp('<span(.*?) rel="(.*?)"(.*?[^>])>', 'gi'), '<span$1$3>');
+ html = html.replace(new RegExp('<img(.*?) rel="(.*?)"(.*?[^>])>', 'gi'), '<img$1$3>');
+ html = html.replace(new RegExp('<span class="redactor-invisible-space">(.*?)</span>', 'gi'), '$1');
+ html = html.replace(/ data-save-url="(.*?[^>])"/gi, '');
- this.callback('keydown', e);
+ // 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, '');
- this.imageResizeHide(false);
+ // tidy html
+ html = this.tidy.load(html);
- // pre & down
- if ((parent && $(parent).get(0).tagName === 'PRE') || (current && $(current).get(0).tagName === 'PRE'))
- {
- pre = true;
- if (key === this.keyCode.DOWN) this.insertAfterLastElement(block);
- }
+ // link nofollow
+ if (this.opts.linkNofollow)
+ {
+ html = html.replace(/<a(.*?)rel="nofollow"(.*?[^>])>/gi, '<a$1$2>');
+ html = html.replace(/<a(.*?[^>])>/gi, '<a$1 rel="nofollow">');
+ }
- // down
- if (key === this.keyCode.DOWN)
- {
- if (parent && $(parent)[0].tagName === 'BLOCKQUOTE') this.insertAfterLastElement(parent);
- if (current && $(current)[0].tagName === 'BLOCKQUOTE') this.insertAfterLastElement(current);
+ // reconvert inline
+ html = html.replace(/<(.*?) data-redactor-tag="(.*?)"(.*?[^>])>/gi, '<$1$3>');
+ html = html.replace(/<(.*?) data-redactor-class="(.*?)"(.*?[^>])>/gi, '<$1$3>');
+ html = html.replace(/<(.*?) data-redactor-style="(.*?)"(.*?[^>])>/gi, '<$1$3>');
+ html = html.replace(new RegExp('<(.*?) data-verified="redactor"(.*?[^>])>', 'gi'), '<$1$2>');
+ html = html.replace(new RegExp('<(.*?) data-verified="redactor">', 'gi'), '<$1>');
- if (parent && $(parent)[0].tagName === 'P' && $(parent).parent()[0].tagName == 'BLOCKQUOTE')
+ return html;
+ },
+ onPaste: function(html, setMode)
{
- this.insertAfterLastElement(parent, $(parent).parent()[0]);
- }
- if (current && $(current)[0].tagName === 'P' && parent && $(parent)[0].tagName == 'BLOCKQUOTE')
- {
- this.insertAfterLastElement(current, parent);
- }
- }
+ html = $.trim(html);
- // shortcuts setup
- if (ctrl && !e.shiftKey) this.shortcuts(e, key);
+ html = html.replace(/\$/g, '$');
+ html = html.replace(/”/g, '"');
+ html = html.replace(/“/g, '"');
+ html = html.replace(/‘/g, '\'');
+ html = html.replace(/’/g, '\'');
- // buffer setup
- if (ctrl && key === 90 && !e.shiftKey && !e.altKey) // z key
- {
- e.preventDefault();
- if (this.opts.buffer.length) this.bufferUndo();
- else this.document.execCommand('undo', false, false);
- return;
- }
- // undo
- else if (ctrl && key === 90 && e.shiftKey && !e.altKey)
- {
- e.preventDefault();
- if (this.opts.rebuffer.length != 0) this.bufferRedo();
- else this.document.execCommand('redo', false, false);
- return;
- }
+ // convert dirty spaces
+ html = html.replace(/<span class="Apple-converted-space"> <\/span>/gi, ' ');
+ html = html.replace(/<span class="Apple-tab-span"[^>]*>\t<\/span>/gi, '\t');
+ html = html.replace(/<span[^>]*>(\s| )<\/span>/gi, ' ');
- // select all
- if (ctrl && key === 65) this.selectall = true;
- else if (key != this.keyCode.LEFT_WIN && !ctrl) this.selectall = false;
+ if (this.opts.pastePlainText)
+ {
+ return this.clean.getPlainText(html);
+ }
- // enter
- if (key == this.keyCode.ENTER && !e.shiftKey && !e.ctrlKey && !e.metaKey )
- {
- // In ie, opera in the tables are created paragraphs, fix it.
- if (this.browser('msie') && (parent.nodeType == 1 && (parent.tagName == 'TD' || parent.tagName == 'TH')))
- {
- e.preventDefault();
- this.bufferSet();
- this.insertNode(document.createElement('br'));
- this.callback('enter', e);
- return false;
- }
-
- // blockquote exit
- if (block && (block.tagName == 'BLOCKQUOTE' || $(block).parent()[0].tagName == 'BLOCKQUOTE'))
- {
- if (this.isEndOfElement())
+ if (!this.utils.isSelectAll() && typeof setMode == 'undefined')
{
- if (this.dblEnter == 1)
+ if (this.utils.isCurrentOrParent(['FIGCAPTION', 'A']))
{
- var element;
- var last;
- if (block.tagName == 'BLOCKQUOTE')
- {
- last = 'br';
- element = block;
- }
- else
- {
- last = 'p';
- element = $(block).parent()[0];
- }
+ return this.clean.getPlainText(html, false);
+ }
- e.preventDefault();
- this.insertingAfterLastElement(element);
- this.dblEnter = 0;
-
- if (last == 'p')
- {
- $(block).parent().find('p').last().remove();
- }
- else
- {
- var tmp = $.trim($(block).html());
- $(block).html(tmp.replace(/<br\s?\/?>$/i, ''));
- }
-
- return;
+ if (this.utils.isCurrentOrParent('PRE'))
+ {
+ return this.clean.getPreCode(html);
}
- else this.dblEnter++;
- }
- else this.dblEnter++;
- }
- // pre
- if (pre === true) return this.buildEventKeydownPre(e, current);
- else
- {
- if (!this.opts.linebreaks)
- {
- // replace div to p
- if (block && this.opts.rBlockTest.test(block.tagName))
+ if (this.utils.isCurrentOrParent(['BLOCKQUOTE', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6']))
{
- // hit enter
- this.bufferSet();
+ html = this.clean.getOnlyImages(html);
- setTimeout($.proxy(function()
+ if (!this.utils.browser('msie'))
{
- var blockElem = this.getBlock();
- if (blockElem.tagName === 'DIV' && !$(blockElem).hasClass('redactor_editor'))
+ var block = this.selection.getBlock();
+ if (block && block.tagName == 'P')
{
- var node = $('<p>' + this.opts.invisibleSpace + '</p>');
- $(blockElem).replaceWith(node);
- this.selectionStart(node);
+ html = html.replace(/<img(.*?)>/gi, '<p><img$1></p>');
}
+ }
- }, this), 1);
+ return html;
}
- else if (block === false)
+
+ if (this.utils.isCurrentOrParent(['TD']))
{
- // hit enter
- this.bufferSet();
+ html = this.clean.onPasteTidy(html, 'td');
- var node = $('<p>' + this.opts.invisibleSpace + '</p>');
- this.insertNode(node[0]);
- this.selectionStart(node);
- this.callback('enter', e);
- return false;
- }
+ if (this.opts.linebreaks) html = this.clean.replaceParagraphsToBr(html);
- }
+ html = this.clean.replaceDivsToBr(html);
- if (this.opts.linebreaks)
- {
- // replace div to br
- if (block && this.opts.rBlockTest.test(block.tagName))
- {
- // hit enter
- this.bufferSet();
+ return html;
+ }
- setTimeout($.proxy(function()
- {
- var blockElem = this.getBlock();
- if ((blockElem.tagName === 'DIV' || blockElem.tagName === 'P') && !$(blockElem).hasClass('redactor_editor'))
- {
- this.replaceLineBreak(blockElem);
- }
- }, this), 1);
- }
- else
+ if (this.utils.isCurrentOrParent(['LI']))
{
- return this.buildEventKeydownInsertLineBreak(e);
+ return this.clean.onPasteTidy(html, 'li');
}
}
- // blockquote, figcaption
- if (block.tagName == 'BLOCKQUOTE' || block.tagName == 'FIGCAPTION')
+
+ html = this.clean.isSingleLine(html, setMode);
+
+ if (!this.clean.singleLine)
{
- return this.buildEventKeydownInsertLineBreak(e);
+ if (this.opts.linebreaks) html = this.clean.replaceParagraphsToBr(html);
+ 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);
- this.callback('enter', e);
- }
- else if (key === this.keyCode.ENTER && (e.ctrlKey || e.shiftKey)) // Shift+Enter or Ctrl+Enter
- {
- this.bufferSet();
+ html = this.clean.onPasteTidy(html, 'all');
- e.preventDefault();
- this.insertLineBreak();
- }
+ // paragraphize
+ if (!this.clean.singleLine && this.opts.paragraphize)
+ {
+ html = this.paragraphize.load(html);
+ }
- // tab
- if (key === this.keyCode.TAB && this.opts.shortcuts) return this.buildEventKeydownTab(e, pre);
+ html = this.clean.removeDirtyStyles(html);
+ html = this.clean.onPasteRemoveSpans(html);
+ html = this.clean.onPasteRemoveEmpty(html);
- // delete zero-width space before the removing
- if (key === this.keyCode.BACKSPACE) this.buildEventKeydownBackspace(current);
+ html = this.clean.convertInline(html);
- },
- buildEventKeydownPre: function(e, current)
- {
- e.preventDefault();
- this.bufferSet();
- var html = $(current).parent().text();
- this.insertNode(document.createTextNode('\n'));
- if (html.search(/\s$/) == -1)
- {
- this.insertNode(document.createTextNode('\n'));
- }
+ return html;
+ },
+ onPasteWord: function(html)
+ {
+ // comments
+ html = html.replace(/<!--[\s\S]*?-->/gi, '');
- this.sync();
- this.callback('enter', e);
- return false;
- },
- buildEventKeydownTab: function(e, pre)
- {
- if (!this.opts.tabFocus) return true;
- if (this.isEmpty(this.get()) && this.opts.tabSpaces === false) return true;
+ // style
+ html = html.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '');
- e.preventDefault();
+ // shapes
+ html = html.replace(/<img(.*?)v:shapes=(.*?)>/gi, '');
+ html = html.replace(/src="file\:\/\/(.*?)"/, 'src=""');
- if (pre === true && !e.shiftKey)
- {
- this.bufferSet();
- this.insertNode(document.createTextNode('\t'));
- this.sync();
- return false;
+ // 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');
- }
- else if (this.opts.tabSpaces !== false)
- {
- this.bufferSet();
- this.insertNode(document.createTextNode(Array(this.opts.tabSpaces + 1).join('\u00a0')));
- this.sync();
- return false;
- }
- else
- {
- if (!e.shiftKey) this.indentingIndent();
- else this.indentingOutdent();
- }
+ // classes
+ html = html.replace(/ class=\"(mso[^\"]*)\"/gi, "");
+ html = html.replace(/ class=(mso\w+)/gi, "");
- return false;
- },
- buildEventKeydownBackspace: function(current)
- {
- if (typeof current.tagName !== 'undefined' && /^(H[1-6])$/i.test(current.tagName))
- {
- var node;
- if (this.opts.linebreaks === false) node = $('<p>' + this.opts.invisibleSpace + '</p>');
- else node = $('<br>' + this.opts.invisibleSpace);
+ // remove ms word tags
+ html = html.replace(/<o:p(.*?)>([\w\W]*?)<\/o:p>/gi, '$2');
- $(current).replaceWith(node);
- this.selectionStart(node);
- }
+ // remove nbsp
+ if (this.opts.cleanSpaces)
+ {
+ html = html.replace(/(\s| )+/g, ' ');
+ }
- if (typeof current.nodeValue !== 'undefined' && current.nodeValue !== null)
- {
- //var value = $.trim(current.nodeValue.replace(/[^\u0000-\u1C7F]/g, ''));
- if (current.remove && current.nodeType === 3 && current.nodeValue.match(/[^/\u200B]/g) == null)
- {
- current.remove();
- }
- }
- },
- buildEventKeydownInsertLineBreak: function(e)
- {
- this.bufferSet();
- e.preventDefault();
- this.insertLineBreak();
- this.callback('enter', e);
- return;
- },
- buildEventKeyup: function(e)
- {
- if (this.rtePaste) return false;
+ // ms word break lines
+ html = html.replace(/\n/g, ' ');
- var key = e.which;
- var parent = this.getParent();
- var current = this.getCurrent();
+ // ms word lists break lines
+ html = html.replace(/<p>\n?<li>/gi, '<li>');
- // replace to p before / after the table or body
- if (!this.opts.linebreaks && current.nodeType == 3 && (parent == false || parent.tagName == 'BODY'))
- {
- var node = $('<p>').append($(current).clone());
- $(current).replaceWith(node);
- var next = $(node).next();
- if (typeof(next[0]) !== 'undefined' && next[0].tagName == 'BR')
+ return html;
+ },
+ onPasteExtra: function(html)
{
- next.remove();
- }
- this.selectionEnd(node);
- }
+ // remove google docs markers
+ html = html.replace(/<b\sid="internal-source-marker(.*?)">([\w\W]*?)<\/b>/gi, "$2");
+ html = html.replace(/<b(.*?)id="docs-internal-guid(.*?)">([\w\W]*?)<\/b>/gi, "$3");
- // convert links
- if ((this.opts.convertLinks || this.opts.convertImageLinks || this.opts.convertVideoLinks) && key === this.keyCode.ENTER)
- {
- this.buildEventKeyupConverters();
- }
+ // google docs styles
+ html = html.replace(/<span[^>]*(font-style: italic; font-weight: bold|font-weight: bold; font-style: italic)[^>]*>/gi, '<span style="font-weight: bold;"><span style="font-style: italic;">');
+ html = html.replace(/<span[^>]*font-style: italic[^>]*>/gi, '<span style="font-style: italic;">');
+ html = html.replace(/<span[^>]*font-weight: bold[^>]*>/gi, '<span style="font-weight: bold;">');
+ html = html.replace(/<span[^>]*text-decoration: underline[^>]*>/gi, '<span style="text-decoration: underline;">');
- // if empty
- if (key === this.keyCode.DELETE || key === this.keyCode.BACKSPACE)
- {
- return this.formatEmpty(e);
- }
+ html = html.replace(/<img>/gi, '');
+ html = html.replace(/\n{3,}/gi, '\n');
+ html = html.replace(/<font(.*?)>([\w\W]*?)<\/font>/gi, '$2');
- this.callback('keyup', e);
- this.sync();
- },
- buildEventKeyupConverters: function()
- {
- this.formatLinkify(this.opts.linkProtocol, this.opts.convertLinks, this.opts.convertImageLinks, this.opts.convertVideoLinks, this.opts.linkSize);
+ // remove dirty p
+ html = html.replace(/<p><p>/gi, '<p>');
+ html = html.replace(/<\/p><\/p>/gi, '</p>');
+ html = html.replace(/<li>(\s*|\t*|\n*)<p>/gi, '<li>');
+ html = html.replace(/<\/p>(\s*|\t*|\n*)<\/li>/gi, '</li>');
- setTimeout($.proxy(function()
- {
- if (this.opts.convertImageLinks) this.observeImages();
- if (this.opts.observeLinks) this.observeLinks();
- }, this), 5);
- },
- buildPlugins: function()
- {
- if (!this.opts.plugins ) return;
+ // remove space between paragraphs
+ html = html.replace(/<\/p>\s<p/gi, '<\/p><p');
- $.each(this.opts.plugins, $.proxy(function(i, s)
- {
- if (RedactorPlugins[s])
- {
- $.extend(this, RedactorPlugins[s]);
- if ($.isFunction( RedactorPlugins[ s ].init)) this.init();
- }
+ // remove safari local images
+ html = html.replace(/<img src="webkit-fake-url\:\/\/(.*?)"(.*?)>/gi, '');
- }, this ));
- },
+ // bullets
+ html = html.replace(/<p>•([\w\W]*?)<\/p>/gi, '<li>$1</li>');
- // IFRAME
- iframeStart: function()
- {
- this.iframeCreate();
+ // FF fix
+ if (this.utils.browser('mozilla'))
+ {
+ html = html.replace(/<br\s?\/?>$/gi, '');
+ }
- if (this.opts.textareamode) this.iframeAppend(this.$source);
- else
- {
- this.$sourceOld = this.$source.hide();
- this.$source = this.buildCodearea(this.$sourceOld);
- this.iframeAppend(this.$sourceOld);
- }
- },
- iframeAppend: function(el)
- {
- this.$source.attr('dir', this.opts.direction).hide();
- this.$box.insertAfter(el).append(this.$frame).append(this.$source);
- },
- iframeCreate: function()
- {
- this.$frame = $('<iframe style="width: 100%;" frameborder="0" />').one('load', $.proxy(function()
- {
- if (this.opts.fullpage)
+ return html;
+ },
+ onPasteTidy: function(html, type)
{
- this.iframePage();
+ // 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',
+ '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']],
+ ['video', '*'],
+ ['audio', '*'],
+ ['embed', '*'],
+ ['object', '*'],
+ ['param', '*'],
+ ['source', '*']
+ ];
- if (this.content === '') this.content = this.opts.invisibleSpace;
+ if (type == 'all')
+ {
+ tagsEmpty = ['p', 'span', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
+ attrAllowed = [
+ ['table', 'class'],
+ ['td', ['colspan', 'rowspan']],
+ ['a', '*'],
+ ['img', ['src', 'alt', 'data-redactor-inserted-image']],
+ ['span', ['class', 'rel', 'data-verified']],
+ ['video', '*'],
+ ['audio', '*'],
+ ['embed', '*'],
+ ['object', '*'],
+ ['param', '*'],
+ ['source', '*']
+ ];
+ }
+ 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'];
- this.$frame.contents()[0].write(this.content);
- this.$frame.contents()[0].close();
-
- var timer = setInterval($.proxy(function()
+ }
+ else if (type == 'li')
{
- if (this.$frame.contents().find('body').html())
- {
- clearInterval(timer);
- this.iframeLoad();
- }
+ // 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'];
+ }
- }, this), 0);
- }
- else this.iframeLoad();
+ var options = {
+ deniedTags: false,
+ allowedTags: tags,
+ removeComments: true,
+ removePhp: true,
+ removeAttr: false,
+ allowedAttr: attrAllowed,
+ removeEmpty: tagsEmpty
+ };
- }, this));
- },
- iframeDoc: function()
- {
- return this.$frame[0].contentWindow.document;
- },
- iframePage: function()
- {
- var doc = this.iframeDoc();
- if (doc.documentElement) doc.removeChild(doc.documentElement);
- return doc;
- },
- iframeAddCss: function(css)
- {
- css = css || this.opts.css;
+ return this.tidy.load(html, options);
- if (this.isString(css))
- {
- this.$frame.contents().find('head').append('<link rel="stylesheet" href="' + css + '" />');
- }
-
- if ($.isArray(css))
- {
- $.each(css, $.proxy(function(i, url)
+ },
+ onPasteRemoveEmpty: function(html)
{
- this.iframeAddCss(url);
+ html = html.replace(/<(p|h[1-6])>(|\s|\n|\t|<br\s?\/?>)<\/(p|h[1-6])>/gi, '');
- }, this));
- }
- },
- iframeLoad: function()
- {
- this.$editor = this.$frame.contents().find('body').attr({ 'contenteditable': true, 'dir': this.opts.direction });
+ // remove br in the end
+ if (!this.opts.linebreaks) html = html.replace(/<br>$/i, '');
- // set document & window
- if (this.$editor[0])
- {
- this.document = this.$editor[0].ownerDocument;
- this.window = this.document.defaultView || window;
- }
+ return html;
+ },
+ onPasteRemoveSpans: function(html)
+ {
+ html = html.replace(/<span>(.*?)<\/span>/gi, '$1');
+ html = html.replace(/<span[^>]*>\s| <\/span>/gi, ' ');
- // iframe css
- this.iframeAddCss();
+ return html;
+ },
+ onPasteIeFixLinks: function(html)
+ {
+ if (!this.utils.browser('msie')) return html;
- if (this.opts.fullpage) this.setFullpageOnInit(this.$editor.html());
- else this.set(this.content, true, false);
+ var tmp = $.trim(html);
+ if (tmp.search(/^<a(.*?)>(.*?)<\/a>$/i) === 0)
+ {
+ html = html.replace(/^<a(.*?)>(.*?)<\/a>$/i, "$2");
+ }
- this.buildOptions();
- this.buildAfter();
- },
-
- // PLACEHOLDER
- placeholderStart: function(html)
- {
- if (this.isEmpty(html))
- {
- if (this.$element.attr('placeholder')) this.opts.placeholder = this.$element.attr('placeholder');
- if (this.opts.placeholder === '') this.opts.placeholder = false;
-
- if (this.opts.placeholder !== false)
+ return html;
+ },
+ isSingleLine: function(html, setMode)
{
- this.opts.focus = false;
- this.$editor.one('focus.redactor_placeholder', $.proxy(this.placeholderFocus, this));
+ this.clean.singleLine = false;
- return $('<span class="redactor_placeholder" data-redactor="verified">').attr('contenteditable', false).text(this.opts.placeholder);
- }
- }
+ if (!this.utils.isSelectAll() && typeof setMode == 'undefined')
+ {
+ var blocks = this.opts.blockLevelElements.join('|').replace('P|', '').replace('DIV|', '');
- return false;
- },
- placeholderFocus: function()
- {
- this.$editor.find('span.redactor_placeholder').remove();
+ var matchBlocks = html.match(new RegExp('</(' + blocks + ')>', 'gi'));
+ var matchContainers = html.match(/<\/(p|div)>/gi);
- var html = '';
- if (this.opts.linebreaks === false) html = this.opts.emptyHtml;
+ if (!matchBlocks && (matchContainers === null || (matchContainers && matchContainers.length <= 1)))
+ {
+ var matchBR = html.match(/<br\s?\/?>/gi);
+ var matchIMG = html.match(/<img(.*?[^>])>/gi);
+ if (!matchBR && !matchIMG)
+ {
+ this.clean.singleLine = true;
+ html = html.replace(/<\/?(p|div)(.*?)>/gi, '');
+ }
+ }
+ }
- this.$editor.off('focus.redactor_placeholder');
- this.$editor.html(html);
+ return html;
+ },
+ stripTags: function(input, allowed)
+ {
+ allowed = (((allowed || '') + '').toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join('');
+ var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi;
- if (this.opts.linebreaks === false)
- {
- // place the cursor inside emptyHtml
- this.selectionStart(this.$editor.children()[0]);
- }
- else
- {
- this.focus();
- }
+ return input.replace(tags, function ($0, $1) {
+ return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : '';
+ });
+ },
+ savePreCode: function(html)
+ {
+ var pre = html.match(/<(pre|code)(.*?)>([\w\W]*?)<\/(pre|code)>/gi);
+ if (pre !== null)
+ {
+ $.each(pre, $.proxy(function(i,s)
+ {
+ var arr = s.match(/<(pre|code)(.*?)>([\w\W]*?)<\/(pre|code)>/i);
- this.sync();
- },
- placeholderRemove: function()
- {
- this.opts.placeholder = false;
- this.$editor.find('span.redactor_placeholder').remove();
- this.$editor.off('focus.redactor_placeholder');
- },
- placeholderRemoveFromCode: function(html)
- {
- return html.replace(/<span class="redactor_placeholder"(.*?)>(.*?)<\/span>/i, '');
- },
+ arr[3] = arr[3].replace(/<br\s?\/?>/g, '\n');
+ arr[3] = arr[3].replace(/ /g, ' ');
- // SHORTCUTS
- shortcuts: function(e, key)
- {
+ if (this.opts.preSpaces)
+ {
+ arr[3] = arr[3].replace(/\t/g, Array(this.opts.preSpaces + 1).join(' '));
+ }
- if (!this.opts.shortcuts) return;
+ arr[3] = this.clean.encodeEntities(arr[3]);
- if (!e.altKey)
- {
- if (key === 77) this.shortcutsLoad(e, 'removeFormat'); // Ctrl + m
- else if (key === 66) this.shortcutsLoad(e, 'bold'); // Ctrl + b
- else if (key === 73) this.shortcutsLoad(e, 'italic'); // Ctrl + i
+ // $ fix
+ arr[3] = arr[3].replace(/\$/g, '$');
- else if (key === 74) this.shortcutsLoad(e, 'insertunorderedlist'); // Ctrl + j
- else if (key === 75) this.shortcutsLoad(e, 'insertorderedlist'); // Ctrl + k
+ html = html.replace(s, '<' + arr[1] + arr[2] + '>' + arr[3] + '</' + arr[1] + '>');
- else if (key === 72) this.shortcutsLoad(e, 'superscript'); // Ctrl + h
- else if (key === 76) this.shortcutsLoad(e, 'subscript'); // Ctrl + l
- }
- else
- {
- if (key === 48) this.shortcutsLoadFormat(e, 'p'); // ctrl + alt + 0
- else if (key === 49) this.shortcutsLoadFormat(e, 'h1'); // ctrl + alt + 1
- else if (key === 50) this.shortcutsLoadFormat(e, 'h2'); // ctrl + alt + 2
- else if (key === 51) this.shortcutsLoadFormat(e, 'h3'); // ctrl + alt + 3
- else if (key === 52) this.shortcutsLoadFormat(e, 'h4'); // ctrl + alt + 4
- else if (key === 53) this.shortcutsLoadFormat(e, 'h5'); // ctrl + alt + 5
- else if (key === 54) this.shortcutsLoadFormat(e, 'h6'); // ctrl + alt + 6
+ }, this));
+ }
- }
+ return html;
+ },
+ getTextFromHtml: function(html)
+ {
+ html = html.replace(/<br\s?\/?>|<\/H[1-6]>|<\/p>|<\/div>|<\/li>|<\/td>/gi, '\n');
- },
- shortcutsLoad: function(e, cmd)
- {
- e.preventDefault();
- this.execCommand(cmd, false);
- },
- shortcutsLoadFormat: function(e, cmd)
- {
- e.preventDefault();
- this.formatBlocks(cmd);
- },
+ var tmp = document.createElement('div');
+ tmp.innerHTML = html;
+ html = tmp.textContent || tmp.innerText;
- // FOCUS
- focus: function()
- {
- if (!this.browser('opera')) this.window.setTimeout($.proxy(this.focusSet, this, true), 1);
- else this.$editor.focus();
- },
- focusEnd: function()
- {
- this.focusSet();
- },
- focusSet: function(collapse)
- {
- this.$editor.focus();
+ return $.trim(html);
+ },
+ getPlainText: function(html, paragraphize)
+ {
+ html = this.clean.getTextFromHtml(html);
+ html = html.replace(/\n/g, '<br />');
- var range = this.getRange();
- range.selectNodeContents(this.$editor[0]);
+ if (this.opts.paragraphize && typeof paragraphize == 'undefined')
+ {
+ html = this.paragraphize.load(html);
+ }
- // collapse - controls the position of focus: the beginning (true), at the end (false).
- range.collapse(collapse || false);
+ return html;
+ },
+ getPreCode: function(html)
+ {
+ html = html.replace(/<img(.*?) style="(.*?)"(.*?[^>])>/gi, '<img$1$3>');
+ html = html.replace(/<img(.*?)>/gi, '<img$1>');
+ html = this.clean.getTextFromHtml(html);
- var sel = this.getSelection();
- sel.removeAllRanges();
- sel.addRange(range);
- },
+ if (this.opts.preSpaces)
+ {
+ html = html.replace(/\t/g, Array(this.opts.preSpaces + 1).join(' '));
+ }
- // TOGGLE
- toggle: function(direct)
- {
- if (this.opts.visual) this.toggleCode(direct);
- else this.toggleVisual();
- },
- toggleVisual: function()
- {
- var html = this.$source.hide().val();
+ html = this.clean.encodeEntities(html);
- if (typeof this.modified !== 'undefined')
- {
- this.modified = this.cleanRemoveSpaces(this.modified, false) !== this.cleanRemoveSpaces(html, false);
- }
-
- if (this.modified)
- {
- // don't remove the iframe even if cleared all.
- if (this.opts.fullpage && html === '') this.setFullpageOnInit(html);
- else
+ return html;
+ },
+ getOnlyImages: function(html)
{
- this.set(html);
- if (this.opts.fullpage) this.buildBindKeyboard();
- }
- }
+ html = html.replace(/<img(.*?)>/gi, '[img$1]');
- if (this.opts.iframe) this.$frame.show();
- else this.$editor.show();
+ // remove all tags
+ html = html.replace(/<(.*?)>/gi, '');
- if (this.opts.fullpage) this.$editor.attr('contenteditable', true );
+ html = html.replace(/\[img(.*?)\]/gi, '<img$1>');
- this.$source.off('keydown.redactor-textarea-indenting');
+ return html;
+ },
+ getOnlyLinksAndImages: function(html)
+ {
+ html = html.replace(/<a(.*?)href="(.*?)"(.*?)>([\w\W]*?)<\/a>/gi, '[a href="$2"]$4[/a]');
+ html = html.replace(/<img(.*?)>/gi, '[img$1]');
- this.$editor.focus();
- this.selectionRestore();
+ // remove all tags
+ html = html.replace(/<(.*?)>/gi, '');
- this.observeStart();
- this.buttonActiveVisual();
- this.buttonInactive('html');
- this.opts.visual = true;
- },
- toggleCode: function(direct)
- {
- if (direct !== false) this.selectionSave();
+ html = html.replace(/\[a href="(.*?)"\]([\w\W]*?)\[\/a\]/gi, '<a href="$1">$2</a>');
+ html = html.replace(/\[img(.*?)\]/gi, '<img$1>');
- var height = null;
- if (this.opts.iframe)
- {
- height = this.$frame.height();
- if (this.opts.fullpage) this.$editor.removeAttr('contenteditable');
- this.$frame.hide();
- }
- else
- {
- height = this.$editor.innerHeight();
- this.$editor.hide();
- }
+ return html;
+ },
+ encodeEntities: function(str)
+ {
+ str = String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
+ return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
+ },
+ removeDirtyStyles: function(html)
+ {
+ if (this.utils.browser('msie')) return html;
- var html = this.$source.val();
+ var div = document.createElement('div');
+ div.innerHTML = html;
- // tidy html
- if (html !== '' && this.opts.tidyHtml)
- {
- this.$source.val(this.cleanHtml(html));
- }
+ this.clean.clearUnverifiedRemove($(div));
- this.modified = html;
+ html = div.innerHTML;
+ $(div).remove();
- this.$source.height(height).show().focus();
+ return html;
+ },
+ clearUnverified: function()
+ {
+ if (this.utils.browser('msie')) return;
- // textarea indenting
- this.$source.on('keydown.redactor-textarea-indenting', this.textareaIndenting);
+ this.clean.clearUnverifiedRemove(this.$editor);
- this.buttonInactiveVisual();
- this.buttonActive('html');
- this.opts.visual = false;
- },
- textareaIndenting: function(e)
- {
- if (e.keyCode === 9)
- {
- var $el = $(this);
- 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;
- }
- },
+ var headers = this.$editor.find('h1, h2, h3, h4, h5, h6');
+ headers.find('span').removeAttr('style');
+ headers.find(this.opts.verifiedTags.join(', ')).removeAttr('style');
- // AUTOSAVE
- autosave: function()
- {
- var savedHtml = false;
- this.autosaveInterval = setInterval($.proxy(function()
- {
- var html = this.get();
- if (savedHtml !== html)
+ this.code.sync();
+ },
+ clearUnverifiedRemove: function($editor)
{
- $.ajax({
- url: this.opts.autosave,
- type: 'post',
- data: this.$source.attr('name') + '=' + escape(encodeURIComponent(html)),
- success: $.proxy(function(data)
- {
- this.callback('autosave', false, data);
- savedHtml = html;
+ $editor.find(this.opts.verifiedTags.join(', ')).removeAttr('style');
+ $editor.find('span').not('[data-verified="redactor"]').removeAttr('style');
- }, this)
+ $editor.find('span[data-verified="redactor"], img[data-verified="redactor"]').each(function(i, s)
+ {
+ var $s = $(s);
+ $s.attr('style', $s.attr('rel'));
});
- }
- }, this), this.opts.autosaveInterval*1000);
- },
- // TOOLBAR
- toolbarBuild: function()
- {
- // extend buttons
- if (this.opts.air)
- {
- this.opts.buttons = this.opts.airButtons;
- }
- else
- {
- if (!this.opts.buttonSource)
+ },
+ setVerified: function(html)
{
- var index = this.opts.buttons.indexOf('html'), next = this.opts.buttons[index + 1];
- this.opts.buttons.splice(index, 1);
- if (next === '|') this.opts.buttons.splice(index, 1);
- }
- }
+ if (this.utils.browser('msie')) return html;
- $.extend(this.opts.toolbar, this.opts.buttonsCustom);
- $.each(this.opts.buttonsAdd, $.proxy(function(i, s)
- {
- this.opts.buttons.push(s);
+ html = html.replace(new RegExp('<img(.*?[^>])>', 'gi'), '<img$1 data-verified="redactor">');
+ html = html.replace(new RegExp('<span(.*?)>', 'gi'), '<span$1 data-verified="redactor">');
- }, this));
+ var matches = html.match(new RegExp('<(span|img)(.*?)style="(.*?)"(.*?[^>])>', 'gi'));
+ 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);
+ }
+ }
- // formatting tags
- if (this.opts.toolbar)
- {
- $.each(this.opts.toolbar.formatting.dropdown, $.proxy(function (i, s)
+ return html;
+ },
+ convertInline: function(html)
{
- if ($.inArray(i, this.opts.formattingTags ) == '-1') delete this.opts.toolbar.formatting.dropdown[i];
+ var $div = $('<div />').html(html);
- }, this));
- }
+ var tags = this.opts.inlineTags;
+ tags.push('span');
- // if no buttons don't create a toolbar
- if (this.opts.buttons.length === 0) return false;
+ $div.find(tags.join(',')).each(function()
+ {
+ var $el = $(this);
+ var tag = this.tagName.toLowerCase();
+ $el.attr('data-redactor-tag', tag);
- // air enable
- this.airEnable();
+ if (tag == 'span')
+ {
+ if ($el.attr('style')) $el.attr('data-redactor-style', $el.attr('style'));
+ else if ($el.attr('class')) $el.attr('data-redactor-class', $el.attr('class'));
+ }
- // toolbar build
- this.$toolbar = $('<ul>').addClass('redactor_toolbar').attr('id', 'redactor_toolbar_' + this.uuid);
+ });
- if (this.opts.air)
- {
- // air box
- this.$air = $('<div class="redactor_air">').attr('id', 'redactor_air_' + this.uuid).hide();
- this.$air.append(this.$toolbar);
- $('body').append(this.$air);
- }
- else
- {
- if (this.opts.toolbarExternal) $(this.opts.toolbarExternal).html(this.$toolbar);
- else this.$box.prepend(this.$toolbar);
- }
+ html = $div.html();
+ $div.remove();
- $.each(this.opts.buttons, $.proxy(function(i, btnName)
- {
- // separator
- if ( btnName === '|' ) this.$toolbar.append($(this.opts.buttonSeparator));
- else if(this.opts.toolbar[btnName])
+ return html;
+ },
+ normalizeLists: function()
{
- var btnObject = this.opts.toolbar[btnName];
- if (this.opts.fileUpload === false && btnName === 'file') return true;
- this.$toolbar.append( $('<li>').append(this.buttonBuild(btnName, btnObject)));
- }
+ this.$editor.find('li').each(function(i,s)
+ {
+ var $next = $(s).next();
+ if ($next.length !== 0 && ($next[0].tagName == 'UL' || $next[0].tagName == 'OL'))
+ {
+ $(s).append($next);
+ }
- }, this));
+ });
+ },
+ removeSpaces: function(html)
+ {
+ html = html.replace(/\n/g, '');
+ html = html.replace(/[\t]*/g, '');
+ html = html.replace(/\n\s*\n/g, "\n");
+ html = html.replace(/^[\s\n]*/g, ' ');
+ html = html.replace(/[\s\n]*$/g, ' ');
+ html = html.replace( />\s{2,}</g, '> <'); // between inline tags can be only one space
+ html = html.replace(/\n\n/g, "\n");
+ html = html.replace(/[\u200B-\u200D\uFEFF]/g, '');
- this.$toolbar.find('a').attr('tabindex', '-1');
+ return html;
+ },
+ replaceDivs: function(html)
+ {
+ if (this.opts.linebreaks)
+ {
+ html = html.replace(/<div><br\s?\/?><\/div>/gi, '<br />');
+ html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '$2<br />');
+ }
+ else
+ {
+ html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '<p$1>$2</p>');
+ }
- // fixed
- if (this.opts.toolbarFixed)
- {
- this.toolbarObserveScroll();
- $(this.opts.toolbarFixedTarget).on('scroll.redactor', $.proxy(this.toolbarObserveScroll, this));
- }
+ return html;
+ },
+ replaceDivsToBr: function(html)
+ {
+ html = html.replace(/<div\s(.*?)>/gi, '<p>');
+ html = html.replace(/<div><br\s?\/?><\/div>/gi, '<br /><br />');
+ html = html.replace(/<div>([\w\W]*?)<\/div>/gi, '$1<br /><br />');
- // buttons response
- if (this.opts.activeButtons)
- {
- var buttonActiveObserver = $.proxy(this.buttonActiveObserver, this);
- this.$editor.on('mouseup.redactor keyup.redactor', buttonActiveObserver);
- }
+ return html;
+ },
+ replaceParagraphsToBr: function(html)
+ {
+ html = html.replace(/<p\s(.*?)>/gi, '<p>');
+ html = html.replace(/<p><br\s?\/?><\/p>/gi, '<br />');
+ html = html.replace(/<p>([\w\W]*?)<\/p>/gi, '$1<br /><br />');
+ html = html.replace(/(<br\s?\/?>){1,}\n?<\/blockquote>/gi, '</blockquote>');
+
+ return html;
+ },
+ saveFormTags: function(html)
+ {
+ return html.replace(/<form(.*?)>([\w\W]*?)<\/form>/gi, '<section$1 rel="redactor-form-tag">$2</section>');
+ },
+ restoreFormTags: function(html)
+ {
+ return html.replace(/<section(.*?) rel="redactor-form-tag"(.*?)>([\w\W]*?)<\/section>/gi, '<form$1$2>$3</form>');
+ }
+ };
},
- toolbarObserveScroll: function()
+ tidy: function()
{
- var scrollTop = $(this.opts.toolbarFixedTarget).scrollTop();
- var boxTop = this.$box.offset().top;
- var left = 0;
+ return {
+ setupAllowed: function()
+ {
+ if (this.opts.allowedTags) this.opts.deniedTags = false;
+ if (this.opts.allowedAttr) this.opts.removeAttr = false;
- var end = boxTop + this.$box.height() + 40;
+ if (this.opts.linebreaks) return;
- if (scrollTop > boxTop)
- {
- var width = '100%';
- if (this.opts.toolbarFixedBox)
+ var tags = ['p', 'section'];
+ if (this.opts.allowedTags) this.tidy.addToAllowed(tags);
+ if (this.opts.deniedTags) this.tidy.removeFromDenied(tags);
+
+ },
+ addToAllowed: function(tags)
{
- left = this.$box.offset().left;
- width = this.$box.innerWidth();
- this.$toolbar.addClass('toolbar_fixed_box');
- }
+ 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]);
+ }
+ }
+ },
+ removeFromDenied: function(tags)
+ {
+ 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);
+ }
+ }
+ },
+ load: function(html, options)
+ {
+ 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
+ };
- this.toolbarFixed = true;
- this.$toolbar.css({
- position: 'fixed',
- width: width,
- zIndex: 1005,
- top: this.opts.toolbarFixedTopOffset + 'px',
- left: left
- });
+ $.extend(this.tidy.settings, options);
- if (scrollTop < end) this.$toolbar.css('visibility', 'visible');
- else this.$toolbar.css('visibility', 'hidden');
- }
- else
- {
- this.toolbarFixed = false;
- this.$toolbar.css({
- position: 'relative',
- width: 'auto',
- top: 0,
- left: left
- });
+ html = this.tidy.removeComments(html);
+ html = this.tidy.replaceTags(html);
- if (this.opts.toolbarFixedBox) this.$toolbar.removeClass('toolbar_fixed_box');
- }
- },
+ // create container
+ this.tidy.$div = $('<div />').append(html);
- // AIR
- airEnable: function()
- {
- if (!this.opts.air) return;
+ // clean
+ this.tidy.replaceStyles();
+ this.tidy.removeTags();
- this.$editor.on('mouseup.redactor keyup.redactor', this, $.proxy(function(e)
- {
- var text = this.getSelectionText();
+ this.tidy.removeAttr();
+ this.tidy.removeEmpty();
+ this.tidy.removeParagraphsInLists();
+ this.tidy.removeDataAttr();
+ this.tidy.removeWithoutAttr();
- if (e.type === 'mouseup' && text != '') this.airShow(e);
- if (e.type === 'keyup' && e.shiftKey && text != '')
+ html = this.tidy.$div.html();
+ this.tidy.$div.remove();
+
+ return html;
+ },
+ removeComments: function(html)
{
- var $focusElem = $(this.getElement(this.getSelection().focusNode)), offset = $focusElem.offset();
- offset.height = $focusElem.height();
- this.airShow(offset, true);
- }
+ if (!this.tidy.settings.removeComments) return html;
- }, this));
- },
- airShow: function (e, keyboard)
- {
- if (!this.opts.air) return;
+ return html.replace(/<!--[\s\S]*?-->/gi, '');
+ },
+ replaceTags: function(html)
+ {
+ if (!this.tidy.settings.replaceTags) return html;
- var left, top;
- $('.redactor_air').hide();
+ var len = this.tidy.settings.replaceTags.length;
+ for (var i = 0; i < len; i++)
+ {
+ var re = new RegExp('<' + this.tidy.settings.replaceTags[i][0] + '(.*?[^>])>', 'gi');
+ html = html.replace(re, '<' + this.tidy.settings.replaceTags[i][1] + '$1>');
- if (keyboard)
- {
- left = e.left;
- top = e.top + e.height + 14;
+ re = new RegExp('</' + this.tidy.settings.replaceTags[i][0] + '>', 'gi');
+ html = html.replace(re, '</' + this.tidy.settings.replaceTags[i][1] + '>');
+ }
- if (this.opts.iframe)
- {
- top += this.$box.position().top - $(this.document).scrollTop();
- left += this.$box.position().left;
- }
- }
- else
- {
- var width = this.$air.innerWidth();
- left = e.clientX;
- if ($(this.document).width() < (left + width)) left -= width;
-
- top = e.clientY + 14;
- if (this.opts.iframe)
+ return html;
+ },
+ replaceStyles: function()
{
- top += this.$box.position().top;
- left += this.$box.position().left;
- }
- else top += $( this.document ).scrollTop();
- }
+ if (!this.tidy.settings.replaceStyles) return;
- this.$air.css({
- left: left + 'px',
- top: top + 'px'
- }).show();
+ var len = this.tidy.settings.replaceStyles.length;
+ this.tidy.$div.find('span').each($.proxy(function(n,s)
+ {
+ 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.airBindHide();
- },
- airBindHide: function()
- {
- if (!this.opts.air) return;
+ }, this));
- var hideHandler = $.proxy(function(doc)
- {
- $(doc).on('mousedown.redactor', $.proxy(function(e)
+ },
+ removeTags: function()
{
- if ($( e.target ).closest(this.$toolbar).length === 0)
+ if (!this.tidy.settings.deniedTags && this.tidy.settings.allowedTags)
{
- this.$air.fadeOut(100);
- this.selectionRemove();
- $(doc).off(e);
+ this.tidy.$div.find('*').not(this.tidy.settings.allowedTags.join(',')).contents().unwrap();
}
- }, this)).on('keydown.redactor', $.proxy(function(e)
- {
- if (e.which === this.keyCode.ESC)
+ if (this.tidy.settings.deniedTags)
{
- this.getSelection().collapseToStart();
+ this.tidy.$div.find(this.tidy.settings.deniedTags.join(',')).contents().unwrap();
}
+ },
+ removeAttr: function()
+ {
+ var len;
+ if (!this.tidy.settings.removeAttr && this.tidy.settings.allowedAttr)
+ {
- this.$air.fadeOut(100);
- $(doc).off(e);
+ 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]);
+ }
- }, this));
- }, this);
- // Hide the toolbar at events in all documents (iframe)
- hideHandler(document);
- if (this.opts.iframe) hideHandler(this.document);
- },
- airBindMousemoveHide: function()
- {
- if (!this.opts.air) return;
+ 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 hideHandler = $.proxy(function(doc)
- {
- $(doc).on('mousemove.redactor', $.proxy(function(e)
- {
- if ($( e.target ).closest(this.$toolbar).length === 0)
+ if (attributesRemove)
+ {
+ $.each(attributesRemove, function(z,f) {
+ $el.removeAttr(f);
+ });
+ }
+ }, this));
+ }
+
+ if (this.tidy.settings.removeAttr)
{
- this.$air.fadeOut(100);
- $(doc).off(e);
+ 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.tidy.$div.find(this.tidy.settings.removeAttr[i][0]).removeAttr(attrs);
+ }
}
- }, this));
- }, this);
+ },
+ removeAttrGetRemoves: function(pos, allowed, $el)
+ {
+ var attributesRemove = [];
- // Hide the toolbar at events in all documents (iframe)
- hideHandler(document);
- if (this.opts.iframe) hideHandler(this.document);
- },
+ // remove all attrs
+ if (pos == -1)
+ {
+ $.each($el[0].attributes, function(i, item)
+ {
+ attributesRemove.push(item.name);
+ });
- // DROPDOWNS
- dropdownBuild: function($dropdown, dropdownObject)
- {
- $.each(dropdownObject, $.proxy(function(btnName, btnObject)
- {
- if (!btnObject.className) btnObject.className = '';
+ }
+ // allow all attrs
+ else if (allowed[pos] == '*')
+ {
+ attributesRemove = [];
+ }
+ // 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);
+ }
- var $item;
- if (btnObject.name === 'separator') $item = $('<a class="redactor_separator_drop">');
- else
+ });
+ }
+
+ return attributesRemove;
+ },
+ removeAttrs: function (el, regex)
{
- $item = $('<a href="#" class="' + btnObject.className + ' redactor_dropdown_' + btnName + '">' + btnObject.title + '</a>');
- $item.on('click', $.proxy(function(e)
+ regex = new RegExp(regex, "g");
+ return el.each(function()
{
- if (e.preventDefault) e.preventDefault();
- if (this.browser('msie')) e.returnValue = false;
+ 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;
- if (btnObject.callback) btnObject.callback.call(this, btnName, $item, btnObject, e);
- if (btnObject.exec) this.execCommand(btnObject.exec, btnName);
- if (btnObject.func) this[btnObject.func](btnName);
+ this.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.buttonActiveObserver();
- if (this.opts.air) this.$air.fadeOut(100);
+ 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));
- }
+ var tags = this.tidy.settings.removeDataAttr;
+ if ($.isArray(this.tidy.settings.removeDataAttr)) tags = this.tidy.settings.removeDataAttr.join(',');
- $dropdown.append($item);
+ this.tidy.removeAttrs(this.tidy.$div.find(tags), '^(data-)');
- }, this));
+ },
+ removeWithoutAttr: function()
+ {
+ if (!this.tidy.settings.removeWithoutAttr) return;
+
+ this.tidy.$div.find(this.tidy.settings.removeWithoutAttr.join(',')).each(function()
+ {
+ if (this.attributes.length === 0)
+ {
+ $(this).contents().unwrap();
+ }
+ });
+ }
+ };
},
- dropdownShow: function(e, key)
+ paragraphize: function()
{
- if (!this.opts.visual)
- {
- e.preventDefault();
- return false;
- }
+ return {
+ load: function(html)
+ {
+ if (this.opts.linebreaks) return html;
+ if (html === '' || html === '<p></p>') return this.opts.emptyHtml;
- var $dropdown = this.$toolbar.find('.redactor_dropdown_box_' + key);
- var $button = this.buttonGet(key);
+ 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'];
- if ($button.hasClass('dropact')) this.dropdownHideAll();
- else
- {
- this.dropdownHideAll();
+ html = html + "\n";
- this.buttonActive(key);
- $button.addClass('dropact');
+ this.paragraphize.safes = [];
+ this.paragraphize.z = 0;
- var keyPosition = $button.position();
- if (this.toolbarFixed)
- {
- keyPosition = $button.offset();
- }
+ html = html.replace(/(<br\s?\/?>){1,}\n?<\/blockquote>/gi, '</blockquote>');
- // fix right placement
- var dropdownWidth = $dropdown.width();
- if ((keyPosition.left + dropdownWidth) > $(document).width())
+ 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);
+ },
+ getSafes: function(html)
{
- keyPosition.left -= dropdownWidth;
- }
+ var $div = $('<div />').append(html);
- var left = keyPosition.left + 'px';
- var btnHeight = 29;
+ // remove paragraphs in blockquotes
+ $div.find('blockquote p').replaceWith(function()
+ {
+ return $(this).append('<br />').contents();
+ });
- var position = 'absolute';
- var top = btnHeight + 'px';
+ html = $div.html();
- if (this.opts.toolbarFixed && this.toolbarFixed) position = 'fixed';
- else if (!this.opts.air) top = keyPosition.top + btnHeight + 'px';
+ $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 + '}');
- $dropdown.css({ position: position, left: left, bottom: top }).show();
- }
+ }, this));
+ return html;
+ },
+ getSafesComments: function(html)
+ {
+ var commentsMatches = html.match(/<!--([\w\W]*?)-->/gi);
- var hdlHideDropDown = $.proxy(function(e)
- {
- this.dropdownHide(e, $dropdown);
+ if (!commentsMatches) return html;
- }, this);
+ $.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));
- $(document).one('click', hdlHideDropDown);
- this.$editor.one('click', hdlHideDropDown);
+ return html;
+ },
+ restoreSafes: function(html)
+ {
+ $.each(this.paragraphize.safes, function(i,s)
+ {
+ html = html.replace('{replace' + i + '}', s);
+ });
- e.stopPropagation();
- this.$editor.focus();
- },
- dropdownHideAll: function()
- {
- this.$toolbar.find('a.dropact').removeClass('redactor_act').removeClass('dropact');
- $('.redactor_dropdown').hide();
- },
- dropdownHide: function (e, $dropdown)
- {
- if (!$(e.target).hasClass('dropact'))
- {
- $dropdown.removeClass('dropact');
- this.dropdownHideAll();
- }
- },
+ return html;
+ },
+ replaceBreaksToParagraphs: function(html)
+ {
+ var htmls = html.split(new RegExp('\n', 'g'), -1);
- // BUTTONS
- buttonBuild: function(btnName, btnObject)
- {
- var $button = $('<a href="javascript:;" title="' + btnObject.title + '" tabindex="-1" class="redactor_btn redactor_btn_' + btnName + '"></a>');
+ html = '';
+ if (htmls)
+ {
+ var len = htmls.length;
+ for (var i = 0; i < len; i++)
+ {
+ if (!htmls.hasOwnProperty(i)) return;
- $button.on('click', $.proxy(function(e)
- {
- if (e.preventDefault) e.preventDefault();
- if (this.browser('msie')) e.returnValue = false;
+ if (htmls[i].search('{replace') == -1)
+ {
+ htmls[i] = htmls[i].replace(/<p>\n\t?<\/p>/gi, '');
+ htmls[i] = htmls[i].replace(/<p><\/p>/gi, '');
- if ($button.hasClass('redactor_button_disabled')) return false;
+ if (htmls[i] !== '')
+ {
+ html += '<p>' + htmls[i].replace(/^\n+|\n+$/g, "") + "</p>";
+ }
+ }
+ else html += htmls[i];
+ }
+ }
- if (this.isFocused() === false && !btnObject.exec)
+ return html;
+ },
+ replaceBreaksToNewLines: function(html)
{
- this.$editor.focus();
- }
+ html = html.replace(/<br \/>\s*<br \/>/gi, "\n\n");
+ html = html.replace(/<br\s?\/?>\n?<br\s?\/?>/gi, "\n<br /><br />");
- if (btnObject.exec)
- {
- this.$editor.focus();
- this.execCommand(btnObject.exec, btnName);
- this.airBindMousemoveHide();
+ 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");
- }
- else if (btnObject.func && btnObject.func !== 'show')
+ return html;
+ },
+ clear: function(html)
{
- this[btnObject.func](btnName);
- this.airBindMousemoveHide();
+ 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>');
- }
- else if (btnObject.callback)
- {
- btnObject.callback.call(this, btnName, $button, btnObject, e);
- this.airBindMousemoveHide();
+ html = html.replace(new RegExp('<p><p ', 'gi'), '<p ');
+ html = html.replace(new RegExp('<p><p>', 'gi'), '<p>');
+ html = html.replace(new RegExp('</p></p>', 'gi'), '</p>');
+ html = html.replace(new RegExp('<p>\\s?</p>', 'gi'), '');
+ html = html.replace(new RegExp("\n</p>", 'gi'), '</p>');
+ html = html.replace(new RegExp('<p>\t?\t?\n?<p>', 'gi'), '<p>');
+ html = html.replace(new RegExp('<p>\t*</p>', 'gi'), '');
+ return html;
}
- else if (btnObject.dropdown)
+ };
+ },
+ tabifier: function()
+ {
+ return {
+ get: function(code)
{
- this.dropdownShow(e, btnName);
- }
+ if (!this.opts.tabifier) return code;
- this.buttonActiveObserver(false, btnName);
+ // 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));
+ 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('|' ) + ')[ >]');
- // dropdown
- if (btnObject.dropdown)
- {
- var $dropdown = $('<div class="redactor_dropdown redactor_dropdown_box_' + btnName + '" style="display: none;">');
- $dropdown.appendTo(this.$toolbar);
- this.dropdownBuild($dropdown, btnObject.dropdown);
- }
+ var i = 0,
+ codeLength = code.length,
+ point = 0,
+ start = null,
+ end = null,
+ tag = '',
+ out = '',
+ cont = '';
- return $button;
- },
- buttonGet: function(key)
- {
- if (!this.opts.toolbar) return false;
- return $(this.$toolbar.find('a.redactor_btn_' + key));
- },
- buttonActiveToggle: function(key)
- {
- var btn = this.buttonGet(key);
+ this.tabifier.cleanlevel = 0;
- if (btn.hasClass('redactor_act')) btn.removeClass('redactor_act');
- else btn.addClass('redactor_act');
- },
- buttonActive: function(key)
- {
- this.buttonGet(key).addClass('redactor_act');
- },
- buttonInactive: function(key)
- {
- this.buttonGet(key).removeClass('redactor_act');
- },
- buttonInactiveAll: function(btnName)
- {
- $.each(this.opts.toolbar, $.proxy(function(k)
- {
- if (k != btnName) this.buttonInactive(k);
+ for (; i < codeLength; i++)
+ {
+ point = i;
- }, this));
- },
- buttonActiveVisual: function()
- {
- this.$toolbar.find('a.redactor_btn').not('a.redactor_btn_html').removeClass('redactor_button_disabled');
- },
- buttonInactiveVisual: function()
- {
- this.$toolbar.find('a.redactor_btn').not('a.redactor_btn_html').addClass('redactor_button_disabled');
- },
- buttonChangeIcon: function (key, classname)
- {
- this.buttonGet(key).addClass('redactor_btn_' + classname);
- },
- buttonRemoveIcon: function(key, classname)
- {
- this.buttonGet(key).removeClass('redactor_btn_' + classname);
- },
- buttonAddSeparator: function()
- {
- this.$toolbar.append($(this.opts.buttonSeparator));
- },
- buttonAddSeparatorAfter: function(key)
- {
- this.buttonGet(key).parent().after($(this.opts.buttonSeparator));
- },
- buttonAddSeparatorBefore: function(key)
- {
- this.buttonGet(key).parent().before($(this.opts.buttonSeparator));
- },
- buttonRemoveSeparatorAfter: function(key)
- {
- this.buttonGet(key).parent().next().remove();
- },
- buttonRemoveSeparatorBefore: function(key)
- {
- this.buttonGet(key).parent().prev().remove();
- },
- buttonSetRight: function(key)
- {
- if (!this.opts.toolbar) return;
- this.buttonGet(key).parent().addClass('redactor_btn_right');
- },
- buttonSetLeft: function(key)
- {
- if (!this.opts.toolbar) return;
- this.buttonGet(key).parent().removeClass('redactor_btn_right');
- },
- buttonAdd: function(key, title, callback, dropdown)
- {
- if (!this.opts.toolbar) return;
- var btn = this.buttonBuild(key, { title: title, callback: callback, dropdown: dropdown });
- this.$toolbar.append( $('<li>').append(btn));
- },
- buttonAddFirst: function(key, title, callback, dropdown)
- {
- if (!this.opts.toolbar) return;
- var btn = this.buttonBuild(key, { title: title, callback: callback, dropdown: dropdown });
- this.$toolbar.prepend($('<li>').append(btn));
- },
- buttonAddAfter: function(afterkey, key, title, callback, dropdown)
- {
- if (!this.opts.toolbar) return;
- var btn = this.buttonBuild(key, { title: title, callback: callback, dropdown: dropdown });
- var $btn = this.buttonGet(afterkey);
+ // if no more tags, copy and exit
+ if (-1 == code.substr(i).indexOf( '<' ))
+ {
+ out += code.substr(i);
- if ($btn.size() !== 0) $btn.parent().after($('<li>').append(btn));
- else this.$toolbar.append($('<li>').append(btn));
- },
- buttonAddBefore: function(beforekey, key, title, callback, dropdown)
- {
- if (!this.opts.toolbar) return;
- var btn = this.buttonBuild(key, { title: title, callback: callback, dropdown: dropdown });
- var $btn = this.buttonGet(beforekey);
+ return this.tabifier.finish(out);
+ }
- if ($btn.size() !== 0) $btn.parent().before($('<li>').append(btn));
- else this.$toolbar.append($('<li>').append(btn));
- },
- buttonRemove: function (key, separator)
- {
- var $btn = this.buttonGet(key);
- if (separator) $btn.parent().next().remove();
- $btn.parent().removeClass('redactor_btn_right');
- $btn.remove();
- },
- buttonActiveObserver: function(e, btnName)
- {
- var parent = this.getParent();
- this.buttonInactiveAll(btnName);
+ // copy verbatim until a tag
+ while (point < codeLength && code.charAt(point) != '<')
+ {
+ point++;
+ }
- if (e === false && btnName !== 'html')
- {
- if ($.inArray(btnName, this.opts.activeButtons) != -1)
- {
- this.buttonActiveToggle(btnName);
- }
- return;
- }
+ 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+/, '');
+ }
- if (parent && parent.tagName === 'A') this.$toolbar.find('a.redactor_dropdown_link').text(this.opts.curLang.link_edit);
- else this.$toolbar.find('a.redactor_dropdown_link').text(this.opts.curLang.link_insert);
+ out += cont;
+ }
- if (this.opts.activeButtonsAdd)
- {
- $.each(this.opts.activeButtonsAdd, $.proxy(function(i,s)
- {
- this.opts.activeButtons.push(s);
+ if (cont.match(/\n/)) out += '\n' + this.tabifier.getTabs();
+ }
- }, this));
+ start = point;
- $.extend(this.opts.activeButtonsStates, this.opts.activeButtonsAdd);
- }
+ // find the end of the tag
+ while (point < codeLength && '>' != code.charAt(point))
+ {
+ point++;
+ }
- $.each(this.opts.activeButtonsStates, $.proxy(function(key, value)
- {
- if ($(parent).closest(key, this.$editor.get()[0]).length != 0)
- {
- this.buttonActive(value);
- }
+ tag = code.substr(start, point - start);
+ i = point;
- }, this));
+ var t;
- var $parent = $(parent).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
- if ($parent.length)
- {
- var align = $parent.css('text-align');
+ if ('!--' == tag.substr(1, 3))
+ {
+ if (!tag.match(/--$/))
+ {
+ while ('-->' != code.substr(point, 3))
+ {
+ point++;
+ }
+ point += 2;
+ tag = code.substr(start, point - start);
+ i = point;
+ }
- switch (align)
- {
- case 'right':
- this.buttonActive('alignright');
- break;
- case 'center':
- this.buttonActive('aligncenter');
- break;
- case 'justify':
- this.buttonActive('justify');
- break;
- default:
- this.buttonActive('alignleft');
- break;
- }
- }
- },
+ if ('\n' != out.charAt(out.length - 1)) out += '\n';
- // EXEC
- execPasteFrag: function(html)
- {
- var sel = this.getSelection();
- if (sel.getRangeAt && sel.rangeCount)
- {
- range = sel.getRangeAt(0);
- range.deleteContents();
+ out += this.tabifier.getTabs();
+ out += tag + '>\n';
+ }
+ else if ('!' == tag[1])
+ {
+ out = this.tabifier.placeTag(tag + '>', out);
+ }
+ 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]);
- var el = document.createElement("div");
- el.innerHTML = html;
- var frag = document.createDocumentFragment(), node, lastNode;
- while ((node = el.firstChild))
- {
- lastNode = frag.appendChild(node);
- }
+ if (end)
+ {
+ cont = code.substr(i + 1, end);
+ i += end;
+ out += cont;
+ }
+ }
+ else
+ {
+ tag = this.tabifier.cleanTag(tag);
+ out = this.tabifier.placeTag(tag, out);
+ }
+ }
- var firstNode = frag.firstChild;
- range.insertNode(frag);
-
- if (lastNode)
+ return this.tabifier.finish(out);
+ },
+ getTabs: function()
{
- range = range.cloneRange();
- range.setStartAfter(lastNode);
- range.collapse(true);
- sel.removeAllRanges();
- sel.addRange(range);
- }
- }
- },
- exec: function(cmd, param, sync)
- {
- if (cmd === 'formatblock' && this.browser('msie')) param = '<' + param + '>';
+ var s = '';
+ for ( var j = 0; j < this.tabifier.cleanlevel; j++ )
+ {
+ s += '\t';
+ }
- if (cmd === 'inserthtml' && this.browser('msie'))
- {
- if (!this.isIe11())
+ return s;
+ },
+ finish: function(code)
{
- this.$editor.focus();
- this.document.selection.createRange().pasteHTML(param);
- }
- else this.execPasteFrag(param);
- }
- else
- {
- this.document.execCommand(cmd, false, param);
- }
+ 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>');
- if (sync !== false) this.sync();
- this.callback('execCommand', cmd, param);
- },
- execCommand: function(cmd, param, sync)
- {
- if (!this.opts.visual)
- {
- this.$source.focus();
- return false;
- }
+ this.tabifier.cleanlevel = 0;
- if (cmd === 'inserthtml')
- {
- this.insertHtml(param, sync);
- this.callback('execCommand', cmd, param);
- return;
- }
+ return code;
+ },
+ cleanTag: function (tag)
+ {
+ var tagout = '';
+ tag = tag.replace(/\n/g, ' ');
+ tag = tag.replace(/\s{2,}/g, ' ');
+ tag = tag.replace(/^\s+|\s+$/g, ' ');
- // Stop formatting pre
- if (this.currentOrParentIs('PRE') && !this.opts.formattingPre) return false;
+ var suffix = '';
+ if (tag.match(/\/$/))
+ {
+ suffix = '/';
+ tag = tag.replace(/\/+$/, '');
+ }
- // Lists
- if (cmd === 'insertunorderedlist' || cmd === 'insertorderedlist') return this.execLists(cmd, param);
+ var m;
+ while (m = /\s*([^= ]+)(?:=((['"']).*?\3|[^ ]+))?/.exec(tag))
+ {
+ if (m[2]) tagout += m[1].toLowerCase() + '=' + m[2];
+ else if (m[1]) tagout += m[1].toLowerCase();
- // Unlink
- if (cmd === 'unlink') return this.execUnlink(cmd, param);
+ tagout += ' ';
+ tag = tag.substr(m[0].length);
+ }
- // Usual exec
- this.exec(cmd, param, sync);
+ return tagout.replace(/\s*$/, '') + suffix + '>';
+ },
+ placeTag: function (tag, out)
+ {
+ var nl = tag.match(this.tabifier.newLevel);
+ if (tag.match(this.tabifier.lineBefore) || nl)
+ {
+ out = out.replace(/\s*$/, '');
+ out += '\n';
+ }
- // Line
- if (cmd === 'inserthorizontalrule') this.$editor.find('hr').removeAttr('id');
+ 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++;
- },
- execUnlink: function(cmd, param)
- {
- this.bufferSet();
+ out += tag;
- var link = this.currentOrParentIs('A');
- if (link)
- {
- $(link).replaceWith($(link).text());
+ if (tag.match(this.tabifier.lineAfter) || tag.match(this.tabifier.newLevel))
+ {
+ out = out.replace(/ *$/, '');
+ out += '\n';
+ }
- this.sync();
- this.callback('execCommand', cmd, param);
- return;
- }
+ return out;
+ }
+ };
},
- execLists: function(cmd, param)
+ focus: function()
{
- this.bufferSet();
+ return {
+ setStart: function()
+ {
+ this.$editor.focus();
- var parent = this.getParent();
- var $list = $(parent).closest('ol, ul');
- var remove = false;
+ var first = this.$editor.children().first();
- if ($list.length)
- {
- remove = true;
- var listTag = $list[0].tagName;
- if ((cmd === 'insertunorderedlist' && listTag === 'OL')
- || (cmd === 'insertorderedlist' && listTag === 'UL'))
- {
- remove = false;
- }
- }
+ if (first.size() === 0) return;
+ if (first[0].length === 0 || first[0].tagName == 'BR' || first[0].nodeType == 3)
+ {
+ return;
+ }
- this.selectionSave();
+ 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() == '')
+ {
+ // empty inline tag in li
+ this.caret.setStart(child);
+ return;
+ }
+ }
- // remove lists
- if (remove)
- {
- var nodes = this.getNodes();
- var elems = this.getBlocks(nodes);
+ if (this.opts.linebreaks && !this.utils.isBlockTag(first[0].tagName))
+ {
+ this.selection.get();
+ this.range.setStart(this.$editor[0], 0);
+ this.range.setEnd(this.$editor[0], 0);
+ this.selection.addRange();
- if (typeof nodes[0] != 'undefined' && nodes.length > 1 && nodes[0].nodeType == 3)
- {
- // fix the adding the first li to the array
- elems.unshift(this.getBlock());
- }
+ return;
+ }
- var data = '', replaced = '';
- $.each(elems, $.proxy(function(i,s)
+ // if node is tag
+ this.caret.setStart(first);
+ },
+ setEnd: function()
{
- if (s.tagName == 'LI')
+ if (this.utils.browser('mozilla') || this.utils.browser('msie'))
{
- var $s = $(s);
- var cloned = $s.clone();
- cloned.find('ul', 'ol').remove();
+ var last = this.$editor.children().last();
+ this.caret.setEnd(last);
+ }
+ else
+ {
+ this.selection.get();
- if (this.opts.linebreaks === false) data += this.outerHtml($('<p>').append(cloned.contents()));
- else data += cloned.html() + '<br>';
+ try {
+ this.range.selectNodeContents(this.$editor[0]);
+ this.range.collapse(false);
- if (i == 0)
- {
- $s.addClass('redactor-replaced').empty();
- replaced = this.outerHtml($s);
+ this.selection.addRange();
}
- else $s.remove();
+ catch (e) {}
}
- }, this));
+ },
+ isFocused: function()
+ {
+ var focusNode = document.getSelection().focusNode;
+ if (focusNode === null) return false;
- html = this.$editor.html().replace(replaced, '</' + listTag + '>' + data + '<' + listTag + '>');
+ if (this.opts.linebreaks && $(focusNode.parentNode).hasClass('redactor-linebreaks')) return true;
+ else if (!this.utils.isRedactorParent(focusNode.parentNode)) return false;
- this.$editor.html(html);
- this.$editor.find(listTag + ':empty').remove();
+ return this.$editor.is(':focus');
+ }
+ };
+ },
+ placeholder: function()
+ {
+ return {
+ enable: function()
+ {
+ if (!this.placeholder.is()) return;
- }
+ this.$editor.attr('placeholder', this.$element.attr('placeholder'));
- // insert lists
- else
- {
- var firstParent = this.getParent();
+ this.placeholder.toggle();
+ this.$editor.on('keyup.redactor-placeholder', $.proxy(this.placeholder.toggle, this));
- this.document.execCommand(cmd);
-
- var parent = this.getParent();
- var $list = $(parent).closest('ol, ul');
-
- if (firstParent && firstParent.tagName == 'TD')
+ },
+ toggle: function()
{
- $list.wrapAll('<td>');
- }
-
- if ($list.length)
+ var func = 'removeClass';
+ if (this.utils.isEmpty(this.$editor.html(), false)) func = 'addClass';
+ this.$editor[func]('redactor-placeholder');
+ },
+ remove: function()
{
- // focus fix
- var li = $list.children().first();
- if ($.trim($(li).text()) == '')
+ this.$editor.removeClass('redactor-placeholder');
+ },
+ is: function()
+ {
+ if (this.opts.placeholder)
{
- var marker = $('<span id="selection-marker-1"></span>');
- $(li).prepend(marker);
+ return this.$element.attr('placeholder', this.opts.placeholder);
}
-
- if ((this.browser('msie') || this.browser('mozilla')) && parent.tagName !== 'LI')
+ else
{
- $(parent).replaceWith($(parent).html());
+ return !(typeof this.$element.attr('placeholder') == 'undefined' || this.$element.attr('placeholder') === '');
}
+ }
+ };
+ },
+ autosave: function()
+ {
+ return {
+ enable: function()
+ {
+ if (!this.opts.autosave) return;
- var $listParent = $list.parent();
- if (this.isParentRedactor($listParent) && this.nodeTestBlocks($listParent[0]))
+ this.autosave.html = false;
+ this.autosave.name = (this.opts.autosaveName) ? this.opts.autosaveName : this.$textarea.attr('name');
+
+ if (!this.opts.autosaveOnChange)
{
- $listParent.replaceWith($listParent.contents());
+ this.autosaveInterval = setInterval($.proxy(this.autosave.load, this), this.opts.autosaveInterval * 1000);
}
- }
+ },
+ onChange: function()
+ {
+ if (!this.opts.autosaveOnChange) return;
- if (this.browser('mozilla')) this.$editor.focus();
- }
+ this.autosave.load();
+ },
+ load: function()
+ {
+ var html = this.code.get();
+ if (this.autosave.html === html) return;
+ if (this.utils.isEmpty(html)) return;
- this.selectionRestore();
- $(marker).remove();
+ $.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.sync();
- this.callback('execCommand', cmd, param);
- return;
- },
+ }, this)
+ });
+ },
+ success: function(data, html)
+ {
+ var json;
+ try
+ {
+ json = $.parseJSON(data);
+ }
+ catch(e)
+ {
+ //data has already been parsed
+ json = data;
+ }
- // INDENTING
- indentingIndent: function()
- {
- this.indentingStart('indent');
+ 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);
+ }
+ };
},
- indentingOutdent: function()
+ buffer: function()
{
- this.indentingStart('outdent');
- },
- indentingStart: function(cmd)
- {
- this.bufferSet();
+ 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;
- if (cmd === 'indent')
- {
- var block = this.getBlock();
+ this.buffer.set('redo');
+ this.buffer.getUndo();
- this.selectionSave();
+ this.selection.restore();
- if (block && block.tagName == 'LI')
+ setTimeout($.proxy(this.observe.load, this), 50);
+ },
+ redo: function()
{
- // li
- var parent = this.getParent();
+ if (this.opts.rebuffer.length === 0) return;
- var $list = $(parent).closest('ol, ul');
- var listTag = $list[0].tagName;
+ this.buffer.set('undo');
+ this.buffer.getRedo();
- var elems = this.getBlocks();
+ this.selection.restore();
- $.each(elems, function(i,s)
- {
- if (s.tagName == 'LI')
- {
- var $prev = $(s).prev();
- if ($prev.size() != 0 && $prev[0].tagName == 'LI')
- {
- var $childList = $prev.children('ul, ol');
- if ($childList.size() == 0)
- {
- $prev.append($('<' + listTag + '>').append(s));
- }
- else $childList.append(s);
- }
- }
- });
+ setTimeout($.proxy(this.observe.load, this), 50);
}
- // linebreaks
- else if (block === false && this.opts.linebreaks === true)
+ };
+ },
+ indent: function()
+ {
+ return {
+ increase: function()
{
- this.exec('formatBlock', 'blockquote');
- var newblock = this.getBlock();
- var block = $('<div data-tagblock="">').html($(newblock).html());
- $(newblock).replaceWith(block);
+ // focus
+ if (!this.utils.browser('msie')) this.$editor.focus();
- var left = this.normalize($(block).css('margin-left')) + this.opts.indentValue;
- $(block).css('margin-left', left + 'px');
- }
- else
- {
- // all block tags
- var elements = this.getBlocks();
- $.each(elements, $.proxy(function(i, elem)
+ this.buffer.set();
+ this.selection.save();
+
+ var block = this.selection.getBlock();
+
+ if (block && block.tagName == 'LI')
{
- var $el = false;
+ this.indent.increaseLists();
+ }
+ else if (block === false && this.opts.linebreaks)
+ {
+ this.indent.increaseText();
+ }
+ else
+ {
+ this.indent.increaseBlocks();
+ }
- if (elem.tagName === 'TD') return;
+ this.selection.restore();
+ this.code.sync();
+ },
+ increaseLists: function()
+ {
+ document.execCommand('indent');
- if ($.inArray(elem.tagName, this.opts.alignmentTags) !== -1)
- {
- $el = $(elem);
- }
- else
- {
- $el = $(elem).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
- }
+ this.indent.fixEmptyIndent();
+ this.clean.normalizeLists();
+ this.clean.clearUnverified();
+ },
+ increaseBlocks: function()
+ {
+ $.each(this.selection.getBlocks(), $.proxy(function(i, elem)
+ {
+ if (elem.tagName === 'TD' || elem.tagName === 'TH') return;
- var left = this.normalize($el.css('margin-left')) + this.opts.indentValue;
+ var $el = this.utils.getAlignmentElement(elem);
+
+ var left = this.utils.normalize($el.css('margin-left')) + this.opts.indentValue;
$el.css('margin-left', left + 'px');
}, this));
- }
+ },
+ increaseText: function()
+ {
+ var wrapper = this.selection.wrap('div');
+ $(wrapper).attr('data-tagblock', 'redactor');
+ $(wrapper).css('margin-left', this.opts.indentValue + 'px');
+ },
+ decrease: function()
+ {
+ this.buffer.set();
+ this.selection.save();
- this.selectionRestore();
+ var block = this.selection.getBlock();
+ if (block && block.tagName == 'LI')
+ {
+ this.indent.decreaseLists();
+ }
+ else
+ {
+ this.indent.decreaseBlocks();
+ }
- }
- // outdent
- else
- {
- this.selectionSave();
-
- var block = this.getBlock();
- if (block && block.tagName == 'LI')
+ this.selection.restore();
+ this.code.sync();
+ },
+ decreaseLists: function ()
{
- // li
- var elems = this.getBlocks();
- var index = 0;
+ document.execCommand('outdent');
- this.insideOutdent(block, index, elems);
- }
- else
- {
- // all block tags
- var elements = this.getBlocks();
- $.each(elements, $.proxy(function(i, elem)
+ var current = this.selection.getCurrent();
+
+ var $item = $(current).closest('li');
+ var $parent = $item.parent();
+ if ($item.size() !== 0 && $parent.size() !== 0 && $parent[0].tagName == 'LI')
{
- var $el = false;
+ $parent.after($item);
+ }
- if ($.inArray(elem.tagName, this.opts.alignmentTags) !== -1)
- {
- $el = $(elem);
- }
- else
- {
- $el = $(elem).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
- }
+ this.indent.fixEmptyIndent();
- var left = this.normalize($el.css('margin-left')) - this.opts.indentValue;
+ if (!this.opts.linebreaks && $item.size() === 0)
+ {
+ document.execCommand('formatblock', false, 'p');
+ this.$editor.find('ul, ol, blockquote, p').each($.proxy(this.utils.removeEmpty, this));
+ }
+
+ this.clean.clearUnverified();
+ },
+ decreaseBlocks: function()
+ {
+ $.each(this.selection.getBlocks(), $.proxy(function(i, elem)
+ {
+ var $el = this.utils.getAlignmentElement(elem);
+ var left = this.utils.normalize($el.css('margin-left')) - this.opts.indentValue;
+
if (left <= 0)
{
- // linebreaks
- if (this.opts.linebreaks === true && typeof($el.data('tagblock')) !== 'undefined')
+ if (this.opts.linebreaks && typeof($el.data('tagblock')) !== 'undefined')
{
- $el.replaceWith($el.html());
+ $el.replaceWith($el.html() + '<br />');
}
- // all block tags
else
{
$el.css('margin-left', '');
- this.removeEmptyAttr($el, 'style');
+ this.utils.removeEmptyAttr($el, 'style');
}
}
else
{
$el.css('margin-left', left + 'px');
}
}, this));
- }
-
-
- this.selectionRestore();
- }
-
- this.sync();
-
- },
- insideOutdent: function (li, index, elems)
- {
- if (li && li.tagName == 'LI')
- {
- var $parent = $(li).parent().parent();
- if ($parent.size() != 0 && $parent[0].tagName == 'LI')
+ },
+ fixEmptyIndent: function()
{
- $parent.after(li);
- }
- else
- {
- if (typeof elems[index] != 'undefined')
- {
- li = elems[index];
- index++;
+ var block = this.selection.getBlock();
- this.insideOutdent(li, index, elems);
- }
- else
+ if (this.range.collapsed && block && block.tagName == 'LI' && this.utils.isEmpty($(block).text()))
{
- this.execCommand('insertunorderedlist');
+ var $block = $(block);
+ $block.find('span').not('.redactor-selection-marker').contents().unwrap();
+ $block.append('<br>');
}
}
- }
+ };
},
-
- // ALIGNMENT
- alignmentLeft: function()
+ alignment: function()
{
- this.alignmentSet('', 'JustifyLeft');
- },
- alignmentRight: function()
- {
- this.alignmentSet('right', 'JustifyRight');
- },
- alignmentCenter: function()
- {
- this.alignmentSet('center', 'JustifyCenter');
- },
- alignmentJustify: function()
- {
- this.alignmentSet('justify', 'JustifyFull');
- },
- alignmentSet: function(type, cmd)
- {
- this.bufferSet();
-
- if (this.oldIE())
- {
- this.document.execCommand(cmd, false, false);
- return true;
- }
-
- this.selectionSave();
-
- var block = this.getBlock();
- if (!block && this.opts.linebreaks)
- {
- // one element
- this.exec('formatBlock', 'blockquote');
- var newblock = this.getBlock();
- var block = $('<div data-tagblock="">').html($(newblock).html());
- $(newblock).replaceWith(block);
-
- $(block).css('text-align', type);
- this.removeEmptyAttr(block, 'style');
-
- if (type == '' && typeof($(block).data('tagblock')) !== 'undefined')
+ return {
+ left: function()
{
- $(block).replaceWith($(block).html());
- }
- }
- else
- {
- var elements = this.getBlocks();
- $.each(elements, $.proxy(function(i, elem)
+ this.alignment.set('');
+ },
+ right: function()
{
- var $el = false;
+ this.alignment.set('right');
+ },
+ center: function()
+ {
+ this.alignment.set('center');
+ },
+ justify: function()
+ {
+ this.alignment.set('justify');
+ },
+ set: function(type)
+ {
+ if (!this.utils.browser('msie')) this.$editor.focus();
- if ($.inArray(elem.tagName, this.opts.alignmentTags) !== -1)
+ this.buffer.set();
+ this.selection.save();
+
+ this.alignment.blocks = this.selection.getBlocks();
+ if (this.opts.linebreaks && this.alignment.blocks[0] === false)
{
- $el = $(elem);
+ this.alignment.setText(type);
}
else
{
- $el = $(elem).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
+ this.alignment.setBlocks(type);
}
- if ($el)
+ this.selection.restore();
+ this.code.sync();
+ },
+ setText: function(type)
+ {
+ var wrapper = this.selection.wrap('div');
+ $(wrapper).attr('data-tagblock', 'redactor');
+ $(wrapper).css('text-align', type);
+ },
+ setBlocks: function(type)
+ {
+ $.each(this.alignment.blocks, $.proxy(function(i, el)
{
- $el.css('text-align', type);
- this.removeEmptyAttr($el, 'style');
- }
+ var $el = this.utils.getAlignmentElement(el);
- }, this));
- }
+ if (!$el) return;
- this.selectionRestore();
+ if (type === '' && typeof($el.data('tagblock')) !== 'undefined')
+ {
+ $el.replaceWith($el.html());
+ }
+ else
+ {
+ $el.css('text-align', type);
+ this.utils.removeEmptyAttr($el, 'style');
+ }
- this.sync();
- },
- // CLEAN
- cleanEmpty: function(html)
+ }, this));
+ }
+ };
+ },
+ paste: function()
{
- var ph = this.placeholderStart(html);
- if (ph !== false) return ph;
+ return {
+ init: function(e)
+ {
+ if (!this.opts.cleanOnPaste) return;
- if (this.opts.linebreaks === false)
- {
- if (html === '') html = this.opts.emptyHtml;
- else if (html.search(/^<hr\s?\/?>$/gi) !== -1) html = '<hr>' + this.opts.emptyHtml;
- }
+ this.rtePaste = true;
- return html;
- },
- cleanConverters: function(html)
- {
- // convert div to p
- if (this.opts.convertDivs) html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '<p$1>$2</p>');
- if (this.opts.paragraphy) html = this.cleanParagraphy(html);
+ this.buffer.set();
+ this.selection.save();
+ this.utils.saveScroll();
- return html;
- },
- cleanConvertProtected: function(html)
- {
- if (this.opts.templateVars)
- {
- html = html.replace(/\{\{(.*?)\}\}/gi, '<!-- template double $1 -->');
- html = html.replace(/\{(.*?)\}/gi, '<!-- template $1 -->');
- }
+ this.paste.createPasteBox();
- html = html.replace(/<script(.*?)>([\w\W]*?)<\/script>/gi, '<title type="text/javascript" style="display: none;" class="redactor-script-tag"$1>$2</title>');
- html = html.replace(/<style(.*?)>([\w\W]*?)<\/style>/gi, '<section$1 style="display: none;" rel="redactor-style-tag">$2</section>');
- html = html.replace(/<form(.*?)>([\w\W]*?)<\/form>/gi, '<section$1 rel="redactor-form-tag">$2</section>');
+ $(window).on('scroll.redactor-freeze', $.proxy(function()
+ {
+ $(window).scrollTop(this.saveBodyScroll);
- // php tags convertation
- if (this.opts.phpTags) html = html.replace(/<\?php([\w\W]*?)\?>/gi, '<section style="display: none;" rel="redactor-php-tag">$1</section>');
- else html = html.replace(/<\?php([\w\W]*?)\?>/gi, '');
+ }, this));
- return html;
- },
- cleanReConvertProtected: function(html)
- {
- if (this.opts.templateVars)
- {
- html = html.replace(/<!-- template double (.*?) -->/gi, '{{$1}}');
- html = html.replace(/<!-- template (.*?) -->/gi, '{$1}');
- }
+ setTimeout($.proxy(function()
+ {
+ var html = this.$pasteBox.html();
- html = html.replace(/<title type="text\/javascript" style="display: none;" class="redactor-script-tag"(.*?)>([\w\W]*?)<\/title>/gi, '<script$1 type="text/javascript">$2</script>');
- html = html.replace(/<section(.*?) style="display: none;" rel="redactor-style-tag">([\w\W]*?)<\/section>/gi, '<style$1>$2</style>');
- html = html.replace(/<section(.*?)rel="redactor-form-tag"(.*?)>([\w\W]*?)<\/section>/gi, '<form$1$2>$3</form>');
+ this.$pasteBox.remove();
- // php tags convertation
- if (this.opts.phpTags) html = html.replace(/<section style="display: none;" rel="redactor-php-tag">([\w\W]*?)<\/section>/gi, '<?php\r\n$1\r\n?>');
+ this.selection.restore();
+ this.utils.restoreScroll();
- return html;
- },
- cleanRemoveSpaces: function(html, buffer)
- {
- if (buffer !== false)
- {
- var buffer = []
- var matches = html.match(/<(pre|style|script|title)(.*?)>([\w\W]*?)<\/(pre|style|script|title)>/gi);
- if (matches === null) matches = [];
+ this.paste.insert(html);
- if (this.opts.phpTags)
+ $(window).off('scroll.redactor-freeze');
+
+ }, this), 1);
+
+ },
+ createPasteBox: function()
{
- var phpMatches = html.match(/<\?php([\w\W]*?)\?>/gi);
- if (phpMatches) matches = $.merge(matches, phpMatches);
- }
+ this.$pasteBox = $('<div>').html(' ').attr('contenteditable', 'true').css({ position: 'fixed', width: 0, top: 0, left: '-9999px' });
- if (matches)
+ $(document.body).append(this.$pasteBox);
+ this.$pasteBox.focus();
+ },
+ insert: function(html)
{
- $.each(matches, function(i, s)
- {
- html = html.replace(s, 'buffer_' + i);
- buffer.push(s);
- });
- }
- }
+ html = this.core.setCallback('pasteBefore', html);
- html = html.replace(/\n/g, ' ');
- html = html.replace(/[\t]*/g, '');
- html = html.replace(/\n\s*\n/g, "\n");
- html = html.replace(/^[\s\n]*/g, ' ');
- html = html.replace(/[\s\n]*$/g, ' ');
- html = html.replace( />\s{2,}</g, '> <'); // between inline tags can be only one space
+ // clean
+ html = (this.utils.isSelectAll()) ? this.clean.onPaste(html, false) : this.clean.onPaste(html);
- html = this.cleanReplacer(html, buffer);
+ html = this.core.setCallback('paste', html);
- html = html.replace(/\n\n/g, "\n");
+ if (this.utils.isSelectAll())
+ {
+ this.insert.set(html, false);
+ }
+ else
+ {
+ this.insert.html(html, false);
+ }
- return html;
- },
- cleanReplacer: function(html, buffer)
- {
- if (buffer === false) return html;
+ this.utils.disableSelectAll();
+ this.rtePaste = false;
- $.each(buffer, function(i,s)
- {
- html = html.replace('buffer_' + i, s);
- });
+ setTimeout($.proxy(this.clean.clearUnverified, this), 10);
- return html;
+ }
+ };
},
- cleanRemoveEmptyTags: function(html)
+ keydown: function()
{
- html = html.replace(/<span>([\w\W]*?)<\/span>/gi, '$1');
+ return {
+ init: function(e)
+ {
+ if (this.rtePaste) return;
- // remove zero width-space
- html = html.replace(/[\u200B-\u200D\uFEFF]/g, '');
+ var key = e.which;
+ var arrow = (key >= 37 && key <= 40);
- var etagsInline = ["<b>\\s*</b>", "<b> </b>", "<em>\\s*</em>"]
- var etags = [
- "<pre></pre>", "<blockquote>\\s*</blockquote>", "<dd></dd>", "<dt></dt>",
- "<ul></ul>", "<ol></ol>", "<li></li>", "<table></table>", "<tr></tr>",
- "<span>\\s*<span>", "<span> <span>", "<p>\\s*</p>", "<p></p>", "<p> </p>",
- "<p>\\s*<br>\\s*</p>", "<div>\\s*</div>", "<div>\\s*<br>\\s*</div>"
- ];
+ this.keydown.ctrl = e.ctrlKey || e.metaKey;
+ this.keydown.current = this.selection.getCurrent();
+ this.keydown.parent = this.selection.getParent();
+ this.keydown.block = this.selection.getBlock();
- if (this.opts.removeEmptyTags)
- {
- etags = etags.concat(etagsInline);
- }
- else etags = etagsInline;
+ // detect tags
+ this.keydown.pre = this.utils.isTag(this.keydown.current, 'pre');
+ this.keydown.blockquote = this.utils.isTag(this.keydown.current, 'blockquote');
+ this.keydown.figcaption = this.utils.isTag(this.keydown.current, 'figcaption');
- var len = etags.length;
- for (var i = 0; i < len; ++i)
- {
- html = html.replace(new RegExp(etags[i], 'gi'), "");
- }
+ // shortcuts setup
+ this.shortcuts.init(e, key);
- return html;
- },
- cleanParagraphy: function(html)
- {
- html = $.trim(html);
+ this.keydown.checkEvents(arrow, key);
+ this.keydown.setupBuffer(e, key);
+ this.keydown.addArrowsEvent(arrow);
+ this.keydown.setupSelectAll(e, key);
- if (this.opts.linebreaks === true) return html;
- if (html === '' || html === '<p></p>') return this.opts.emptyHtml;
+ // callback
+ var keydownStop = this.core.setCallback('keydown', e);
+ if (keydownStop === false)
+ {
+ e.preventDefault();
+ return false;
+ }
- html = html + "\n";
+ // down
+ if (this.opts.enterKey && key === this.keyCode.DOWN)
+ {
+ this.keydown.onArrowDown();
+ }
+ // turn off enter key
+ if (!this.opts.enterKey && key === this.keyCode.ENTER)
+ {
+ e.preventDefault();
+ // remove selected
+ if (!this.range.collapsed) this.range.deleteContents();
+ return;
+ }
- var safes = [];
- var matches = html.match(/<(table|div|pre|object)(.*?)>([\w\W]*?)<\/(table|div|pre|object)>/gi);
- if (!matches) matches = [];
+ // on enter
+ if (key == this.keyCode.ENTER && !e.shiftKey && !e.ctrlKey && !e.metaKey)
+ {
+ var stop = this.core.setCallback('enter', e);
+ if (stop === false)
+ {
+ e.preventDefault();
+ return false;
+ }
- var commentsMatches = html.match(/<!--([\w\W]*?)-->/gi);
- if (commentsMatches) matches = $.merge(matches, commentsMatches);
+ if (this.keydown.blockquote && this.keydown.exitFromBlockquote(e) === true)
+ {
+ return false;
+ }
- if (this.opts.phpTags)
- {
- var phpMatches = html.match(/<section(.*?)rel="redactor-php-tag">([\w\W]*?)<\/section>/gi);
- if (phpMatches) matches = $.merge(matches, phpMatches);
- }
+ var current, $next;
+ if (this.keydown.pre)
+ {
+ return this.keydown.insertNewLine(e);
+ }
+ else if (this.keydown.blockquote || this.keydown.figcaption)
+ {
+ current = this.selection.getCurrent();
+ $next = $(current).next();
- if (matches)
- {
- $.each(matches, function(i,s)
- {
- safes[i] = s;
- html = html.replace(s, '{replace' + i + '}\n');
- });
- }
+ if ($next.size() !== 0 && $next[0].tagName == 'BR')
+ {
+ return this.keydown.insertBreakLine(e);
+ }
+ else if (this.utils.isEndOfElement() && (current && current != 'SPAN'))
+ {
+ return this.keydown.insertDblBreakLine(e);
+ }
+ else
+ {
+ return this.keydown.insertBreakLine(e);
+ }
+ }
+ else if (this.opts.linebreaks && !this.keydown.block)
+ {
+ current = this.selection.getCurrent();
+ $next = $(this.keydown.current).next();
- html = html.replace(/<br \/>\s*<br \/>/gi, "\n\n");
+ if (current !== false && $(current).hasClass('redactor-invisible-space'))
+ {
+ $(current).remove();
+ return this.keydown.insertDblBreakLine(e);
+ }
+ else
+ {
+ if ($next.length === 0 && current === false && typeof $next.context != 'undefined')
+ {
+ return this.keydown.insertDblBreakLine(e);
+ }
- function R(str, mod, r)
- {
- return html.replace(new RegExp(str, mod), r);
- }
+ return this.keydown.insertBreakLine(e);
+ }
+ }
+ else if (this.opts.linebreaks && this.keydown.block)
+ {
+ setTimeout($.proxy(this.keydown.replaceDivToBreakLine, this), 1);
+ }
+ // paragraphs
+ else if (!this.opts.linebreaks && this.keydown.block && this.keydown.block.tagName !== 'LI')
+ {
+ setTimeout($.proxy(this.keydown.replaceDivToParagraph, this), 1);
+ }
+ else if (!this.opts.linebreaks && !this.keydown.block)
+ {
+ return this.keydown.insertParagraph(e);
+ }
+ }
- var blocks = '(comment|html|body|head|title|meta|style|script|link|iframe|table|thead|tfoot|caption|col|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|select|option|form|map|area|blockquote|address|math|style|p|h[1-6]|hr|fieldset|legend|section|article|aside|hgroup|header|footer|nav|figure|figcaption|details|menu|summary)';
- html = R('(<' + blocks + '[^>]*>)', 'gi', "\n$1");
- html = R('(</' + blocks + '>)', 'gi', "$1\n\n");
- html = R("\r\n", 'g', "\n");
- html = R("\r", 'g', "\n");
- html = R("/\n\n+/", 'g', "\n\n");
+ // Shift+Enter or Ctrl+Enter
+ if (key === this.keyCode.ENTER && (e.ctrlKey || e.shiftKey))
+ {
+ return this.keydown.onShiftEnter(e);
+ }
- var htmls = html.split(new RegExp('\n\s*\n', 'g'), -1);
- html = '';
- for (var i in htmls)
- {
- if (htmls.hasOwnProperty(i))
- {
- if (htmls[i].search('{replace') == -1)
+ // tab or cmd + [
+ if (key === this.keyCode.TAB || e.metaKey && key === 221 || e.metaKey && key === 219)
{
- htmls[i] = htmls[i].replace(/<p>\n\t<\/p>/gi, '');
- htmls[i] = htmls[i].replace(/<p><\/p>/gi, '');
+ return this.keydown.onTab(e, key);
+ }
- if (htmls[i] != '')
+
+ // image delete and backspace
+ if (key === this.keyCode.BACKSPACE || key === this.keyCode.DELETE)
+ {
+ var nodes = this.selection.getNodes();
+ if (nodes)
{
- html += '<p>' + htmls[i].replace(/^\n+|\n+$/g, "") + "</p>";
+ var len = nodes.length;
+ var last;
+ for (var i = 0; i < len; i++)
+ {
+ var children = $(nodes[i]).children('img');
+ if (children.size() !== 0)
+ {
+ var self = this;
+ $.each(children, function(z,s)
+ {
+ var $s = $(s);
+ if ($s.css('float') != 'none') return;
+
+ // image delete callback
+ self.core.setCallback('imageDelete', s.src, $s);
+ last = s;
+ });
+ }
+ else if (nodes[i].tagName == 'IMG')
+ {
+ if (last != nodes[i])
+ {
+ // image delete callback
+ this.core.setCallback('imageDelete', nodes[i].src, $(nodes[i]));
+ last = nodes[i];
+ }
+ }
+ }
}
}
- else html += htmls[i];
- }
- }
- html = R('<p>\s*</p>', 'gi', '');
- html = R('<p>([^<]+)</(div|address|form)>', 'gi', "<p>$1</p></$2>");
- html = R('<p>\s*(</?' + blocks + '[^>]*>)\s*</p>', 'gi', "$1");
- html = R("<p>(<li.+?)</p>", 'gi', "$1");
- html = R('<p>\s*(</?' + blocks + '[^>]*>)', 'gi', "$1");
+ // backspace
+ if (key === this.keyCode.BACKSPACE)
+ {
+ this.keydown.removeInvisibleSpace();
+ this.keydown.removeEmptyListInTable(e);
+ }
- html = R('(</?' + blocks + '[^>]*>)\s*</p>', 'gi', "$1");
- html = R('(</?' + blocks + '[^>]*>)\s*<br />', 'gi', "$1");
- html = R('<br />(\s*</?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)[^>]*>)', 'gi', '$1');
- html = R("\n</p>", 'gi', '</p>');
+ this.code.sync();
+ },
+ checkEvents: function(arrow, key)
+ {
+ if (!arrow && (this.core.getEvent() == 'click' || this.core.getEvent() == 'arrow'))
+ {
+ this.core.addEvent(false);
- html = R('<li><p>', 'gi', '<li>');
- html = R('</p></li>', 'gi', '</li>');
- html = R('</li><p>', 'gi', '</li>');
- //html = R('</ul><p>(.*?)</li>', 'gi', '</ul></li>');
- /* html = R('</ol><p>', 'gi', '</ol>'); */
- html = R('<p>\t?\n?<p>', 'gi', '<p>');
- html = R('</dt><p>', 'gi', '</dt>');
- html = R('</dd><p>', 'gi', '</dd>');
- html = R('<br></p></blockquote>', 'gi', '</blockquote>');
- html = R('<p>\t*</p>', 'gi', '');
+ if (this.keydown.checkKeyEvents(key))
+ {
+ this.buffer.set();
+ }
+ }
+ },
+ checkKeyEvents: function(key)
+ {
+ var k = this.keyCode;
+ var keys = [k.BACKSPACE, k.DELETE, k.ENTER, k.SPACE, k.ESC, k.TAB, k.CTRL, k.META, k.ALT, k.SHIFT];
- // restore safes
- $.each(safes, function(i,s)
- {
- html = html.replace('{replace' + i + '}', s);
- });
+ return ($.inArray(key, keys) == -1) ? true : false;
- return $.trim(html);
- },
- cleanConvertInlineTags: function(html, set)
- {
- var boldTag = 'strong';
- if (this.opts.boldTag === 'b') boldTag = 'b';
+ },
+ addArrowsEvent: function(arrow)
+ {
+ if (!arrow) return;
- var italicTag = 'em';
- if (this.opts.italicTag === 'i') italicTag = 'i';
+ if ((this.core.getEvent() == 'click' || this.core.getEvent() == 'arrow'))
+ {
+ this.core.addEvent(false);
+ return;
+ }
- html = html.replace(/<span style="font-style: italic;">([\w\W]*?)<\/span>/gi, '<' + italicTag + '>$1</' + italicTag + '>');
- html = html.replace(/<span style="font-weight: bold;">([\w\W]*?)<\/span>/gi, '<' + boldTag + '>$1</' + boldTag + '>');
+ this.core.addEvent('arrow');
+ },
+ setupBuffer: function(e, key)
+ {
+ if (this.keydown.ctrl && key === 90 && !e.shiftKey && !e.altKey && this.opts.buffer.length) // z key
+ {
+ e.preventDefault();
+ this.buffer.undo();
+ return;
+ }
+ // undo
+ else if (this.keydown.ctrl && key === 90 && e.shiftKey && !e.altKey && this.opts.rebuffer.length !== 0)
+ {
+ e.preventDefault();
+ this.buffer.redo();
+ return;
+ }
+ else if (!this.keydown.ctrl)
+ {
+ if (key == this.keyCode.BACKSPACE || key == this.keyCode.DELETE || (key == this.keyCode.ENTER && !e.ctrlKey && !e.shiftKey) || key == this.keyCode.SPACE)
+ {
+ this.buffer.set();
+ }
+ }
+ },
+ setupSelectAll: function(e, key)
+ {
+ if (this.keydown.ctrl && key === 65)
+ {
+ this.utils.enableSelectAll();
+ }
+ else if (key != this.keyCode.LEFT_WIN && !this.keydown.ctrl)
+ {
+ this.utils.disableSelectAll();
+ }
+ },
+ onArrowDown: function()
+ {
+ var tags = [this.keydown.blockquote, this.keydown.pre, this.keydown.figcaption];
- // bold, italic, del
- if (this.opts.boldTag === 'strong') html = html.replace(/<b>([\w\W]*?)<\/b>/gi, '<strong>$1</strong>');
- else html = html.replace(/<strong>([\w\W]*?)<\/strong>/gi, '<b>$1</b>');
+ for (var i = 0; i < tags.length; i++)
+ {
+ if (tags[i])
+ {
+ this.keydown.insertAfterLastElement(tags[i]);
+ return false;
+ }
+ }
+ },
+ onShiftEnter: function(e)
+ {
+ this.buffer.set();
- if (this.opts.italicTag === 'em') html = html.replace(/<i>([\w\W]*?)<\/i>/gi, '<em>$1</em>');
- else html = html.replace(/<em>([\w\W]*?)<\/em>/gi, '<i>$1</i>');
+ if (this.keydown.blockquote && this.utils.isEndOfElement())
+ {
+ return this.keydown.insertDblBreakLine(e);
+ }
- if (set !== true)
- {
- html = html.replace(/<strike>([\w\W]*?)<\/strike>/gi, '<del>$1</del>');
- }
- else
- {
- html = html.replace(/<del>([\w\W]*?)<\/del>/gi, '<strike>$1</strike>');
- }
+ return this.keydown.insertBreakLine(e);
+ },
+ onTab: function(e, key)
+ {
+ if (!this.opts.tabFocus) return true;
+ if (this.utils.isEmpty(this.code.get()) && this.opts.tabAsSpaces === false) return true;
- return html;
- },
- cleanStripTags: function(html)
- {
- if (html == '' || typeof html == 'undefined') return html;
+ e.preventDefault();
- var allowed = false;
- if (this.opts.allowedTags !== false) allowed = true;
+ var node;
+ if (this.keydown.pre && !e.shiftKey)
+ {
+ node = (this.opts.preSpaces) ? document.createTextNode(Array(this.opts.preSpaces + 1).join('\u00a0')) : document.createTextNode('\t');
+ this.insert.node(node);
+ this.code.sync();
+ }
+ else if (this.opts.tabAsSpaces !== false)
+ {
+ node = document.createTextNode(Array(this.opts.tabAsSpaces + 1).join('\u00a0'));
+ this.insert.node(node);
+ this.code.sync();
+ }
+ else
+ {
+ if (e.metaKey && key === 219) this.indent.decrease();
+ else if (e.metaKey && key === 221) this.indent.increase();
+ else if (!e.shiftKey) this.indent.increase();
+ else this.indent.decrease();
+ }
- var arr = allowed === true ? this.opts.allowedTags : this.opts.deniedTags;
+ return false;
+ },
+ replaceDivToBreakLine: function()
+ {
+ var blockElem = this.selection.getBlock();
+ var blockHtml = blockElem.innerHTML.replace(/<br\s?\/?>/gi, '');
+ if ((blockElem.tagName === 'DIV' || blockElem.tagName === 'P') && blockHtml === '' && !$(blockElem).hasClass('redactor-editor'))
+ {
+ var br = document.createElement('br');
- var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi;
- html = html.replace(tags, function ($0, $1)
- {
- if (allowed === true) return $.inArray($1.toLowerCase(), arr) > '-1' ? $0 : '';
- else return $.inArray($1.toLowerCase(), arr) > '-1' ? '' : $0;
- });
+ $(blockElem).replaceWith(br);
+ this.caret.setBefore(br);
- html = this.cleanConvertInlineTags(html);
+ this.code.sync();
- return html;
-
- },
- cleanSavePreCode: function(html, encode)
- {
- var pre = html.match(/<(pre|code)(.*?)>([\w\W]*?)<\/(pre|code)>/gi);
- if (pre !== null)
- {
- $.each(pre, $.proxy(function(i,s)
+ return false;
+ }
+ },
+ replaceDivToParagraph: function()
{
- var arr = s.match(/<(pre|code)(.*?)>([\w\W]*?)<\/(pre|code)>/i);
+ var blockElem = this.selection.getBlock();
+ var blockHtml = blockElem.innerHTML.replace(/<br\s?\/?>/gi, '');
+ if (blockElem.tagName === 'DIV' && blockHtml === '' && !$(blockElem).hasClass('redactor-editor'))
+ {
+ var p = document.createElement('p');
+ p.innerHTML = this.opts.invisibleSpace;
- arr[3] = arr[3].replace(/ /g, ' ');
+ $(blockElem).replaceWith(p);
+ this.caret.setStart(p);
- if (encode !== false) arr[3] = this.cleanEncodeEntities(arr[3]);
+ this.code.sync();
- // $ fix
- arr[3] = arr[3].replace(/\$/g, '$');
+ return false;
+ }
+ else if (this.opts.cleanStyleOnEnter && blockElem.tagName == 'P')
+ {
+ $(blockElem).removeAttr('class').removeAttr('style');
+ }
+ },
+ insertParagraph: function(e)
+ {
+ e.preventDefault();
- html = html.replace(s, '<' + arr[1] + arr[2] + '>' + arr[3] + '</' + arr[1] + '>');
+ this.selection.get();
- }, this));
- }
+ var p = document.createElement('p');
+ p.innerHTML = this.opts.invisibleSpace;
- return html;
- },
- cleanEncodeEntities: function(str)
- {
- str = String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
- return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
- },
- cleanUnverified: function()
- {
- // label, abbr, mark, meter, code, q, dfn, ins, time, kbd, var
- var $elem = this.$editor.find('li, img, a, b, strong, sub, sup, i, em, u, small, strike, del, span, cite');
+ this.range.deleteContents();
+ this.range.insertNode(p);
- $elem.filter('[style*="background-color: transparent;"][style*="line-height"]')
- .css('background-color', '')
- .css('line-height', '');
+ this.caret.setStart(p);
- $elem.filter('[style*="background-color: transparent;"]')
- .css('background-color', '');
+ this.code.sync();
- $elem.css('line-height', '');
+ return false;
+ },
+ exitFromBlockquote: function(e)
+ {
+ if (!this.utils.isEndOfElement()) return;
- $.each($elem, $.proxy(function(i,s)
- {
- this.removeEmptyAttr(s, 'style');
- }, this));
+ var tmp = $.trim($(this.keydown.block).html());
+ if (tmp.search(/(<br\s?\/?>){2}$/i) != -1)
+ {
+ e.preventDefault();
- // When we paste text in Safari is wrapping inserted div (remove it)
- this.$editor.find('div[style="text-align: -webkit-auto;"]').contents().unwrap();
- },
- cleanHtml: function(code)
- {
- var i = 0,
- codeLength = code.length,
- point = 0,
- start = null,
- end = null,
- tag = '',
- out = '',
- cont = '';
+ if (this.opts.linebreaks)
+ {
+ var br = document.createElement('br');
+ $(this.keydown.blockquote).after(br);
- this.cleanlevel = 0;
+ this.caret.setBefore(br);
+ $(this.keydown.block).html(tmp.replace(/<br\s?\/?>$/i, ''));
+ }
+ else
+ {
+ var node = $(this.opts.emptyHtml);
+ $(this.keydown.blockquote).after(node);
+ this.caret.setStart(node);
+ }
- for (; i < codeLength; i++)
- {
- point = i;
+ return true;
- // if no more tags, copy and exit
- if (-1 == code.substr(i).indexOf( '<' ))
- {
- out += code.substr(i);
+ }
- return this.cleanFinish(out);
- }
+ return;
- // copy verbatim until a tag
- while (point < codeLength && code.charAt(point) != '<')
+ },
+ insertAfterLastElement: function(element)
{
- point++;
- }
+ if (!this.utils.isEndOfElement()) return;
- if (i != point)
- {
- cont = code.substr(i, point - i);
- if (!cont.match(/^\s{2,}$/g))
+ this.buffer.set();
+
+ if (this.opts.linebreaks)
{
- if ('\n' == out.charAt(out.length - 1)) out += this.cleanGetTabs();
- else if ('\n' == cont.charAt(0))
+ var contents = $('<div>').append($.trim(this.$editor.html())).contents();
+ var last = contents.last()[0];
+ if (last.tagName == 'SPAN' && last.innerHTML === '')
{
- out += '\n' + this.cleanGetTabs();
- cont = cont.replace(/^\s+/, '');
+ last = contents.prev()[0];
}
- out += cont;
- }
+ if (this.utils.getOuterHtml(last) != this.utils.getOuterHtml(element)) return;
- if (cont.match(/\n/)) out += '\n' + this.cleanGetTabs();
- }
+ var br = document.createElement('br');
+ $(element).after(br);
+ this.caret.setAfter(br);
- start = point;
+ }
+ else
+ {
+ if (this.$editor.contents().last()[0] !== element) return;
- // find the end of the tag
- while (point < codeLength && '>' != code.charAt(point))
+ var node = $(this.opts.emptyHtml);
+ $(element).after(node);
+ this.caret.setStart(node);
+ }
+ },
+ insertNewLine: function(e)
{
- point++;
- }
+ e.preventDefault();
- tag = code.substr(start, point - start);
- i = point;
+ var node = document.createTextNode('\n');
- var t;
+ this.selection.get();
- if ('!--' == tag.substr(1, 3))
- {
- if (!tag.match(/--$/))
- {
- while ('-->' != code.substr(point, 3))
- {
- point++;
- }
- point += 2;
- tag = code.substr(start, point - start);
- i = point;
- }
+ this.range.deleteContents();
+ this.range.insertNode(node);
- if ('\n' != out.charAt(out.length - 1)) out += '\n';
+ this.caret.setAfter(node);
- out += this.cleanGetTabs();
- out += tag + '>\n';
- }
- else if ('!' == tag[1])
+ this.code.sync();
+
+ return false;
+ },
+ insertBreakLine: function(e)
{
- out = this.placeTag(tag + '>', out);
- }
- else if ('?' == tag[1])
+ return this.keydown.insertBreakLineProcessing(e);
+ },
+ insertDblBreakLine: function(e)
{
- out += tag + '>\n';
- }
- else if (t = tag.match(/^<(script|style|pre)/i))
+ return this.keydown.insertBreakLineProcessing(e, true);
+ },
+ insertBreakLineProcessing: function(e, dbl)
{
- t[1] = t[1].toLowerCase();
- tag = this.cleanTag(tag);
- out = this.placeTag(tag, out);
- end = String(code.substr(i + 1)).toLowerCase().indexOf('</' + t[1]);
+ e.stopPropagation();
- if (end)
+ this.selection.get();
+ var br1 = document.createElement('br');
+
+ this.range.deleteContents();
+ this.range.insertNode(br1);
+
+ if (dbl === true)
{
- cont = code.substr(i + 1, end);
- i += end;
- out += cont;
+ var br2 = document.createElement('br');
+ this.range.insertNode(br2);
+ this.caret.setAfter(br2);
}
- }
- else
+ else
+ {
+ this.caret.setAfter(br1);
+ }
+
+ this.code.sync();
+
+ return false;
+ },
+ removeInvisibleSpace: function()
{
- tag = this.cleanTag(tag);
- out = this.placeTag(tag, out);
- }
- }
+ var $current = $(this.keydown.current);
+ if ($current.text().search(/^\u200B$/g) === 0)
+ {
+ $current.remove();
+ }
+ },
+ removeEmptyListInTable: function(e)
+ {
+ var $current = $(this.keydown.current);
+ var $parent = $(this.keydown.parent);
+ var td = $current.closest('td');
- return this.cleanFinish( out );
- },
- cleanGetTabs: function()
- {
- var s = '';
- for ( var j = 0; j < this.cleanlevel; j++ )
- {
- s += '\t';
- }
+ if (td.size() !== 0 && $current.closest('li') && $parent.children('li').size() === 1)
+ {
+ if (!this.utils.isEmpty($current.text())) return;
- return s;
- },
- cleanFinish: function(code)
- {
- code = code.replace( /\n\s*\n/g, '\n' );
- code = code.replace( /^[\s\n]*/, '' );
- code = code.replace( /[\s\n]*$/, '' );
- code = code.replace( /<script(.*?)>\n<\/script>/gi, '<script$1></script>' );
+ e.preventDefault();
- this.cleanlevel = 0;
+ $current.remove();
+ $parent.remove();
- return code;
+ this.caret.setStart(td);
+ }
+ }
+ };
},
- cleanTag: function (tag)
+ keyup: function()
{
- var tagout = '';
- tag = tag.replace(/\n/g, ' ');
- tag = tag.replace(/\s{2,}/g, ' ');
- tag = tag.replace(/^\s+|\s+$/g, ' ');
+ return {
+ init: function(e)
+ {
+ if (this.rtePaste) return;
- var suffix = '';
- if (tag.match(/\/$/))
- {
- suffix = '/';
- tag = tag.replace(/\/+$/, '');
- }
+ var key = e.which;
- var m;
- while (m = /\s*([^= ]+)(?:=((['"']).*?\3|[^ ]+))?/.exec(tag))
- {
- if (m[2]) tagout += m[1].toLowerCase() + '=' + m[2];
- else if (m[1]) tagout += m[1].toLowerCase();
+ this.keyup.current = this.selection.getCurrent();
+ this.keyup.parent = this.selection.getParent();
+ var $parent = this.utils.isRedactorParent($(this.keyup.parent).parent());
- tagout += ' ';
- tag = tag.substr(m[0].length);
- }
+ // callback
+ var keyupStop = this.core.setCallback('keyup', e);
+ if (keyupStop === false)
+ {
+ e.preventDefault();
+ return false;
+ }
- return tagout.replace(/\s*$/, '') + suffix + '>';
- },
- placeTag: function (tag, out)
- {
- var nl = tag.match(this.cleannewLevel);
- if (tag.match(this.cleanlineBefore) || nl)
- {
- out = out.replace(/\s*$/, '');
- out += '\n';
- }
+ // replace to p before / after the table or body
+ 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 (nl && '/' == tag.charAt(1)) this.cleanlevel--;
- if ('\n' == out.charAt(out.length - 1)) out += this.cleanGetTabs();
- if (nl && '/' != tag.charAt(1)) this.cleanlevel++;
+ if ($(this.keyup.parent).hasClass('redactor-invisible-space') && ($parent === false || $parent[0].tagName == 'BODY'))
+ {
+ $(this.keyup.parent).contents().unwrap();
+ this.keyup.replaceToParagraph();
+ }
- out += tag;
+ // linkify
+ if (this.opts.convertLinks && (this.opts.convertUrlLinks || this.opts.convertImageLinks || this.opts.convertVideoLinks) && key === this.keyCode.ENTER)
+ {
+ this.formatLinkify(this.opts.linkProtocol, this.opts.convertLinks, this.opts.convertUrlLinks, this.opts.convertImageLinks, this.opts.convertVideoLinks, this.opts.linkSize);
- if (tag.match(this.cleanlineAfter) || tag.match(this.cleannewLevel))
- {
- out = out.replace(/ *$/, '');
- out += '\n';
- }
+ this.observe.load();
+ this.code.sync();
+ }
- return out;
- },
+ if (key === this.keyCode.DELETE || key === this.keyCode.BACKSPACE)
+ {
+ // clear unverified
+ this.clean.clearUnverified();
- // FORMAT
- formatEmpty: function(e)
- {
- var html = $.trim(this.$editor.html());
+ if (this.observe.image)
+ {
+ e.preventDefault();
- if (this.opts.linebreaks)
- {
- if (html == '')
- {
- e.preventDefault();
- this.$editor.html('');
- this.focus();
- }
- }
- else
- {
- html = html.replace(/<br\s?\/?>/i, '');
- var thtml = html.replace(/<p>\s?<\/p>/gi, '');
+ this.image.hideResize();
- if (html === '' || thtml === '')
- {
- e.preventDefault();
+ this.buffer.set();
+ this.image.remove(this.observe.image);
+ this.observe.image = false;
- var node = $(this.opts.emptyHtml).get(0);
- this.$editor.html(node);
- this.focus();
- }
- }
+ return false;
+ }
- this.sync();
- },
- formatBlocks: function(tag)
- {
- this.bufferSet();
+ // remove empty paragraphs
+ this.$editor.find('p').each($.proxy(this.utils.removeEmpty, this));
- var nodes = this.getBlocks();
- this.selectionSave();
-
- $.each(nodes, $.proxy(function(i, node)
- {
- if (node.tagName !== 'LI')
- {
- var parent = $(node).parent();
-
- if (tag === 'p')
- {
- if ((node.tagName === 'P'
- && parent.size() != 0
- && parent[0].tagName === 'BLOCKQUOTE')
- ||
- node.tagName === 'BLOCKQUOTE')
+ // remove invisible space
+ if (this.keyup.current && this.keyup.current.tagName == 'DIV' && this.utils.isEmpty(this.keyup.current.innerHTML))
{
- this.formatQuote();
- return;
+ if (this.opts.linebreaks)
+ {
+ $(this.keyup.current).after(this.selection.getMarkerAsHtml());
+ this.selection.restore();
+ $(this.keyup.current).remove();
+ }
}
- else if (this.opts.linebreaks) return;
- else
- {
- this.formatBlock(tag, node);
- }
+
+ // if empty
+ return this.keyup.formatEmpty(e);
}
- else
+ },
+ replaceToParagraph: function()
+ {
+ var $current = $(this.keyup.current);
+ var node = $('<p>').append($current.clone());
+ $current.replaceWith(node);
+ var next = $(node).next();
+ if (typeof(next[0]) !== 'undefined' && next[0].tagName == 'BR')
{
- this.formatBlock(tag, node);
+ next.remove();
}
- }
- }, this));
+ this.caret.setEnd(node);
+ },
+ formatEmpty: function(e)
+ {
+ var html = $.trim(this.$editor.html());
- this.selectionRestore();
- this.sync();
- },
- formatBlock: function(tag, block)
- {
- if (block === false) block = this.getBlock();
- if (block === false)
- {
- if (this.opts.linebreaks === true) this.execCommand('formatblock', tag);
- return true;
- }
+ if (!this.utils.isEmpty(html)) return;
- var contents = '';
- if (tag !== 'pre')
- {
- contents = $(block).contents();
- }
- else
- {
- //contents = this.cleanEncodeEntities($(block).text());
- contents = $(block).html();
- if ($.trim(contents) === '')
- {
- contents = '<span id="selection-marker-1"></span>';
- }
- }
+ e.preventDefault();
- if (block.tagName === 'PRE') tag = 'p';
+ if (this.opts.linebreaks)
+ {
+ this.$editor.html(this.selection.getMarkerAsHtml());
+ this.selection.restore();
+ }
+ else
+ {
+ html = '<p><br /></p>';
- if (this.opts.linebreaks === true && tag === 'p')
- {
- $(block).replaceWith($('<div>').append(contents).html() + '<br>');
- }
- else
- {
- var parent = this.getParent();
+ this.$editor.html(html);
+ this.focus.setStart();
+ }
- var node = $('<' + tag + '>').append(contents);
- $(block).replaceWith(node);
+ this.code.sync();
- if (parent && parent.tagName == 'TD')
- {
- $(node).wrapAll('<td>');
+ return false;
}
- }
+ };
},
- formatChangeTag: function(fromElement, toTagName, save)
+ shortcuts: function()
{
- if (save !== false) this.selectionSave();
-
- var newElement = $('<' + toTagName + '/>');
- $(fromElement).replaceWith(function() { return newElement.append($(this).contents()); });
-
- if (save !== false) this.selectionRestore();
-
- return newElement;
- },
-
- // QUOTE
- formatQuote: function()
- {
- this.bufferSet();
-
- // paragraphy
- if (this.opts.linebreaks === false)
- {
- this.selectionSave();
-
- var blocks = this.getBlocks();
-
- var blockquote = false;
- var blocksLen = blocks.length;
- if (blocks)
+ return {
+ init: function(e, key)
{
- var data = '';
- var replaced = '';
- var replace = false;
- var paragraphsOnly = true;
-
- $.each(blocks, function(i,s)
+ // disable browser's hot keys for bold and italic
+ if (!this.opts.shortcuts)
{
- if (s.tagName !== 'P') paragraphsOnly = false;
- });
+ if ((e.ctrlKey || e.metaKey) && (key === 66 || key === 73)) e.preventDefault();
+ return false;
+ }
- $.each(blocks, $.proxy(function(i,s)
+ $.each(this.opts.shortcuts, $.proxy(function(str, command)
{
- if (s.tagName === 'BLOCKQUOTE')
+ var keys = str.split(',');
+ var len = keys.length;
+ for (var i = 0; i < len; i++)
{
- this.formatBlock('p', s, false);
- }
- else if (s.tagName === 'P')
- {
- blockquote = $(s).parent();
- // from blockquote
- if (blockquote[0].tagName == 'BLOCKQUOTE')
+ if (typeof keys[i] === 'string')
{
- var count = $(blockquote).children('p').size();
-
- // one
- if (count == 1)
+ this.shortcuts.handler(e, $.trim(keys[i]), $.proxy(function()
{
- $(blockquote).replaceWith(s);
- }
- // all
- else if (count == blocksLen)
- {
- replace = 'blockquote';
- data += this.outerHtml(s);
- }
- // some
- else
- {
- replace = 'html';
- data += this.outerHtml(s);
-
- if (i == 0)
+ var func;
+ if (command.func.search(/\./) != '-1')
{
- $(s).addClass('redactor-replaced').empty();
- replaced = this.outerHtml(s);
+ func = command.func.split('.');
+ if (typeof this[func[0]] != 'undefined')
+ {
+ this[func[0]][func[1]].apply(this, command.params);
+ }
}
- else $(s).remove();
- }
+ else
+ {
+ this[command.func].apply(this, command.params);
+ }
+
+ }, this));
}
- // to blockquote
- else
- {
- if (paragraphsOnly === false || blocks.length == 1)
- {
- this.formatBlock('blockquote', s, false);
- }
- else
- {
- replace = 'paragraphs';
- data += this.outerHtml(s);
- }
- }
}
- else if (s.tagName !== 'LI')
- {
- this.formatBlock('blockquote', s, false);
- }
}, this));
+ },
+ handler: function(e, keys, origHandler)
+ {
+ // 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: "'"
+ };
- if (replace)
+
+ var hotkeysShiftNums =
{
- if (replace == 'paragraphs')
+ "`": "~", "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)
{
- $(blocks[0]).replaceWith('<blockquote>' + data + '</blockquote>');
- $(blocks).remove();
+ modif += specialKey + '+';
}
- else if (replace == 'blockquote')
+ });
+
+
+ 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+")
{
- $(blockquote).replaceWith(data);
+ possible[hotkeysShiftNums[character]] = true;
}
- else if (replace == 'html')
- {
- var html = this.$editor.html().replace(replaced, '</blockquote>' + data + '<blockquote>');
+ }
- this.$editor.html(html);
- this.$editor.find('blockquote').each(function()
- {
- if ($.trim($(this).html()) == '') $(this).remove();
- })
+ for (var i = 0, len = keys.length; i < len; i++)
+ {
+ if (possible[keys[i]])
+ {
+ e.preventDefault();
+ return origHandler.apply(this, arguments);
}
}
}
-
- this.selectionRestore();
- }
- // linebreaks
- else
- {
- var block = this.getBlock();
- if (block.tagName === 'BLOCKQUOTE')
+ };
+ },
+ line: function()
+ {
+ return {
+ insert: function()
{
- this.selectionSave();
+ this.buffer.set();
- var html = $.trim($(block).html());
- var selection = $.trim(this.getSelectionHtml());
+ var blocks = this.selection.getBlocks();
+ if (blocks[0] !== false && this.line.isExceptLastOrFirst(blocks))
+ {
+ if (!this.utils.browser('msie')) this.$editor.focus();
+ return;
+ }
- html = html.replace(/<span(.*?)id="selection-marker(.*?)<\/span>/gi, '');
-
- if (html == selection)
+ if (this.utils.browser('msie'))
{
- $(block).replaceWith($(block).html() + '<br>');
+ this.line.insertInIe();
}
else
{
- // replace
- this.inlineFormat('tmp');
- var tmp = this.$editor.find('tmp');
- tmp.empty();
+ this.line.insertInOthersBrowsers();
+ }
+ },
+ isExceptLastOrFirst: function(blocks)
+ {
+ var exceptTags = ['li', 'td', 'th', 'blockquote', 'figcaption', 'pre', 'dl', 'dt', 'dd'];
- var newhtml = this.$editor.html().replace('<tmp></tmp>', '</blockquote><span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>' + selection + '<blockquote>');
+ var first = blocks[0].tagName.toLowerCase();
+ var last = this.selection.getLastBlock();
- this.$editor.html(newhtml);
- tmp.remove();
- this.$editor.find('blockquote').each(function()
- {
- if ($.trim($(this).html()) == '') $(this).remove();
- })
+ last = (typeof last == 'undefined') ? first : last.tagName.toLowerCase();
+
+ var firstFound = $.inArray(first, exceptTags) != -1;
+ var lastFound = $.inArray(last, exceptTags) != -1;
+
+ if ((firstFound && lastFound) || firstFound)
+ {
+ return true;
}
+ },
+ insertInIe: function()
+ {
+ this.utils.saveScroll();
+ this.buffer.set();
- this.selectionRestore();
- this.$editor.find('span#selection-marker-1').attr('id', false);
- }
- else
+ this.insert.node(document.createElement('hr'));
+
+ this.utils.restoreScroll();
+ this.code.sync();
+ },
+ insertInOthersBrowsers: function()
{
- var wrapper = this.selectionWrap('blockquote');
- var html = $(wrapper).html();
+ this.buffer.set();
- var blocksElemsRemove = ['ul', 'ol', 'table', 'tr', 'tbody', 'thead', 'tfoot', 'dl'];
- $.each(blocksElemsRemove, function(i,s)
- {
- html = html.replace(new RegExp('<' + s + '(.*?)>', 'gi'), '');
- html = html.replace(new RegExp('</' + s + '>', 'gi'), '');
- });
+ var extra = '<p id="redactor-insert-line"><br /></p>';
+ if (this.opts.linebreaks) extra = '<br id="redactor-insert-line">';
- var blocksElems = this.opts.blockLevelElements;
- blocksElems.push('td');
- $.each(blocksElems, function(i,s)
- {
- html = html.replace(new RegExp('<' + s + '(.*?)>', 'gi'), '');
- html = html.replace(new RegExp('</' + s + '>', 'gi'), '<br>');
- });
+ document.execCommand('insertHTML', false, '<hr>' + extra);
- $(wrapper).html(html);
- this.selectionElement(wrapper);
- var next = $(wrapper).next();
- if (next.size() != 0 && next[0].tagName === 'BR')
+ this.line.setFocus();
+ this.code.sync();
+ },
+ setFocus: function()
+ {
+ var node = this.$editor.find('#redactor-insert-line');
+ var next = $(node).next()[0];
+
+ if (next)
{
- next.remove();
+ this.caret.setAfter(node);
+ node.remove();
}
+ else
+ {
+ node.removeAttr('id');
+ }
}
- }
-
- this.sync();
+ };
},
-
- // BLOCK
- blockRemoveAttr: function(attr, value)
+ list: function()
{
- var nodes = this.getBlocks();
- $(nodes).removeAttr(attr);
+ return {
+ toggle: function(cmd)
+ {
+ if (!this.utils.browser('msie')) this.$editor.focus();
- this.sync();
- },
- blockSetAttr: function(attr, value)
- {
- var nodes = this.getBlocks();
- $(nodes).attr(attr, value);
+ this.buffer.set();
+ this.selection.save();
- this.sync();
- },
- blockRemoveStyle: function(rule)
- {
- var nodes = this.getBlocks();
- $(nodes).css(rule, '');
- this.removeEmptyAttr(nodes, 'style');
+ var parent = this.selection.getParent();
+ var $list = $(parent).closest('ol, ul');
- this.sync();
- },
- blockSetStyle: function (rule, value)
- {
- var nodes = this.getBlocks();
- $(nodes).css(rule, value);
+ if (!this.utils.isRedactorParent($list) && $list.size() !== 0)
+ {
+ $list = false;
+ }
- this.sync();
- },
- blockRemoveClass: function(className)
- {
- var nodes = this.getBlocks();
- $(nodes).removeClass(className);
- this.removeEmptyAttr(nodes, 'class');
+ var isUnorderedCmdOrdered, isOrderedCmdUnordered;
+ var remove = false;
+ if ($list && $list.length)
+ {
+ remove = true;
+ var listTag = $list[0].tagName;
- this.sync();
- },
- blockSetClass: function(className)
- {
- var nodes = this.getBlocks();
- $(nodes).addClass(className);
+ isUnorderedCmdOrdered = (cmd === 'orderedlist' && listTag === 'UL');
+ isOrderedCmdUnordered = (cmd === 'unorderedlist' && listTag === 'OL');
+ }
- this.sync();
- },
+ if (isUnorderedCmdOrdered)
+ {
+ this.utils.replaceToTag($list, 'ol');
+ }
+ else if (isOrderedCmdUnordered)
+ {
+ this.utils.replaceToTag($list, 'ul');
+ }
+ else
+ {
+ if (remove)
+ {
+ this.list.remove(cmd);
+ }
+ else
+ {
+ this.list.insert(cmd);
+ }
+ }
- // INLINE
- inlineRemoveClass: function(className)
- {
- this.selectionSave();
- this.inlineEachNodes(function(node)
- {
- $(node).removeClass(className);
- this.removeEmptyAttr(node, 'class');
- });
+ this.selection.restore();
+ this.code.sync();
+ },
+ insert: function(cmd)
+ {
+ if (this.utils.browser('msie') && this.opts.linebreaks)
+ {
+ this.list.insertInIe(cmd);
+ }
+ else
+ {
+ document.execCommand('insert' + cmd);
+ }
- this.selectionRestore();
- this.sync();
- },
+ var parent = this.selection.getParent();
+ var $list = $(parent).closest('ol, ul');
- inlineSetClass: function(className)
- {
- var current = this.getCurrent();
- if (!$(current).hasClass(className)) this.inlineMethods('addClass', className);
- },
- inlineRemoveStyle: function (rule)
- {
- this.selectionSave();
+ if (this.utils.isEmpty($list.find('li').text()))
+ {
+ var $children = $list.children('li');
+ $children.find('br').remove();
+ $children.append(this.selection.getMarkerAsHtml());
+ }
- this.inlineEachNodes(function(node)
- {
- $(node).css(rule, '');
- this.removeEmptyAttr(node, 'style');
- });
+ if ($list.length)
+ {
+ // remove block-element list wrapper
+ var $listParent = $list.parent();
+ if (this.utils.isRedactorParent($listParent) && $listParent[0].tagName != 'LI' && this.utils.isBlock($listParent[0]))
+ {
+ $listParent.replaceWith($listParent.contents());
+ }
+ }
- this.selectionRestore();
- this.sync();
- },
- inlineSetStyle: function(rule, value)
- {
- this.inlineMethods('css', rule, value);
- },
- inlineRemoveAttr: function (attr)
- {
- this.selectionSave();
+ if (!this.utils.browser('msie'))
+ {
+ this.$editor.focus();
+ }
- var range = this.getRange(), node = this.getElement(), nodes = this.getNodes();
+ this.clean.clearUnverified();
+ },
+ insertInIe: function(cmd)
+ {
+ var wrapper = this.selection.wrap('div');
+ var wrapperHtml = $(wrapper).html();
- if (range.collapsed || range.startContainer === range.endContainer && node)
- {
- nodes = $( node );
- }
+ var tmpList = (cmd == 'orderedlist') ? $('<ol>') : $('<ul>');
+ var tmpLi = $('<li>');
- $(nodes).removeAttr(attr);
+ if ($.trim(wrapperHtml) === '')
+ {
+ tmpLi.append(this.selection.getMarkerAsHtml());
+ tmpList.append(tmpLi);
+ this.$editor.find('#selection-marker-1').replaceWith(tmpList);
+ }
+ else
+ {
+ var items = wrapperHtml.split(/<br\s?\/?>/gi);
+ if (items)
+ {
+ for (var i = 0; i < items.length; i++)
+ {
+ if ($.trim(items[i]) !== '')
+ {
+ tmpList.append($('<li>').html(items[i]));
+ }
+ }
+ }
+ else
+ {
+ tmpLi.append(wrapperHtml);
+ tmpList.append(tmpLi);
+ }
- this.inlineUnwrapSpan();
+ $(wrapper).replaceWith(tmpList);
+ }
+ },
+ remove: function(cmd)
+ {
+ document.execCommand('insert' + cmd);
- this.selectionRestore();
- this.sync();
+ var $current = $(this.selection.getCurrent());
+
+ 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));
+ }
+
+ 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')
+ {
+ $prev.remove();
+ }
+
+ this.clean.clearUnverified();
+
+ }
+ };
},
- inlineSetAttr: function(attr, value)
+ block: function()
{
- this.inlineMethods('attr', attr, value );
- },
- inlineMethods: function(type, attr, value)
- {
- this.bufferSet();
- this.selectionSave();
+ return {
+ formatting: function(name)
+ {
+ var type, value;
- var range = this.getRange()
- var el = this.getElement();
+ 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 ((range.collapsed || range.startContainer === range.endContainer) && el && !this.nodeTestBlocks(el))
- {
- $(el)[type](attr, value);
- }
- else
- {
- this.document.execCommand('fontSize', false, 4 );
+ if (type) value = this.formatting[name][type];
- var fonts = this.$editor.find('font');
- $.each(fonts, $.proxy(function(i, s)
+ this.block.format(this.formatting[name].tag, type, value);
+
+ },
+ format: function(tag, type, value)
{
- this.inlineSetMethods(type, s, attr, value);
+ if (tag == 'quote') tag = 'blockquote';
- }, this));
+ 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);
- this.selectionRestore();
- this.sync();
- },
- inlineSetMethods: function(type, s, attr, value)
- {
- var parent = $(s).parent(), el;
+ // focus
+ if (!this.utils.browser('msie')) this.$editor.focus();
- var selectionHtml = this.getSelectionText();
- var parentHtml = $(parent).text();
- var selected = selectionHtml == parentHtml;
+ this.block.blocks = this.selection.getBlocks();
- if (selected && parent && parent[0].tagName === 'INLINE' && parent[0].attributes.length != 0)
- {
- el = parent;
- $(s).replaceWith($(s).html());
- }
- else
- {
- el = $('<inline>').append($(s).contents());
- $(s).replaceWith(el);
- }
+ this.block.blocksSize = this.block.blocks.length;
+ this.block.type = type;
+ this.block.value = value;
+ this.buffer.set();
+ this.selection.save();
- $(el)[type](attr, value);
+ this.block.set(tag);
- return el;
- },
- // Sort elements and execute callback
- inlineEachNodes: function(callback)
- {
- var range = this.getRange(),
- node = this.getElement(),
- nodes = this.getNodes(),
- collapsed;
+ this.selection.restore();
+ this.code.sync();
- if (range.collapsed || range.startContainer === range.endContainer && node)
- {
- nodes = $(node);
- collapsed = true;
- }
-
- $.each(nodes, $.proxy(function(i, node)
- {
- if (!collapsed && node.tagName !== 'INLINE')
+ },
+ set: function(tag)
{
- var selectionHtml = this.getSelectionText();
- var parentHtml = $(node).parent().text();
- var selected = selectionHtml == parentHtml;
+ this.selection.get();
+ this.block.containerTag = this.range.commonAncestorContainer.tagName;
- if (selected && node.parentNode.tagName === 'INLINE' && !$(node.parentNode).hasClass('redactor_editor'))
+ if (this.range.collapsed)
{
- node = node.parentNode;
+ this.block.setCollapsed(tag);
}
- else return;
- }
- callback.call(this, node);
+ else
+ {
+ this.block.setMultiple(tag);
+ }
+ },
+ setCollapsed: function(tag)
+ {
+ var block = this.block.blocks[0];
+ if (block === false) return;
- }, this ) );
- },
- inlineUnwrapSpan: function()
- {
- var $spans = this.$editor.find('inline');
+ if (block.tagName == 'LI')
+ {
+ if (tag != 'blockquote') return;
- $.each($spans, $.proxy(function(i, span)
- {
- var $span = $(span);
+ this.block.formatListToBlockquote();
+ return;
+ }
- if ($span.attr('class') === undefined && $span.attr('style') === undefined)
- {
- $span.contents().unwrap();
- }
+ var isContainerTable = (this.block.containerTag == 'TD' || this.block.containerTag == 'TH');
+ if (isContainerTable)
+ {
+ if (!this.opts.linebreaks && tag == 'p')
+ {
+ document.execCommand('formatblock', false, '<' + tag + '>');
- }, this));
- },
- inlineFormat: function(tag)
- {
- this.selectionSave();
+ 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.document.execCommand('fontSize', false, 4 );
+ this.block.toggle($formatted);
- var fonts = this.$editor.find('font');
- var last;
- $.each(fonts, function(i, s)
- {
- var el = $('<' + tag + '/>').append($(s).contents());
- $(s).replaceWith(el);
- last = el;
- });
+ 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));
+ }
- this.selectionRestore();
+ },
+ setMultiple: function(tag)
+ {
+ var block = this.block.blocks[0];
+ var isContainerTable = (this.block.containerTag == 'TD' || this.block.containerTag == 'TH');
- this.sync();
- },
- inlineRemoveFormat: function(tag)
- {
- this.selectionSave();
+ 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;
- var utag = tag.toUpperCase();
- var nodes = this.getNodes();
- var parent = $(this.getParent()).parent();
+ 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
+ {
- $.each(nodes, function(i, s)
- {
- if (s.tagName === utag) this.inlineRemoveFormatReplace(s);
- });
+ var $formatted = this.utils.replaceToTag(block, tag);
- if (parent && parent[0].tagName === utag) this.inlineRemoveFormatReplace(parent);
+ this.block.toggle($formatted);
- this.selectionRestore();
- this.sync();
- },
- inlineRemoveFormatReplace: function(el)
- {
- $(el).replaceWith($(el).contents());
- },
+ if (this.block.isRemoveInline) this.utils.removeInlineTags($formatted);
+ if (tag == 'p' || this.block.headTag) $formatted.find('p').contents().unwrap();
+ }
+ }
+ }
+ else
+ {
+ 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++;
+ }
- // INSERT
- insertHtml: function (html, sync)
- {
- var current = this.getCurrent();
- var parent = current.parentNode;
+ // 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.$editor.focus();
+ }, this));
- this.bufferSet();
+ return;
+ }
- var $html = $('<div>').append($.parseHTML(html));
- html = $html.html();
+ }
- html = this.cleanRemoveEmptyTags(html);
+ 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();
- // Update value
- $html = $('<div>').append($.parseHTML(html));
+ if (this.block.blocksSize == classSize) toggleType = 'toggle';
+ else if (this.block.blocksSize > classSize) toggleType = 'set';
+ else if (classSize === 0) toggleType = 'set';
- var currBlock = this.getBlock();
+ }
- if ($html.contents().length == 1)
- {
- var htmlTagName = $html.contents()[0].tagName;
+ 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;
- // If the inserted and received text tags match
- if (htmlTagName != 'P' && htmlTagName == currBlock.tagName || htmlTagName == 'PRE')
- {
- html = $html.text();
- $html = $('<div>').append(html);
- }
- }
+ var $formatted = this.utils.replaceToTag(s, tag);
- // add text in a paragraph
- if (!this.opts.linebreaks && $html.contents().length == 1 && $html.contents()[0].nodeType == 3
- && (this.getRangeSelectedNodes().length > 2 || (!current || current.tagName == 'BODY' && !parent || parent.tagName == 'HTML')))
- {
- html = '<p>' + html + '</p>';
- }
+ 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);
- html = this.setSpansVerifiedHtml(html);
+ if (tag != 'p' && tag != 'blockquote') $formatted.find('img').remove();
+ if (this.block.isRemoveInline) this.utils.removeInlineTags($formatted);
+ if (tag == 'p' || this.block.headTag) $formatted.find('p').contents().unwrap();
- if ($html.contents().length > 1 && currBlock
- || $html.contents().is('p, :header, ul, ol, li, div, table, td, blockquote, pre, address, section, header, footer, aside, article'))
- {
- if (this.browser('msie'))
+
+ }, this));
+ }
+ }
+ },
+ toggle: function($el)
{
- if (!this.isIe11())
+ if (this.block.type == 'class')
{
- this.document.selection.createRange().pasteHTML(html);
+ $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
{
- this.execPasteFrag(html);
+ $el.removeAttr('style class');
+ return;
}
- }
- else this.document.execCommand('inserthtml', false, html);
- }
- else this.insertHtmlAdvanced(html, false);
-
- if (this.selectall)
- {
- this.window.setTimeout($.proxy(function()
+ },
+ remove: function($el)
{
- if (!this.opts.linebreaks) this.selectionEnd(this.$editor.contents().last());
- else this.focusEnd();
+ $el.removeClass(this.block.value);
+ },
+ formatListToBlockquote: function()
+ {
+ var block = $(this.block.blocks[0]).closest('ul, ol');
- }, this), 1);
- }
+ $(block).find('ul, ol').contents().unwrap();
+ $(block).find('li').append($('<br>')).contents().unwrap();
- this.observeStart();
+ var $el = this.utils.replaceToTag(block, 'blockquote');
+ this.block.toggle($el);
+ },
+ formatBlockquote: function(tag)
+ {
+ document.execCommand('outdent');
+ document.execCommand('formatblock', false, tag);
- // set no editable
- this.setNonEditable();
+ this.clean.clearUnverified();
+ this.$editor.find('p:empty').remove();
- if (sync !== false) this.sync();
- },
- insertHtmlAdvanced: function(html, sync)
- {
- html = this.setSpansVerifiedHtml(html);
+ var formatted = this.selection.getBlock();
- var sel = this.getSelection();
+ if (tag != 'p')
+ {
+ $(formatted).find('img').remove();
+ }
- if (sel.getRangeAt && sel.rangeCount)
- {
- var range = sel.getRangeAt(0);
- range.deleteContents();
+ if (!this.opts.linebreaks)
+ {
+ this.block.toggle($(formatted));
+ }
- var el = this.document.createElement('div');
- el.innerHTML = html;
- var frag = this.document.createDocumentFragment(), node, lastNode;
- while ((node = el.firstChild))
- {
- lastNode = frag.appendChild(node);
- }
+ this.$editor.find('ul, ol, tr, blockquote, p').each($.proxy(this.utils.removeEmpty, this));
- range.insertNode(frag);
+ if (this.opts.linebreaks && tag == 'p')
+ {
+ this.utils.replaceWithContents(formatted);
+ }
- if (lastNode)
+ },
+ formatWrap: function(tag)
{
- range = range.cloneRange();
- range.setStartAfter(lastNode);
- range.collapse(true);
- sel.removeAllRanges();
- sel.addRange(range);
- }
- }
+ if (this.block.containerTag == 'UL' || this.block.containerTag == 'OL')
+ {
+ if (tag == 'blockquote')
+ {
+ this.block.formatListToBlockquote();
+ }
+ else
+ {
+ return;
+ }
+ }
- if (sync !== false)
- {
- this.sync();
- }
+ var formatted = this.selection.wrap(tag);
+ if (formatted === false) return;
- },
- insertBeforeCursor: function(html)
- {
- html = this.setSpansVerifiedHtml(html);
+ var $formatted = $(formatted);
- var node = $(html);
+ this.block.formatTableWrapping($formatted);
- var space = document.createElement("span");
- space.innerHTML = "\u200B";
+ var $elements = $formatted.find(this.opts.blockLevelElements.join(',') + ', td, table, thead, tbody, tfoot, th, tr');
- var range = this.getRange();
- range.insertNode(space);
- range.insertNode(node[0]);
- range.collapse(false);
+ if ((this.opts.linebreaks && tag == 'p') || tag == 'pre' || tag == 'blockquote')
+ {
+ $elements.append('<br />');
+ }
- var sel = this.getSelection();
- sel.removeAllRanges();
- sel.addRange(range);
+ $elements.contents().unwrap();
- this.sync();
- },
- insertText: function(html)
- {
- var $html = $($.parseHTML(html));
+ if (tag != 'p' && tag != 'blockquote') $formatted.find('img').remove();
- if ($html.length) html = $html.text();
+ $.each(this.block.blocks, $.proxy(this.utils.removeEmpty, this));
- this.$editor.focus();
+ $formatted.append(this.selection.getMarker(2));
- if (this.browser('msie') && !this.isIe11()) this.document.selection.createRange().pasteHTML(html);
- else this.document.execCommand('inserthtml', false, html);
+ if (!this.opts.linebreaks)
+ {
+ this.block.toggle($formatted);
+ }
- this.sync();
- },
- insertNode: function(node)
- {
- node = node[0] || node;
+ this.$editor.find('ul, ol, tr, blockquote, p').each($.proxy(this.utils.removeEmpty, this));
+ $formatted.find('blockquote:empty').remove();
- if (node.tagName == 'SPAN')
- {
- var replacementTag = 'inline';
+ if (this.block.isRemoveInline)
+ {
+ this.utils.removeInlineTags($formatted);
+ }
- var outer = node.outerHTML;
+ if (this.opts.linebreaks && tag == 'p')
+ {
+ this.utils.replaceWithContents($formatted);
+ }
- // Replace opening tag
- var regex = new RegExp('<' + node.tagName, 'i');
- var newTag = outer.replace(regex, '<' + replacementTag);
+ },
+ formatTableWrapping: function($formatted)
+ {
+ if ($formatted.closest('table').size() === 0) return;
- // Replace closing tag
- regex = new RegExp('</' + node.tagName, 'i');
- newTag = newTag.replace(regex, '</' + replacementTag);
- node = $(newTag)[0];
- }
+ if ($formatted.closest('tr').size() === 0) $formatted.wrap('<tr>');
+ if ($formatted.closest('td').size() === 0) $formatted.wrap('<td>');
+ },
+ removeData: function(name, value)
+ {
+ var blocks = this.selection.getBlocks();
+ $(blocks).removeAttr('data-' + name);
- var sel = this.getSelection();
- if (sel.getRangeAt && sel.rangeCount)
- {
- // with delete contents
- range = sel.getRangeAt(0);
- range.deleteContents();
- range.insertNode(node);
- range.setEndAfter(node);
- range.setStartAfter(node);
- sel.removeAllRanges();
- sel.addRange(range);
- }
- },
- insertNodeToCaretPositionFromPoint: function(e, node)
- {
- var range;
- var x = e.clientX, y = e.clientY;
- if (this.document.caretPositionFromPoint)
- {
- var pos = this.document.caretPositionFromPoint(x, y);
- range = this.getRange();
- range.setStart(pos.offsetNode, pos.offset);
- range.collapse(true);
- range.insertNode(node);
- }
- else if (this.document.caretRangeFromPoint)
- {
- range = this.document.caretRangeFromPoint(x, y);
- range.insertNode(node);
- }
- else if (typeof document.body.createTextRange != "undefined")
- {
- range = this.document.body.createTextRange();
- range.moveToPoint(x, y);
- var endRange = range.duplicate();
- endRange.moveToPoint(x, y);
- range.setEndPoint("EndToEnd", endRange);
- range.select();
- }
+ this.code.sync();
+ },
+ setData: function(name, value)
+ {
+ var blocks = this.selection.getBlocks();
+ $(blocks).attr('data-' + name, value);
- },
- insertAfterLastElement: function(element, parent)
- {
- if (typeof(parent) != 'undefined') element = parent;
-
- if (this.isEndOfElement())
- {
- if (this.opts.linebreaks)
+ this.code.sync();
+ },
+ toggleData: function(name, value)
{
- var contents = $('<div>').append($.trim(this.$editor.html())).contents();
- if (this.outerHtml(contents.last()[0]) != this.outerHtml(element))
+ var blocks = this.selection.getBlocks();
+ $.each(blocks, function()
{
- return false;
- }
- }
- else
+ if ($(this).attr('data-' + name))
+ {
+ $(this).removeAttr('data-' + name);
+ }
+ else
+ {
+ $(this).attr('data-' + name, value);
+ }
+ });
+ },
+ removeAttr: function(attr, value)
{
- if (this.$editor.contents().last()[0] !== element)
+ 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()
{
- return false;
- }
- }
+ if ($(this).attr(name))
+ {
+ $(this).removeAttr(name);
+ }
+ else
+ {
+ $(this).attr(name, value);
+ }
+ });
+ },
+ removeClass: function(className)
+ {
+ var blocks = this.selection.getBlocks();
+ $(blocks).removeClass(className);
- this.insertingAfterLastElement(element);
- }
- },
- insertingAfterLastElement: function(element)
- {
- this.bufferSet();
+ this.utils.removeEmptyAttr(blocks, 'class');
- if (this.opts.linebreaks === false)
- {
- var node = $(this.opts.emptyHtml);
- $(element).after(node);
- this.selectionStart(node);
- }
- else
- {
- var node = $('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>', this.document)[0];
- $(element).after(node);
- $(node).after(this.opts.invisibleSpace);
- this.selectionRestore();
- this.$editor.find('span#selection-marker-1').removeAttr('id');
- }
+ 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();
+ }
+ };
},
- insertLineBreak: function()
+ inline: function()
{
- this.selectionSave();
- this.$editor.find('#selection-marker-1').before('<br>' + (this.browser('webkit') ? this.opts.invisibleSpace : ''));
- this.selectionRestore();
- },
- insertDoubleLineBreak: function()
- {
- this.selectionSave();
- this.$editor.find('#selection-marker-1').before('<br><br>' + (this.browser('webkit') ? this.opts.invisibleSpace : ''));
- this.selectionRestore();
- },
- replaceLineBreak: function(element)
- {
- //var node = this.document.createTextNode('\uFEFF');
- var node = $('<br>' + this.opts.invisibleSpace);
- $(element).replaceWith(node);
- this.selectionStart(node);
- },
+ return {
+ formatting: function(name)
+ {
+ var type, value;
- // PASTE
- pasteClean: function(html)
- {
- html = this.callback('pasteBefore', false, html);
+ if (typeof this.formatting[name].style != 'undefined') type = 'style';
+ else if (typeof this.formatting[name].class != 'undefined') type = 'class';
- // ie10 fix paste links
- if (this.browser('msie'))
- {
- var tmp = $.trim(html);
- if (tmp.search(/^<a(.*?)>(.*?)<\/a>$/i) == 0)
+ if (type) value = this.formatting[name][type];
+
+ this.inline.format(this.formatting[name].tag, type, value);
+
+ },
+ format: function(tag, type, value)
{
- html = html.replace(/^<a(.*?)>(.*?)<\/a>$/i, "$2");
- }
- }
+ // Stop formatting pre
+ if (this.utils.isCurrentOrParent('PRE')) return;
- if (this.opts.pastePlainText)
- {
- var tmp = this.document.createElement('div');
+ var tags = ['b', 'bold', 'i', 'italic', 'underline', 'strikethrough', 'deleted', 'superscript', 'subscript'];
+ var replaced = ['strong', 'strong', 'em', 'em', 'u', 'del', 'del', 'sup', 'sub'];
- html = html.replace(/<br>|<\/H[1-6]>|<\/p>|<\/div>/gi, '\n');
+ for (var i = 0; i < tags.length; i++)
+ {
+ if (tag == tags[i]) tag = replaced[i];
+ }
- tmp.innerHTML = html;
- html = tmp.textContent || tmp.innerText;
+ this.inline.type = type || false;
+ this.inline.value = value || false;
- html = $.trim(html);
- html = html.replace('\n', '<br>');
- html = this.cleanParagraphy(html);
+ this.buffer.set();
+ this.$editor.focus();
- this.pasteInsert(html);
- return false;
- }
+ this.selection.get();
- // clean up pre
- if (this.currentOrParentIs('PRE'))
- {
- html = this.pastePre(html);
- this.pasteInsert(html);
- return true;
- }
+ if (this.range.collapsed)
+ {
+ this.inline.formatCollapsed(tag);
+ }
+ else
+ {
+ this.inline.formatMultiple(tag);
+ }
+ },
+ formatCollapsed: function(tag)
+ {
+ var current = this.selection.getCurrent();
+ var $parent = $(current).closest(tag + '[data-redactor-tag=' + tag + ']');
- // ms word 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, '');
+ // inline there is
+ if ($parent.size() !== 0)
+ {
+ this.caret.setAfter($parent[0]);
- // remove comments and php tags
- html = html.replace(/<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi, '');
+ // remove empty
+ if (this.utils.isEmpty($parent.text())) $parent.remove();
- // remove nbsp
- html = html.replace(/( ){2,}/gi, ' ');
- html = html.replace(/ /gi, ' ');
+ this.code.sync();
- // remove google docs marker
- html = html.replace(/<b\sid="internal-source-marker(.*?)">([\w\W]*?)<\/b>/gi, "$2");
- html = html.replace(/<b(.*?)id="docs-internal-guid(.*?)">([\w\W]*?)<\/b>/gi, "$3");
+ return;
+ }
- // strip tags
- html = this.cleanStripTags(html);
+ // create empty inline
+ var node = $('<' + tag + '>').attr('data-verified', 'redactor').attr('data-redactor-tag', tag);
+ node.html(this.opts.invisibleSpace);
- // prevert
- html = html.replace(/<td><\/td>/gi, '[td]');
- html = html.replace(/<td> <\/td>/gi, '[td]');
- html = html.replace(/<td><br><\/td>/gi, '[td]');
- html = html.replace(/<td(.*?)colspan="(.*?)"(.*?)>([\w\W]*?)<\/td>/gi, '[td colspan="$2"]$4[/td]');
- html = html.replace(/<td(.*?)rowspan="(.*?)"(.*?)>([\w\W]*?)<\/td>/gi, '[td rowspan="$2"]$4[/td]');
- html = html.replace(/<a(.*?)href="(.*?)"(.*?)>([\w\W]*?)<\/a>/gi, '[a href="$2"]$4[/a]');
- html = html.replace(/<iframe(.*?)>([\w\W]*?)<\/iframe>/gi, '[iframe$1]$2[/iframe]');
- html = html.replace(/<video(.*?)>([\w\W]*?)<\/video>/gi, '[video$1]$2[/video]');
- html = html.replace(/<audio(.*?)>([\w\W]*?)<\/audio>/gi, '[audio$1]$2[/audio]');
- html = html.replace(/<embed(.*?)>([\w\W]*?)<\/embed>/gi, '[embed$1]$2[/embed]');
- html = html.replace(/<object(.*?)>([\w\W]*?)<\/object>/gi, '[object$1]$2[/object]');
- html = html.replace(/<param(.*?)>/gi, '[param$1]');
- html = html.replace(/<img(.*?)>/gi, '[img$1]');
+ node = this.inline.setFormat(node);
- // remove classes
- html = html.replace(/ class="(.*?)"/gi, '');
+ this.insert.node(node);
+ this.caret.setEnd(node);
- // remove all attributes
- html = html.replace(/<(\w+)([\w\W]*?)>/gi, '<$1>');
+ this.code.sync();
- // remove empty
- html = html.replace(/<[^\/>][^>]*>(\s*|\t*|\n*| |<br>)<\/[^>]+>/gi, '');
- html = html.replace(/<div>\s*?\t*?\n*?(<ul>|<ol>|<p>)/gi, '$1');
+ return;
+ },
+ formatMultiple: function(tag)
+ {
+ this.inline.formatConvert(tag);
- // revert
- html = html.replace(/\[td colspan="(.*?)"\]([\w\W]*?)\[\/td\]/gi, '<td colspan="$1">$2</td>');
- html = html.replace(/\[td rowspan="(.*?)"\]([\w\W]*?)\[\/td\]/gi, '<td rowspan="$1">$2</td>');
- html = html.replace(/\[td\]/gi, '<td> </td>');
- html = html.replace(/\[a href="(.*?)"\]([\w\W]*?)\[\/a\]/gi, '<a href="$1">$2</a>');
- html = html.replace(/\[iframe(.*?)\]([\w\W]*?)\[\/iframe\]/gi, '<iframe$1>$2</iframe>');
- html = html.replace(/\[video(.*?)\]([\w\W]*?)\[\/video\]/gi, '<video$1>$2</video>');
- html = html.replace(/\[audio(.*?)\]([\w\W]*?)\[\/audio\]/gi, '<audio$1>$2</audio>');
- html = html.replace(/\[embed(.*?)\]([\w\W]*?)\[\/embed\]/gi, '<embed$1>$2</embed>');
- html = html.replace(/\[object(.*?)\]([\w\W]*?)\[\/object\]/gi, '<object$1>$2</object>');
- html = html.replace(/\[param(.*?)\]/gi, '<param$1>');
- html = html.replace(/\[img(.*?)\]/gi, '<img$1>');
+ this.selection.save();
+ document.execCommand('strikethrough');
- // convert div to p
- if (this.opts.convertDivs)
- {
- html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '<p>$2</p>');
- html = html.replace(/<\/div><p>/gi, '<p>');
- html = html.replace(/<\/p><\/div>/gi, '</p>');
- }
+ this.$editor.find('strike').each($.proxy(function(i,s)
+ {
+ var $el = $(s);
- if (this.currentOrParentIs('LI'))
- {
- html = html.replace(/<p>([\w\W]*?)<\/p>/gi, '$1<br>');
- }
- else
- {
- html = this.cleanParagraphy(html);
- }
+ this.inline.formatRemoveSameChildren($el, tag);
- // remove span
- html = html.replace(/<span(.*?)>([\w\W]*?)<\/span>/gi, '$2');
+ 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');
+ }
- // remove empty
- html = html.replace(/<img>/gi, '');
- html = html.replace(/<[^\/>][^>][^img|param|source]*>(\s*|\t*|\n*| |<br>)<\/[^>]+>/gi, '');
+ $el.replaceWith($span.html($el.contents()));
- html = html.replace(/\n{3,}/gi, '\n');
+ if (tag == 'span')
+ {
+ var $parent = $span.parent();
+ if ($parent && $parent[0].tagName == 'SPAN' && this.inline.type == 'style')
+ {
+ var arr = this.inline.value.split(';');
- // remove dirty p
- html = html.replace(/<p><p>/gi, '<p>');
- html = html.replace(/<\/p><\/p>/gi, '</p>');
+ for (var z = 0; z < arr.length; z++)
+ {
+ if (arr[z] === '') return;
+ var style = arr[z].split(':');
+ $parent.css(style[0], '');
- html = html.replace(/<li>(\s*|\t*|\n*)<p>/gi, '<li>');
- html = html.replace(/<\/p>(\s*|\t*|\n*)<\/li>/gi, '</li>');
+ if (this.utils.removeEmptyAttr($parent, 'style'))
+ {
+ $parent.replaceWith($parent.contents());
+ }
- if (this.opts.linebreaks === true)
- {
- html = html.replace(/<p(.*?)>([\w\W]*?)<\/p>/gi, '$2<br>');
- }
+ }
- // remove empty finally
- html = html.replace(/<[^\/>][^>][^img|param|source]*>(\s*|\t*|\n*| |<br>)<\/[^>]+>/gi, '');
+ }
+ }
- // remove safari local images
- html = html.replace(/<img src="webkit-fake-url\:\/\/(.*?)"(.*?)>/gi, '');
+ }, this));
- // remove p in td
- html = html.replace(/<td(.*?)>(\s*|\t*|\n*)<p>([\w\W]*?)<\/p>(\s*|\t*|\n*)<\/td>/gi, '<td$1>$3</td>');
+ if (tag != 'del')
+ {
+ var self = this;
+ this.$editor.find('inline').each(function(i,s)
+ {
+ self.utils.replaceToTag(s, 'del');
+ });
+ }
- // remove divs
- html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '$2');
- html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '$2');
+ this.selection.restore();
+ this.code.sync();
- // FF specific
- this.pasteClipboardMozilla = false;
- if (this.browser('mozilla'))
- {
- if (this.opts.clipboardUpload)
+ },
+ formatRemoveSameChildren: function($el, tag)
{
- var matches = html.match(/<img src="data:image(.*?)"(.*?)>/gi);
- if (matches !== null)
+ $el.children(tag).each(function()
{
- this.pasteClipboardMozilla = matches;
- for (k in matches)
+ var $child = $(this);
+ if (!$child.hasClass('redactor-selection-marker'))
{
- var img = matches[k].replace('<img', '<img data-mozilla-paste-image="' + k + '" ');
- html = html.replace(matches[k], img);
+ $child.contents().unwrap();
}
- }
- }
-
- // FF fix
- while (/<br>$/gi.test(html))
+ });
+ },
+ formatConvert: function(tag)
{
- html = html.replace(/<br>$/gi, '');
- }
- }
+ this.selection.save();
- // bullets again
- html = html.replace(/<p>•([\w\W]*?)<\/p>/gi, '<li>$1</li>');
+ 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 + '"]';
+ }
- // ie inserts a blank font tags when pasting
- while (/<font>([\w\W]*?)<\/font>/gi.test(html))
- {
- html = html.replace(/<font>([\w\W]*?)<\/font>/gi, '$1');
- }
+ if (tag != 'del')
+ {
+ var self = this;
+ this.$editor.find('del').each(function(i,s)
+ {
+ self.utils.replaceToTag(s, 'inline');
+ });
+ }
- // ms word lists breakline
- html = html.replace(/<p>\n?<li>/gi, '<li>');
- if (this.browser('msie') && !this.isIe11())
- {
- html = html.replace(/\n/g, '');
- }
+ this.$editor.find('[data-redactor-tag="' + tag + '"]' + find).each(function()
+ {
+ if (find === '' && tag == 'span' && this.tagName.toLowerCase() == tag) return;
- this.pasteInsert(html);
+ var $el = $(this);
+ $el.replaceWith($('<strike />').html($el.contents()));
- },
- pastePre: function(s)
- {
- s = s.replace(/<br>|<\/H[1-6]>|<\/p>|<\/div>/gi, '\n');
+ });
- var tmp = this.document.createElement('div');
- tmp.innerHTML = s;
- return this.cleanEncodeEntities(tmp.textContent || tmp.innerText);
- },
- pasteInsert: function(html)
- {
- if (this.selectall)
- {
- if (!this.opts.linebreaks) this.$editor.html(this.opts.emptyHtml);
- else this.$editor.html('');
+ this.selection.restore();
+ },
+ setFormat: function(node)
+ {
+ switch (this.inline.type)
+ {
+ case 'class':
- this.$editor.focus();
- }
+ 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);
+ }
- html = this.callback('pasteAfter', false, html);
- this.insertHtml(html);
+ break;
+ case 'style':
- this.selectall = false;
- setTimeout($.proxy(function()
- {
- this.rtePaste = false;
+ node[0].style.cssText = this.inline.value;
+ node.attr('data-redactor-style', this.inline.value);
- // FF specific
- if (this.browser('mozilla'))
+ break;
+ }
+
+ return node;
+ },
+ removeStyle: function()
{
- this.$editor.find('p:empty').remove()
- }
- if (this.pasteClipboardMozilla !== false)
- {
- this.pasteClipboardUploadMozilla();
- }
+ this.buffer.set();
+ var current = this.selection.getCurrent();
+ var nodes = this.selection.getInlines();
- }, this), 100);
+ this.selection.save();
- if (this.opts.autoresize && this.fullscreen !== true) $(this.document.body).scrollTop(this.saveScroll);
- else this.$editor.scrollTop(this.saveScroll);
- },
- pasteClipboardUploadMozilla: function()
- {
- var imgs = this.$editor.find('img[data-mozilla-paste-image]');
- $.each(imgs, $.proxy(function(i,s)
- {
- var $s = $(s);
- var arr = s.src.split(",");
- var data = arr[1]; // raw base64
- var contentType = arr[0].split(";")[0].split(":")[1];
+ if (current && current.tagName === 'SPAN')
+ {
+ var $s = $(current);
- $.post(this.opts.clipboardUploadUrl, {
- contentType: contentType,
- data: data
+ $s.removeAttr('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.removeAttr('style');
+ if ($s[0].attributes.length === 0)
+ {
+ $s.replaceWith($s.contents());
+ }
+ }
+ }, this));
+
+ this.selection.restore();
+ this.code.sync();
+
},
- $.proxy(function(data)
+ removeStyleRule: function(name)
{
- var json = (typeof data === 'string' ? $.parseJSON(data) : data);
- $s.attr('src', json.filelink);
- $s.removeAttr('data-mozilla-paste-image');
+ this.buffer.set();
+ var parent = this.selection.getParent();
+ var nodes = this.selection.getInlines();
- this.sync();
+ this.selection.save();
- // upload callback
- this.callback('imageUpload', $s, json);
+ if (parent && parent.tagName === 'SPAN')
+ {
+ var $s = $(parent);
- }, this));
+ $s.css(name, '');
+ this.utils.removeEmptyAttr($s, 'style');
+ if ($s[0].attributes.length === 0)
+ {
+ $s.replaceWith($s.contents());
+ }
+ }
- }, this));
- },
- pasteClipboardUpload: function(e)
- {
- var result = e.target.result;
- var arr = result.split(",");
- var data = arr[1]; // raw base64
- var contentType = arr[0].split(";")[0].split(":")[1];
+ $.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));
- if (this.opts.clipboardUpload)
- {
- $.post(this.opts.clipboardUploadUrl, {
- contentType: contentType,
- data: data
+ this.selection.restore();
+ this.code.sync();
},
- $.proxy(function(data)
+ removeFormat: function()
{
- var json = (typeof data === 'string' ? $.parseJSON(data) : data);
+ this.buffer.set();
+ var current = this.selection.getCurrent();
- var html = '<img src="' + json.filelink + '" id="clipboard-image-marker" />';
- this.execCommand('inserthtml', html, false);
+ this.selection.save();
- var image = $(this.$editor.find('img#clipboard-image-marker'));
+ document.execCommand('removeFormat');
- if (image.length) image.removeAttr('id');
- else image = false;
+ if (current && current.tagName === 'SPAN')
+ {
+ $(current).replaceWith($(current).contents());
+ }
- this.sync();
- // upload callback
- if (image)
+ $.each(this.selection.getNodes(), $.proxy(function(i,s)
{
- this.callback('imageUpload', image, json);
- }
+ 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();
- }, this));
- }
- else
- {
- this.insertHtml('<img src="' + result + '" />');
- }
+ },
+ toggleClass: function(className)
+ {
+ this.inline.format('span', 'class', className);
+ },
+ toggleStyle: function(value)
+ {
+ this.inline.format('span', 'style', value);
+ }
+ };
},
-
- // BUFFER
- bufferSet: function(html)
+ insert: function()
{
- if (html !== undefined) this.opts.buffer.push(html);
- else
- {
- this.selectionSave();
- this.opts.buffer.push(this.$editor.html());
- this.selectionRemoveMarkers('buffer');
- }
- },
- bufferUndo: function()
- {
- if (this.opts.buffer.length === 0)
- {
- this.$editor.focus();
- return;
- }
+ return {
+ set: function(html, clean)
+ {
+ this.placeholder.remove();
- // rebuffer
- this.selectionSave();
- this.opts.rebuffer.push(this.$editor.html());
- this.selectionRestore(false, true);
+ html = this.clean.setVerified(html);
- this.$editor.html(this.opts.buffer.pop());
+ if (typeof clean == 'undefined')
+ {
+ html = this.clean.onPaste(html, false);
+ }
- this.selectionRestore();
- setTimeout($.proxy(this.observeStart, this), 100);
- },
- bufferRedo: function()
- {
- if (this.opts.rebuffer.length === 0)
- {
- this.$editor.focus();
- return false;
- }
+ this.$editor.html(html);
+ this.selection.remove();
+ this.focus.setEnd();
+ this.clean.normalizeLists();
+ this.code.sync();
+ this.observe.load();
- // buffer
- this.selectionSave();
- this.opts.buffer.push(this.$editor.html());
- this.selectionRestore(false, true);
+ if (typeof clean == 'undefined')
+ {
+ setTimeout($.proxy(this.clean.clearUnverified, this), 10);
+ }
+ },
+ text: function(text)
+ {
+ this.placeholder.remove();
- this.$editor.html(this.opts.rebuffer.pop());
- this.selectionRestore(true);
- setTimeout($.proxy(this.observeStart, this), 4);
- },
+ text = text.toString();
+ text = $.trim(text);
+ text = this.clean.getPlainText(text, false);
- // OBSERVE
- observeStart: function()
- {
- this.observeImages();
+ this.$editor.focus();
- if (this.opts.observeLinks) this.observeLinks();
- },
- observeLinks: function()
- {
- this.$editor.find('a').on('click', $.proxy(this.linkObserver, this));
- this.$editor.on('click.redactor', $.proxy(function(e)
- {
- this.linkObserverTooltipClose(e);
+ if (this.utils.browser('msie'))
+ {
+ this.insert.htmlIe(text);
+ }
+ else
+ {
+ this.selection.get();
- }, this));
- $(document).on('click.redactor', $.proxy(function(e)
- {
- this.linkObserverTooltipClose(e);
+ 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));
- },
- observeImages: function()
- {
- if (this.opts.observeImages === false) return false;
+ this.range.insertNode(frag);
- this.$editor.find('img').each($.proxy(function(i, elem)
- {
- if (this.browser('msie')) $(elem).attr('unselectable', 'on');
- this.imageResize(elem);
+ if (lastNode)
+ {
+ var range = this.range.cloneRange();
+ range.setStartAfter(lastNode);
+ range.collapse(true);
+ this.sel.removeAllRanges();
+ this.sel.addRange(range);
+ }
+ }
- }, this));
- },
- linkObserver: function(e)
- {
- var $link = $(e.target);
- var pos = $link.offset();
- if (this.opts.iframe)
- {
- var posFrame = this.$frame.offset();
- pos.top = posFrame.top + (pos.top - $(this.document).scrollTop());
- pos.left += posFrame.left;
- }
+ this.code.sync();
+ this.clean.clearUnverified();
+ },
+ html: function(html, clean)
+ {
+ this.placeholder.remove();
- var tooltip = $('<span class="redactor-link-tooltip"></span>');
+ if (typeof clean == 'undefined') clean = true;
- var href = $link.attr('href');
- if (href.length > 24) href = href.substring(0,24) + '...';
+ this.$editor.focus();
- var aLink = $('<a href="' + $link.attr('href') + '" target="_blank">' + href + '</a>').on('click', $.proxy(function(e)
- {
- this.linkObserverTooltipClose(false);
- }, this));
+ html = this.clean.setVerified(html);
- var aEdit = $('<a href="#">' + this.opts.curLang.edit + '</a>').on('click', $.proxy(function(e)
- {
- e.preventDefault();
- this.linkShow();
- this.linkObserverTooltipClose(false);
+ if (clean)
+ {
+ html = this.clean.onPaste(html);
+ }
- }, this));
+ if (this.utils.browser('msie'))
+ {
+ this.insert.htmlIe(html);
+ }
+ else
+ {
+ if (this.clean.singleLine) this.insert.execHtml(html);
+ else document.execCommand('insertHTML', null, html);
- var aUnlink = $('<a href="#">' + this.opts.curLang.unlink + '</a>').on('click', $.proxy(function(e)
- {
- e.preventDefault();
- this.execCommand('unlink');
- this.linkObserverTooltipClose(false);
+ this.insert.htmlFixMozilla();
+ }
- }, this));
+ this.clean.normalizeLists();
+ // remove empty paragraphs finaly
+ if (!this.opts.linebreaks)
+ {
+ this.$editor.find('p').each($.proxy(this.utils.removeEmpty, this));
+ }
- tooltip.append(aLink);
- tooltip.append(' | ');
- tooltip.append(aEdit);
- tooltip.append(' | ');
- tooltip.append(aUnlink);
- tooltip.css({
- top: (pos.top + 20) + 'px',
- left: pos.left + 'px'
- });
+ this.code.sync();
+ this.observe.load();
- $('.redactor-link-tooltip').remove();
- $('body').append(tooltip);
- },
- linkObserverTooltipClose: function(e)
- {
- if (e !== false && e.target.tagName == 'A') return false;
- $('.redactor-link-tooltip').remove();
- },
+ if (clean)
+ {
+ this.clean.clearUnverified();
+ }
- // SELECTION
- getSelection: function()
- {
- if (!this.opts.rangy) return this.document.getSelection();
- else // rangy
- {
- if (!this.opts.iframe) return rangy.getSelection();
- else return rangy.getSelection(this.$frame[0]);
- }
- },
- getRange: function()
- {
- if (!this.opts.rangy)
- {
- if (this.document.getSelection)
+ },
+ htmlFixMozilla: function()
{
- var sel = this.getSelection();
- if (sel.getRangeAt && sel.rangeCount) return sel.getRangeAt(0);
- }
+ // FF inserts empty p when content was selected dblclick
+ if (!this.utils.browser('mozilla')) return;
- return this.document.createRange();
- }
- else // rangy
- {
- if (!this.opts.iframe) return rangy.createRange();
- else return rangy.createRange(this.iframeDoc());
- }
- },
- selectionElement: function(node)
- {
- this.setCaret(node);
- },
- selectionStart: function(node)
- {
- this.selectionSet(node[0] || node, 0, null, 0);
- },
- selectionEnd: function(node)
- {
- this.selectionSet(node[0] || node, 1, null, 1);
- },
- selectionSet: function(orgn, orgo, focn, foco)
- {
- if (focn == null) focn = orgn;
- if (foco == null) foco = orgo;
+ var $next = $(this.selection.getBlock()).next();
+ if ($next.length > 0 && $next[0].tagName == 'P' && $next.html() === '')
+ {
+ $next.remove();
+ }
- var sel = this.getSelection();
- if (!sel) return;
+ },
+ htmlIe: function(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');
- var range = this.getRange();
- range.setStart(orgn, orgo);
- range.setEnd(focn, foco );
+ if (parent && blocksMatch) this.insert.ie11FixInserting(parent, html);
+ else this.insert.ie11PasteFrag(html);
- try {
- sel.removeAllRanges();
- } catch (e) {}
+ return;
+ }
- sel.addRange(range);
- },
- selectionWrap: function(tag)
- {
- tag = tag.toLowerCase();
+ document.selection.createRange().pasteHTML(html);
- var block = this.getBlock();
- if (block)
- {
- var wrapper = this.formatChangeTag(block, tag);
- this.sync();
- return wrapper;
- }
+ },
+ execHtml: function(html)
+ {
+ html = this.clean.setVerified(html);
- var sel = this.getSelection();
- var range = sel.getRangeAt(0);
- var wrapper = document.createElement(tag);
- wrapper.appendChild(range.extractContents());
- range.insertNode(wrapper);
+ this.selection.get();
- this.selectionElement(wrapper);
+ this.range.deleteContents();
- return wrapper;
- },
- selectionAll: function()
- {
- var range = this.getRange();
- range.selectNodeContents(this.$editor[0]);
+ var el = document.createElement('div');
+ el.innerHTML = html;
- var sel = this.getSelection();
- sel.removeAllRanges();
- sel.addRange(range);
- },
- selectionRemove: function()
- {
- this.getSelection().removeAllRanges();
- },
- getCaretOffset: function (element)
- {
- var caretOffset = 0;
+ var frag = document.createDocumentFragment(), node, lastNode;
+ while ((node = el.firstChild))
+ {
+ lastNode = frag.appendChild(node);
+ }
- var range = this.getRange();
- var preCaretRange = range.cloneRange();
- preCaretRange.selectNodeContents(element);
- preCaretRange.setEnd(range.endContainer, range.endOffset);
- caretOffset = $.trim(preCaretRange.toString()).length;
+ this.range.insertNode(frag);
- return caretOffset;
- },
- getCaretOffsetRange: function()
- {
- return new Range(this.getSelection().getRangeAt(0));
- },
- setCaret: function (el, start, end)
- {
- if (typeof end === 'undefined') end = start;
- el = el[0] || el;
+ this.range.collapse(true);
+ this.caret.setAfter(lastNode);
- var range = this.getRange();
- range.selectNodeContents(el);
+ },
+ node: function(node)
+ {
+ node = node[0] || node;
- var textNodes = this.getTextNodesIn(el);
- var foundStart = false;
- var charCount = 0, endCharCount;
+ this.selection.get();
+ this.range.deleteContents();
+ this.range.insertNode(node);
+ this.range.collapse(false);
+ this.selection.addRange();
- if (textNodes.length == 1 && start)
- {
- range.setStart(textNodes[0], start);
- range.setEnd(textNodes[0], end);
- }
- else
- {
- for (var i = 0, textNode; textNode = textNodes[i++];)
+ return node;
+ },
+ nodeToPoint: function(node, x, y)
{
- endCharCount = charCount + textNode.length;
- if (!foundStart && start >= charCount && (start < endCharCount || (start == endCharCount && i < textNodes.length)))
+ node = node[0] || node;
+
+ this.selection.get();
+
+ var range;
+ if (document.caretPositionFromPoint)
{
- range.setStart(textNode, start - charCount);
- foundStart = true;
+ 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;
- if (foundStart && end <= endCharCount)
+ var range;
+ var x = e.clientX, y = e.clientY;
+ if (document.caretPositionFromPoint)
{
- range.setEnd( textNode, end - charCount );
- break;
+ 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();
+ }
- charCount = endCharCount;
- }
- }
+ },
+ ie11FixInserting: function(parent, html)
+ {
+ var node = document.createElement('span');
+ node.className = 'redactor-ie-paste';
+ this.insert.node(node);
- var sel = this.getSelection();
- sel.removeAllRanges();
- sel.addRange( range );
- },
- getTextNodesIn: function (node)
- {
- var textNodes = [];
+ var parHtml = $(parent).html();
- if (node.nodeType == 3) textNodes.push(node);
- else
- {
- var children = node.childNodes;
- for (var i = 0, len = children.length; i < len; ++i)
+ parHtml = '<p>' + parHtml.replace(/<span class="redactor-ie-paste"><\/span>/gi, '</p>' + html + '<p>') + '</p>';
+ $(parent).replaceWith(parHtml);
+ },
+ ie11PasteFrag: function(html)
{
- textNodes.push.apply(textNodes, this.getTextNodesIn(children[i]));
- }
- }
+ this.selection.get();
+ this.range.deleteContents();
- return textNodes;
- },
+ var el = document.createElement("div");
+ el.innerHTML = html;
- // GET ELEMENTS
- getCurrent: function()
- {
- var el = false;
- var sel = this.getSelection();
+ var frag = document.createDocumentFragment(), node, lastNode;
+ while ((node = el.firstChild))
+ {
+ lastNode = frag.appendChild(node);
+ }
- if (sel && sel.rangeCount > 0) el = sel.getRangeAt(0).startContainer;
-
- return this.isParentRedactor(el);
+ this.range.insertNode(frag);
+ }
+ };
},
- getParent: function(elem)
+ caret: function()
{
- elem = elem || this.getCurrent();
- if (elem) return this.isParentRedactor( $( elem ).parent()[0] );
- else return false;
- },
- getBlock: function(node)
- {
- if (typeof node === 'undefined') node = this.getCurrent();
+ return {
+ setStart: function(node)
+ {
+ // inline tag
+ if (!this.utils.isBlock(node))
+ {
+ var space = this.utils.createSpaceElement();
- while (node)
- {
- if (this.nodeTestBlocks(node))
+ $(node).prepend(space);
+ this.caret.setEnd(space);
+ }
+ else
+ {
+ this.caret.set(node, 0, node, 0);
+ }
+ },
+ setEnd: function(node)
{
- if ($(node).hasClass('redactor_editor')) return false;
- return node;
- }
+ this.caret.set(node, 1, node, 1);
+ },
+ set: function(orgn, orgo, focn, foco)
+ {
+ // focus
+ if (!this.utils.browser('msie')) this.$editor.focus();
- node = node.parentNode;
- }
+ orgn = orgn[0] || orgn;
+ focn = focn[0] || focn;
- return false;
- },
- getBlocks: function(nodes)
- {
- var newnodes = [];
- if (typeof nodes == 'undefined')
- {
- var range = this.getRange();
- if (range && range.collapsed === true) return [this.getBlock()];
- var nodes = this.getNodes(range);
- }
+ if (this.utils.isBlockTag(orgn.tagName) && orgn.innerHTML === '')
+ {
+ orgn.innerHTML = this.opts.invisibleSpace;
+ }
- $.each(nodes, $.proxy(function(i,node)
- {
- if (this.opts.iframe === false && $(node).parents('div.redactor_editor').size() == 0) return false;
- if (this.nodeTestBlocks(node)) newnodes.push(node);
+ if (orgn.tagName == 'BR' && this.opts.linebreaks === false)
+ {
+ var par = $(this.opts.emptyHtml)[0];
+ $(orgn).replaceWith(par);
+ orgn = par;
+ focn = orgn;
+ }
- }, this));
+ this.selection.get();
- if (newnodes.length === 0) newnodes = [this.getBlock()];
+ try {
+ this.range.setStart(orgn, orgo);
+ this.range.setEnd(focn, foco);
+ }
+ catch (e) {}
- return newnodes;
- },
- nodeTestBlocks: function(node)
- {
- return node.nodeType == 1 && this.rTestBlock.test(node.nodeName);
- },
- tagTestBlock: function(tag)
- {
- return this.rTestBlock.test(tag);
- },
- getNodes: function(range, tag)
- {
- if (typeof range == 'undefined' || range == false) var range = this.getRange();
- if (range && range.collapsed === true)
- {
- if (typeof tag === 'undefined' && this.tagTestBlock(tag))
+ this.selection.addRange();
+ },
+ setAfter: function(node)
{
- var block = this.getBlock();
- if (block.tagName == tag) return [block];
- else return [];
- }
- else
+ var tag = $(node)[0].tagName;
+
+ // inline tag
+ if (tag != 'BR' && !this.utils.isBlock(node))
+ {
+ var space = this.utils.createSpaceElement();
+
+ $(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');
+ }
+ }
+ },
+ setBefore: function(node)
{
- return [this.getCurrent()];
- }
- }
+ // block tag
+ if (this.utils.isBlock(node))
+ {
+ this.caret.setEnd($(node).prev());
+ }
+ else
+ {
+ this.caret.setAfterOrBefore(node, 'before');
+ }
+ },
+ setAfterOrBefore: function(node, type)
+ {
+ // focus
+ if (!this.utils.browser('msie')) this.$editor.focus();
- var nodes = [], finalnodes = [];
+ node = node[0] || node;
- var sel = this.document.getSelection();
- if (!sel.isCollapsed) nodes = this.getRangeSelectedNodes(sel.getRangeAt(0));
+ this.selection.get();
- $.each(nodes, $.proxy(function(i,node)
- {
- if (this.opts.iframe === false && $(node).parents('div.redactor_editor').size() == 0) return false;
+ if (type == 'after')
+ {
+ try {
- if (typeof tag === 'undefined')
- {
- if ($.trim(node.textContent) != '')
+ this.range.setStartAfter(node);
+ this.range.setEndAfter(node);
+ }
+ catch (e) {}
+ }
+ else
{
- finalnodes.push(node);
+ try {
+ this.range.setStartBefore(node);
+ this.range.setEndBefore(node);
+ }
+ catch (e) {}
}
- }
- else if (node.tagName == tag)
- {
- finalnodes.push(node);
- }
- }, this));
- if (finalnodes.length == 0)
- {
- if (typeof tag === 'undefined' && this.tagTestBlock(tag))
+ this.range.collapse(false);
+ this.selection.addRange();
+ },
+ getOffsetOfElement: function(node)
{
- var block = this.getBlock();
- if (block.tagName == tag) return finalnodes.push(block);
- else return [];
- }
- else
- {
- finalnodes.push(this.getCurrent());
- }
- }
+ node = node[0] || node;
- // last element filtering
- var last = finalnodes[finalnodes.length-1];
- if (this.nodeTestBlocks(last))
- {
- finalnodes = finalnodes.slice(0, -1);
- }
+ this.selection.get();
- return finalnodes;
- },
- getElement: function(node)
- {
- if (!node) node = this.getCurrent();
- while (node)
- {
- if (node.nodeType == 1)
+ var cloned = this.range.cloneRange();
+ cloned.selectNodeContents(node);
+ cloned.setEnd(this.range.endContainer, this.range.endOffset);
+
+ return $.trim(cloned.toString()).length;
+ },
+ getOffset: function()
{
- if ($(node).hasClass('redactor_editor')) return false;
- return node;
- }
+ 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;
+ }
- node = node.parentNode;
- }
+ return offset;
+ },
+ setOffset: function(start, end)
+ {
+ if (typeof end == 'undefined') end = start;
+ if (!this.focus.isFocused()) this.focus.setStart();
- return false;
- },
- getRangeSelectedNodes: function(range)
- {
- range = range || this.getRange();
- var node = range.startContainer;
- var endNode = range.endContainer;
+ var range = document.createRange();
+ var sel = document.getSelection();
+ var node, offset = 0;
+ var walker = document.createTreeWalker(this.$editor[0], NodeFilter.SHOW_TEXT, null, null);
- if (node == endNode) return [node];
+ while (node = walker.nextNode())
+ {
+ offset += node.nodeValue.length;
+ if (offset > start)
+ {
+ range.setStart(node, node.nodeValue.length + start - offset);
+ start = Infinity;
+ }
- var rangeNodes = [];
- while (node && node != endNode)
- {
- rangeNodes.push(node = this.nextNode(node));
- }
+ if (offset >= end)
+ {
+ range.setEnd(node, node.nodeValue.length + end - offset);
+ break;
+ }
+ }
- node = range.startContainer;
- while (node && node != range.commonAncestorContainer)
- {
- rangeNodes.unshift(node);
- node = node.parentNode;
- }
-
- return rangeNodes;
- },
- nextNode: function(node)
- {
- if (node.hasChildNodes()) return node.firstChild;
- else
- {
- while (node && !node.nextSibling)
+ sel.removeAllRanges();
+ sel.addRange(range);
+ },
+ setToPoint: function(start, end)
{
- node = node.parentNode;
+ this.caret.setOffset(start, end);
+ },
+ getCoords: function()
+ {
+ return this.caret.getOffset();
}
-
- if (!node) return null;
- return node.nextSibling;
- }
+ };
},
-
- // GET SELECTION HTML OR TEXT
- getSelectionText: function()
+ selection: function()
{
- return this.getSelection().toString();
- },
- getSelectionHtml: function()
- {
- var html = '';
+ return {
+ get: function()
+ {
+ this.sel = document.getSelection();
- var sel = this.getSelection();
- if (sel.rangeCount)
- {
- var container = this.document.createElement( "div" );
- var len = sel.rangeCount;
- for (var i = 0; i < len; ++i)
+ if (document.getSelection && this.sel.getRangeAt && this.sel.rangeCount)
+ {
+ this.range = this.sel.getRangeAt(0);
+ }
+ else
+ {
+ this.range = document.createRange();
+ }
+ },
+ addRange: function()
{
- container.appendChild(sel.getRangeAt(i).cloneContents());
- }
+ try {
+ this.sel.removeAllRanges();
+ } catch (e) {}
- html = container.innerHTML;
- }
+ this.sel.addRange(this.range);
+ },
+ getCurrent: function()
+ {
+ var el = false;
+ this.selection.get();
- return this.syncClean(html);
- },
+ if (this.sel && this.sel.rangeCount > 0)
+ {
+ el = this.sel.getRangeAt(0).startContainer;
+ }
- // SAVE & RESTORE
- selectionSave: function()
- {
- if (!this.isFocused()) this.$editor.focus();
+ return this.utils.isRedactorParent(el);
+ },
+ getParent: function(elem)
+ {
+ elem = elem || this.selection.getCurrent();
+ if (elem)
+ {
+ return this.utils.isRedactorParent($(elem).parent()[0]);
+ }
- if (!this.opts.rangy)
- {
- this.selectionCreateMarker(this.getRange());
- }
- // rangy
- else
- {
- this.savedSel = rangy.saveSelection();
- }
- },
- selectionCreateMarker: function(range, remove)
- {
- if (!range) return;
+ return false;
+ },
+ getBlock: function(node)
+ {
+ node = node || this.selection.getCurrent();
- var node1 = $('<span id="selection-marker-1" class="redactor-selection-marker">' + this.opts.invisibleSpace + '</span>', this.document)[0];
- var node2 = $('<span id="selection-marker-2" class="redactor-selection-marker">' + this.opts.invisibleSpace + '</span>', this.document)[0];
+ while (node)
+ {
+ if (this.utils.isBlockTag(node.tagName))
+ {
+ return ($(node).hasClass('redactor-editor')) ? false : node;
+ }
- if (range.collapsed === true)
- {
- this.selectionSetMarker(range, node1, true);
- }
- else
- {
- this.selectionSetMarker(range, node1, true);
- this.selectionSetMarker(range, node2, false);
- }
+ node = node.parentNode;
+ }
- this.savedSel = this.$editor.html();
+ return false;
+ },
+ getInlines: function(nodes)
+ {
+ this.selection.get();
- this.selectionRestore(false, false);
- },
- selectionSetMarker: function(range, node, type)
- {
- var boundaryRange = range.cloneRange();
+ if (this.range && this.range.collapsed)
+ {
+ return false;
+ }
- boundaryRange.collapse(type);
+ var inlines = [];
+ nodes = (typeof nodes == 'undefined') ? this.selection.getNodes() : nodes;
+ var inlineTags = this.opts.inlineTags;
+ inlineTags.push('span');
+ $.each(nodes, $.proxy(function(i,node)
+ {
+ if ($.inArray(node.tagName.toLowerCase(), inlineTags) != -1)
+ {
+ inlines.push(node);
+ }
- boundaryRange.insertNode(node);
- boundaryRange.detach();
- },
- selectionRestore: function(replace, remove)
- {
- if (!this.opts.rangy)
- {
- if (replace === true && this.savedSel)
+ }, this));
+
+ return (inlines.length === 0) ? false : inlines;
+ },
+ getBlocks: function(nodes)
{
- this.$editor.html(this.savedSel);
- }
+ this.selection.get();
- var node1 = this.$editor.find('span#selection-marker-1');
- var node2 = this.$editor.find('span#selection-marker-2');
+ if (this.range && this.range.collapsed)
+ {
+ return [this.selection.getBlock()];
+ }
- if (this.browser('mozilla'))
- {
- this.$editor.focus();
- }
- else if (!this.isFocused())
- {
- this.$editor.focus();
- }
+ var blocks = [];
+ nodes = (typeof nodes == 'undefined') ? this.selection.getNodes() : nodes;
+ $.each(nodes, $.proxy(function(i,node)
+ {
+ if (this.utils.isBlock(node))
+ {
+ this.selection.lastBlock = node;
+ blocks.push(node);
+ }
- if (node1.length != 0 && node2.length != 0)
- {
- this.selectionSet(node1[0], 0, node2[0], 0);
- }
- else if (node1.length != 0)
- {
- this.selectionSet(node1[0], 0, null, 0);
- }
+ }, this));
- if (remove !== false)
+ return (blocks.length === 0) ? [this.selection.getBlock()] : blocks;
+ },
+ getLastBlock: function()
{
- this.selectionRemoveMarkers();
- this.savedSel = false;
- }
- }
- // rangy
- else
- {
- rangy.restoreSelection(this.savedSel);
- }
- },
- selectionRemoveMarkers: function(type)
- {
- if (!this.opts.rangy)
- {
- $.each(this.$editor.find('span.redactor-selection-marker'), function()
+ return this.selection.lastBlock;
+ },
+ getNodes: function()
{
- var html = $.trim($(this).html().replace(/[^\u0000-\u1C7F]/g, ''));
- if (html == '')
+ this.selection.get();
+
+ var startNode = this.selection.getNodesMarker(1);
+ var endNode = this.selection.getNodesMarker(2);
+
+ this.selection.setNodesMarker(this.range, startNode, true);
+
+ if (this.range.collapsed === false)
{
- $(this).remove();
+ this.selection.setNodesMarker(this.range, endNode, false);
}
else
{
- $(this).removeAttr('class').removeAttr('id');
+ endNode = startNode;
}
- });
- }
- // rangy
- else
- {
- rangy.removeMarkers(this.savedSel);
- }
- },
- // TABLE
- tableShow: function()
- {
- this.selectionSave();
+ var nodes = [];
+ var counter = 0;
- this.modalInit(this.opts.curLang.table, this.opts.modal_table, 300, $.proxy(function()
- {
- $('#redactor_insert_table_btn').click($.proxy(this.tableInsert, this));
+ var self = this;
+ this.$editor.find('*').each(function()
+ {
+ if (this == startNode)
+ {
+ var parent = $(this).parent();
+ if (parent.length !== 0 && parent[0].tagName != 'BODY' && self.utils.isRedactorParent(parent[0]))
+ {
+ nodes.push(parent[0]);
+ }
- setTimeout(function()
- {
- $('#redactor_table_rows').focus();
+ nodes.push(this);
+ counter = 1;
+ }
+ else
+ {
+ if (counter > 0)
+ {
+ nodes.push(this);
+ counter = counter + 1;
+ }
+ }
- }, 200);
+ if (this == endNode)
+ {
+ return false;
+ }
- }, this));
- },
- tableInsert: function()
- {
- var rows = $('#redactor_table_rows').val(),
- columns = $('#redactor_table_columns').val(),
- $table_box = $('<div></div>'),
- tableId = Math.floor(Math.random() * 99999),
- $table = $('<table id="table' + tableId + '"><tbody></tbody></table>'),
- i, $row, z, $column;
+ });
- for (i = 0; i < rows; i++)
- {
- $row = $('<tr></tr>');
+ var finalNodes = [];
+ var len = nodes.length;
+ for (var i = 0; i < len; i++)
+ {
+ if (nodes[i].id != 'nodes-marker-1' && nodes[i].id != 'nodes-marker-2')
+ {
+ finalNodes.push(nodes[i]);
+ }
+ }
- for (z = 0; z < columns; z++)
- {
- $column = $('<td>' + this.opts.invisibleSpace + '</td>');
+ this.selection.removeNodesMarkers();
- // set the focus to the first td
- if (i === 0 && z === 0) $column.append('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>');
+ return finalNodes;
- $($row).append($column);
- }
+ },
+ getNodesMarker: function(num)
+ {
+ return $('<span id="nodes-marker-' + num + '" class="redactor-nodes-marker" data-verified="redactor">' + this.opts.invisibleSpace + '</span>')[0];
+ },
+ setNodesMarker: function(range, node, type)
+ {
+ range = range.cloneRange();
- $table.append($row);
- }
+ try {
+ range.collapse(type);
+ range.insertNode(node);
+ }
+ catch (e) {}
+ },
+ removeNodesMarkers: function()
+ {
+ $(document).find('span.redactor-nodes-marker').remove();
+ this.$editor.find('span.redactor-nodes-marker').remove();
+ },
+ fromPoint: function(start, end)
+ {
+ this.caret.setOffset(start, end);
+ },
+ wrap: function(tag)
+ {
+ this.selection.get();
- $table_box.append($table);
- var html = $table_box.html();
+ if (this.range.collapsed) return false;
- this.modalClose();
- this.selectionRestore();
+ var wrapper = document.createElement(tag);
+ wrapper.appendChild(this.range.extractContents());
+ this.range.insertNode(wrapper);
- var current = this.getBlock() || this.getCurrent();
+ return wrapper;
+ },
+ selectElement: function(node)
+ {
+ this.caret.set(node, 0, node, 1);
+ },
+ selectAll: function()
+ {
+ this.selection.get();
+ this.range.selectNodeContents(this.$editor[0]);
+ this.selection.addRange();
+ },
+ remove: function()
+ {
+ this.selection.get();
+ this.sel.removeAllRanges();
+ },
+ save: function()
+ {
+ this.selection.createMarkers();
+ },
+ createMarkers: function()
+ {
+ this.selection.get();
- if (current && current.tagName != 'BODY') $(current).after(html)
- else this.insertHtmlAdvanced(html, false);
+ var node1 = this.selection.getMarker(1);
- this.selectionRestore();
+ this.selection.setMarker(this.range, node1, true);
- var table = this.$editor.find('#table' + tableId);
- this.buttonActiveObserver();
+ if (this.range.collapsed === false)
+ {
+ var node2 = this.selection.getMarker(2);
+ this.selection.setMarker(this.range, node2, false);
+ }
- table.find('span#selection-marker-1').remove();
- table.removeAttr('id');
+ this.savedSel = this.$editor.html();
+ },
+ getMarker: function(num)
+ {
+ if (typeof num == 'undefined') num = 1;
- this.sync();
- },
- tableDeleteTable: function()
- {
- var $table = $(this.getParent()).closest('table');
- if (!this.isParentRedactor($table)) return false;
- if ($table.size() == 0) return false;
+ return $('<span id="selection-marker-' + num + '" class="redactor-selection-marker" data-verified="redactor">' + this.opts.invisibleSpace + '</span>')[0];
+ },
+ getMarkerAsHtml: function(num)
+ {
+ return this.utils.getOuterHtml(this.selection.getMarker(num));
+ },
+ setMarker: function(range, node, type)
+ {
+ range = range.cloneRange();
- this.bufferSet();
+ try {
+ range.collapse(type);
+ range.insertNode(node);
+ }
+ catch (e)
+ {
+ this.focus.setStart();
+ }
+ },
+ restore: function()
+ {
+ var node1 = this.$editor.find('span#selection-marker-1');
+ var node2 = this.$editor.find('span#selection-marker-2');
- $table.remove();
- this.sync();
- },
- tableDeleteRow: function()
- {
- var $table = $(this.getParent()).closest('table');
- if (!this.isParentRedactor($table)) return false;
- if ($table.size() == 0) return false;
+ if (node1.length !== 0 && node2.length !== 0)
+ {
+ this.caret.set(node1, 0, node2, 0);
+ }
+ else if (node1.length !== 0)
+ {
+ this.caret.set(node1, 0, node1, 0);
+ }
+ else
+ {
+ this.$editor.focus();
+ }
- this.bufferSet();
+ this.selection.removeMarkers();
+ this.savedSel = false;
- var $current_tr = $(this.getParent()).closest('tr');
- var $focus_tr = $current_tr.prev().length ? $current_tr.prev() : $current_tr.next();
- if ($focus_tr.length)
- {
- var $focus_td = $focus_tr.children('td' ).first();
- if ($focus_td.length)
+ },
+ removeMarkers: function()
{
- $focus_td.prepend('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>');
- }
- }
+ this.$editor.find('span.redactor-selection-marker').remove();
+ },
+ getText: function()
+ {
+ this.selection.get();
- $current_tr.remove();
- this.selectionRestore();
- this.sync();
- },
- tableDeleteColumn: function()
- {
- var $table = $(this.getParent()).closest('table');
- if (!this.isParentRedactor($table)) return false;
- if ($table.size() == 0) return false;
-
- this.bufferSet();
-
- var $current_td = $(this.getParent()).closest('td');
- var index = $current_td.get(0).cellIndex;
-
- // Set the focus correctly
- $table.find('tr').each($.proxy(function(i, elem)
- {
- var focusIndex = index - 1 < 0 ? index + 1 : index - 1;
- if (i === 0)
+ return this.sel.toString();
+ },
+ getHtml: function()
{
- $(elem).find('td').eq(focusIndex).prepend('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>');
- }
+ var html = '';
- $(elem).find('td').eq(index).remove();
+ this.selection.get();
+ if (this.sel.rangeCount)
+ {
+ var container = document.createElement('div');
+ var len = this.sel.rangeCount;
+ for (var i = 0; i < len; ++i)
+ {
+ container.appendChild(this.sel.getRangeAt(i).cloneContents());
+ }
- }, this));
+ html = container.innerHTML;
+ }
- this.selectionRestore();
- this.sync();
+ return this.clean.onSync(html);
+ }
+ };
},
- tableAddHead: function()
+ observe: function()
{
- var $table = $(this.getParent()).closest('table');
- if (!this.isParentRedactor($table)) return false;
- if ($table.size() == 0) return false;
+ return {
+ load: function()
+ {
+ this.observe.images();
+ this.observe.links();
+ },
+ buttons: function(e, btnName)
+ {
+ var current = this.selection.getCurrent();
+ var parent = this.selection.getParent();
- this.bufferSet();
+ this.button.setInactiveAll(btnName);
- if ($table.find('thead').size() !== 0) this.tableDeleteHead();
- else
- {
- var tr = $table.find('tr').first().clone();
- tr.find('td').html(this.opts.invisibleSpace);
- $thead = $('<thead></thead>');
- $thead.append(tr);
- $table.prepend($thead);
+ if (e === false && btnName !== 'html')
+ {
+ if ($.inArray(btnName, this.opts.activeButtons) != -1) this.button.toggleActive(btnName);
+ return;
+ }
- this.sync();
- }
- },
- tableDeleteHead: function()
- {
- var $table = $(this.getParent()).closest('table');
- if (!this.isParentRedactor($table)) return false;
- var $thead = $table.find('thead');
+ var linkButtonName = (this.utils.isCurrentOrParent('A')) ? this.lang.get('link_edit') : this.lang.get('link_insert');
+ $('body').find('a.redactor-dropdown-link').text(linkButtonName);
- if ($thead.size() == 0) return false;
+ $.each(this.opts.activeButtonsStates, $.proxy(function(key, value)
+ {
+ var parentEl = $(parent).closest(key);
+ var currentEl = $(current).closest(key);
- this.bufferSet();
+ if (!this.utils.isRedactorParent(parentEl)) return;
+ if (!this.utils.isRedactorParent(currentEl)) return;
+ if (parentEl.length !== 0 || currentEl.closest(key).length !== 0)
+ {
- $thead.remove();
- this.sync();
- },
- tableAddRowAbove: function()
- {
- this.tableAddRow('before');
- },
- tableAddRowBelow: function()
- {
- this.tableAddRow('after');
- },
- tableAddColumnLeft: function()
- {
- this.tableAddColumn('before');
- },
- tableAddColumnRight: function()
- {
- this.tableAddColumn('after');
- },
- tableAddRow: function(type)
- {
- var $table = $(this.getParent()).closest('table');
- if (!this.isParentRedactor($table)) return false;
- if ($table.size() == 0) return false;
+ this.button.setActive(value);
+ }
- this.bufferSet();
+ }, this));
- var $current_tr = $(this.getParent()).closest('tr');
- var new_tr = $current_tr.clone();
- new_tr.find('td').html(this.opts.invisibleSpace);
+ 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)
+ {
+ this.opts.activeButtons.push(btnName);
+ this.opts.activeButtonsStates[tag] = btnName;
+ },
+ images: function()
+ {
+ this.$editor.find('img').each($.proxy(function(i, img)
+ {
+ var $img = $(img);
- if (type === 'after') $current_tr.after(new_tr);
- else $current_tr.before(new_tr);
+ // 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(); });
- this.sync();
- },
- tableAddColumn: function (type)
- {
- var $table = $(this.getParent()).closest('table');
- if (!this.isParentRedactor($table)) return false;
- if ($table.size() == 0) return false;
+ if (this.utils.browser('msie')) $img.attr('unselectable', 'on');
- this.bufferSet();
+ this.image.setEditable($img);
- var index = 0;
+ }, this));
- var $current_tr = $(this.getParent()).closest('tr');
- var $current_td = $(this.getParent()).closest('td');
+ $(document).on('click.redactor-image-delete', $.proxy(function(e)
+ {
+ this.observe.image = false;
+ if (e.target.tagName == 'IMG' && this.utils.isRedactorParent(e.target))
+ {
+ this.observe.image = (this.observe.image && this.observe.image == e.target) ? false : e.target;
+ }
- $current_tr.find('td').each($.proxy(function(i, elem)
- {
- if ($(elem)[0] === $current_td[0]) index = i;
+ }, this));
- }, this));
+ },
+ links: function()
+ {
+ if (!this.opts.linkTooltip) return;
- $table.find('tr').each($.proxy(function(i, elem)
- {
- var $current = $(elem).find('td').eq(index);
+ 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 td = $current.clone();
- td.html(this.opts.invisibleSpace);
+ var pos = this.observe.getTooltipPosition($link);
- type === 'after' ? $current.after(td) : $current.before(td);
+ var tooltip = $('<span class="redactor-link-tooltip"></span>');
- }, this));
+ var href = $link.attr('href');
+ if (href === undefined)
+ {
+ href = '';
+ }
- this.sync();
- },
+ if (href.length > 24) href = href.substring(0, 24) + '...';
- // VIDEO
- videoShow: function()
- {
- this.selectionSave();
+ 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');
- this.modalInit(this.opts.curLang.video, this.opts.modal_video, 600, $.proxy(function()
- {
- $('#redactor_insert_video_btn').click($.proxy(this.videoInsert, this));
+ tooltip.append(aLink).append(' | ').append(aEdit).append(' | ').append(aUnlink);
+ tooltip.css({
+ top: (pos.top + 20) + 'px',
+ left: pos.left + 'px'
+ });
- setTimeout(function()
+ $('.redactor-link-tooltip').remove();
+ $('body').append(tooltip);
+ },
+ closeTooltip: function(e)
{
- $('#redactor_insert_video_area').focus();
+ e = e.originalEvent || e;
- }, 200);
+ if (e.target.tagName == 'A' && !$(e.target).hasClass('redactor-link-tooltip-action') && this.utils.isRedactorParent(e.target))
+ {
+ return;
+ }
- }, this));
+ $('.redactor-link-tooltip').remove();
+ }
+
+ };
},
- videoInsert: function ()
+ link: function()
{
- var data = $('#redactor_insert_video_area').val();
- data = this.cleanStripTags(data);
+ return {
+ show: function(e)
+ {
+ if (typeof e != 'undefined' && e.preventDefault) e.preventDefault();
- this.selectionRestore();
+ this.modal.load('link', this.lang.get('link_insert'), 600);
- var current = this.getBlock() || this.getCurrent();
+ this.modal.createCancelButton();
+ this.link.buttonInsert = this.modal.createActionButton(this.lang.get('insert'));
- if (current) $(current).after(data)
- else this.insertHtmlAdvanced(data, false);
+ this.selection.get();
- this.sync();
- this.modalClose();
- },
+ this.link.getData();
+ this.link.cleanUrl();
- // LINK
- linkShow: function()
- {
- this.selectionSave();
+ if (this.link.target == '_blank') $('#redactor-link-blank').prop('checked', true);
- var callback = $.proxy(function()
- {
- this.insert_link_node = false;
+ this.link.$inputUrl = $('#redactor-link-url');
+ this.link.$inputText = $('#redactor-link-url-text');
- var sel = this.getSelection();
- var url = '', text = '', target = '', title = '';
+ this.link.$inputText.val(this.link.text);
+ this.link.$inputUrl.val(this.link.url);
- var elem = this.getParent();
- var par = $(elem).parent().get(0);
- if (par && par.tagName === 'A')
- {
- elem = par;
- }
+ this.link.buttonInsert.on('click', $.proxy(this.link.insert, this));
- if (elem && elem.tagName === 'A')
+ // show modal
+ this.selection.save();
+ this.modal.show();
+ this.link.$inputUrl.focus();
+ },
+ cleanUrl: function()
{
- url = elem.href;
- text = $(elem).text();
- target = elem.target;
- title = elem.title;
+ 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:', '');
- this.insert_link_node = elem;
- }
- else text = sel.toString();
+ // 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, '');
+ }
- $('.redactor_link_text').val(text);
-
- var thref = self.location.href.replace(/\/$/i, '');
- var turl = url.replace(thref, '');
-
- // remove host from href
- if (this.opts.linkProtocol === false)
+ },
+ getData: function()
{
- var re = new RegExp('^(http|ftp|https)://' + self.location.host, 'i');
- turl = turl.replace(re, '');
- }
+ this.link.$node = false;
- var tabs = $('#redactor_tabs').find('a');
+ var $el = $(this.selection.getCurrent()).closest('a');
+ if ($el.size() !== 0 && $el[0].tagName === 'A')
+ {
+ this.link.$node = $el;
- if (this.opts.linkEmail === false) tabs.eq(1).remove();
- if (this.opts.linkAnchor === false) tabs.eq(2).remove();
+ 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 = '';
+ }
- if (this.opts.linkEmail === false && this.opts.linkAnchor === false)
+ },
+ insert: function()
{
- $('#redactor_tabs').remove();
- $('#redactor_link_url').val(turl);
- }
- else
- {
- if (url.search('mailto:') === 0)
- {
- this.modalSetTab.call(this, 2);
+ var target = '';
+ var link = this.link.$inputUrl.val();
+ var text = this.link.$inputText.val();
- $('#redactor_tab_selected').val(2);
- $('#redactor_link_mailto').val(url.replace('mailto:', ''));
- }
- else if (turl.search(/^#/gi) === 0)
+ if ($.trim(link) === '')
{
- this.modalSetTab.call(this, 3);
+ this.link.$inputUrl.addClass('redactor-input-error').on('keyup', function()
+ {
+ $(this).removeClass('redactor-input-error');
+ $(this).off('keyup');
- $('#redactor_tab_selected').val(3);
- $('#redactor_link_anchor').val(turl.replace(/^#/gi, '' ));
+ });
+ return;
}
- else
+
+ // mailto
+ if (link.search('@') != -1 && /(http|ftp|https):\/\//i.test(link) === false)
{
- $('#redactor_link_url').val(turl);
+ link = 'mailto:' + link;
}
- }
+ // url, not anchor
+ else if (link.search('#') !== 0)
+ {
+ if ($('#redactor-link-blank').prop('checked'))
+ {
+ target = '_blank';
+ }
- if (target === '_blank') $('#redactor_link_blank').prop('checked', true);
+ // 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');
- $('#redactor_insert_link_btn').click($.proxy(this.linkProcess, this));
+ if (link.search(re) == -1 && link.search(re2) === 0 && this.opts.linkProtocol)
+ {
+ link = this.opts.linkProtocol + '://' + link;
+ }
+ }
- setTimeout(function()
+ this.link.set(text, link, target);
+ this.modal.close();
+ },
+ set: function(text, link, target)
{
- $('#redactor_link_url').focus();
+ text = $.trim(text.replace(/<|>/g, ''));
- }, 200);
+ this.selection.restore();
- }, this);
+ if (text === '' && link === '') return;
+ if (text === '' && link !== '') text = link;
- this.modalInit(this.opts.curLang.link, this.opts.modal_link, 460, callback);
+ if (this.link.$node)
+ {
+ this.buffer.set();
- },
- linkProcess: function()
- {
- var tab_selected = $('#redactor_tab_selected').val();
- var link = '', text = '', target = '', targetBlank = '', title = '';
+ this.link.$node.text(text).attr('href', link);
+ if (target !== '')
+ {
+ this.link.$node.attr('target', target);
+ }
+ else
+ {
+ this.link.$node.removeAttr('target');
+ }
- // url
- if (tab_selected === '1')
- {
- link = $('#redactor_link_url').val();
- text = $('#redactor_link_url_text').val();
- title = $('#redactor_link_url_title').val();
+ 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);
- if ($('#redactor_link_blank').prop('checked'))
- {
- target = ' target="_blank"';
- targetBlank = '_blank';
- }
+ this.insert.node($a);
+ this.selection.selectElement($a);
+ }
+ else
+ {
+ document.execCommand('createLink', false, link);
- // test url (add protocol)
- var pattern = '((xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}';
- var re = new RegExp('^(http|ftp|https)://' + pattern, 'i');
- var re2 = new RegExp('^' + pattern, 'i');
+ var $a = $(this.selection.getCurrent()).closest('a');
- if (link.search(re) == -1 && link.search(re2) == 0 && this.opts.linkProtocol)
- {
- link = this.opts.linkProtocol + link;
- }
- }
- // mailto
- else if (tab_selected === '2')
- {
- link = 'mailto:' + $('#redactor_link_mailto').val();
- text = $('#redactor_link_mailto_text').val();
- }
- // anchor
- else if (tab_selected === '3')
- {
- link = '#' + $('#redactor_link_anchor').val();
- text = $('#redactor_link_anchor_text').val();
- title = $('#redactor_link_anchor_title').val();
- }
+ if (target !== '') $a.attr('target', target);
+ $a.removeAttr('style');
- if (title != '')
- {
- title = ' title="'+title+'"';
- }
+ if (this.link.text === '')
+ {
+ $a.text(text);
+ this.selection.selectElement($a);
+ }
+ }
- text = text.replace(/<|>/g, '');
- this.linkInsert('<a href="'+link+'"'+target+title+'>'+text+'</a>', $.trim(text), link, targetBlank);
+ this.code.sync();
+ this.core.setCallback('insertedLink', $a);
- },
- linkInsert: function (a, text, link, target)
- {
- this.selectionRestore();
+ }
- if (text !== '')
- {
- if (this.insert_link_node)
+ // link tooltip
+ setTimeout($.proxy(function()
+ {
+ this.observe.links();
+
+ }, this), 5);
+ },
+ unlink: function(e)
{
- this.bufferSet();
+ if (typeof e != 'undefined' && e.preventDefault) e.preventDefault();
- $(this.insert_link_node).text(text).attr('href', link);
+ var nodes = this.selection.getNodes();
+ if (!nodes) return;
- if (target !== '') $(this.insert_link_node).attr('target', target);
- else $(this.insert_link_node).removeAttr('target');
+ this.buffer.set();
- this.sync();
- }
- else
- {
- var $a = $(a).addClass('redactor-added-link');
- this.exec('inserthtml', this.outerHtml($a), false);
- this.$editor.find('a.redactor-added-link').removeAttr('style').removeClass('redactor-added-link').each(function()
+ var len = nodes.length;
+ for (var i = 0; i < len; i++)
{
- if (this.className == '') $(this).removeAttr('class');
- });
- this.sync();
- }
- }
+ if (nodes[i].tagName == 'A')
+ {
+ var $node = $(nodes[i]);
+ $node.replaceWith($node.contents());
+ }
+ }
- // link tooltip
- setTimeout($.proxy(function()
- {
+ // remove tooltip
+ $('.redactor-link-tooltip').remove();
- if (this.opts.observeLinks) this.observeLinks();
+ this.code.sync();
- }, this), 5);
-
- this.modalClose();
+ }
+ };
},
-
- // FILE
- fileShow: function ()
+ image: function()
{
+ return {
+ show: function()
+ {
+ this.modal.load('image', this.lang.get('image'), 700);
+ this.upload.init('#redactor-modal-image-droparea', this.opts.imageUpload, this.image.insert);
- this.selectionSave();
+ this.selection.save();
+ this.modal.show();
- var callback = $.proxy(function()
- {
- var sel = this.getSelection();
+ },
+ showEdit: function($image)
+ {
+ var $link = $image.closest('a');
- var text = '';
- if (this.oldIE()) text = sel.text;
- else text = sel.toString();
+ this.modal.load('imageEdit', this.lang.get('edit'), 705);
- $('#redactor_filename').val(text);
+ this.modal.createCancelButton();
+ this.image.buttonDelete = this.modal.createDeleteButton(this.lang.get('_delete'));
+ this.image.buttonSave = this.modal.createActionButton(this.lang.get('save'));
- // dragupload
- if (!this.isMobile())
- {
- this.draguploadInit('#redactor_file', {
- url: this.opts.fileUpload,
- uploadFields: this.opts.uploadFields,
- success: $.proxy(this.fileCallback, this),
- error: $.proxy( function(obj, json)
- {
- this.callback('fileUploadError', json);
+ this.image.buttonDelete.on('click', $.proxy(function()
+ {
+ this.image.remove($image);
- }, this),
- uploadParam: this.opts.fileUploadParam
- });
- }
+ }, this));
- this.uploadInit('redactor_file', {
- auto: true,
- url: this.opts.fileUpload,
- success: $.proxy(this.fileCallback, this),
- error: $.proxy(function(obj, json)
+ this.image.buttonSave.on('click', $.proxy(function()
{
- this.callback('fileUploadError', json);
+ this.image.update($image);
- }, this)
- });
+ }, this));
- }, this);
- this.modalInit(this.opts.curLang.file, this.opts.modal_file, 500, callback);
- },
- fileCallback: function(json)
- {
+ $('#redactor-image-title').val($image.attr('alt'));
- this.selectionRestore();
+ if (!this.opts.imageLink) $('.redactor-image-link-option').hide();
+ else
+ {
+ var $redactorImageLink = $('#redactor-image-link');
- if (json !== false)
- {
+ $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);
+ }
+ }
- var text = $('#redactor_filename').val();
- if (text === '') text = json.filename;
+ if (!this.opts.imagePosition) $('.redactor-image-position-option').hide();
+ else
+ {
+ var floatValue = ($image.css('display') == 'block' && $image.css('float') == 'none') ? 'center' : $image.css('float');
+ $('#redactor-image-align').val(floatValue);
+ }
- var link = '<a href="' + json.filelink + '" id="filelink-marker">' + text + '</a>';
+ this.modal.show();
- // chrome fix
- if (this.browser('webkit') && !!this.window.chrome)
+ },
+ setFloating: function($image)
{
- link = link + ' ';
- }
+ var floating = $('#redactor-image-align').val();
- this.execCommand('inserthtml', link, false);
+ var imageFloat = '';
+ var imageDisplay = '';
+ var imageMargin = '';
- var linkmarker = $(this.$editor.find('a#filelink-marker'));
- if (linkmarker.size() != 0) linkmarker.removeAttr('id');
- else linkmarker = false;
+ switch (floating)
+ {
+ case 'left':
+ imageFloat = 'left';
+ imageMargin = '0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin + ' 0';
+ break;
+ case 'right':
+ imageFloat = 'right';
+ imageMargin = '0 0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin;
+ break;
+ case 'center':
+ imageDisplay = 'block';
+ imageMargin = 'auto';
+ break;
+ }
- this.sync();
+ $image.css({ 'float': imageFloat, display: imageDisplay, margin: imageMargin });
+ $image.attr('rel', $image.attr('style'));
+ },
+ update: function($image)
+ {
+ this.image.hideResize();
+ this.buffer.set();
- // file upload callback
- this.callback('fileUpload', linkmarker, json);
- }
+ var $link = $image.closest('a');
- this.modalClose();
- },
+ $image.attr('alt', $('#redactor-image-title').val());
- // IMAGE
- imageShow: function()
- {
+ this.image.setFloating($image);
- this.selectionSave();
-
- var callback = $.proxy(function()
- {
- // json
- if (this.opts.imageGetJson)
- {
- $.getJSON(this.opts.imageGetJson, $.proxy(function(data)
+ // as link
+ var link = $.trim($('#redactor-image-link').val());
+ if (link !== '')
{
- var folders = {}, count = 0;
+ var target = ($('#redactor-image-link-blank').prop('checked')) ? true : false;
- // folders
- $.each(data, $.proxy(function(key, val)
+ if ($link.size() === 0)
{
- if (typeof val.folder !== 'undefined')
- {
- count++;
- folders[val.folder] = count;
- }
+ var a = $('<a href="' + link + '">' + this.utils.getOuterHtml($image) + '</a>');
+ if (target) a.attr('target', '_blank');
- }, this));
-
- var folderclass = false;
- $.each(data, $.proxy(function(key, val)
+ $image.replaceWith(a);
+ }
+ else
{
- // title
- var thumbtitle = '';
- if (typeof val.title !== 'undefined') thumbtitle = val.title;
-
- var folderkey = 0;
- if (!$.isEmptyObject(folders) && typeof val.folder !== 'undefined')
+ $link.attr('href', link);
+ if (target)
{
- folderkey = folders[val.folder];
- if (folderclass === false) folderclass = '.redactorfolder' + folderkey;
+ $link.attr('target', '_blank');
}
-
- var img = $('<img src="' + val.thumb + '" class="redactorfolder redactorfolder' + folderkey + '" rel="' + val.image + '" title="' + thumbtitle + '" />');
- $('#redactor_image_box').append(img);
- $(img).click($.proxy(this.imageThumbClick, this));
-
- }, this));
-
- // folders
- if (!$.isEmptyObject(folders))
- {
- $('.redactorfolder').hide();
- $(folderclass).show();
-
- var onchangeFunc = function(e)
+ else
{
- $('.redactorfolder').hide();
- $('.redactorfolder' + $(e.target).val()).show();
- };
-
- var select = $('<select id="redactor_image_box_select">');
- $.each( folders, function(k, v)
- {
- select.append( $('<option value="' + v + '">' + k + '</option>'));
- });
-
- $('#redactor_image_box').before(select);
- select.change(onchangeFunc);
+ $link.removeAttr('target');
+ }
}
- }, this));
-
- }
- else
- {
- $('#redactor_tabs').find('a').eq(1).remove();
- }
-
- if (this.opts.imageUpload || this.opts.s3)
- {
- // dragupload
- if (!this.isMobile() && this.opts.s3 === false)
+ }
+ else if ($link.size() !== 0)
{
- if ($('#redactor_file' ).length)
- {
- this.draguploadInit('#redactor_file', {
- url: this.opts.imageUpload,
- uploadFields: this.opts.uploadFields,
- success: $.proxy(this.imageCallback, this),
- error: $.proxy(function(obj, json)
- {
- this.callback('imageUploadError', json);
+ $link.replaceWith(this.utils.getOuterHtml($image));
- }, this),
- uploadParam: this.opts.imageUploadParam
- });
- }
}
- if (this.opts.s3 === false)
- {
- // ajax upload
- this.uploadInit('redactor_file', {
- auto: true,
- url: this.opts.imageUpload,
- success: $.proxy(this.imageCallback, this),
- error: $.proxy(function(obj, json)
- {
- this.callback('imageUploadError', json);
+ this.modal.close();
+ this.observe.images();
+ this.code.sync();
- }, this)
- });
- }
- // s3 upload
- else
- {
- $('#redactor_file').on('change.redactor', $.proxy(this.s3handleFileSelect, this));
- }
- }
- else
+ },
+ setEditable: function($image)
{
- $('.redactor_tab').hide();
- if (!this.opts.imageGetJson)
- {
- $('#redactor_tabs').remove();
- $('#redactor_tab3').show();
- }
- else
- {
- var tabs = $('#redactor_tabs').find('a');
- tabs.eq(0).remove();
- tabs.eq(1).addClass('redactor_tabs_act');
- $('#redactor_tab2').show();
- }
- }
+ if (!this.opts.imageEditable) return;
- $('#redactor_upload_btn').click($.proxy(this.imageCallbackLink, this));
+ $image.on('dragstart', $.proxy(this.image.onDrag, this));
- if (!this.opts.imageUpload && !this.opts.imageGetJson)
- {
- setTimeout(function()
+ $image.on('mousedown', $.proxy(this.image.hideResize, this));
+ $image.on('click touchstart', $.proxy(function(e)
{
- $('#redactor_file_link').focus();
+ this.observe.image = $image;
- }, 200);
- }
+ if (this.$editor.find('#redactor-image-box').size() !== 0) return false;
- }, this);
- this.modalInit(this.opts.curLang.image, this.opts.modal_image, 610, callback);
+ this.image.resizer = this.image.loadEditableControls($image);
- },
- imageEdit: function(image)
- {
- var $el = image;
- var parent = $el.parent().parent();
+ $(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));
- var callback = $.proxy(function()
- {
- $('#redactor_file_alt').val($el.attr('alt'));
- $('#redactor_image_edit_src').attr('href', $el.attr('src'));
- $('#redactor_form_image_align').val($el.css('float'));
+ // resize
+ if (!this.opts.imageResizable) return;
- if ($(parent).get(0).tagName === 'A')
- {
- $('#redactor_file_link').val($(parent).attr('href'));
+ this.image.resizer.on('mousedown.redactor touchstart.redactor', $.proxy(function(e)
+ {
+ e.preventDefault();
- if ($(parent).attr('target') == '_blank')
- {
- $('#redactor_link_blank').prop('checked', true);
- }
- }
+ this.image.resizeHandle = {
+ x : e.pageX,
+ y : e.pageY,
+ el : $image,
+ ratio: $image.width() / $image.height(),
+ h: $image.height()
+ };
- $('#redactor_image_delete_btn').click($.proxy(function()
- {
- this.imageRemove($el);
+ e = e.originalEvent || e;
- }, this));
+ if (e.targetTouches)
+ {
+ this.image.resizeHandle.x = e.targetTouches[0].pageX;
+ this.image.resizeHandle.y = e.targetTouches[0].pageY;
+ }
- $('#redactorSaveBtn').click($.proxy(function()
- {
- this.imageSave($el);
+ this.image.startResize();
- }, this));
+ }, this));
- }, this);
- this.modalInit(this.opts.curLang.edit, this.opts.modal_image_edit, 380, callback);
+ }, this));
+ },
+ startResize: function()
+ {
+ $(document).on('mousemove.redactor-image-resize touchmove.redactor-image-resize', $.proxy(this.image.moveResize, this));
+ $(document).on('mouseup.redactor-image-resize touchend.redactor-image-resize', $.proxy(this.image.stopResize, this));
+ },
+ moveResize: function(e)
+ {
+ e.preventDefault();
- },
- imageRemove: function(el)
- {
- var parent = $(el).parent();
- $(el).remove();
+ e = e.originalEvent || e;
- if (parent.length && parent[0].tagName === 'P')
- {
- this.$editor.focus();
- this.selectionStart(parent);
- }
+ var height = this.image.resizeHandle.h;
- // delete callback
- this.callback('imageDelete', el);
+ if (e.targetTouches) height += (e.targetTouches[0].pageY - this.image.resizeHandle.y);
+ else height += (e.pageY - this.image.resizeHandle.y);
- this.modalClose();
- this.sync();
- },
- imageSave: function(el)
- {
- var $el = $(el);
- var parent = $el.parent();
- $el.attr('alt', $('#redactor_file_alt').val());
+ var width = Math.round(height * this.image.resizeHandle.ratio);
- var floating = $('#redactor_form_image_align').val();
+ if (height < 50 || width < 100) return;
- if (floating === 'left')
- {
- this.imageMargin = '0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin + ' 0';
- $el.css({ 'float': 'left', 'margin': this.imageMargin });
- }
- else if (floating === 'right')
- {
- this.imageMargin = '0 0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin + '';
- $el.css({ 'float': 'right', 'margin': this.imageMargin });
- }
- else
- {
- this.imageMargin = '0px';
- var imageBox = $el.closest('#redactor-image-box');
- if (imageBox.size() != 0) imageBox.css({ 'float': '', 'margin': '' });
- $el.css({ 'float': '', 'margin': '' });
- }
+ this.image.resizeHandle.el.height(height);
+ this.image.resizeHandle.el.width(width);
- // as link
- var link = $.trim($('#redactor_file_link').val());
- if (link !== '')
- {
- var target = false;
- if ($('#redactor_link_blank').prop('checked'))
+ this.code.sync();
+ },
+ stopResize: function()
{
- target = true;
- }
-
- if (parent.get(0).tagName !== 'A')
+ this.handle = false;
+ $(document).off('.redactor-image-resize');
+ this.image.hideResize();
+ },
+ onDrag: function(e)
{
- var a = $('<a href="' + link + '">' + this.outerHtml(el) + '</a>');
-
- if (target)
+ if (this.$editor.find('#redactor-image-box').size() !== 0)
{
- a.attr('target', '_blank');
+ e.preventDefault();
+ return false;
}
- $el.replaceWith(a);
- }
- else
+ this.$editor.on('drop.redactor-image-inside-drop', $.proxy(function()
+ {
+ setTimeout($.proxy(this.image.onDrop, this), 1);
+
+ }, this));
+ },
+ onDrop: function()
{
- parent.attr('href', link);
- if (target)
+ 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()
{
- parent.attr('target', '_blank');
- }
- else
+ 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')
{
- parent.removeAttr('target');
+ var $image = $(e.target);
+ $image.attr('data-save-url', $image.attr('src'));
}
- }
- }
- else
- {
- if (parent.get(0).tagName === 'A')
- {
- parent.replaceWith(this.outerHtml(el));
- }
- }
- this.modalClose();
- this.observeImages();
- this.sync();
+ var imageBox = this.$editor.find('#redactor-image-box');
+ if (imageBox.size() === 0) return;
- },
- imageResizeHide: function(e)
- {
- if (e !== false && $(e.target).parent().size() != 0 && $(e.target).parent()[0].id === 'redactor-image-box')
- {
- return false;
- }
+ this.image.editter.remove();
+ $(this.image.resizer).remove();
- var imageBox = this.$editor.find('#redactor-image-box');
- if (imageBox.size() == 0)
- {
- return false;
- }
+ imageBox.find('img').css({
+ marginTop: imageBox[0].style.marginTop,
+ marginBottom: imageBox[0].style.marginBottom,
+ marginLeft: imageBox[0].style.marginLeft,
+ marginRight: imageBox[0].style.marginRight
+ });
- this.$editor.find('#redactor-image-editter, #redactor-image-resizer').remove();
+ 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 (this.imageMargin != '0px')
- {
- imageBox.find('img').css('margin', this.imageMargin);
- imageBox.css('margin', '');
- this.imageMargin = '0px';
- }
+ this.code.sync();
- imageBox.find('img').css('opacity', '');
- imageBox.replaceWith(function()
- {
- return $(this).contents();
- });
+ },
+ loadEditableControls: function($image)
+ {
+ var imageBox = $('<span id="redactor-image-box" data-redactor="verified">');
+ imageBox.css('float', $image.css('float')).attr('contenteditable', false);
- $(document).off('click.redactor-image-resize-hide');
- this.$editor.off('click.redactor-image-resize-hide');
- this.$editor.off('keydown.redactor-image-delete');
+ 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
+ });
- this.sync()
+ $image.css('margin', '');
+ }
+ else
+ {
+ imageBox.css({ 'display': 'block', 'margin': 'auto' });
+ }
- },
- imageResize: function(image)
- {
- var $image = $(image);
+ $image.css('opacity', '.5').after(imageBox);
- $image.on('mousedown', $.proxy(function()
- {
- this.imageResizeHide(false);
- }, this));
-
- $image.on('dragstart', $.proxy(function()
- {
- this.$editor.on('drop.redactor-image-inside-drop', $.proxy(function()
- {
- setTimeout($.proxy(function()
+ // 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.observeImages();
- this.$editor.off('drop.redactor-image-inside-drop');
- this.sync();
+ this.image.showEdit($image);
+ }, this));
- }, this), 1);
+ imageBox.append(this.image.editter);
- },this));
- }, this));
+ // position correction
+ var editerWidth = this.image.editter.innerWidth();
+ this.image.editter.css('margin-left', '-' + editerWidth/2 + 'px');
- $image.on('click', $.proxy(function(e)
- {
- if (this.$editor.find('#redactor-image-box').size() != 0)
- {
- return false;
- }
- var clicked = false,
- start_x,
- start_y,
- ratio = $image.width() / $image.height(),
- min_w = 20,
- min_h = 10;
+ // resizer
+ if (this.opts.imageResizable && !this.utils.isMobile())
+ {
+ var imageResizer = $('<span id="redactor-image-resizer" data-redactor="verified"></span>');
- var imageResizer = this.imageResizeControls($image);
+ if (!this.utils.isDesktop())
+ {
+ imageResizer.css({ width: '15px', height: '15px' });
+ }
- // resize
- var isResizing = false;
- imageResizer.on('mousedown', function(e)
- {
- isResizing = true;
- e.preventDefault();
+ imageResizer.attr('contenteditable', false);
+ imageBox.append(imageResizer);
+ imageBox.append($image);
- ratio = $image.width() / $image.height();
-
- start_x = Math.round(e.pageX - $image.eq(0).offset().left);
- start_y = Math.round(e.pageY - $image.eq(0).offset().top);
-
- });
-
- $(this.document.body).on('mousemove', $.proxy(function(e)
+ return imageResizer;
+ }
+ else
+ {
+ imageBox.append($image);
+ return false;
+ }
+ },
+ remove: function(image)
{
- if (isResizing)
+ var $image = $(image);
+ var $link = $image.closest('a');
+ var $figure = $image.closest('figure');
+ var $parent = $image.parent();
+ if ($('#redactor-image-box').size() !== 0)
{
- var mouse_x = Math.round(e.pageX - $image.eq(0).offset().left) - start_x;
- var mouse_y = Math.round(e.pageY - $image.eq(0).offset().top) - start_y;
+ $parent = $('#redactor-image-box').parent();
+ }
- var div_h = $image.height();
+ var $next;
+ if ($figure.size() !== 0)
+ {
+ $next = $figure.next();
+ $figure.remove();
+ }
+ else if ($link.size() !== 0)
+ {
+ $parent = $link.parent();
+ $link.remove();
+ }
+ else
+ {
+ $image.remove();
+ }
- var new_h = parseInt(div_h, 10) + mouse_y;
- var new_w = Math.round(new_h * ratio);
+ $('#redactor-image-box').remove();
- if (new_w > min_w)
- {
- $image.width(new_w);
-
- if (new_w < 100)
- {
- this.imageEditter.css({
- marginTop: '-7px',
- marginLeft: '-13px',
- fontSize: '9px',
- padding: '3px 5px'
- });
- }
- else
- {
- this.imageEditter.css({
- marginTop: '-11px',
- marginLeft: '-18px',
- fontSize: '11px',
- padding: '7px 10px'
- });
- }
- }
-
- start_x = Math.round(e.pageX - $image.eq(0).offset().left);
- start_y = Math.round(e.pageY - $image.eq(0).offset().top);
-
- this.sync()
+ if ($figure.size() !== 0)
+ {
+ this.caret.setStart($next);
}
- }, this)).on('mouseup', function()
- {
- isResizing = false;
- });
+ else
+ {
+ this.caret.setStart($parent);
+ }
+ // delete callback
+ this.core.setCallback('imageDelete', $image[0].src, $image);
- this.$editor.on('keydown.redactor-image-delete', $.proxy(function(e)
+ this.modal.close();
+ this.code.sync();
+ },
+ insert: function(json, direct, e)
{
- var key = e.which;
+ // error callback
+ if (typeof json.error != 'undefined')
+ {
+ this.modal.close();
+ this.selection.restore();
+ this.core.setCallback('imageUploadError', json);
+ return;
+ }
- if (this.keyCode.BACKSPACE == key || this.keyCode.DELETE == key)
+ var $img;
+ if (typeof json == 'string')
{
- this.bufferSet();
- this.imageResizeHide(false);
- this.imageRemove($image);
+ $img = $(json).attr('data-redactor-inserted-image', 'true');
}
+ else
+ {
+ $img = $('<img>');
+ $img.attr('src', json.filelink).attr('data-redactor-inserted-image', 'true');
+ }
- }, this));
- $(document).on('click.redactor-image-resize-hide', $.proxy(this.imageResizeHide, this));
- this.$editor.on('click.redactor-image-resize-hide', $.proxy(this.imageResizeHide, this));
+ 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));
- },
- imageResizeControls: function($image)
- {
- var imageBox = $('<span id="redactor-image-box" data-redactor="verified">');
- imageBox.css({
- position: 'relative',
- display: 'inline-block',
- lineHeight: 0,
- outline: '1px dashed rgba(0, 0, 0, .6)',
- 'float': $image.css('float')
- });
- imageBox.attr('contenteditable', false);
+ this.selection.restore();
+ this.buffer.set();
- this.imageMargin = $image[0].style.margin;
- if (this.imageMargin != '0px')
- {
- imageBox.css('margin', this.imageMargin);
- $image.css('margin', '');
- }
- $image.css('opacity', .5).after(imageBox);
+ this.insert.html(this.utils.getOuterHtml(node), false);
- // editter
- this.imageEditter = $('<span id="redactor-image-editter" data-redactor="verified">' + this.opts.curLang.edit + '</span>');
- this.imageEditter.css({
- position: 'absolute',
- zIndex: 2,
- top: '50%',
- left: '50%',
- marginTop: '-11px',
- marginLeft: '-18px',
- lineHeight: 1,
- backgroundColor: '#000',
- color: '#fff',
- fontSize: '11px',
- padding: '7px 10px',
- cursor: 'pointer'
- });
- this.imageEditter.attr('contenteditable', false);
- this.imageEditter.on('click', $.proxy(function()
- {
- this.imageEdit($image);
- }, this));
- imageBox.append(this.imageEditter);
+ var $image = this.$editor.find('img[data-redactor-inserted-image=true]').removeAttr('data-redactor-inserted-image');
- // resizer
- var imageResizer = $('<span id="redactor-image-resizer" data-redactor="verified"></span>');
- imageResizer.css({
- position: 'absolute',
- zIndex: 2,
- lineHeight: 1,
- cursor: 'nw-resize',
- bottom: '-4px',
- right: '-5px',
- border: '1px solid #fff',
- backgroundColor: '#000',
- width: '8px',
- height: '8px'
- });
- imageResizer.attr('contenteditable', false);
- imageBox.append(imageResizer);
+ if (isP)
+ {
+ $image.parent().contents().unwrap().wrap('<p />');
+ }
+ else if (this.opts.linebreaks)
+ {
+ $image.before('<br>').after('<br>');
+ }
- imageBox.append($image);
+ if (typeof json == 'string') return;
- return imageResizer;
- },
- imageThumbClick: function(e)
- {
- var img = '<img id="image-marker" src="' + $(e.target).attr('rel') + '" alt="' + $(e.target).attr('title') + '" />';
+ this.core.setCallback('imageUpload', $image, json);
- var parent = this.getParent();
- if (this.opts.paragraphy && $(parent).closest('li').size() == 0) img = '<p>' + img + '</p>';
-
- this.imageInsert(img, true);
+ }
+ };
},
- imageCallbackLink: function()
+ file: function()
{
- var val = $('#redactor_file_link').val();
+ 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);
- if (val !== '')
- {
- var data = '<img id="image-marker" src="' + val + '" />';
- if (this.opts.linebreaks === false) data = '<p>' + data + '</p>';
+ this.selection.save();
- this.imageInsert(data, true);
+ this.selection.get();
+ var text = this.sel.toString();
- }
- else this.modalClose();
- },
- imageCallback: function(data)
- {
- this.imageInsert(data);
- },
- imageInsert: function(json, link)
- {
- this.selectionRestore();
+ $('#redactor-filename').val(text);
- if (json !== false)
- {
- var html = '';
- if (link !== true)
+ this.modal.show();
+ },
+ insert: function(json, direct, e)
{
- html = '<img id="image-marker" src="' + json.filelink + '" />';
+ // error callback
+ if (typeof json.error != 'undefined')
+ {
+ this.modal.close();
+ this.selection.restore();
+ this.core.setCallback('fileUploadError', json);
+ return;
+ }
- var parent = this.getParent();
- if (this.opts.paragraphy && $(parent).closest('li').size() == 0)
+ var link;
+ if (typeof json == 'string')
{
- html = '<p>' + html + '</p>';
+ link = json;
}
- }
- else
- {
- html = json;
- }
+ else
+ {
+ var text = $('#redactor-filename').val();
+ if (typeof text == 'undefined' || text === '') text = json.filename;
- this.execCommand('inserthtml', html, false);
+ link = '<a href="' + json.filelink + '" id="filelink-marker">' + text + '</a>';
+ }
- var image = $(this.$editor.find('img#image-marker'));
+ if (direct)
+ {
+ this.selection.removeMarkers();
+ var marker = this.selection.getMarker();
+ this.insert.nodeToCaretPositionFromPoint(e, marker);
+ }
+ else
+ {
+ this.modal.close();
+ }
- if (image.length) image.removeAttr('id');
- else image = false;
+ this.selection.restore();
+ this.buffer.set();
- this.sync();
- // upload image callback
- link !== true && this.callback('imageUpload', image, json);
- }
+ this.insert.html(link);
- this.modalClose();
- this.observeImages();
- },
+ if (typeof json == 'string') return;
- // MODAL
- modalTemplatesInit: function()
- {
- $.extend( this.opts,
- {
- modal_file: String()
- + '<section>'
- + '<div id="redactor-progress" class="redactor-progress redactor-progress-striped" style="display: none;">'
- + '<div id="redactor-progress-bar" class="redactor-progress-bar" style="width: 100%;"></div>'
- + '</div>'
- + '<form id="redactorUploadFileForm" method="post" action="" enctype="multipart/form-data">'
- + '<label>' + this.opts.curLang.filename + '</label>'
- + '<input type="text" id="redactor_filename" class="redactor_input" />'
- + '<div style="margin-top: 7px;">'
- + '<input type="file" id="redactor_file" name="' + this.opts.fileUploadParam + '" />'
- + '</div>'
- + '</form>'
- + '</section>',
+ var linkmarker = $(this.$editor.find('a#filelink-marker'));
+ if (linkmarker.size() !== 0) linkmarker.removeAttr('id');
+ else linkmarker = false;
- modal_image_edit: String()
- + '<section>'
- + '<label>' + this.opts.curLang.title + '</label>'
- + '<input id="redactor_file_alt" class="redactor_input" />'
- + '<label>' + this.opts.curLang.link + '</label>'
- + '<input id="redactor_file_link" class="redactor_input" />'
- + '<label><input type="checkbox" id="redactor_link_blank"> ' + this.opts.curLang.link_new_tab + '</label>'
- + '<label>' + this.opts.curLang.image_position + '</label>'
- + '<select id="redactor_form_image_align">'
- + '<option value="none">' + this.opts.curLang.none + '</option>'
- + '<option value="left">' + this.opts.curLang.left + '</option>'
- + '<option value="right">' + this.opts.curLang.right + '</option>'
- + '</select>'
- + '</section>'
- + '<footer>'
- + '<button id="redactor_image_delete_btn" class="redactor_modal_btn redactor_modal_delete_btn">' + this.opts.curLang._delete + '</button> '
- + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
- + '<input type="button" name="save" class="redactor_modal_btn redactor_modal_action_btn" id="redactorSaveBtn" value="' + this.opts.curLang.save + '" />'
- + '</footer>',
- modal_image: String()
- + '<section>'
- + '<div id="redactor_tabs">'
- + '<a href="#" class="redactor_tabs_act">' + this.opts.curLang.upload + '</a>'
- + '<a href="#">' + this.opts.curLang.choose + '</a>'
- + '<a href="#">' + this.opts.curLang.link + '</a>'
- + '</div>'
- + '<div id="redactor-progress" class="redactor-progress redactor-progress-striped" style="display: none;">'
- + '<div id="redactor-progress-bar" class="redactor-progress-bar" style="width: 100%;"></div>'
- + '</div>'
- + '<form id="redactorInsertImageForm" method="post" action="" enctype="multipart/form-data">'
- + '<div id="redactor_tab1" class="redactor_tab">'
- + '<input type="file" id="redactor_file" name="' + this.opts.imageUploadParam + '" />'
- + '</div>'
- + '<div id="redactor_tab2" class="redactor_tab" style="display: none;">'
- + '<div id="redactor_image_box"></div>'
- + '</div>'
- + '</form>'
- + '<div id="redactor_tab3" class="redactor_tab" style="display: none;">'
- + '<label>' + this.opts.curLang.image_web_link + '</label>'
- + '<input type="text" name="redactor_file_link" id="redactor_file_link" class="redactor_input" />'
- + '</div>'
- + '</section>'
- + '<footer>'
- + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
- + '<input type="button" name="upload" class="redactor_modal_btn redactor_modal_action_btn" id="redactor_upload_btn" value="' + this.opts.curLang.insert + '" />'
- + '</footer>',
+ this.core.setCallback('fileUpload', linkmarker, json);
- modal_link: String()
- + '<section>'
- + '<form id="redactorInsertLinkForm" method="post" action="">'
- + '<div id="redactor_tabs">'
- + '<a href="#" class="redactor_tabs_act">URL</a>'
- + '<a href="#">Email</a>'
- + '<a href="#">' + this.opts.curLang.anchor + '</a>'
- + '</div>'
- + '<input type="hidden" id="redactor_tab_selected" value="1" />'
- + '<div class="redactor_tab" id="redactor_tab1">'
- + '<label>URL</label>'
- + '<input type="text" id="redactor_link_url" class="redactor_input" />'
- + '<label>' + this.opts.curLang.text + '</label>'
- + '<input type="text" class="redactor_input redactor_link_text" id="redactor_link_url_text" />'
- + '<label>' + this.opts.curLang.title + '</label>'
- + '<input type="text" class="redactor_input redactor_link_url_title" id="redactor_link_url_title" />'
- + '<label><input type="checkbox" id="redactor_link_blank"> ' + this.opts.curLang.link_new_tab + '</label>'
- + '</div>'
- + '<div class="redactor_tab" id="redactor_tab2" style="display: none;">'
- + '<label>Email</label>'
- + '<input type="text" id="redactor_link_mailto" class="redactor_input" />'
- + '<label>' + this.opts.curLang.text + '</label>'
- + '<input type="text" class="redactor_input redactor_link_text" id="redactor_link_mailto_text" />'
- + '</div>'
- + '<div class="redactor_tab" id="redactor_tab3" style="display: none;">'
- + '<label>' + this.opts.curLang.anchor + '</label>'
- + '<input type="text" class="redactor_input" id="redactor_link_anchor" />'
- + '<label>' + this.opts.curLang.text + '</label>'
- + '<input type="text" class="redactor_input redactor_link_text" id="redactor_link_anchor_text" />'
- + '<label>' + this.opts.curLang.title + '</label>'
- + '<input type="text" class="redactor_input redactor_link_anchor_title" id="redactor_link_anchor_title" />'
- + '</div>'
- + '</form>'
- + '</section>'
- + '<footer>'
- + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
- + '<input type="button" class="redactor_modal_btn redactor_modal_action_btn" id="redactor_insert_link_btn" value="' + this.opts.curLang.insert + '" />'
- + '</footer>',
-
- modal_table: String()
- + '<section>'
- + '<label>' + this.opts.curLang.rows + '</label>'
- + '<input type="text" size="5" value="2" id="redactor_table_rows" />'
- + '<label>' + this.opts.curLang.columns + '</label>'
- + '<input type="text" size="5" value="3" id="redactor_table_columns" />'
- + '</section>'
- + '<footer>'
- + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
- + '<input type="button" name="upload" class="redactor_modal_btn redactor_modal_action_btn" id="redactor_insert_table_btn" value="' + this.opts.curLang.insert + '" />'
- + '</footer>',
-
- modal_video: String()
- + '<section>'
- + '<form id="redactorInsertVideoForm">'
- + '<label>' + this.opts.curLang.video_html_code + '</label>'
- + '<textarea id="redactor_insert_video_area" style="width: 99%; height: 160px;"></textarea>'
- + '</form>'
- + '</section>'
- + '<footer>'
- + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
- + '<input type="button" class="redactor_modal_btn redactor_modal_action_btn" id="redactor_insert_video_btn" value="' + this.opts.curLang.insert + '" />'
- + '</footer>'
-
- });
+ }
+ };
},
- modalInit: function(title, content, width, callback)
+ modal: function()
{
- var $redactorModalOverlay = $('#redactor_modal_overlay');
+ 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>',
- // modal overlay
- if (!$redactorModalOverlay.length)
- {
- this.$overlay = $redactorModalOverlay = $('<div id="redactor_modal_overlay" style="display: none;"></div>');
- $('body').prepend(this.$overlay);
- }
+ image: String()
+ + '<section id="redactor-modal-image-insert">'
+ + '<div id="redactor-modal-image-droparea"></div>'
+ + '</section>',
- if (this.opts.modalOverlay)
- {
- $redactorModalOverlay.show().on('click', $.proxy(this.modalClose, this));
- }
+ 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>',
- var $redactorModal = $('#redactor_modal');
+ 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>'
+ };
- if (!$redactorModal.length)
- {
- this.$modal = $redactorModal = $('<div id="redactor_modal" style="display: none;"><div id="redactor_modal_close">×</div><header id="redactor_modal_header"></header><div id="redactor_modal_inner"></div></div>');
- $('body').append(this.$modal);
- }
- $('#redactor_modal_close').on('click', $.proxy(this.modalClose, this));
+ $.extend(this.opts, this.opts.modal);
- this.hdlModalClose = $.proxy(function(e)
- {
- if (e.keyCode === this.keyCode.ESC)
+ },
+ addCallback: function(name, callback)
{
- this.modalClose();
- return false;
- }
+ this.modal.callbacks[name] = callback;
+ },
+ createTabber: function($modal)
+ {
+ this.modal.$tabber = $('<div>').attr('id', 'redactor-modal-tabber');
- }, this);
+ $modal.prepend(this.modal.$tabber);
+ },
+ addTab: function(id, name, active)
+ {
+ var $tab = $('<a href="#" rel="tab' + id + '">').text(name);
+ if (active)
+ {
+ $tab.addClass('active');
+ }
- $(document).keyup(this.hdlModalClose);
- this.$editor.keyup(this.hdlModalClose);
+ var self = this;
+ $tab.on('click', function(e)
+ {
+ e.preventDefault();
+ $('.redactor-tab').hide();
+ $('.redactor-' + $(this).attr('rel')).show();
- // set content
- this.modalcontent = false;
- if (content.indexOf('#') == 0)
- {
- this.modalcontent = $(content);
- $('#redactor_modal_inner').empty().append(this.modalcontent.html());
- this.modalcontent.html('');
+ self.modal.$tabber.find('a').removeClass('active');
+ $(this).addClass('active');
- }
- else
- {
- $('#redactor_modal_inner').empty().append(content);
- }
+ });
- $redactorModal.find('#redactor_modal_header').html(title);
+ this.modal.$tabber.append($tab);
+ },
+ addTemplate: function(name, template)
+ {
+ this.opts.modal[name] = template;
+ },
+ getTemplate: function(name)
+ {
+ return this.opts.modal[name];
+ },
+ getModal: function()
+ {
+ return this.$modalBody.find('section');
+ },
+ load: function(templateName, title, width)
+ {
+ this.modal.templateName = templateName;
+ this.modal.width = width;
- // draggable
- if (typeof $.fn.draggable !== 'undefined')
- {
- $redactorModal.draggable({ handle: '#redactor_modal_header' });
- $redactorModal.find('#redactor_modal_header').css('cursor', 'move');
- }
+ this.modal.build();
+ this.modal.enableEvents();
+ this.modal.setTitle(title);
+ this.modal.setDraggable();
+ this.modal.setContent();
- var $redactor_tabs = $('#redactor_tabs');
+ // callbacks
+ if (typeof this.modal.callbacks[templateName] != 'undefined')
+ {
+ this.modal.callbacks[templateName].call(this);
+ }
- // tabs
- if ($redactor_tabs.length )
- {
- var that = this;
- $redactor_tabs.find('a').each(function(i, s)
+ },
+ show: function()
{
- i++;
- $(s).on('click', function(e)
+ this.modal.bodyOveflow = $(document.body).css('overflow');
+ $(document.body).css('overflow', 'hidden');
+
+ if (this.utils.isMobile())
{
- e.preventDefault();
+ this.modal.showOnMobile();
+ }
+ else
+ {
+ this.modal.showOnDesktop();
+ }
- $redactor_tabs.find('a').removeClass('redactor_tabs_act');
- $(this).addClass('redactor_tabs_act');
- $('.redactor_tab').hide();
- $('#redactor_tab' + i ).show();
- $('#redactor_tab_selected').val(i);
+ this.$modalOverlay.show();
+ this.$modalBox.show();
- if (that.isMobile() === false)
- {
- var height = $redactorModal.outerHeight();
- $redactorModal.css('margin-top', '-' + (height + 10) / 2 + 'px');
- }
- });
- });
- }
+ this.modal.setButtonsWidth();
- $redactorModal.find('.redactor_btn_modal_close').on('click', $.proxy(this.modalClose, this));
+ this.utils.saveScroll();
- // save scroll
- if (this.opts.autoresize === true)
- {
- this.saveModalScroll = this.document.body.scrollTop;
- }
- else
- {
- this.saveModalScroll = this.$editor.scrollTop();
- }
+ // resize
+ if (!this.utils.isMobile())
+ {
+ setTimeout($.proxy(this.modal.showOnDesktop, this), 0);
+ $(window).on('resize.redactor-modal', $.proxy(this.modal.resize, this));
+ }
- if (this.isMobile() === false)
- {
- $redactorModal.css({
- position: 'fixed',
- top: '-2000px',
- left: '50%',
- width: width + 'px',
- marginLeft: '-' + (width + 60) / 2 + 'px'
- }).show();
+ // modal shown callback
+ this.core.setCallback('modalOpened', this.modal.templateName, this.$modal);
- this.modalSaveBodyOveflow = $(document.body).css('overflow');
- $(document.body).css('overflow', 'hidden');
+ // fix bootstrap modal focus
+ $(document).off('focusin.modal');
- }
- else
- {
- $redactorModal.css({
- position: 'fixed',
- width: '100%',
- height: '100%',
- top: '0',
- left: '0',
- margin: '0',
- minHeight: '300px'
- }).show();
- }
+ // enter
+ this.$modal.find('input[type=text]').on('keypress.redactor-modal', $.proxy(this.modal.setEnter, this));
- // callback
- if (typeof callback === 'function') callback();
+ },
+ showOnDesktop: function()
+ {
+ var height = this.$modal.outerHeight();
+ var windowHeight = $(window).height();
+ var windowWidth = $(window).width();
- if (this.isMobile() === false)
- {
- setTimeout(function()
+ if (this.modal.width > windowWidth)
+ {
+ this.$modal.css({
+ width: '96%',
+ marginTop: (windowHeight/2 - height/2) + 'px'
+ });
+ return;
+ }
+
+ if (height > windowHeight)
+ {
+ this.$modal.css({
+ width: this.modal.width + 'px',
+ marginTop: '20px'
+ });
+ }
+ else
+ {
+ this.$modal.css({
+ width: this.modal.width + 'px',
+ marginTop: (windowHeight/2 - height/2) + 'px'
+ });
+ }
+ },
+ showOnMobile: function()
{
- var height = $redactorModal.outerHeight();
- $redactorModal.css({
- top: '50%',
- height: 'auto',
- minHeight: 'auto',
- marginTop: '-' + (height + 10) / 2 + 'px'
+ this.$modal.css({
+ width: '96%',
+ marginTop: '2%'
});
- }, 10);
- }
- },
- modalClose: function()
- {
- $('#redactor_modal_close').off('click', this.modalClose );
- $('#redactor_modal').fadeOut('fast', $.proxy(function()
- {
- var redactorModalInner = $('#redactor_modal_inner');
+ },
+ resize: 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;
- if (this.modalcontent !== false)
+ this.$modal.draggable({ handle: this.$modalHeader });
+ this.$modalHeader.css('cursor', 'move');
+ },
+ setEnter: function(e)
{
- this.modalcontent.html(redactorModalInner.html());
- this.modalcontent = false;
- }
+ if (e.which != 13) return;
- redactorModalInner.html('');
+ e.preventDefault();
+ this.$modal.find('button.redactor-modal-action-btn').click();
+ },
+ createCancelButton: 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));
- if (this.opts.modalOverlay)
+ this.$modalFooter.append(button);
+ },
+ createDeleteButton: function(label)
{
- $('#redactor_modal_overlay').hide().off('click', this.modalClose);
- }
+ 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);
- $(document).unbind('keyup', this.hdlModalClose);
- this.$editor.unbind('keyup', this.hdlModalClose);
+ return button;
+ },
+ setButtonsWidth: function()
+ {
+ var buttons = this.$modalFooter.find('button');
+ var buttonsSize = buttons.size();
+ if (buttonsSize === 0) return;
- this.selectionRestore();
+ buttons.css('width', (100/buttonsSize) + '%');
+ },
+ build: function()
+ {
+ this.modal.buildOverlay();
- // restore scroll
- if (this.opts.autoresize && this.saveModalScroll)
+ 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.document.body).scrollTop(this.saveModalScroll);
- }
- else if (this.opts.autoresize === false && this.saveModalScroll)
+ this.$modalOverlay = $('<div id="redactor-modal-overlay">').hide();
+ $('body').prepend(this.$modalOverlay);
+ },
+ enableEvents: function()
{
- this.$editor.scrollTop(this.saveModalScroll);
- }
+ 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));
+ this.modal.close(false);
+ },
+ close: function(e)
+ {
+ if (e)
+ {
+ if (!$(e.target).hasClass('redactor-modal-close-btn') && e.target != this.$modalClose[0] && e.target != this.$modalBox[0])
+ {
+ return;
+ }
+ e.preventDefault();
+ }
- if (this.isMobile() === false)
- {
- $(document.body).css('overflow', this.modalSaveBodyOveflow ? this.modalSaveBodyOveflow : 'visible');
- }
+ if (!this.$modalBox) return;
- return false;
- },
- modalSetTab: function(num)
- {
- $('.redactor_tab').hide();
- $('#redactor_tabs').find('a').removeClass('redactor_tabs_act').eq(num - 1).addClass('redactor_tabs_act');
- $('#redactor_tab' + num).show();
- },
+ this.modal.disableEvents();
- // S3
- s3handleFileSelect: function(e)
- {
- var files = e.target.files;
+ this.$modalOverlay.remove();
- for (var i = 0, f; f = files[i]; i++)
- {
- this.s3uploadFile(f);
- }
- },
- s3uploadFile: function(file)
- {
- this.s3executeOnSignedUrl(file, $.proxy(function(signedURL)
- {
- this.s3uploadToS3(file, signedURL);
- }, this));
- },
- s3executeOnSignedUrl: function(file, callback)
- {
- var xhr = new XMLHttpRequest();
+ this.$modalBox.fadeOut('fast', $.proxy(function()
+ {
+ this.$modalBox.remove();
- var mark = '?';
- if (this.opts.s3.search(/\?/) != '-1') mark = '&';
+ setTimeout($.proxy(this.utils.restoreScroll, this), 0);
- xhr.open('GET', this.opts.s3 + mark + 'name=' + file.name + '&type=' + file.type, true);
+ if (e !== undefined) this.selection.restore();
- // Hack to pass bytes through unprocessed.
- if (xhr.overrideMimeType) xhr.overrideMimeType('text/plain; charset=x-user-defined');
+ $(document.body).css('overflow', this.modal.bodyOveflow);
+ this.core.setCallback('modalClosed', this.modal.templateName);
- xhr.onreadystatechange = function(e)
- {
- if (this.readyState == 4 && this.status == 200)
+ }, this));
+
+ }
+ };
+ },
+ progress: function()
+ {
+ return {
+ show: function()
{
+ $(document.body).append($('<div id="redactor-progress"><span></span></div>'));
$('#redactor-progress').fadeIn();
- callback(decodeURIComponent(this.responseText));
- }
- else if(this.readyState == 4 && this.status != 200)
+ },
+ hide: function()
{
- //setProgress(0, 'Could not contact signing script. Status = ' + this.status);
+ $('#redactor-progress').fadeOut(1500, function()
+ {
+ $(this).remove();
+ });
}
- };
- xhr.send();
+ };
},
- s3createCORSRequest: function(method, url)
+ upload: function()
{
- var xhr = new XMLHttpRequest();
- if ("withCredentials" in xhr)
- {
- xhr.open(method, url, true);
- }
- else if (typeof XDomainRequest != "undefined")
- {
- xhr = new XDomainRequest();
- xhr.open(method, url);
- }
- else
- {
- xhr = null;
- }
-
- return xhr;
- },
- s3uploadToS3: function(file, url)
- {
- var xhr = this.s3createCORSRequest('PUT', url);
- if (!xhr)
- {
- //setProgress(0, 'CORS not supported');
- }
- else
- {
- xhr.onload = $.proxy(function()
+ return {
+ init: function(id, url, callback)
{
- if (xhr.status == 200)
- {
- //setProgress(100, 'Upload completed.');
+ this.upload.direct = false;
+ this.upload.callback = callback;
+ this.upload.url = url;
+ this.upload.$el = $(id);
+ this.upload.$droparea = $('<div id="redactor-droparea" />');
- $('#redactor-progress, #redactor-progress-drag').hide();
+ this.upload.$placeholdler = $('<div id="redactor-droparea-placeholder" />').text('Drop file here or ');
+ this.upload.$input = $('<input type="file" name="file" />');
- var s3image = url.split('?');
+ this.upload.$placeholdler.append(this.upload.$input);
+ this.upload.$droparea.append(this.upload.$placeholdler);
+ this.upload.$el.append(this.upload.$droparea);
- if (!s3image[0])
- {
- // url parsing is fail
- return false;
- }
+ this.upload.$droparea.off('redactor.upload');
+ this.upload.$input.off('redactor.upload');
- this.selectionRestore();
+ this.upload.$droparea.on('dragover.redactor.upload', $.proxy(this.upload.onDrag, this));
+ this.upload.$droparea.on('dragleave.redactor.upload', $.proxy(this.upload.onDragLeave, this));
- var html = '';
- html = '<img id="image-marker" src="' + s3image[0] + '" />';
- if (this.opts.paragraphy) html = '<p>' + html + '</p>';
+ // change
+ this.upload.$input.on('change.redactor.upload', $.proxy(function(e)
+ {
+ e = e.originalEvent || e;
+ this.upload.traverseFile(this.upload.$input[0].files[0], e);
+ }, this));
- this.execCommand('inserthtml', html, false);
+ // drop
+ this.upload.$droparea.on('drop.redactor.upload', $.proxy(function(e)
+ {
+ e.preventDefault();
- var image = $(this.$editor.find('img#image-marker'));
+ this.upload.$droparea.removeClass('drag-hover').addClass('drag-drop');
+ this.upload.onDrop(e);
- if (image.length) image.removeAttr('id');
- else image = false;
+ }, this));
+ },
+ directUpload: function(file, e)
+ {
+ this.upload.direct = true;
+ this.upload.traverseFile(file, e);
+ },
+ onDrop: function(e)
+ {
+ e = e.originalEvent || e;
+ var files = e.dataTransfer.files;
- this.sync();
+ this.upload.traverseFile(files[0], e);
+ },
+ traverseFile: function(file, e)
+ {
+ if (this.opts.s3)
+ {
+ this.upload.setConfig(file);
+ this.upload.s3uploadFile(file);
+ return;
+ }
- // upload image callback
- this.callback('imageUpload', image, false);
+ var formData = !!window.FormData ? new FormData() : null;
+ if (window.FormData)
+ {
+ this.upload.setConfig(file);
- this.modalClose();
- this.observeImages();
+ var name = (this.upload.type == 'image') ? this.opts.imageUploadParam : this.opts.fileUploadParam;
+ formData.append(name, file);
+ }
+ this.progress.show();
+ this.upload.sendData(formData, e);
+ },
+ setConfig: function(file)
+ {
+ this.upload.getType(file);
+
+ if (this.upload.direct)
+ {
+ this.upload.url = (this.upload.type == 'image') ? this.opts.imageUpload : this.opts.fileUpload;
+ this.upload.callback = (this.upload.type == 'image') ? this.image.insert : this.file.insert;
}
- else
+ },
+ getType: function(file)
+ {
+ this.upload.type = 'image';
+ if (this.opts.imageTypes.indexOf(file.type) == -1)
{
- //setProgress(0, 'Upload error: ' + xhr.status);
+ this.upload.type = 'file';
}
- }, this);
-
- xhr.onerror = function()
+ },
+ getHiddenFields: function(obj, fd)
{
- //setProgress(0, 'XHR error.');
- };
+ if (obj === false || typeof obj !== 'object') return fd;
- xhr.upload.onprogress = function(e)
+ $.each(obj, $.proxy(function(k, v)
+ {
+ if (v !== null && v.toString().indexOf('#') === 0) v = $(v).val();
+ fd.append(k, v);
+
+ }, this));
+
+ return fd;
+
+ },
+ sendData: function(formData, e)
{
- /*
- if (e.lengthComputable)
+ // append hidden fields
+ if (this.upload.type == 'image')
{
- var percentLoaded = Math.round((e.loaded / e.total) * 100);
- setProgress(percentLoaded, percentLoaded == 100 ? 'Finalizing.' : 'Uploading.');
+ formData = this.upload.getHiddenFields(this.opts.uploadImageFields, formData);
+ formData = this.upload.getHiddenFields(this.upload.imageFields, formData);
}
- */
- };
+ else
+ {
+ formData = this.upload.getHiddenFields(this.opts.uploadFileFields, formData);
+ formData = this.upload.getHiddenFields(this.upload.fileFields, formData);
+ }
- xhr.setRequestHeader('Content-Type', file.type);
- xhr.setRequestHeader('x-amz-acl', 'public-read');
+ var xhr = new XMLHttpRequest();
+ xhr.open('POST', this.upload.url);
- xhr.send(file);
- }
- },
+ // complete
+ xhr.onreadystatechange = $.proxy(function()
+ {
+ if (xhr.readyState == 4)
+ {
+ var data = xhr.responseText;
- // UPLOAD
- uploadInit: function(el, options)
- {
- this.uploadOptions = {
- url: false,
- success: false,
- error: false,
- start: false,
- trigger: false,
- auto: false,
- input: false
- };
+ data = data.replace(/^\[/, '');
+ data = data.replace(/\]$/, '');
- $.extend(this.uploadOptions, options);
+ var json;
+ try
+ {
+ json = (typeof data === 'string' ? $.parseJSON(data) : data);
+ }
+ catch(err)
+ {
+ json = {
+ error: true
+ };
+ }
- var $el = $('#' + el);
- // Test input or form
- if ($el.length && $el[0].tagName === 'INPUT')
- {
- this.uploadOptions.input = $el;
- this.el = $($el[0].form);
- }
- else this.el = $el;
+ this.progress.hide();
- this.element_action = this.el.attr('action');
+ if (!this.upload.direct)
+ {
+ this.upload.$droparea.removeClass('drag-drop');
+ }
- // Auto or trigger
- if (this.uploadOptions.auto)
- {
- $(this.uploadOptions.input).change($.proxy(function(e)
- {
- this.el.submit(function(e)
+ this.upload.callback(json, this.upload.direct, e);
+ }
+ }, this);
+
+
+ /*
+ xhr.upload.onprogress = $.proxy(function(e)
{
- return false;
- });
+ if (e.lengthComputable)
+ {
+ var complete = (e.loaded / e.total * 100 | 0);
+ //progress.value = progress.innerHTML = complete;
+ }
- this.uploadSubmit(e);
+ }, this);
+ */
- }, this));
- }
- else if (this.uploadOptions.trigger)
- {
- $('#' + this.uploadOptions.trigger).click($.proxy(this.uploadSubmit, this));
- }
- },
- uploadSubmit: function(e)
- {
- $('#redactor-progress').fadeIn();
- this.uploadForm(this.element, this.uploadFrame());
- },
- uploadFrame: function()
- {
- this.id = 'f' + Math.floor(Math.random() * 99999);
+ xhr.send(formData);
+ },
+ onDrag: function(e)
+ {
+ e.preventDefault();
+ this.upload.$droparea.addClass('drag-hover');
+ },
+ onDragLeave: function(e)
+ {
+ e.preventDefault();
+ this.upload.$droparea.removeClass('drag-hover');
+ },
+ clearImageFields: function()
+ {
+ this.upload.imageFields = {};
+ },
+ addImageFields: function(name, value)
+ {
+ this.upload.imageFields[name] = value;
+ },
+ removeImageFields: function(name)
+ {
+ delete this.upload.imageFields[name];
+ },
+ clearFileFields: function()
+ {
+ this.upload.fileFields = {};
+ },
+ addFileFields: function(name, value)
+ {
+ this.upload.fileFields[name] = value;
+ },
+ removeFileFields: function(name)
+ {
+ delete this.upload.fileFields[name];
+ },
- var d = this.document.createElement('div');
- var iframe = '<iframe style="display:none" id="' + this.id + '" name="' + this.id + '"></iframe>';
- d.innerHTML = iframe;
- $(d).appendTo("body");
+ // S3
+ s3uploadFile: function(file)
+ {
+ this.upload.s3executeOnSignedUrl(file, $.proxy(function(signedURL)
+ {
+ this.upload.s3uploadToS3(file, signedURL);
+ }, this));
+ },
+ s3executeOnSignedUrl: function(file, callback)
+ {
+ var xhr = new XMLHttpRequest();
- // Start
- if (this.uploadOptions.start) this.uploadOptions.start();
+ var mark = '?';
+ if (this.opts.s3.search(/\?/) != '-1') mark = '&';
- $( '#' + this.id ).load($.proxy(this.uploadLoaded, this));
+ xhr.open('GET', this.opts.s3 + mark + 'name=' + file.name + '&type=' + file.type, true);
- return this.id;
- },
- uploadForm: function(f, name)
- {
- if (this.uploadOptions.input)
- {
- var formId = 'redactorUploadForm' + this.id,
- fileId = 'redactorUploadFile' + this.id;
+ // Hack to pass bytes through unprocessed.
+ if (xhr.overrideMimeType) xhr.overrideMimeType('text/plain; charset=x-user-defined');
- this.form = $('<form action="'+this.uploadOptions.url+'" method="POST" target="'+name+'" name="'+formId+'" id="'+formId+'" enctype="multipart/form-data" />');
+ var that = this;
+ xhr.onreadystatechange = function(e)
+ {
+ if (this.readyState == 4 && this.status == 200)
+ {
+ that.progress.show();
+ callback(decodeURIComponent(this.responseText));
+ }
+ else if (this.readyState == 4 && this.status != 200)
+ {
+ //setProgress(0, 'Could not contact signing script. Status = ' + this.status);
+ }
+ };
- // append hidden fields
- if (this.opts.uploadFields !== false && typeof this.opts.uploadFields === 'object')
+ xhr.send();
+ },
+ s3createCORSRequest: function(method, url)
{
- $.each(this.opts.uploadFields, $.proxy(function(k, v)
+ var xhr = new XMLHttpRequest();
+ if ("withCredentials" in xhr)
{
- if (v != null && v.toString().indexOf('#') === 0) v = $(v).val();
+ xhr.open(method, url, true);
+ }
+ else if (typeof XDomainRequest != "undefined")
+ {
+ xhr = new XDomainRequest();
+ xhr.open(method, url);
+ }
+ else
+ {
+ xhr = null;
+ }
- var hidden = $('<input/>', {
- 'type': "hidden",
- 'name': k,
- 'value': v
- });
+ return xhr;
+ },
+ s3uploadToS3: function(file, url)
+ {
+ var xhr = this.upload.s3createCORSRequest('PUT', url);
+ if (!xhr)
+ {
+ //setProgress(0, 'CORS not supported');
+ }
+ else
+ {
+ xhr.onload = $.proxy(function()
+ {
+ if (xhr.status == 200)
+ {
+ //setProgress(100, 'Upload completed.');
- $(this.form).append(hidden);
+ this.progress.hide();
- }, this));
- }
+ var s3file = url.split('?');
- var oldElement = this.uploadOptions.input;
- var newElement = $(oldElement).clone();
+ if (!s3file[0])
+ {
+ // url parsing is fail
+ return false;
+ }
- $(oldElement).attr('id', fileId).before(newElement).appendTo(this.form);
- $(this.form).css('position', 'absolute')
- .css('top', '-2000px')
- .css('left', '-2000px')
- .appendTo('body');
+ if (!this.upload.direct)
+ {
+ this.upload.$droparea.removeClass('drag-drop');
+ }
- this.form.submit();
+ var json = { filelink: s3file[0] };
+ if (this.upload.type == 'file')
+ {
+ var arr = s3file[0].split('/');
+ json.filename = arr[arr.length-1];
+ }
- }
- else
- {
- f.attr('target', name)
- .attr('method', 'POST')
- .attr('enctype', 'multipart/form-data')
- .attr('action', this.uploadOptions.url);
+ this.upload.callback(json, this.upload.direct, false);
- this.element.submit();
- }
- },
- uploadLoaded: function()
- {
- var i = $( '#' + this.id)[0], d;
- if (i.contentDocument) d = i.contentDocument;
- else if (i.contentWindow) d = i.contentWindow.document;
- else d = window.frames[this.id].document;
+ }
+ else
+ {
+ //setProgress(0, 'Upload error: ' + xhr.status);
+ }
+ }, this);
- // Success
- if (this.uploadOptions.success)
- {
- $('#redactor-progress').hide();
+ xhr.onerror = function()
+ {
+ //setProgress(0, 'XHR error.');
+ };
- if (typeof d !== 'undefined')
- {
- // Remove bizarre <pre> tag wrappers around our json data:
- var rawString = d.body.innerHTML;
- var jsonString = rawString.match(/\{(.|\n)*\}/)[0];
+ xhr.upload.onprogress = function(e)
+ {
+ /*
+ if (e.lengthComputable)
+ {
+ var percentLoaded = Math.round((e.loaded / e.total) * 100);
+ setProgress(percentLoaded, percentLoaded == 100 ? 'Finalizing.' : 'Uploading.');
+ }
+ */
+ };
- jsonString = jsonString.replace(/^\[/, '');
- jsonString = jsonString.replace(/\]$/, '');
+ xhr.setRequestHeader('Content-Type', file.type);
+ xhr.setRequestHeader('x-amz-acl', 'public-read');
- var json = $.parseJSON(jsonString);
-
- if (typeof json.error == 'undefined') this.uploadOptions.success(json);
- else
- {
- this.uploadOptions.error(this, json);
- this.modalClose();
+ xhr.send(file);
}
}
- else
- {
- this.modalClose();
- alert('Upload failed!');
- }
- }
-
- this.el.attr('action', this.element_action);
- this.el.attr('target', '');
+ };
},
-
- // DRAGUPLOAD
- draguploadInit: function (el, options)
+ utils: function()
{
- this.draguploadOptions = $.extend({
- url: false,
- success: false,
- error: false,
- preview: false,
- uploadFields: false,
- text: this.opts.curLang.drop_file_here,
- atext: this.opts.curLang.or_choose,
- uploadParam: false
- }, options);
+ return {
+ isMobile: function()
+ {
+ return /(iPhone|iPod|BlackBerry|Android)/.test(navigator.userAgent);
+ },
+ isDesktop: function()
+ {
+ return !/(iPhone|iPod|iPad|BlackBerry|Android)/.test(navigator.userAgent);
+ },
+ isString: function(obj)
+ {
+ return Object.prototype.toString.call(obj) == '[object String]';
+ },
+ isEmpty: function(html, removeEmptyTags)
+ {
+ 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, '');
- if (window.FormData === undefined) return false;
+ // remove empty tags
+ if (removeEmptyTags !== false)
+ {
+ html = html.replace(/<[^\/>][^>]*><\/[^>]+>/gi, '');
+ html = html.replace(/<[^\/>][^>]*><\/[^>]+>/gi, '');
+ }
- this.droparea = $('<div class="redactor_droparea"></div>');
- this.dropareabox = $('<div class="redactor_dropareabox">' + this.draguploadOptions.text + '</div>');
- this.dropalternative = $('<div class="redactor_dropalternative">' + this.draguploadOptions.atext + '</div>');
+ html = $.trim(html);
- this.droparea.append(this.dropareabox);
+ return html === '';
+ },
+ normalize: function(str)
+ {
+ if (typeof(str) === 'undefined') return 0;
+ return parseInt(str.replace('px',''), 10);
+ },
+ hexToRgb: function(hex)
+ {
+ if (typeof hex == 'undefined') return;
+ if (hex.search(/^#/) == -1) return hex;
- $(el).before(this.droparea);
- $(el).before(this.dropalternative);
+ var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
+ hex = hex.replace(shorthandRegex, function(m, r, g, b)
+ {
+ return r + r + g + g + b + b;
+ });
- // drag over
- this.dropareabox.on('dragover', $.proxy(function()
- {
- return this.draguploadOndrag();
+ var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+ return 'rgb(' + parseInt(result[1], 16) + ', ' + parseInt(result[2], 16) + ', ' + parseInt(result[3], 16) + ')';
+ },
+ getOuterHtml: function(el)
+ {
+ return $('<div>').append($(el).eq(0).clone()).html();
+ },
+ getAlignmentElement: function(el)
+ {
+ if ($.inArray(el.tagName, this.opts.alignmentTags) !== -1)
+ {
+ return $(el);
+ }
+ else
+ {
+ return $(el).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
+ }
+ },
+ removeEmptyAttr: function(el, attr)
+ {
+ var $el = $(el);
+ if (typeof $el.attr(attr) == 'undefined')
+ {
+ return true;
+ }
- }, this));
+ if ($el.attr(attr) === '')
+ {
+ $el.removeAttr(attr);
+ return true;
+ }
- // drag leave
- this.dropareabox.on('dragleave', $.proxy(function()
- {
- return this.draguploadOndragleave();
+ return false;
+ },
+ removeEmpty: function(i, s)
+ {
+ var $s = $(s);
- }, this));
+ $s.find('.redactor-invisible-space').replaceWith(function()
+ {
+ return $(this).contents();
+ });
- // drop
- this.dropareabox.get(0).ondrop = $.proxy(function(e)
- {
- e.preventDefault();
+ if ($s.find('hr, br, img').length !== 0) return;
+ var text = $.trim($s.text());
+ if (this.utils.isEmpty(text, false))
+ {
+ $s.remove();
+ }
+ },
- this.dropareabox.removeClass('hover').addClass('drop');
+ // save and restore scroll
+ saveScroll: function()
+ {
+ if (this.utils.isSelectAll()) return;
- this.dragUploadAjax(this.draguploadOptions.url, e.dataTransfer.files[0], false, false, false, this.draguploadOptions.uploadParam);
+ this.saveEditorScroll = this.$editor.scrollTop();
+ this.saveBodyScroll = $(window).scrollTop();
- }, this );
- },
- dragUploadAjax: function(url, file, directupload, progress, e, uploadParam)
- {
- if (!directupload)
- {
- var xhr = $.ajaxSettings.xhr();
- if (xhr.upload)
+ if (this.opts.scrollTarget) this.saveTargetScroll = $(this.opts.scrollTarget).scrollTop();
+ },
+ restoreScroll: function()
{
- xhr.upload.addEventListener('progress', $.proxy(this.uploadProgress, this), false);
- }
+ if (typeof this.saveScroll === 'undefined' && typeof this.saveBodyScroll === 'undefined') return;
- $.ajaxSetup({
- xhr: function () { return xhr; }
- });
- }
+ $(window).scrollTop(this.saveBodyScroll);
+ this.$editor.scrollTop(this.saveEditorScroll);
- var fd = new FormData();
+ if (this.opts.scrollTarget) $(this.opts.scrollTarget).scrollTop(this.saveTargetScroll);
+ },
- // append file data
- if (uploadParam !== false)
- {
- fd.append(uploadParam, file);
- }
- else
- {
- fd.append('file', file);
- }
-
- // append hidden fields
- if (this.opts.uploadFields !== false && typeof this.opts.uploadFields === 'object')
- {
- $.each(this.opts.uploadFields, $.proxy(function(k, v)
+ // get invisible space element
+ createSpaceElement: function()
{
- if (v != null && v.toString().indexOf('#') === 0) v = $(v).val();
- fd.append(k, v);
+ var space = document.createElement('span');
+ space.className = 'redactor-invisible-space';
+ space.innerHTML = this.opts.invisibleSpace;
- }, this));
- }
+ return space;
+ },
- $.ajax({
- url: url,
- dataType: 'html',
- data: fd,
- cache: false,
- contentType: false,
- processData: false,
- type: 'POST',
- success: $.proxy(function(data)
+ // replace
+ removeInlineTags: function(node)
{
- data = data.replace(/^\[/, '');
- data = data.replace(/\]$/, '');
+ var tags = this.opts.inlineTags;
+ tags.push('span');
- var json = (typeof data === 'string' ? $.parseJSON(data) : data);
+ if (node.tagName == 'PRE') tags.push('a');
- if (directupload)
+ $(node).find(tags.join(',')).not('span.redactor-selection-marker').contents().unwrap();
+ },
+ replaceWithContents: function(node, removeInlineTags)
+ {
+ var self = this;
+ $(node).replaceWith(function()
{
- progress.fadeOut('slow', function()
- {
- $(this).remove();
- });
+ if (removeInlineTags === true) self.utils.removeInlineTags(this);
- var $img = $('<img>');
- $img.attr('src', json.filelink).attr('id', 'drag-image-marker');
+ return $(this).contents();
+ });
+ },
+ replaceToTag: function(node, tag, removeInlineTags)
+ {
+ var replacement;
+ var self = this;
+ $(node).replaceWith(function()
+ {
+ replacement = $('<' + tag + ' />').append($(this).contents());
- this.insertNodeToCaretPositionFromPoint(e, $img[0]);
+ for (var i = 0; i < this.attributes.length; i++)
+ {
+ replacement.attr(this.attributes[i].name, this.attributes[i].value);
+ }
- var image = $(this.$editor.find('img#drag-image-marker'));
- if (image.length) image.removeAttr('id');
- else image = false;
+ if (removeInlineTags === true) self.utils.removeInlineTags(replacement);
- this.sync();
- this.observeImages();
+ return replacement;
+ });
- // upload callback
- if (image) this.callback('imageUpload', image, json);
+ return replacement;
+ },
- // error callback
- if (typeof json.error !== 'undefined') this.callback('imageUploadError', json);
- }
- else
- {
- if (typeof json.error == 'undefined')
- {
- this.draguploadOptions.success(json);
- }
- else
- {
- this.draguploadOptions.error(this, json);
- this.draguploadOptions.success(false);
- }
- }
+ // start and end of element
+ isStartOfElement: function()
+ {
+ var block = this.selection.getBlock();
+ if (!block) return false;
- }, this)
- });
- },
- draguploadOndrag: function()
- {
- this.dropareabox.addClass('hover');
- return false;
- },
- draguploadOndragleave: function()
- {
- this.dropareabox.removeClass('hover');
- return false;
- },
- uploadProgress: function(e, text)
- {
- var percent = e.loaded ? parseInt(e.loaded / e.total * 100, 10) : e;
- this.dropareabox.text('Loading ' + percent + '% ' + (text || ''));
- },
+ var offset = this.caret.getOffsetOfElement(block);
- // UTILS
- isMobile: function()
- {
- return /(iPhone|iPod|BlackBerry|Android)/.test(navigator.userAgent);
- },
- normalize: function(str)
- {
- if (typeof(str) === 'undefined') return 0;
- return parseInt(str.replace('px',''), 10);
- },
- outerHtml: function(el)
- {
- return $('<div>').append($(el).eq(0).clone()).html();
- },
- isString: function(obj)
- {
- return Object.prototype.toString.call(obj) == '[object String]';
- },
- isEmpty: function(html)
- {
- html = html.replace(/​|<br>|<br\/>| /gi, '');
- html = html.replace(/\s/g, '');
- html = html.replace(/^<p>[^\W\w\D\d]*?<\/p>$/i, '');
+ return (offset === 0) ? true : false;
+ },
+ isEndOfElement: function()
+ {
+ var block = this.selection.getBlock();
+ if (!block) return false;
+ var offset = this.caret.getOffsetOfElement(block);
+ var text = $.trim($(block).text()).replace(/\n\r\n/g, '');
- return html == '';
- },
- isIe11: function()
- {
- return !!navigator.userAgent.match(/Trident\/7\./);
- },
- browser: function(browser)
- {
- var ua = navigator.userAgent.toLowerCase();
- var match = /(opr)[\/]([\w.]+)/.exec( ua ) ||
- /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
- /(webkit)[ \/]([\w.]+).*(safari)[ \/]([\w.]+)/.exec(ua) ||
- /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
- /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
- /(msie) ([\w.]+)/.exec( ua ) ||
- ua.indexOf("trident") >= 0 && /(rv)(?::| )([\w.]+)/.exec( ua ) ||
- ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
- [];
+ return (offset == text.length) ? true : false;
+ },
- if (browser == 'version') return match[2];
- if (browser == 'webkit') return (match[1] == 'chrome' || match[1] == 'webkit');
- if (match[1] == 'rv') return browser == 'msie';
- if (match[1] == 'opr') return browser == 'webkit';
+ // test blocks
+ isBlock: function(block)
+ {
+ block = block[0] || block;
- return browser == match[1];
+ return block && this.utils.isBlockTag(block.tagName);
+ },
+ isBlockTag: function(tag)
+ {
+ if (typeof tag == 'undefined') return false;
- },
- oldIE: function()
- {
- if (this.browser('msie') && parseInt(this.browser('version'), 10) < 9) return true;
- return false;
- },
- getFragmentHtml: function (fragment)
- {
- var cloned = fragment.cloneNode(true);
- var div = this.document.createElement('div');
+ return this.reIsBlock.test(tag);
+ },
- div.appendChild(cloned);
- return div.innerHTML;
- },
- extractContent: function()
- {
- var node = this.$editor[0];
- var frag = this.document.createDocumentFragment();
- var child;
+ // tag detection
+ isTag: function(current, tag)
+ {
+ var element = $(current).closest(tag);
+ if (element.size() == 1)
+ {
+ return element[0];
+ }
- while ((child = node.firstChild))
- {
- frag.appendChild(child);
- }
+ return false;
+ },
- return frag;
- },
- isParentRedactor: function(el)
- {
- if (!el) return false;
- if (this.opts.iframe) return el;
+ // select all
+ isSelectAll: function()
+ {
+ return this.selectAll;
+ },
+ enableSelectAll: function()
+ {
+ this.selectAll = true;
+ },
+ disableSelectAll: function()
+ {
+ this.selectAll = false;
+ },
- if ($(el).parents('div.redactor_editor').length == 0 || $(el).hasClass('redactor_editor')) return false;
- else return el;
- },
- currentOrParentIs: function(tagName)
- {
- var parent = this.getParent(), current = this.getCurrent();
- return parent && parent.tagName === tagName ? parent : current && current.tagName === tagName ? current : false;
- },
- isEndOfElement: function()
- {
- var current = this.getBlock();
- var offset = this.getCaretOffset(current);
+ // parents detection
+ isRedactorParent: function(el)
+ {
+ if (!el)
+ {
+ return false;
+ }
- var text = $.trim($(current).text()).replace(/\n\r\n/g, '');
+ if ($(el).parents('.redactor-editor').length === 0 || $(el).hasClass('redactor-editor'))
+ {
+ return false;
+ }
- var len = text.length;
+ return el;
+ },
+ isCurrentOrParent: function(tagName)
+ {
+ var parent = this.selection.getParent();
+ var current = this.selection.getCurrent();
- if (offset == len) return true;
- else return false;
- },
- isFocused: function()
- {
- var el, sel = this.getSelection();
+ if ($.isArray(tagName))
+ {
+ var matched = 0;
+ $.each(tagName, $.proxy(function(i, s)
+ {
+ if (this.utils.isCurrentOrParentOne(current, parent, s))
+ {
+ matched++;
+ }
+ }, this));
- if (sel && sel.rangeCount && sel.rangeCount > 0) el = sel.getRangeAt(0).startContainer;
- if (!el) return false;
- if (this.opts.iframe)
- {
- if (this.getCaretOffsetRange().equals()) return !this.$editor.is(el);
- else return true;
- }
+ return (matched === 0) ? false : true;
+ }
+ else
+ {
+ return this.utils.isCurrentOrParentOne(current, parent, tagName);
+ }
+ },
+ isCurrentOrParentOne: function(current, parent, tagName)
+ {
+ return parent && parent.tagName === tagName ? parent : current && current.tagName === tagName ? current : false;
+ },
- return $(el).closest('div.redactor_editor').length != 0;
- },
- removeEmptyAttr: function (el, attr)
- {
- if ($(el).attr(attr) == '') $(el).removeAttr(attr);
- },
- removeFromArrayByValue: function(array, value)
- {
- var index = null;
- while ((index = array.indexOf(value)) !== -1)
- {
- array.splice(index, 1);
- }
+ // browsers detection
+ isOldIe: function()
+ {
+ return (this.utils.browser('msie') && parseInt(this.utils.browser('version'), 10) < 9) ? true : false;
+ },
+ isLessIe10: function()
+ {
+ return (this.utils.browser('msie') && parseInt(this.utils.browser('version'), 10) < 10) ? true : false;
+ },
+ isIe11: function()
+ {
+ return !!navigator.userAgent.match(/Trident\/7\./);
+ },
+ browser: function(browser)
+ {
+ var ua = navigator.userAgent.toLowerCase();
+ var match = /(opr)[\/]([\w.]+)/.exec( ua ) ||
+ /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
+ /(webkit)[ \/]([\w.]+).*(safari)[ \/]([\w.]+)/.exec(ua) ||
+ /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
+ /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
+ /(msie) ([\w.]+)/.exec( ua ) ||
+ ua.indexOf("trident") >= 0 && /(rv)(?::| )([\w.]+)/.exec( ua ) ||
+ ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
+ [];
- return array;
- }
+ if (browser == 'version') return match[2];
+ if (browser == 'webkit') return (match[1] == 'chrome' || match[1] == 'webkit');
+ if (match[1] == 'rv') return browser == 'msie';
+ if (match[1] == 'opr') return browser == 'webkit';
+ return browser == match[1];
+ }
+ };
+ }
};
// constructor
Redactor.prototype.init.prototype = Redactor.prototype;
// LINKIFY
- $.Redactor.fn.formatLinkify = function(protocol, convertLinks, convertImageLinks, convertVideoLinks, linkSize)
+ $.Redactor.fn.formatLinkify = function(protocol, convertLinks, convertUrlLinks, convertImageLinks, convertVideoLinks, linkSize)
{
- var url1 = /(^|<|\s)(www\..+?\..+?)(\s|>|$)/g,
- url2 = /(^|<|\s)(((https?|ftp):\/\/|mailto:).+?)(\s|>|$)/g,
- urlImage = /(https?:\/\/.*\.(?:png|jpg|jpeg|gif))/gi,
- urlYoutube = /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w.-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig,
- urlVimeo = /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/;
+ var 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.get(0) : this).childNodes, i = childNodes.length;
+ var childNodes = (this.$editor ? this.$editor[0] : this).childNodes, i = childNodes.length;
while (i--)
{
var n = childNodes[i];
if (n.nodeType === 3)
{
@@ -7259,49 +7818,64 @@
if (convertVideoLinks && html)
{
var iframeStart = '<iframe width="500" height="281" src="',
iframeEnd = '" frameborder="0" allowfullscreen></iframe>';
- if (html.match(urlYoutube))
+ if (html.match(reUrlYoutube))
{
- html = html.replace(urlYoutube, iframeStart + '//www.youtube.com/embed/$1' + iframeEnd);
+ html = html.replace(reUrlYoutube, iframeStart + '//www.youtube.com/embed/$1' + iframeEnd);
$(n).after(html).remove();
}
- else if (html.match(urlVimeo))
+ else if (html.match(reUrlVimeo))
{
- html = html.replace(urlVimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd);
+ html = html.replace(reUrlVimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd);
$(n).after(html).remove();
}
}
// image
if (convertImageLinks && html && html.match(urlImage))
{
- html = html.replace(urlImage, '<img src="$1">');
+ html = html.replace(urlImage, '<img src="$1" />');
$(n).after(html).remove();
+ return;
}
// link
- if (convertLinks && html && (html.match(url1) || html.match(url2)))
+ if (html.search(/\$/g) != -1) html = html.replace(/\$/g, '$');
+
+ var matches = html.match(regex);
+ if (convertUrlLinks && html && matches)
{
- var href = (html.match(url1) || html.match(url2));
- href = href[0];
- if (href.length > linkSize) href = href.substring(0, linkSize) + '...';
- html = html.replace(/&/g, '&')
- .replace(/</g, '<')
- .replace(/>/g, '>')
- .replace(url1, '$1<a href="' + protocol + '$2">' + $.trim(href) + '</a>$3')
- .replace(url2, '$1<a href="$2">' + $.trim(href) + '</a>$5');
+ var len = matches.length;
+ for (var z = 0; z < len; z++)
+ {
+ // remove dot in the end
+ if (matches[z].match(/\.$/) !== null) matches[z] = matches[z].replace(/\.$/, '');
+ var href = matches[z];
+ var text = href;
+ var space = '';
+ if (href.match(/\s$/) !== null) space = ' ';
+
+ var addProtocol = protocol + '://';
+ if (href.match(rProtocol) !== null) addProtocol = '';
+
+ if (text.length > linkSize) text = text.substring(0, linkSize) + '...';
+ text = text.replace(/$/g, '$').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
+
+ html = html.replace(href, '<a href=\"' + addProtocol + $.trim(href) + '\">' + $.trim(text) + '</a>' + space);
+ }
+
$(n).after(html).remove();
}
}
else if (n.nodeType === 1 && !/^(a|button|textarea)$/i.test(n.tagName))
{
- $.Redactor.fn.formatLinkify.call(n, protocol, convertLinks, convertImageLinks, convertVideoLinks, linkSize);
+ $.Redactor.fn.formatLinkify.call(n, protocol, convertLinks, convertUrlLinks, convertImageLinks, convertVideoLinks, linkSize);
}
}
};
})(jQuery);
\ No newline at end of file