5 http://imperavi.com/redactor/
7 Copyright (c) 2009-2014, Imperavi LLC.
8 License: http://imperavi.com/redactor/license/
10 Usage: $('#content').redactor();
19 var Range = function(range
)
21 this[0] = range
.startOffset
;
22 this[1] = range
.endOffset
;
29 Range
.prototype.equals = function()
31 return this[0] === this[1];
35 $.fn
.redactor = function(options
)
38 var args
= Array
.prototype.slice
.call(arguments
, 1);
40 if (typeof options
=== 'string')
44 var instance
= $.data(this, 'redactor');
45 if (typeof instance
!== 'undefined' && $.isFunction(instance
[options
]))
47 var methodVal
= instance
[options
].apply(instance
, args
);
48 if (methodVal
!== undefined && methodVal
!== instance
) val
.push(methodVal
);
50 else return $.error('No such method "' + options
+ '" for Redactor');
57 if (!$.data(this, 'redactor')) $.data(this, 'redactor', Redactor(this, options
));
61 if (val
.length
=== 0) return this;
62 else if (val
.length
=== 1) return val
[0];
68 function Redactor(el
, options
)
70 return new Redactor
.prototype.init(el
, options
);
73 $.Redactor
= Redactor
;
74 $.Redactor
.VERSION
= '9.2.1';
85 direction
: 'ltr', // ltr or rtl
94 pastePlainText
: false,
95 removeEmptyTags
: true,
109 autosave
: false, // false or url
110 autosaveInterval
: 60, // seconds
112 plugins
: false, // array
116 linkProtocol
: 'http://',
120 imageFloatMargin
: '10px',
121 imageGetJson
: false, // url (ex. /folder/images.json ) or false
123 dragUpload
: true, // false
125 imageUpload
: false, // url
126 imageUploadParam
: 'file', // input name
128 fileUpload
: false, // url
129 fileUploadParam
: 'file', // input name
130 clipboardUpload
: true, // or false
131 clipboardUploadUrl
: false, // url
133 dnbImageTypes
: ['image/png', 'image/jpeg', 'image/gif'], // or false
143 tabSpaces
: false, // true or number of spaces
147 airButtons
: ['formatting', 'bold', 'italic', 'deleted', 'unorderedlist', 'orderedlist', 'outdent', 'indent'],
151 toolbarFixedTarget
: document
,
152 toolbarFixedTopOffset
: 0, // pixels
153 toolbarFixedBox
: false,
154 toolbarExternal
: false, // ID selector
155 toolbarOverflow
: false,
158 buttons
: ['html', 'formatting', 'bold', 'italic', 'deleted', 'unorderedlist', 'orderedlist',
159 'outdent', 'indent', 'image', 'video', 'file', 'table', 'link', 'alignment', '|',
160 'horizontalrule'], // 'underline', 'alignleft', 'aligncenter', 'alignright', 'justify'
161 buttonsHideOnMobile
: [],
163 activeButtons
: ['deleted', 'italic', 'bold', 'underline', 'unorderedlist', 'orderedlist',
164 'alignleft', 'aligncenter', 'alignright', 'justify', 'table'],
165 activeButtonsStates
: {
180 formattingTags
: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
186 convertImageLinks
: false,
187 convertVideoLinks
: false,
188 formattingPre
: false,
192 deniedTags
: ['html', 'head', 'link', 'body', 'meta', 'script', 'style', 'applet'],
202 emptyHtml
: '<p>​</p>',
203 invisibleSpace
: '​',
204 rBlockTest
: /^(P|H[1-6]|LI|ADDRESS|SECTION|HEADER|FOOTER|ASIDE|ARTICLE)$/i,
205 alignmentTags
: ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DD', 'DL', 'DT', 'DIV', 'TD',
206 'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'ADDRESS', 'SECTION',
207 'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE'],
208 ownLine
: ['area', 'body', 'head', 'hr', 'i?frame', 'link', 'meta', 'noscript', 'style', 'script', 'table', 'tbody', 'thead', 'tfoot'],
209 contOwnLine
: ['li', 'dt', 'dt', 'h[1-6]', 'option', 'script'],
210 newLevel
: ['blockquote', 'div', 'dl', 'fieldset', 'form', 'frameset', 'map', 'ol', 'p', 'pre', 'select', 'td', 'th', 'tr', 'ul'],
211 blockLevelElements
: ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DD', 'DL', 'DT', 'DIV', 'LI',
212 'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'PRE', 'ADDRESS', 'SECTION',
213 'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE', 'TD'],
219 video
: 'Insert Video',
220 image
: 'Insert Image',
223 link_insert
: 'Insert link',
224 link_edit
: 'Edit link',
226 formatting
: 'Formatting',
227 paragraph
: 'Normal text',
237 fontcolor
: 'Font Color',
238 backcolor
: 'Back Color',
239 unorderedlist
: 'Unordered List',
240 orderedlist
: 'Ordered List',
247 insert_table
: 'Insert Table',
248 insert_row_above
: 'Add Row Above',
249 insert_row_below
: 'Add Row Below',
250 insert_column_left
: 'Add Column Left',
251 insert_column_right
: 'Add Column Right',
252 delete_column
: 'Delete Column',
253 delete_row
: 'Delete Row',
254 delete_table
: 'Delete Table',
257 add_head
: 'Add Head',
258 delete_head
: 'Delete Head',
260 image_position
: 'Position',
265 image_web_link
: 'Image Web Link',
269 video_html_code
: 'Video Embed Code',
272 download
: 'Download',
274 or_choose
: 'Or choose',
275 drop_file_here
: 'Drop file here',
276 align_left
: 'Align text to the left',
277 align_center
: 'Center text',
278 align_right
: 'Align text to the right',
279 align_justify
: 'Justify text',
280 horizontalrule
: 'Insert Horizontal Rule',
283 link_new_tab
: 'Open link in new tab',
284 underline
: 'Underline',
285 alignment
: 'Alignment',
286 filename
: 'Name (optional)',
293 Redactor
.fn
= $.Redactor
.prototype = {
309 init: function(el
, options
)
311 this.rtePaste
= false;
312 this.$element
= this.$source
= $(el
);
316 var opts
= $.extend(true, {}, $.Redactor
.opts
);
319 this.opts
= $.extend(
322 this.$element
.data(),
330 this.sourceHeight
= this.$source
.css('height');
331 this.sourceWidth
= this.$source
.css('width');
333 // dependency of the editor modes
334 if (this.opts
.fullpage
) this.opts
.iframe
= true;
335 if (this.opts
.linebreaks
) this.opts
.paragraphy
= false;
336 if (this.opts
.paragraphy
) this.opts
.linebreaks
= false;
337 if (this.opts
.toolbarFixedBox
) this.opts
.toolbarFixed
= true;
339 // the alias for iframe mode
340 this.document
= document
;
341 this.window
= window
;
344 this.savedSel
= false;
347 this.cleanlineBefore
= new RegExp('^<(/?' + this.opts
.ownLine
.join('|/?' ) + '|' + this.opts
.contOwnLine
.join('|') + ')[ >]');
348 this.cleanlineAfter
= new RegExp('^<(br|/?' + this.opts
.ownLine
.join('|/?' ) + '|/' + this.opts
.contOwnLine
.join('|/') + ')[ >]');
349 this.cleannewLevel
= new RegExp('^</?(' + this.opts
.newLevel
.join('|' ) + ')[ >]');
352 this.rTestBlock
= new RegExp('^(' + this.opts
.blockLevelElements
.join('|' ) + ')$', 'i');
354 // setup formatting permissions
355 if (this.opts
.linebreaks
=== false)
357 if (this.opts
.allowedTags
!== false)
359 var arrSearch
= ['strong', 'em', 'del'];
360 var arrAdd
= ['b', 'i', 'strike'];
362 if ($.inArray('p', this.opts
.allowedTags
) === '-1') this.opts
.allowedTags
.push('p');
366 if ($.inArray(arrSearch
[i
], this.opts
.allowedTags
) != '-1') this.opts
.allowedTags
.push(arrAdd
[i
]);
370 if (this.opts
.deniedTags
!== false)
372 var pos
= $.inArray('p', this.opts
.deniedTags
);
373 if (pos
!== '-1') this.opts
.deniedTags
.splice(pos
, pos
);
378 if (this.browser('msie') || this.browser('opera'))
380 this.opts
.buttons
= this.removeFromArrayByValue(this.opts
.buttons
, 'horizontalrule');
384 this.opts
.curLang
= this.opts
.langs
[this.opts
.lang
];
390 toolbarInit: function(lang
)
400 title
: lang
.formatting
,
406 title
: lang
.paragraph
,
413 className
: 'redactor_format_blockquote'
418 func
: 'formatBlocks',
419 className
: 'redactor_format_pre'
424 func
: 'formatBlocks',
425 className
: 'redactor_format_h1'
430 func
: 'formatBlocks',
431 className
: 'redactor_format_h2'
436 func
: 'formatBlocks',
437 className
: 'redactor_format_h3'
442 func
: 'formatBlocks',
443 className
: 'redactor_format_h4'
448 func
: 'formatBlocks',
449 className
: 'redactor_format_h5'
466 exec
: 'strikethrough'
470 title
: lang
.underline
,
475 title
: '• ' + lang
.unorderedlist
,
476 exec
: 'insertunorderedlist'
480 title
: '1. ' + lang
.orderedlist
,
481 exec
: 'insertorderedlist'
485 title
: '< ' + lang
.outdent
,
486 func
: 'indentingOutdent'
490 title
: '> ' + lang
.indent
,
491 func
: 'indentingIndent'
516 title
: lang
.insert_table
,
525 title
: lang
.insert_row_above
,
526 func
: 'tableAddRowAbove'
530 title
: lang
.insert_row_below
,
531 func
: 'tableAddRowBelow'
535 title
: lang
.insert_column_left
,
536 func
: 'tableAddColumnLeft'
540 title
: lang
.insert_column_right
,
541 func
: 'tableAddColumnRight'
549 title
: lang
.add_head
,
554 title
: lang
.delete_head
,
555 func
: 'tableDeleteHead'
563 title
: lang
.delete_column
,
564 func
: 'tableDeleteColumn'
568 title
: lang
.delete_row
,
569 func
: 'tableDeleteRow'
573 title
: lang
.delete_table
,
574 func
: 'tableDeleteTable'
585 title
: lang
.link_insert
,
597 title
: lang
.alignment
,
603 title
: lang
.align_left
,
604 func
: 'alignmentLeft'
608 title
: lang
.align_center
,
609 func
: 'alignmentCenter'
613 title
: lang
.align_right
,
614 func
: 'alignmentRight'
618 title
: lang
.align_justify
,
619 func
: 'alignmentJustify'
625 title
: lang
.align_left
,
626 func
: 'alignmentLeft'
630 title
: lang
.align_center
,
631 func
: 'alignmentCenter'
635 title
: lang
.align_right
,
636 func
: 'alignmentRight'
640 title
: lang
.align_justify
,
641 func
: 'alignmentJustify'
645 exec
: 'inserthorizontalrule',
646 title
: lang
.horizontalrule
653 callback: function(type
, event
, data
)
655 var callback
= this.opts
[ type
+ 'Callback' ];
656 if ($.isFunction(callback
))
658 if (event
=== false) return callback
.call(this, data
);
659 else return callback
.call(this, event
, data
);
668 clearInterval(this.autosaveInterval
);
670 $(window
).off('.redactor');
671 this.$source
.off('redactor-textarea');
672 this.$element
.off('.redactor').removeData('redactor');
674 var html
= this.get();
676 if (this.opts
.textareamode
)
678 this.$box
.after(this.$source
);
680 this.$source
.val(html
).show();
684 var $elem
= this.$editor
;
685 if (this.opts
.iframe
) $elem
= this.$element
;
687 this.$box
.after($elem
);
690 $elem
.removeClass('redactor_editor').removeClass('redactor_editor_wym').removeAttr('contenteditable').html(html
).show();
693 if (this.opts
.toolbarExternal
)
695 $(this.opts
.toolbarExternal
).html('');
700 $('#redactor_air_' + this.uuid
).remove();
705 getObject: function()
707 return $.extend({}, this);
709 getEditor: function()
717 getIframe: function()
719 return (this.opts
.iframe
) ? this.$frame
: false;
721 getToolbar: function()
723 return (this.$toolbar
) ? this.$toolbar
: false;
729 return this.$source
.val();
731 getCodeIframe: function()
733 this.$editor
.removeAttr('contenteditable').removeAttr('dir');
734 var html
= this.outerHtml(this.$frame
.contents().children());
735 this.$editor
.attr({ 'contenteditable': true, 'dir': this.opts
.direction
});
739 set: function(html
, strip
, placeholderRemove
)
741 html
= html
.toString();
742 html
= html
.replace(/\$/g, '$');
744 if (this.opts
.fullpage
) this.setCodeIframe(html
);
745 else this.setEditor(html
, strip
);
747 if (html
== '') placeholderRemove
= false;
748 if (placeholderRemove
!== false) this.placeholderRemoveFromEditor();
750 setEditor: function(html
, strip
)
755 html
= this.cleanSavePreCode(html
);
757 html
= this.cleanStripTags(html
);
758 html
= this.cleanConvertProtected(html
);
759 html
= this.cleanConvertInlineTags(html
, true);
761 if (this.opts
.linebreaks
=== false) html
= this.cleanConverters(html
);
762 else html
= html
.replace(/<p(.*?)>([\w\W]*?)<\/p>/gi, '$2<br>');
766 html
= html
.replace(/&#36;/g, '$');
768 html
= this.cleanEmpty(html
);
770 this.$editor
.html(html
);
773 this.setNonEditable();
774 this.setSpansVerified();
778 setCodeIframe: function(html
)
780 var doc
= this.iframePage();
781 this.$frame
[0].src
= "about:blank";
783 html
= this.cleanConvertProtected(html
);
784 html
= this.cleanConvertInlineTags(html
);
785 html
= this.cleanRemoveSpaces(html
);
791 // redefine editor for fullpage mode
792 if (this.opts
.fullpage
)
794 this.$editor
= this.$frame
.contents().find('body').attr({ 'contenteditable': true, 'dir': this.opts
.direction
});
798 this.setNonEditable();
799 this.setSpansVerified();
803 setFullpageOnInit: function(html
)
805 html
= this.cleanSavePreCode(html
, true);
806 html
= this.cleanConverters(html
);
807 html
= this.cleanEmpty(html
);
810 this.$editor
.html(html
);
813 this.setNonEditable();
814 this.setSpansVerified();
817 setSpansVerified: function()
819 var spans
= this.$editor
.find('span');
820 var replacementTag
= 'inline';
822 $.each(spans
, function() {
823 var outer
= this.outerHTML
;
825 // Replace opening tag
826 var regex
= new RegExp('<' + this.tagName
, 'gi');
827 var newTag
= outer
.replace(regex
, '<' + replacementTag
);
829 // Replace closing tag
830 regex
= new RegExp('</' + this.tagName
, 'gi');
831 newTag
= newTag
.replace(regex
, '</' + replacementTag
);
833 $(this).replaceWith(newTag
);
837 setSpansVerifiedHtml: function(html
)
839 html
= html
.replace(/<span(.*?)>/, '<inline$1>');
840 return html
.replace(/<\/span>/, '</inline>');
842 setNonEditable: function()
844 this.$editor
.find('.noneditable').attr('contenteditable', false);
852 this.cleanUnverified();
854 if (this.opts
.fullpage
) html
= this.getCodeIframe();
855 else html
= this.$editor
.html();
857 html
= this.syncClean(html
);
858 html
= this.cleanRemoveEmptyTags(html
);
860 // is there a need to synchronize
861 var source
= this.cleanRemoveSpaces(this.$source
.val(), false);
862 var editor
= this.cleanRemoveSpaces(html
, false);
864 if (source
== editor
)
871 // fix second level up ul, ol
872 html
= html
.replace(/<\/li><(ul|ol)>([\w\W]*?)<\/(ul|ol)>/gi, '<$1>$2</$1></li>');
874 if ($.trim(html
) === '<br>') html
= '';
879 var xhtmlTags
= ['br', 'hr', 'img', 'link', 'input', 'meta'];
880 $.each(xhtmlTags
, function(i
,s
)
882 html
= html
.replace(new RegExp('<' + s
+ '(.*?[^\/$]?)>', 'gi'), '<' + s
+ '$1 />');
888 html
= this.callback('syncBefore', false, html
);
890 this.$source
.val(html
);
892 // onchange & after callback
893 this.callback('syncAfter', false, html
);
895 if (this.start
=== false)
898 if (typeof e
!= 'undefined')
911 default: this.callback('change', false, html
);
916 this.callback('change', false, html
);
921 syncClean: function(html
)
923 if (!this.opts
.fullpage
) html
= this.cleanStripTags(html
);
929 html
= this.placeholderRemoveFromCode(html
);
932 html
= html
.replace(/​/gi, '');
933 html
= html
.replace(/​/gi, '');
934 html
= html
.replace(/<\/a> /gi, '<\/a> ');
935 html
= html
.replace(/\u200B/g, '');
937 if (html
== '<p></p>' || html
== '<p> </p>' || html
== '<p> </p>')
943 if (this.opts
.linkNofollow
)
945 html
= html
.replace(/<a(.*?)rel="nofollow"(.*?)>/gi, '<a$1$2>');
946 html
= html
.replace(/<a(.*?)>/gi, '<a$1 rel="nofollow">');
950 html
= html
.replace('<!--?php', '<?php');
951 html
= html
.replace('?-->', '?>');
953 // revert no editable
954 html
= html
.replace(/<(.*?)class="noeditable"(.*?) contenteditable="false"(.*?)>/gi, '<$1class="noeditable"$2$3>');
956 html
= html
.replace(/ data
-tagblock
=""/gi
, '');
957 html
= html
.replace(/<br\s?\/?>\n?<\/(P|H[1-6]|LI|ADDRESS|SECTION|HEADER|FOOTER|ASIDE|ARTICLE)>/gi, '</$1>');
959 // remove image resize
960 html
= html
.replace(/<span(.*?)id="redactor-image-box"(.*?)>([\w\W]*?)<img(.*?)><\/span>/gi, '$3<img$4>');
961 html
= html
.replace(/<span(.*?)id="redactor-image-resizer"(.*?)>(.*?)<\/span>/gi, '');
962 html
= html
.replace(/<span(.*?)id="redactor-image-editter"(.*?)>(.*?)<\/span>/gi, '');
964 // remove empty lists
965 html
= html
.replace(/<(ul|ol)>\s*\t*\n*<\/(ul|ol)>/gi, '');
968 if (this.opts
.cleanFontTag
)
970 html
= html
.replace(/<font(.*?)>([\w\W]*?)<\/font>/gi, '$2');
974 html
= html
.replace(/<span(.*?)>([\w\W]*?)<\/span>/gi, '$2');
975 html
= html
.replace(/<inline>/gi, '<span>');
976 html
= html
.replace(/<inline /gi, '<span ');
977 html
= html
.replace(/<\/inline>/gi, '</span>');
978 html
= html
.replace(/<span(.*?)class="redactor_placeholder"(.*?)>([\w\W]*?)<\/span>/gi, '');
979 html
= html
.replace(/<span>([\w\W]*?)<\/span>/gi, '$1');
981 // special characters
982 html
= html
.replace(/&/gi, '&');
983 html
= html
.replace(/™/gi, '™');
984 html
= html
.replace(/©/gi, '©');
985 html
= html
.replace(/…/gi, '…');
986 html
= html
.replace(/—/gi, '—');
987 html
= html
.replace(/‐/gi, '‐');
990 html
= this.cleanReConvertProtected(html
);
997 buildStart: function()
1003 this.$box
= $('<div class="redactor_box" />');
1004 this.$box
.css('z-index', 100-this.uuid
);
1007 if (this.$source
[0].tagName
=== 'TEXTAREA') this.opts
.textareamode
= true;
1010 if (this.opts
.mobile
=== false && this.isMobile())
1016 // get the content at the start
1017 this.buildContent();
1019 if (this.opts
.iframe
)
1022 this.opts
.autoresize
= false;
1025 else if (this.opts
.textareamode
) this.buildFromTextarea();
1026 else this.buildFromElement();
1028 // options and final setup
1029 if (!this.opts
.iframe
)
1031 this.buildOptions();
1036 buildMobile: function()
1038 if (!this.opts
.textareamode
)
1040 this.$editor
= this.$source
;
1041 this.$editor
.hide();
1042 this.$source
= this.buildCodearea(this.$editor
);
1043 this.$source
.val(this.content
);
1046 this.$box
.insertAfter(this.$source
).append(this.$source
);
1048 buildContent: function()
1050 if (this.opts
.textareamode
) this.content
= $.trim(this.$source
.val());
1051 else this.content
= $.trim(this.$source
.html());
1053 buildFromTextarea: function()
1055 this.$editor
= $('<div />');
1056 this.$box
.insertAfter(this.$source
).append(this.$editor
).append(this.$source
);
1059 this.buildAddClasses(this.$editor
);
1062 buildFromElement: function()
1064 this.$editor
= this.$source
;
1065 this.$source
= this.buildCodearea(this.$editor
);
1066 this.$box
.insertAfter(this.$editor
).append(this.$editor
).append(this.$source
);
1071 buildCodearea: function($source
)
1073 return $('<textarea />').attr('name', $source
.attr('id')).css('height', this.sourceHeight
);
1075 buildAddClasses: function(el
)
1077 // append textarea classes to editable layer
1078 $.each(this.$source
.get(0).className
.split(/\s+/), function(i
,s
)
1080 el
.addClass('redactor_' + s
);
1083 buildEnable: function()
1085 this.$editor
.addClass('redactor_editor').attr({ 'contenteditable': true, 'dir': this.opts
.direction
});
1086 this.$source
.attr('dir', this.opts
.direction
).hide();
1089 this.set(this.content
, true, false);
1091 buildOptions: function()
1093 var $source
= this.$editor
;
1094 if (this.opts
.iframe
) $source
= this.$frame
;
1097 if (this.opts
.tabindex
) $source
.attr('tabindex', this.opts
.tabindex
);
1099 if (this.opts
.minHeight
) $source
.css('min-height', this.opts
.minHeight
+ 'px');
1100 // FF fix bug with line-height rendering
1101 else if (this.browser('mozilla') && this.opts
.linebreaks
)
1103 this.$editor
.css('min-height', '45px');
1105 // FF fix bug with line-height rendering
1106 if (this.browser('mozilla') && this.opts
.linebreaks
)
1108 this.$editor
.css('padding-bottom', '10px');
1112 if (this.opts
.maxHeight
)
1114 this.opts
.autoresize
= false;
1115 this.sourceHeight
= this.opts
.maxHeight
;
1117 if (this.opts
.wym
) this.$editor
.addClass('redactor_editor_wym');
1118 if (this.opts
.typewriter
) this.$editor
.addClass('redactor-editor-typewriter');
1119 if (!this.opts
.autoresize
) $source
.css('height', this.sourceHeight
);
1122 buildAfter: function()
1127 if (this.opts
.toolbar
)
1129 this.opts
.toolbar
= this.toolbarInit(this.opts
.curLang
);
1130 this.toolbarBuild();
1134 this.modalTemplatesInit();
1137 this.buildPlugins();
1140 this.buildBindKeyboard();
1143 if (this.opts
.autosave
) this.autosave();
1146 setTimeout($.proxy(this.observeStart
, this), 4);
1149 if (this.browser('mozilla'))
1152 this.document
.execCommand('enableObjectResizing', false, false);
1153 this.document
.execCommand('enableInlineTableEditing', false, false);
1158 if (this.opts
.focus
) setTimeout($.proxy(this.focus
, this), 100);
1161 if (!this.opts
.visual
)
1163 setTimeout($.proxy(function()
1165 this.opts
.visual
= true;
1172 this.callback('init');
1174 buildBindKeyboard: function()
1178 if (this.opts
.dragUpload
&& this.opts
.imageUpload
!== false)
1180 this.$editor
.on('drop.redactor', $.proxy(this.buildEventDrop
, this));
1183 this.$editor
.on('input.redactor', $.proxy(this.sync
, this));
1184 this.$editor
.on('paste.redactor', $.proxy(this.buildEventPaste
, this));
1185 this.$editor
.on('keydown.redactor', $.proxy(this.buildEventKeydown
, this));
1186 this.$editor
.on('keyup.redactor', $.proxy(this.buildEventKeyup
, this));
1188 // textarea callback
1189 if ($.isFunction(this.opts
.textareaKeydownCallback
))
1191 this.$source
.on('keydown.redactor-textarea', $.proxy(this.opts
.textareaKeydownCallback
, this));
1195 if ($.isFunction(this.opts
.focusCallback
))
1197 this.$editor
.on('focus.redactor', $.proxy(this.opts
.focusCallback
, this));
1201 $(document
).mousedown(function(e
) {
1202 clickedElement
= $(e
.target
);
1206 this.$editor
.on('blur.redactor', $.proxy(function(e
)
1208 if (!$(clickedElement
).hasClass('redactor_toolbar') && $(clickedElement
).parents('.redactor_toolbar').size() == 0)
1210 this.selectall
= false;
1211 if ($.isFunction(this.opts
.blurCallback
)) this.callback('blur', e
);
1216 buildEventDrop: function(e
)
1218 e
= e
.originalEvent
|| e
;
1220 if (window
.FormData
=== undefined || !e
.dataTransfer
) return true;
1222 var length
= e
.dataTransfer
.files
.length
;
1223 if (length
== 0) return true;
1227 var file
= e
.dataTransfer
.files
[0];
1229 if (this.opts
.dnbImageTypes
!== false && this.opts
.dnbImageTypes
.indexOf(file
.type
) == -1)
1236 var progress
= $('<div id="redactor-progress"><span></span></div>');
1237 $(document
.body
).append(progress
);
1239 if (this.opts
.s3
=== false)
1241 this.dragUploadAjax(this.opts
.imageUpload
, file
, true, progress
, e
, this.opts
.imageUploadParam
);
1245 this.s3uploadFile(file
);
1250 buildEventPaste: function(e
)
1252 var oldsafari
= false;
1253 if (this.browser('webkit') && navigator
.userAgent
.indexOf('Chrome') === -1)
1255 var arr
= this.browser('version').split('.');
1256 if (arr
[0] < 536) oldsafari
= true;
1259 if (oldsafari
) return true;
1261 // paste except opera (not webkit)
1262 if (this.browser('opera')) return true;
1265 if (this.opts
.clipboardUpload
&& this.buildEventClipboardUpload(e
)) return true;
1267 if (this.opts
.cleanup
)
1269 this.rtePaste
= true;
1271 this.selectionSave();
1273 if (!this.selectall
)
1275 if (this.opts
.autoresize
=== true && this.fullscreen
!== true)
1277 this.$editor
.height(this.$editor
.height());
1278 this.saveScroll
= this.document
.body
.scrollTop
;
1282 this.saveScroll
= this.$editor
.scrollTop();
1286 var frag
= this.extractContent();
1288 setTimeout($.proxy(function()
1290 var pastedFrag
= this.extractContent();
1291 this.$editor
.append(frag
);
1293 this.selectionRestore();
1295 var html
= this.getFragmentHtml(pastedFrag
);
1296 this.pasteClean(html
);
1298 if (this.opts
.autoresize
=== true && this.fullscreen
!== true) this.$editor
.css('height', 'auto');
1303 buildEventClipboardUpload: function(e
)
1305 var event
= e
.originalEvent
|| e
;
1306 this.clipboardFilePaste
= false;
1308 if (typeof(event
.clipboardData
) === 'undefined') return false;
1309 if (event
.clipboardData
.items
)
1311 var file
= event
.clipboardData
.items
[0].getAsFile();
1315 this.clipboardFilePaste
= true;
1317 var reader
= new FileReader();
1318 reader
.onload
= $.proxy(this.pasteClipboardUpload
, this);
1319 reader
.readAsDataURL(file
);
1328 buildEventKeydown: function(e
)
1330 if (this.rtePaste
) return false;
1333 var ctrl
= e
.ctrlKey
|| e
.metaKey
;
1334 var parent
= this.getParent();
1335 var current
= this.getCurrent();
1336 var block
= this.getBlock();
1339 this.callback('keydown', e
);
1341 // disabling cmd|ctrl + left
1342 if (this.browser('mozilla') && ctrl
&& key
=== 37)
1348 this.imageResizeHide(false);
1351 if ((parent
&& $(parent
).get(0).tagName
=== 'PRE') || (current
&& $(current
).get(0).tagName
=== 'PRE'))
1354 if (key
=== this.keyCode
.DOWN
) this.insertAfterLastElement(block
);
1358 if (key
=== this.keyCode
.DOWN
)
1360 if (parent
&& $(parent
)[0].tagName
=== 'BLOCKQUOTE') this.insertAfterLastElement(parent
);
1361 if (current
&& $(current
)[0].tagName
=== 'BLOCKQUOTE') this.insertAfterLastElement(current
);
1363 if (parent
&& $(parent
)[0].tagName
=== 'P' && $(parent
).parent()[0].tagName
== 'BLOCKQUOTE')
1365 this.insertAfterLastElement(parent
, $(parent
).parent()[0]);
1367 if (current
&& $(current
)[0].tagName
=== 'P' && parent
&& $(parent
)[0].tagName
== 'BLOCKQUOTE')
1369 this.insertAfterLastElement(current
, parent
);
1374 if (ctrl
&& !e
.shiftKey
) this.shortcuts(e
, key
);
1377 if (ctrl
&& key
=== 90 && !e
.shiftKey
&& !e
.altKey
) // z key
1380 if (this.opts
.buffer
.length
) this.bufferUndo();
1381 else this.document
.execCommand('undo', false, false);
1385 else if (ctrl
&& key
=== 90 && e
.shiftKey
&& !e
.altKey
)
1388 if (this.opts
.rebuffer
.length
!= 0) this.bufferRedo();
1389 else this.document
.execCommand('redo', false, false);
1400 if (ctrl
&& key
=== 65)
1403 this.selectall
= true;
1405 else if (key
!= this.keyCode
.LEFT_WIN
&& !ctrl
)
1407 this.selectall
= false;
1411 if (key
== this.keyCode
.ENTER
&& !e
.shiftKey
&& !e
.ctrlKey
&& !e
.metaKey
)
1414 var range
= this.getRange();
1415 if (range
&& range
.collapsed
=== false)
1417 sel
= this.getSelection();
1420 range
.deleteContents();
1424 // In ie, opera in the tables are created paragraphs, fix it.
1425 if (this.browser('msie') && (parent
.nodeType
== 1 && (parent
.tagName
== 'TD' || parent
.tagName
== 'TH')))
1429 this.insertNode(document
.createElement('br'));
1430 this.callback('enter', e
);
1435 if (block
&& (block
.tagName
== 'BLOCKQUOTE' || $(block
).parent()[0].tagName
== 'BLOCKQUOTE'))
1437 if (this.isEndOfElement())
1439 if (this.dblEnter
== 1)
1443 if (block
.tagName
== 'BLOCKQUOTE')
1451 element
= $(block
).parent()[0];
1455 this.insertingAfterLastElement(element
);
1460 $(block
).parent().find('p').last().remove();
1464 var tmp
= $.trim($(block
).html());
1465 $(block
).html(tmp
.replace(/<br\s?\/?>$/i, ''));
1470 else this.dblEnter
++;
1472 else this.dblEnter
++;
1476 if (pre
=== true) return this.buildEventKeydownPre(e
, current
);
1479 if (!this.opts
.linebreaks
)
1482 if (block
&& this.opts
.rBlockTest
.test(block
.tagName
))
1487 setTimeout($.proxy(function()
1489 var blockElem
= this.getBlock();
1490 if (blockElem
.tagName
=== 'DIV' && !$(blockElem
).hasClass('redactor_editor'))
1492 var node
= $('<p>' + this.opts
.invisibleSpace
+ '</p>');
1493 $(blockElem
).replaceWith(node
);
1494 this.selectionStart(node
);
1499 else if (block
=== false)
1504 var node
= $('<p>' + this.opts
.invisibleSpace
+ '</p>');
1505 this.insertNode(node
[0]);
1506 this.selectionStart(node
);
1507 this.callback('enter', e
);
1513 if (this.opts
.linebreaks
)
1515 // replace div to br
1516 if (block
&& this.opts
.rBlockTest
.test(block
.tagName
))
1521 setTimeout($.proxy(function()
1523 var blockElem
= this.getBlock();
1524 if ((blockElem
.tagName
=== 'DIV' || blockElem
.tagName
=== 'P') && !$(blockElem
).hasClass('redactor_editor'))
1526 this.replaceLineBreak(blockElem
);
1533 return this.buildEventKeydownInsertLineBreak(e
);
1537 // blockquote, figcaption
1538 if (block
.tagName
== 'BLOCKQUOTE' || block
.tagName
== 'FIGCAPTION')
1540 return this.buildEventKeydownInsertLineBreak(e
);
1545 this.callback('enter', e
);
1547 else if (key
=== this.keyCode
.ENTER
&& (e
.ctrlKey
|| e
.shiftKey
)) // Shift+Enter or Ctrl+Enter
1552 this.insertLineBreak();
1556 if ((key
=== this.keyCode
.TAB
|| e
.metaKey
&& key
=== 219) && this.opts
.shortcuts
)
1558 return this.buildEventKeydownTab(e
, pre
, key
);
1561 // delete zero-width space before the removing
1562 if (key
=== this.keyCode
.BACKSPACE
) this.buildEventKeydownBackspace(current
);
1565 buildEventKeydownPre: function(e
, current
)
1569 var html
= $(current
).parent().text();
1570 this.insertNode(document
.createTextNode('\n'));
1571 if (html
.search(/\s$/) == -1)
1573 this.insertNode(document
.createTextNode('\n'));
1577 this.callback('enter', e
);
1580 buildEventKeydownTab: function(e
, pre
, key
)
1582 if (!this.opts
.tabFocus
) return true;
1583 if (this.isEmpty(this.get()) && this.opts
.tabSpaces
=== false) return true;
1587 if (pre
=== true && !e
.shiftKey
)
1590 this.insertNode(document
.createTextNode('\t'));
1595 else if (this.opts
.tabSpaces
!== false)
1598 this.insertNode(document
.createTextNode(Array(this.opts
.tabSpaces
+ 1).join('\u00a0')));
1604 if (!e
.shiftKey
) this.indentingIndent();
1605 else this.indentingOutdent();
1610 buildEventKeydownBackspace: function(current
)
1612 if (typeof current
.tagName
!== 'undefined' && /^(H[1-6])$/i.test(current
.tagName
))
1615 if (this.opts
.linebreaks
=== false) node
= $('<p>' + this.opts
.invisibleSpace
+ '</p>');
1616 else node
= $('<br>' + this.opts
.invisibleSpace
);
1618 $(current
).replaceWith(node
);
1619 this.selectionStart(node
);
1622 if (typeof current
.nodeValue
!== 'undefined' && current
.nodeValue
!== null)
1625 //var value = $.trim(current.nodeValue.replace(/[^\u0000-\u1C7F]/g, ''));
1626 if (current
.remove
&& current
.nodeType
=== 3 && current
.nodeValue
.match(/[^\u200B]/g) == null)
1632 buildEventKeydownInsertLineBreak: function(e
)
1636 this.insertLineBreak();
1637 this.callback('enter', e
);
1640 buildEventKeyup: function(e
)
1642 if (this.rtePaste
) return false;
1645 var parent
= this.getParent();
1646 var current
= this.getCurrent();
1648 // replace to p before / after the table or body
1649 if (!this.opts
.linebreaks
&& current
.nodeType
== 3 && (parent
== false || parent
.tagName
== 'BODY'))
1651 var node
= $('<p>').append($(current
).clone());
1652 $(current
).replaceWith(node
);
1653 var next
= $(node
).next();
1654 if (typeof(next
[0]) !== 'undefined' && next
[0].tagName
== 'BR')
1659 this.selectionEnd(node
);
1663 if ((this.opts
.convertLinks
|| this.opts
.convertImageLinks
|| this.opts
.convertVideoLinks
) && key
=== this.keyCode
.ENTER
)
1665 this.buildEventKeyupConverters();
1669 if (key
=== this.keyCode
.DELETE
|| key
=== this.keyCode
.BACKSPACE
)
1671 return this.formatEmpty(e
);
1674 this.callback('keyup', e
);
1677 buildEventKeyupConverters: function()
1679 this.formatLinkify(this.opts
.linkProtocol
, this.opts
.convertLinks
, this.opts
.convertImageLinks
, this.opts
.convertVideoLinks
, this.opts
.linkSize
);
1681 setTimeout($.proxy(function()
1683 if (this.opts
.convertImageLinks
) this.observeImages();
1684 if (this.opts
.observeLinks
) this.observeLinks();
1687 buildPlugins: function()
1689 if (!this.opts
.plugins
) return;
1691 $.each(this.opts
.plugins
, $.proxy(function(i
, s
)
1693 if (RedactorPlugins
[s
])
1695 $.extend(this, RedactorPlugins
[s
]);
1696 if ($.isFunction( RedactorPlugins
[ s
].init
)) this.init();
1703 iframeStart: function()
1705 this.iframeCreate();
1707 if (this.opts
.textareamode
) this.iframeAppend(this.$source
);
1710 this.$sourceOld
= this.$source
.hide();
1711 this.$source
= this.buildCodearea(this.$sourceOld
);
1712 this.iframeAppend(this.$sourceOld
);
1715 iframeAppend: function(el
)
1717 this.$source
.attr('dir', this.opts
.direction
).hide();
1718 this.$box
.insertAfter(el
).append(this.$frame
).append(this.$source
);
1720 iframeCreate: function()
1722 this.$frame
= $('<iframe style="width: 100%;" frameborder="0" />').one('load', $.proxy(function()
1724 if (this.opts
.fullpage
)
1728 if (this.content
=== '') this.content
= this.opts
.invisibleSpace
;
1730 this.$frame
.contents()[0].write(this.content
);
1731 this.$frame
.contents()[0].close();
1733 var timer
= setInterval($.proxy(function()
1735 if (this.$frame
.contents().find('body').html())
1737 clearInterval(timer
);
1743 else this.iframeLoad();
1747 iframeDoc: function()
1749 return this.$frame
[0].contentWindow
.document
;
1751 iframePage: function()
1753 var doc
= this.iframeDoc();
1754 if (doc
.documentElement
) doc
.removeChild(doc
.documentElement
);
1758 iframeAddCss: function(css
)
1760 css
= css
|| this.opts
.css
;
1762 if (this.isString(css
))
1764 this.$frame
.contents().find('head').append('<link rel="stylesheet" href="' + css
+ '" />');
1769 $.each(css
, $.proxy(function(i
, url
)
1771 this.iframeAddCss(url
);
1776 iframeLoad: function()
1778 this.$editor
= this.$frame
.contents().find('body').attr({ 'contenteditable': true, 'dir': this.opts
.direction
});
1780 // set document & window
1781 if (this.$editor
[0])
1783 this.document
= this.$editor
[0].ownerDocument
;
1784 this.window
= this.document
.defaultView
|| window
;
1788 this.iframeAddCss();
1790 if (this.opts
.fullpage
) this.setFullpageOnInit(this.$editor
.html());
1791 else this.set(this.content
, true, false);
1793 this.buildOptions();
1798 placeholderStart: function(html
)
1800 if (this.isEmpty(html
))
1802 if (this.$element
.attr('placeholder'))
1804 this.opts
.placeholder
= this.$element
.attr('placeholder');
1807 if (this.opts
.placeholder
!== '')
1809 this.opts
.focus
= false;
1810 this.placeholderOnFocus();
1811 this.placeholderOnBlur();
1813 return this.placeholderGet();
1818 this.placeholderOnBlur();
1823 placeholderOnFocus: function()
1825 this.$editor
.on('focus.redactor_placeholder', $.proxy(this.placeholderFocus
, this));
1827 placeholderOnBlur: function()
1829 this.$editor
.on('blur.redactor_placeholder', $.proxy(this.placeholderBlur
, this));
1831 placeholderGet: function()
1833 return $('<span class="redactor_placeholder">').data('redactor', 'verified').attr('contenteditable', false).text(this.opts
.placeholder
);
1835 placeholderBlur: function()
1837 var html
= this.get();
1838 if (this.isEmpty(html
))
1840 this.placeholderOnFocus();
1841 this.$editor
.html(this.placeholderGet());
1844 placeholderFocus: function()
1846 this.$editor
.find('span.redactor_placeholder').remove();
1849 if (this.opts
.linebreaks
=== false)
1851 html
= this.opts
.emptyHtml
;
1854 this.$editor
.off('focus.redactor_placeholder');
1855 this.$editor
.html(html
);
1857 if (this.opts
.linebreaks
=== false)
1859 // place the cursor inside emptyHtml
1860 this.selectionStart(this.$editor
.children()[0]);
1869 placeholderRemoveFromEditor: function()
1871 this.$editor
.find('span.redactor_placeholder').remove();
1872 this.$editor
.off('focus.redactor_placeholder');
1874 placeholderRemoveFromCode: function(html
)
1876 return html
.replace(/<span class="redactor_placeholder"(.*?)>(.*?)<\/span>/i, '');
1880 shortcuts: function(e
, key
)
1883 if (!this.opts
.shortcuts
)
1885 if (key
=== 66 || key
=== 73) e
.preventDefault();
1889 if (key
=== 77) this.shortcutsLoad(e
, 'removeFormat'); // Ctrl + m
1890 else if (key
=== 66) this.shortcutsLoad(e
, 'bold'); // Ctrl + b
1891 else if (key
=== 73) this.shortcutsLoad(e
, 'italic'); // Ctrl + i
1893 else if (key
=== 74) this.shortcutsLoad(e
, 'insertunorderedlist'); // Ctrl + j
1894 else if (key
=== 75) this.shortcutsLoad(e
, 'insertorderedlist'); // Ctrl + k
1896 else if (key
=== 72) this.shortcutsLoad(e
, 'superscript'); // Ctrl + h
1897 else if (key
=== 76) this.shortcutsLoad(e
, 'subscript'); // Ctrl + l
1900 shortcutsLoad: function(e
, cmd
)
1903 this.execCommand(cmd
, false);
1905 shortcutsLoadFormat: function(e
, cmd
)
1908 this.formatBlocks(cmd
);
1914 if (!this.browser('opera'))
1916 this.window
.setTimeout($.proxy(this.focusSet
, this, true), 1);
1920 this.$editor
.focus();
1923 focusWithSaveScroll: function()
1925 if (this.browser('msie'))
1927 var top
= this.document
.documentElement
.scrollTop
;
1930 this.$editor
.focus();
1932 if (this.browser('msie'))
1934 this.document
.documentElement
.scrollTop
= top
;
1937 focusEnd: function()
1939 if (!this.browser('mozilla'))
1945 if (this.opts
.linebreaks
=== false)
1947 var last
= this.$editor
.children().last();
1949 this.$editor
.focus();
1950 this.selectionEnd(last
);
1958 focusSet: function(collapse
, element
)
1960 this.$editor
.focus();
1962 if (typeof element
== 'undefined')
1964 element
= this.$editor
[0];
1967 var range
= this.getRange();
1968 range
.selectNodeContents(element
);
1970 // collapse - controls the position of focus: the beginning (true), at the end (false).
1971 range
.collapse(collapse
|| false);
1973 var sel
= this.getSelection();
1974 sel
.removeAllRanges();
1975 sel
.addRange(range
);
1979 toggle: function(direct
)
1981 if (this.opts
.visual
) this.toggleCode(direct
);
1982 else this.toggleVisual();
1984 toggleVisual: function()
1986 var html
= this.$source
.hide().val();
1988 if (typeof this.modified
!== 'undefined')
1990 this.modified
= this.cleanRemoveSpaces(this.modified
, false) !== this.cleanRemoveSpaces(html
, false);
1995 // don't remove the iframe even if cleared all.
1996 if (this.opts
.fullpage
&& html
=== '') this.setFullpageOnInit(html
);
2000 if (this.opts
.fullpage
) this.buildBindKeyboard();
2004 if (this.opts
.iframe
) this.$frame
.show();
2005 else this.$editor
.show();
2007 if (this.opts
.fullpage
) this.$editor
.attr('contenteditable', true );
2009 this.$source
.off('keydown.redactor-textarea-indenting');
2011 this.$editor
.focus();
2012 this.selectionRestore();
2014 this.observeStart();
2015 this.buttonActiveVisual();
2016 this.buttonInactive('html');
2017 this.opts
.visual
= true;
2019 toggleCode: function(direct
)
2021 if (direct
!== false) this.selectionSave();
2024 if (this.opts
.iframe
)
2026 height
= this.$frame
.height();
2027 if (this.opts
.fullpage
) this.$editor
.removeAttr('contenteditable');
2032 height
= this.$editor
.innerHeight();
2033 this.$editor
.hide();
2036 var html
= this.$source
.val();
2039 if (html
!== '' && this.opts
.tidyHtml
)
2041 this.$source
.val(this.cleanHtml(html
));
2044 this.modified
= html
;
2046 this.$source
.height(height
).show().focus();
2048 // textarea indenting
2049 this.$source
.on('keydown.redactor-textarea-indenting', this.textareaIndenting
);
2051 this.buttonInactiveVisual();
2052 this.buttonActive('html');
2053 this.opts
.visual
= false;
2055 textareaIndenting: function(e
)
2057 if (e
.keyCode
=== 9)
2060 var start
= $el
.get(0).selectionStart
;
2061 $el
.val($el
.val().substring(0, start
) + "\t" + $el
.val().substring($el
.get(0).selectionEnd
));
2062 $el
.get(0).selectionStart
= $el
.get(0).selectionEnd
= start
+ 1;
2068 autosave: function()
2070 var savedHtml
= false;
2071 this.autosaveInterval
= setInterval($.proxy(function()
2073 var html
= this.get();
2074 if (savedHtml
!== html
)
2076 var name
= this.$source
.attr('name');
2078 url
: this.opts
.autosave
,
2080 data
: 'name=' + name
+ '&' + name
+ '=' + escape(encodeURIComponent(html
)),
2081 success
: $.proxy(function(data
)
2083 var json
= $.parseJSON(data
);
2084 if (typeof json
.error
== 'undefined')
2087 this.callback('autosave', false, json
);
2092 this.callback('autosaveError', false, json
);
2100 }, this), this.opts
.autosaveInterval
*1000);
2104 toolbarBuild: function()
2107 if (this.isMobile() && this.opts
.buttonsHideOnMobile
.length
> 0)
2109 $.each(this.opts
.buttonsHideOnMobile
, $.proxy(function(i
, s
)
2111 var index
= this.opts
.buttons
.indexOf(s
);
2112 this.opts
.buttons
.splice(index
, 1);
2120 this.opts
.buttons
= this.opts
.airButtons
;
2124 if (!this.opts
.buttonSource
)
2126 var index
= this.opts
.buttons
.indexOf('html');
2127 this.opts
.buttons
.splice(index
, 1);
2132 if (this.opts
.toolbar
)
2134 $.each(this.opts
.toolbar
.formatting
.dropdown
, $.proxy(function (i
, s
)
2136 if ($.inArray(i
, this.opts
.formattingTags
) == '-1') delete this.opts
.toolbar
.formatting
.dropdown
[i
];
2141 // if no buttons don't create a toolbar
2142 if (this.opts
.buttons
.length
=== 0) return false;
2148 this.$toolbar
= $('<ul>').addClass('redactor_toolbar').attr('id', 'redactor_toolbar_' + this.uuid
);
2150 if (this.opts
.typewriter
)
2152 this.$toolbar
.addClass('redactor-toolbar-typewriter');
2155 if (this.opts
.toolbarOverflow
&& this.isMobile())
2157 this.$toolbar
.addClass('redactor-toolbar-overflow');
2163 this.$air
= $('<div class="redactor_air">').attr('id', 'redactor_air_' + this.uuid
).hide();
2164 this.$air
.append(this.$toolbar
);
2165 $('body').append(this.$air
);
2169 if (this.opts
.toolbarExternal
)
2171 this.$toolbar
.addClass('redactor-toolbar-external');
2172 $(this.opts
.toolbarExternal
).html(this.$toolbar
);
2174 else this.$box
.prepend(this.$toolbar
);
2177 $.each(this.opts
.buttons
, $.proxy(function(i
, btnName
)
2179 if (this.opts
.toolbar
[btnName
])
2181 var btnObject
= this.opts
.toolbar
[btnName
];
2182 if (this.opts
.fileUpload
=== false && btnName
=== 'file') return true;
2183 this.$toolbar
.append( $('<li>').append(this.buttonBuild(btnName
, btnObject
)));
2188 this.$toolbar
.find('a').attr('tabindex', '-1');
2191 if (this.opts
.toolbarFixed
)
2193 this.toolbarObserveScroll();
2194 $(this.opts
.toolbarFixedTarget
).on('scroll.redactor', $.proxy(this.toolbarObserveScroll
, this));
2198 if (this.opts
.activeButtons
)
2200 this.$editor
.on('mouseup.redactor keyup.redactor', $.proxy(this.buttonActiveObserver
, this));
2203 toolbarObserveScroll: function()
2205 var scrollTop
= $(this.opts
.toolbarFixedTarget
).scrollTop();
2211 if (this.opts
.toolbarFixedTarget
=== document
)
2213 boxTop
= this.$box
.offset().top
;
2220 end
= boxTop
+ this.$box
.height() + 40;
2222 if (scrollTop
> boxTop
)
2225 if (this.opts
.toolbarFixedBox
)
2227 left
= this.$box
.offset().left
;
2228 width
= this.$box
.innerWidth();
2229 this.$toolbar
.addClass('toolbar_fixed_box');
2232 this.toolbarFixed
= true;
2234 if (this.opts
.toolbarFixedTarget
=== document
)
2240 top
: this.opts
.toolbarFixedTopOffset
+ 'px',
2247 position
: 'absolute',
2250 top
: (this.opts
.toolbarFixedTopOffset
+ scrollTop
) + 'px',
2255 if (scrollTop
< end
) this.$toolbar
.css('visibility', 'visible');
2256 else this.$toolbar
.css('visibility', 'hidden');
2260 this.toolbarFixed
= false;
2262 position
: 'relative',
2268 if (this.opts
.toolbarFixedBox
) this.$toolbar
.removeClass('toolbar_fixed_box');
2273 airEnable: function()
2275 if (!this.opts
.air
) return;
2277 this.$editor
.on('mouseup.redactor keyup.redactor', this, $.proxy(function(e
)
2279 var text
= this.getSelectionText();
2281 if (e
.type
=== 'mouseup' && text
!= '') this.airShow(e
);
2282 if (e
.type
=== 'keyup' && e
.shiftKey
&& text
!= '')
2284 var $focusElem
= $(this.getElement(this.getSelection().focusNode
)), offset
= $focusElem
.offset();
2285 offset
.height
= $focusElem
.height();
2286 this.airShow(offset
, true);
2291 airShow: function (e
, keyboard
)
2293 if (!this.opts
.air
) return;
2296 $('.redactor_air').hide();
2301 top
= e
.top
+ e
.height
+ 14;
2303 if (this.opts
.iframe
)
2305 top
+= this.$box
.position().top
- $(this.document
).scrollTop();
2306 left
+= this.$box
.position().left
;
2311 var width
= this.$air
.innerWidth();
2314 if ($(this.document
).width() < (left
+ width
)) left
-= width
;
2316 top
= e
.clientY
+ 14;
2317 if (this.opts
.iframe
)
2319 top
+= this.$box
.position().top
;
2320 left
+= this.$box
.position().left
;
2322 else top
+= $( this.document
).scrollTop();
2332 airBindHide: function()
2334 if (!this.opts
.air
) return;
2336 var hideHandler
= $.proxy(function(doc
)
2338 $(doc
).on('mousedown.redactor', $.proxy(function(e
)
2340 if ($( e
.target
).closest(this.$toolbar
).length
=== 0)
2342 this.$air
.fadeOut(100);
2343 this.selectionRemove();
2347 }, this)).on('keydown.redactor', $.proxy(function(e
)
2349 if (e
.which
=== this.keyCode
.ESC
)
2351 this.getSelection().collapseToStart();
2354 this.$air
.fadeOut(100);
2360 // Hide the toolbar at events in all documents (iframe)
2361 hideHandler(document
);
2362 if (this.opts
.iframe
) hideHandler(this.document
);
2364 airBindMousemoveHide: function()
2366 if (!this.opts
.air
) return;
2368 var hideHandler
= $.proxy(function(doc
)
2370 $(doc
).on('mousemove.redactor', $.proxy(function(e
)
2372 if ($( e
.target
).closest(this.$toolbar
).length
=== 0)
2374 this.$air
.fadeOut(100);
2381 // Hide the toolbar at events in all documents (iframe)
2382 hideHandler(document
);
2383 if (this.opts
.iframe
) hideHandler(this.document
);
2387 dropdownBuild: function($dropdown
, dropdownObject
)
2389 $.each(dropdownObject
, $.proxy(function(btnName
, btnObject
)
2391 if (!btnObject
.className
) btnObject
.className
= '';
2394 if (btnObject
.name
=== 'separator') $item
= $('<a class="redactor_separator_drop">');
2397 $item
= $('<a href="#" class="' + btnObject
.className
+ ' redactor_dropdown_' + btnName
+ '">' + btnObject
.title
+ '</a>');
2398 $item
.on('click', $.proxy(function(e
)
2400 if (e
.preventDefault
) e
.preventDefault();
2401 if (this.browser('msie')) e
.returnValue
= false;
2403 if (btnObject
.callback
) btnObject
.callback
.call(this, btnName
, $item
, btnObject
, e
);
2404 if (btnObject
.exec
) this.execCommand(btnObject
.exec
, btnName
);
2405 if (btnObject
.func
) this[btnObject
.func
](btnName
);
2407 this.buttonActiveObserver();
2408 if (this.opts
.air
) this.$air
.fadeOut(100);
2413 $dropdown
.append($item
);
2417 dropdownShow: function(e
, key
)
2419 if (!this.opts
.visual
)
2425 var $dropdown
= this.$toolbar
.find('.redactor_dropdown_box_' + key
);
2426 var $button
= this.buttonGet(key
);
2428 if ($button
.hasClass('dropact')) this.dropdownHideAll();
2431 this.dropdownHideAll();
2432 this.callback('dropdownShow', { dropdown
: $dropdown
, key
: key
, button
: $button
});
2434 this.buttonActive(key
);
2435 $button
.addClass('dropact');
2437 var keyPosition
= $button
.position();
2438 if (this.toolbarFixed
)
2440 keyPosition
= $button
.offset();
2443 // fix right placement
2444 var dropdownWidth
= $dropdown
.width();
2445 if ((keyPosition
.left
+ dropdownWidth
) > $(document
).width())
2447 keyPosition
.left
-= dropdownWidth
;
2450 var left
= keyPosition
.left
+ 'px';
2451 var btnHeight
= $button
.innerHeight();
2453 var position
= 'absolute';
2454 var top
= btnHeight
+ 'px';
2456 if (this.opts
.toolbarFixed
&& this.toolbarFixed
) position
= 'fixed';
2457 else if (!this.opts
.air
) top
= keyPosition
.top
+ btnHeight
+ 'px';
2459 $dropdown
.css({ position
: position
, left
: left
, top
: top
}).show();
2460 this.callback('dropdownShown', { dropdown
: $dropdown
, key
: key
, button
: $button
});
2464 var hdlHideDropDown
= $.proxy(function(e
)
2466 this.dropdownHide(e
, $dropdown
);
2470 $(document
).one('click', hdlHideDropDown
);
2471 this.$editor
.one('click', hdlHideDropDown
);
2473 e
.stopPropagation();
2474 this.focusWithSaveScroll();
2476 dropdownHideAll: function()
2478 this.$toolbar
.find('a.dropact').removeClass('redactor_act').removeClass('dropact');
2479 $('.redactor_dropdown').hide();
2480 this.callback('dropdownHide');
2482 dropdownHide: function (e
, $dropdown
)
2484 if (!$(e
.target
).hasClass('dropact'))
2486 $dropdown
.removeClass('dropact');
2487 this.dropdownHideAll();
2492 buttonBuild: function(btnName
, btnObject
, buttonImage
)
2494 var $button
= $('<a href="javascript:;" title="' + btnObject
.title
+ '" tabindex="-1" class="re-icon re-' + btnName
+ '"></a>');
2496 if (typeof buttonImage
!= 'undefined')
2498 $button
.addClass('redactor-btn-image');
2501 $button
.on('click', $.proxy(function(e
)
2503 if (e
.preventDefault
) e
.preventDefault();
2504 if (this.browser('msie')) e
.returnValue
= false;
2506 if ($button
.hasClass('redactor_button_disabled')) return false;
2508 if (this.isFocused() === false && !btnObject
.exec
)
2510 this.focusWithSaveScroll();
2515 this.focusWithSaveScroll();
2517 this.execCommand(btnObject
.exec
, btnName
);
2518 this.airBindMousemoveHide();
2521 else if (btnObject
.func
&& btnObject
.func
!== 'show')
2523 this[btnObject
.func
](btnName
);
2524 this.airBindMousemoveHide();
2527 else if (btnObject
.callback
)
2529 btnObject
.callback
.call(this, btnName
, $button
, btnObject
, e
);
2530 this.airBindMousemoveHide();
2533 else if (btnObject
.dropdown
)
2535 this.dropdownShow(e
, btnName
);
2538 this.buttonActiveObserver(false, btnName
);
2543 if (btnObject
.dropdown
)
2545 var $dropdown
= $('<div class="redactor_dropdown redactor_dropdown_box_' + btnName
+ '" style="display: none;">');
2546 $dropdown
.appendTo(this.$toolbar
);
2547 this.dropdownBuild($dropdown
, btnObject
.dropdown
);
2552 buttonGet: function(key
)
2554 if (!this.opts
.toolbar
) return false;
2555 return $(this.$toolbar
.find('a.re-' + key
));
2557 buttonTagToActiveState: function(buttonName
, tagName
)
2559 this.opts
.activeButtons
.push(buttonName
);
2560 this.opts
.activeButtonsStates
[tagName
] = buttonName
;
2562 buttonActiveToggle: function(key
)
2564 var btn
= this.buttonGet(key
);
2566 if (btn
.hasClass('redactor_act'))
2568 this.buttonInactive(key
);
2572 this.buttonActive(key
);
2575 buttonActive: function(key
)
2577 var btn
= this.buttonGet(key
);
2578 btn
.addClass('redactor_act');
2580 buttonInactive: function(key
)
2582 var btn
= this.buttonGet(key
);
2583 btn
.removeClass('redactor_act');
2585 buttonInactiveAll: function(btnName
)
2587 this.$toolbar
.find('a.re-icon').not('.re-' + btnName
).removeClass('redactor_act');
2589 buttonActiveVisual: function()
2591 this.$toolbar
.find('a.re-icon').not('a.re-html').removeClass('redactor_button_disabled');
2593 buttonInactiveVisual: function()
2595 this.$toolbar
.find('a.re-icon').not('a.re-html').addClass('redactor_button_disabled');
2597 buttonChangeIcon: function (key
, classname
)
2599 this.buttonGet(key
).addClass('re-' + classname
);
2601 buttonRemoveIcon: function(key
, classname
)
2603 this.buttonGet(key
).removeClass('re-' + classname
);
2605 buttonAwesome: function(key
, name
)
2607 var button
= this.buttonGet(key
);
2608 button
.removeClass('redactor-btn-image');
2609 button
.addClass('fa-redactor-btn');
2610 button
.html('<i class="fa ' + name
+ '"></i>');
2612 buttonAdd: function(key
, title
, callback
, dropdown
)
2614 if (!this.opts
.toolbar
) return;
2615 var btn
= this.buttonBuild(key
, { title
: title
, callback
: callback
, dropdown
: dropdown
}, true);
2617 this.$toolbar
.append($('<li>').append(btn
));
2621 buttonAddFirst: function(key
, title
, callback
, dropdown
)
2623 if (!this.opts
.toolbar
) return;
2624 var btn
= this.buttonBuild(key
, { title
: title
, callback
: callback
, dropdown
: dropdown
}, true);
2625 this.$toolbar
.prepend($('<li>').append(btn
));
2627 buttonAddAfter: function(afterkey
, key
, title
, callback
, dropdown
)
2629 if (!this.opts
.toolbar
) return;
2630 var btn
= this.buttonBuild(key
, { title
: title
, callback
: callback
, dropdown
: dropdown
}, true);
2631 var $btn
= this.buttonGet(afterkey
);
2633 if ($btn
.size() !== 0) $btn
.parent().after($('<li>').append(btn
));
2634 else this.$toolbar
.append($('<li>').append(btn
));
2638 buttonAddBefore: function(beforekey
, key
, title
, callback
, dropdown
)
2640 if (!this.opts
.toolbar
) return;
2641 var btn
= this.buttonBuild(key
, { title
: title
, callback
: callback
, dropdown
: dropdown
}, true);
2642 var $btn
= this.buttonGet(beforekey
);
2644 if ($btn
.size() !== 0) $btn
.parent().before($('<li>').append(btn
));
2645 else this.$toolbar
.append($('<li>').append(btn
));
2649 buttonRemove: function (key
)
2651 var $btn
= this.buttonGet(key
);
2654 buttonActiveObserver: function(e
, btnName
)
2656 var parent
= this.getParent();
2657 this.buttonInactiveAll(btnName
);
2659 if (e
=== false && btnName
!== 'html')
2661 if ($.inArray(btnName
, this.opts
.activeButtons
) != -1)
2663 this.buttonActiveToggle(btnName
);
2668 if (parent
&& parent
.tagName
=== 'A') this.$toolbar
.find('a.redactor_dropdown_link').text(this.opts
.curLang
.link_edit
);
2669 else this.$toolbar
.find('a.redactor_dropdown_link').text(this.opts
.curLang
.link_insert
);
2671 $.each(this.opts
.activeButtonsStates
, $.proxy(function(key
, value
)
2673 if ($(parent
).closest(key
, this.$editor
.get()[0]).length
!= 0)
2675 this.buttonActive(value
);
2680 var $parent
= $(parent
).closest(this.opts
.alignmentTags
.toString().toLowerCase(), this.$editor
[0]);
2683 var align
= $parent
.css('text-align');
2688 this.buttonActive('alignright');
2691 this.buttonActive('aligncenter');
2694 this.buttonActive('alignjustify');
2697 this.buttonActive('alignleft');
2704 execPasteFrag: function(html
)
2706 var sel
= this.getSelection();
2707 if (sel
.getRangeAt
&& sel
.rangeCount
)
2709 var range
= this.getRange();
2710 range
.deleteContents();
2712 var el
= this.document
.createElement("div");
2713 el
.innerHTML
= html
;
2715 var frag
= this.document
.createDocumentFragment(), node
, lastNode
;
2716 while ((node
= el
.firstChild
))
2718 lastNode
= frag
.appendChild(node
);
2721 var firstNode
= frag
.firstChild
;
2722 range
.insertNode(frag
);
2726 range
= range
.cloneRange();
2727 range
.setStartAfter(lastNode
);
2728 range
.collapse(true);
2730 sel
.removeAllRanges();
2731 sel
.addRange(range
);
2734 exec: function(cmd
, param
, sync
)
2736 if (cmd
=== 'formatblock' && this.browser('msie'))
2738 param
= '<' + param
+ '>';
2741 if (cmd
=== 'inserthtml' && this.browser('msie'))
2745 this.focusWithSaveScroll();
2746 this.document
.selection
.createRange().pasteHTML(param
);
2748 else this.execPasteFrag(param
);
2752 this.document
.execCommand(cmd
, false, param
);
2755 if (sync
!== false) this.sync();
2756 this.callback('execCommand', cmd
, param
);
2758 execCommand: function(cmd
, param
, sync
)
2760 if (!this.opts
.visual
)
2762 this.$source
.focus();
2768 || cmd
=== 'underline'
2769 || cmd
=== 'strikethrough')
2775 if (cmd
=== 'superscript' || cmd
=== 'subscript')
2777 var parent
= this.getParent();
2778 if (parent
.tagName
=== 'SUP' || parent
.tagName
=== 'SUB')
2780 this.inlineRemoveFormatReplace(parent
);
2784 if (cmd
=== 'inserthtml')
2786 this.insertHtml(param
, sync
);
2787 this.callback('execCommand', cmd
, param
);
2791 // Stop formatting pre
2792 if (this.currentOrParentIs('PRE') && !this.opts
.formattingPre
) return false;
2795 if (cmd
=== 'insertunorderedlist' || cmd
=== 'insertorderedlist') return this.execLists(cmd
, param
);
2798 if (cmd
=== 'unlink') return this.execUnlink(cmd
, param
);
2801 this.exec(cmd
, param
, sync
);
2804 if (cmd
=== 'inserthorizontalrule') this.$editor
.find('hr').removeAttr('id');
2807 execUnlink: function(cmd
, param
)
2811 var link
= this.currentOrParentIs('A');
2814 $(link
).replaceWith($(link
).text());
2817 this.callback('execCommand', cmd
, param
);
2821 execLists: function(cmd
, param
)
2825 var parent
= this.getParent();
2826 var $list
= $(parent
).closest('ol, ul');
2828 if (!this.isParentRedactor($list
) && $list
.size() != 0)
2835 if ($list
&& $list
.length
)
2838 var listTag
= $list
[0].tagName
;
2839 if ((cmd
=== 'insertunorderedlist' && listTag
=== 'OL')
2840 || (cmd
=== 'insertorderedlist' && listTag
=== 'UL'))
2846 this.selectionSave();
2852 var nodes
= this.getNodes();
2853 var elems
= this.getBlocks(nodes
);
2855 if (typeof nodes
[0] != 'undefined' && nodes
.length
> 1 && nodes
[0].nodeType
== 3)
2857 // fix the adding the first li to the array
2858 elems
.unshift(this.getBlock());
2861 var data
= '', replaced
= '';
2862 $.each(elems
, $.proxy(function(i
,s
)
2864 if (s
.tagName
== 'LI')
2867 var cloned
= $s
.clone();
2868 cloned
.find('ul', 'ol').remove();
2870 if (this.opts
.linebreaks
=== false)
2872 data
+= this.outerHtml($('<p>').append(cloned
.contents()));
2876 data
+= cloned
.html() + '<br>';
2881 $s
.addClass('redactor-replaced').empty();
2882 replaced
= this.outerHtml($s
);
2890 html
= this.$editor
.html().replace(replaced
, '</' + listTag
+ '>' + data
+ '<' + listTag
+ '>');
2892 this.$editor
.html(html
);
2893 this.$editor
.find(listTag
+ ':empty').remove();
2900 var firstParent
= $(this.getParent()).closest('td');
2902 this.document
.execCommand(cmd
);
2904 var parent
= this.getParent();
2905 var $list
= $(parent
).closest('ol, ul');
2907 if (firstParent
.size() != 0)
2909 $list
.wrapAll('<td>');
2914 // remove block-element list wrapper
2915 var $listParent
= $list
.parent();
2916 if (this.isParentRedactor($listParent
) && $listParent
[0].tagName
!= 'LI' && this.nodeTestBlocks($listParent
[0]))
2918 $listParent
.replaceWith($listParent
.contents());
2922 if (this.browser('mozilla'))
2924 this.$editor
.focus();
2928 this.selectionRestore();
2931 this.callback('execCommand', cmd
, param
);
2936 indentingIndent: function()
2938 this.indentingStart('indent');
2940 indentingOutdent: function()
2942 this.indentingStart('outdent');
2944 indentingStart: function(cmd
)
2948 if (cmd
=== 'indent')
2950 var block
= this.getBlock();
2952 this.selectionSave();
2954 if (block
&& block
.tagName
== 'LI')
2957 var parent
= this.getParent();
2959 var $list
= $(parent
).closest('ol, ul');
2960 var listTag
= $list
[0].tagName
;
2962 var elems
= this.getBlocks();
2964 $.each(elems
, function(i
,s
)
2966 if (s
.tagName
== 'LI')
2968 var $prev
= $(s
).prev();
2969 if ($prev
.size() != 0 && $prev
[0].tagName
== 'LI')
2971 var $childList
= $prev
.children('ul, ol');
2972 if ($childList
.size() == 0)
2974 $prev
.append($('<' + listTag
+ '>').append(s
));
2976 else $childList
.append(s
);
2982 else if (block
=== false && this.opts
.linebreaks
=== true)
2984 this.exec('formatBlock', 'blockquote');
2985 var newblock
= this.getBlock();
2986 var block
= $('<div data-tagblock="">').html($(newblock
).html());
2987 $(newblock
).replaceWith(block
);
2989 var left
= this.normalize($(block
).css('margin-left')) + this.opts
.indentValue
;
2990 $(block
).css('margin-left', left
+ 'px');
2995 var elements
= this.getBlocks();
2996 $.each(elements
, $.proxy(function(i
, elem
)
3000 if (elem
.tagName
=== 'TD') return;
3002 if ($.inArray(elem
.tagName
, this.opts
.alignmentTags
) !== -1)
3008 $el
= $(elem
).closest(this.opts
.alignmentTags
.toString().toLowerCase(), this.$editor
[0]);
3011 var left
= this.normalize($el
.css('margin-left')) + this.opts
.indentValue
;
3012 $el
.css('margin-left', left
+ 'px');
3017 this.selectionRestore();
3023 this.selectionSave();
3025 var block
= this.getBlock();
3026 if (block
&& block
.tagName
== 'LI')
3029 var elems
= this.getBlocks();
3032 this.insideOutdent(block
, index
, elems
);
3037 var elements
= this.getBlocks();
3038 $.each(elements
, $.proxy(function(i
, elem
)
3042 if ($.inArray(elem
.tagName
, this.opts
.alignmentTags
) !== -1)
3048 $el
= $(elem
).closest(this.opts
.alignmentTags
.toString().toLowerCase(), this.$editor
[0]);
3051 var left
= this.normalize($el
.css('margin-left')) - this.opts
.indentValue
;
3055 if (this.opts
.linebreaks
=== true && typeof($el
.data('tagblock')) !== 'undefined')
3057 $el
.replaceWith($el
.html());
3062 $el
.css('margin-left', '');
3063 this.removeEmptyAttr($el
, 'style');
3068 $el
.css('margin-left', left
+ 'px');
3075 this.selectionRestore();
3081 insideOutdent: function (li
, index
, elems
)
3083 if (li
&& li
.tagName
== 'LI')
3085 var $parent
= $(li
).parent().parent();
3086 if ($parent
.size() != 0 && $parent
[0].tagName
== 'LI')
3092 if (typeof elems
[index
] != 'undefined')
3097 this.insideOutdent(li
, index
, elems
);
3101 this.execCommand('insertunorderedlist');
3108 alignmentLeft: function()
3110 this.alignmentSet('', 'JustifyLeft');
3112 alignmentRight: function()
3114 this.alignmentSet('right', 'JustifyRight');
3116 alignmentCenter: function()
3118 this.alignmentSet('center', 'JustifyCenter');
3120 alignmentJustify: function()
3122 this.alignmentSet('justify', 'JustifyFull');
3124 alignmentSet: function(type
, cmd
)
3130 this.document
.execCommand(cmd
, false, false);
3134 this.selectionSave();
3136 var block
= this.getBlock();
3137 if (!block
&& this.opts
.linebreaks
)
3140 this.exec('formatblock', 'div');
3142 var newblock
= this.getBlock();
3143 var block
= $('<div data-tagblock="">').html($(newblock
).html());
3144 $(newblock
).replaceWith(block
);
3146 $(block
).css('text-align', type
);
3147 this.removeEmptyAttr(block
, 'style');
3149 if (type
== '' && typeof($(block
).data('tagblock')) !== 'undefined')
3151 $(block
).replaceWith($(block
).html());
3156 var elements
= this.getBlocks();
3157 $.each(elements
, $.proxy(function(i
, elem
)
3161 if ($.inArray(elem
.tagName
, this.opts
.alignmentTags
) !== -1)
3167 $el
= $(elem
).closest(this.opts
.alignmentTags
.toString().toLowerCase(), this.$editor
[0]);
3172 $el
.css('text-align', type
);
3173 this.removeEmptyAttr($el
, 'style');
3179 this.selectionRestore();
3185 cleanEmpty: function(html
)
3187 var ph
= this.placeholderStart(html
);
3188 if (ph
!== false) return ph
;
3190 if (this.opts
.linebreaks
=== false)
3192 if (html
=== '') html
= this.opts
.emptyHtml
;
3193 else if (html
.search(/^<hr\s?\/?>$/gi) !== -1) html
= '<hr>' + this.opts
.emptyHtml
;
3198 cleanConverters: function(html
)
3201 if (this.opts
.convertDivs
) html
= html
.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '<p$1>$2</p>');
3202 if (this.opts
.paragraphy
) html
= this.cleanParagraphy(html
);
3206 cleanConvertProtected: function(html
)
3208 if (this.opts
.templateVars
)
3210 html
= html
.replace(/\{\{(.*?)\}\}/gi, '<!-- template double $1 -->');
3211 html
= html
.replace(/\{(.*?)\}/gi, '<!-- template $1 -->');
3214 html
= html
.replace(/<script(.*?)>([\w\W]*?)<\/script>/gi, '<title type="text/javascript" style="display: none;" class="redactor-script-tag"$1>$2</title>');
3215 html
= html
.replace(/<style(.*?)>([\w\W]*?)<\/style>/gi, '<section$1 style="display: none;" rel="redactor-style-tag">$2</section>');
3216 html
= html
.replace(/<form(.*?)>([\w\W]*?)<\/form>/gi, '<section$1 rel="redactor-form-tag">$2</section>');
3218 // php tags convertation
3219 if (this.opts
.phpTags
) html
= html
.replace(/<\?php([\w\W]*?)\?>/gi, '<section style="display: none;" rel="redactor-php-tag">$1</section>');
3220 else html
= html
.replace(/<\?php([\w\W]*?)\?>/gi, '');
3224 cleanReConvertProtected: function(html
)
3226 if (this.opts
.templateVars
)
3228 html
= html
.replace(/<!-- template double (.*?) -->/gi, '{{$1}}');
3229 html
= html
.replace(/<!-- template (.*?) -->/gi, '{$1}');
3232 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>');
3233 html
= html
.replace(/<section(.*?) style="display: none;" rel="redactor-style-tag">([\w\W]*?)<\/section>/gi, '<style$1>$2</style>');
3234 html
= html
.replace(/<section(.*?)rel="redactor-form-tag"(.*?)>([\w\W]*?)<\/section>/gi, '<form$1$2>$3</form>');
3236 // php tags convertation
3237 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?>');
3241 cleanRemoveSpaces: function(html
, buffer
)
3243 if (buffer
!== false)
3246 var matches
= html
.match(/<(pre|style|script|title)(.*?)>([\w\W]*?)<\/(pre|style|script|title)>/gi);
3247 if (matches
=== null) matches
= [];
3249 if (this.opts
.phpTags
)
3251 var phpMatches
= html
.match(/<\?php([\w\W]*?)\?>/gi);
3252 if (phpMatches
) matches
= $.merge(matches
, phpMatches
);
3257 $.each(matches
, function(i
, s
)
3259 html
= html
.replace(s
, 'buffer_' + i
);
3265 html
= html
.replace(/\n/g, ' ');
3266 html
= html
.replace(/[\t]*/g, '');
3267 html
= html
.replace(/\n\s*\n/g, "\n");
3268 html
= html
.replace(/^[\s\n]*/g, ' ');
3269 html
= html
.replace(/[\s\n]*$/g, ' ');
3270 html
= html
.replace( />\s{2,}</g, '> <'); // between inline tags can be only one space
3272 html
= this.cleanReplacer(html
, buffer
);
3274 html
= html
.replace(/\n\n/g, "\n");
3278 cleanReplacer: function(html
, buffer
)
3280 if (buffer
=== false) return html
;
3282 $.each(buffer
, function(i
,s
)
3284 html
= html
.replace('buffer_' + i
, s
);
3289 cleanRemoveEmptyTags: function(html
)
3291 html
= html
.replace(/<span>([\w\W]*?)<\/span>/gi, '$1');
3293 // remove zero width-space
3294 html
= html
.replace(/[\u200B-\u200D\uFEFF]/g, '');
3296 var etagsInline
= ["<b>\\s*</b>", "<b> </b>", "<em>\\s*</em>"]
3297 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>"];
3299 if (this.opts
.removeEmptyTags
)
3301 etags
= etags
.concat(etagsInline
);
3303 else etags
= etagsInline
;
3305 var len
= etags
.length
;
3306 for (var i
= 0; i
< len
; ++i
)
3308 html
= html
.replace(new RegExp(etags
[i
], 'gi'), "");
3313 cleanParagraphy: function(html
)
3315 html
= $.trim(html
);
3317 if (this.opts
.linebreaks
=== true) return html
;
3318 if (html
=== '' || html
=== '<p></p>') return this.opts
.emptyHtml
;
3322 if (this.opts
.removeEmptyTags
=== false)
3328 var matches
= html
.match(/<(table|div|pre|object)(.*?)>([\w\W]*?)<\/(table|div|pre|object)>/gi);
3329 if (!matches
) matches
= [];
3331 var commentsMatches
= html
.match(/<!--([\w\W]*?)-->/gi);
3332 if (commentsMatches
) matches
= $.merge(matches
, commentsMatches
);
3334 if (this.opts
.phpTags
)
3336 var phpMatches
= html
.match(/<section(.*?)rel="redactor-php-tag">([\w\W]*?)<\/section>/gi);
3337 if (phpMatches
) matches
= $.merge(matches
, phpMatches
);
3342 $.each(matches
, function(i
,s
)
3345 html
= html
.replace(s
, '{replace' + i
+ '}\n');
3349 html
= html
.replace(/<br \/>\s*<br \/>/gi, "\n\n");
3351 function R(str
, mod
, r
)
3353 return html
.replace(new RegExp(str
, mod
), r
);
3356 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)';
3358 html
= R('(<' + blocks
+ '[^>]*>)', 'gi', "\n$1");
3359 html
= R('(</' + blocks
+ '>)', 'gi', "$1\n\n");
3360 html
= R("\r\n", 'g', "\n");
3361 html
= R("\r", 'g', "\n");
3362 html
= R("/\n\n+/", 'g', "\n\n");
3364 var htmls
= html
.split(new RegExp('\n\s*\n', 'g'), -1);
3367 for (var i
in htmls
)
3369 if (htmls
.hasOwnProperty(i
))
3371 if (htmls
[i
].search('{replace') == -1)
3373 htmls
[i
] = htmls
[i
].replace(/<p>\n\t<\/p>/gi, '');
3374 htmls
[i
] = htmls
[i
].replace(/<p><\/p>/gi, '');
3378 html
+= '<p>' + htmls
[i
].replace(/^\n+|\n+$/g, "") + "</p>";
3381 else html
+= htmls
[i
];
3385 html
= R('<p><p>', 'gi', '<p>');
3386 html
= R('</p></p>', 'gi', '</p>');
3388 html
= R('<p>\s?</p>', 'gi', '');
3390 html
= R('<p>([^<]+)</(div|address|form)>', 'gi', "<p>$1</p></$2>");
3392 html
= R('<p>(</?' + blocks
+ '[^>]*>)</p>', 'gi', "$1");
3393 html
= R("<p>(<li.+?)</p>", 'gi', "$1");
3394 html
= R('<p>\s?(</?' + blocks
+ '[^>]*>)', 'gi', "$1");
3396 html
= R('(</?' + blocks
+ '[^>]*>)\s?</p>', 'gi', "$1");
3397 html
= R('(</?' + blocks
+ '[^>]*>)\s?<br />', 'gi', "$1");
3398 html
= R('<br />(\s*</?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)[^>]*>)', 'gi', '$1');
3399 html
= R("\n</p>", 'gi', '</p>');
3401 html
= R('<li><p>', 'gi', '<li>');
3402 html
= R('</p></li>', 'gi', '</li>');
3403 html
= R('</li><p>', 'gi', '</li>');
3404 //html = R('</ul><p>(.*?)</li>', 'gi', '</ul></li>');
3405 // html = R('</ol><p>', 'gi', '</ol>');
3406 html
= R('<p>\t?\n?<p>', 'gi', '<p>');
3407 html
= R('</dt><p>', 'gi', '</dt>');
3408 html
= R('</dd><p>', 'gi', '</dd>');
3409 html
= R('<br></p></blockquote>', 'gi', '</blockquote>');
3410 html
= R('<p>\t*</p>', 'gi', '');
3413 $.each(safes
, function(i
,s
)
3415 html
= html
.replace('{replace' + i
+ '}', s
);
3418 return $.trim(html
);
3420 cleanConvertInlineTags: function(html
, set)
3422 var boldTag
= 'strong';
3423 if (this.opts
.boldTag
=== 'b') boldTag
= 'b';
3425 var italicTag
= 'em';
3426 if (this.opts
.italicTag
=== 'i') italicTag
= 'i';
3428 html
= html
.replace(/<span style="font-style: italic;">([\w\W]*?)<\/span>/gi, '<' + italicTag
+ '>$1</' + italicTag
+ '>');
3429 html
= html
.replace(/<span style="font-weight: bold;">([\w\W]*?)<\/span>/gi, '<' + boldTag
+ '>$1</' + boldTag
+ '>');
3431 // bold, italic, del
3432 if (this.opts
.boldTag
=== 'strong') html
= html
.replace(/<b>([\w\W]*?)<\/b>/gi, '<strong>$1</strong>');
3433 else html
= html
.replace(/<strong>([\w\W]*?)<\/strong>/gi, '<b>$1</b>');
3435 if (this.opts
.italicTag
=== 'em') html
= html
.replace(/<i>([\w\W]*?)<\/i>/gi, '<em>$1</em>');
3436 else html
= html
.replace(/<em>([\w\W]*?)<\/em>/gi, '<i>$1</i>');
3440 html
= html
.replace(/<strike>([\w\W]*?)<\/strike>/gi, '<del>$1</del>');
3444 html
= html
.replace(/<del>([\w\W]*?)<\/del>/gi, '<strike>$1</strike>');
3449 cleanStripTags: function(html
)
3451 if (html
== '' || typeof html
== 'undefined') return html
;
3453 var allowed
= false;
3454 if (this.opts
.allowedTags
!== false) allowed
= true;
3456 var arr
= allowed
=== true ? this.opts
.allowedTags
: this.opts
.deniedTags
;
3458 var tags
= /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi;
3459 html
= html
.replace(tags
, function ($0, $1)
3461 if (allowed
=== true) return $.inArray($1.toLowerCase(), arr
) > '-1' ? $0 : '';
3462 else return $.inArray($1.toLowerCase(), arr
) > '-1' ? '' : $0;
3465 html
= this.cleanConvertInlineTags(html
);
3470 cleanSavePreCode: function(html
, encode
)
3472 var pre
= html
.match(/<(pre|code)(.*?)>([\w\W]*?)<\/(pre|code)>/gi);
3475 $.each(pre
, $.proxy(function(i
,s
)
3477 var arr
= s
.match(/<(pre|code)(.*?)>([\w\W]*?)<\/(pre|code)>/i);
3479 arr
[3] = arr
[3].replace(/ /g, ' ');
3481 if (encode
!== false) arr
[3] = this.cleanEncodeEntities(arr
[3]);
3484 arr
[3] = arr
[3].replace(/\$/g, '$');
3486 html
= html
.replace(s
, '<' + arr
[1] + arr
[2] + '>' + arr
[3] + '</' + arr
[1] + '>');
3493 cleanEncodeEntities: function(str
)
3495 str
= String(str
).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
3496 return str
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
3498 cleanUnverified: function()
3500 // label, abbr, mark, meter, code, q, dfn, ins, time, kbd, var
3501 var $elem = this.$editor.find('li, img, a, b, strong, sub, sup, i, em, u, small, strike, del, span, cite');
3503 $elem.filter('[style*="background
-color
: transparent
;"][style*="line
-height
"]')
3504 .css('background-color', '')
3505 .css('line-height', '');
3507 $elem.filter('[style*="background
-color
: transparent
;"]')
3508 .css('background-color', '');
3510 $elem.css('line-height', '');
3512 $.each($elem, $.proxy(function(i,s)
3514 this.removeEmptyAttr(s, 'style');
3517 // When we paste text in Safari is wrapping inserted div (remove it)
3518 this.$editor.find('div[style="text
-align
: -webkit
-auto
;"]').contents().unwrap();
3520 // Remove all styles in ul, ol, li
3521 this.$editor.find('ul, ol, li').removeAttr('style');
3525 // TEXTAREA CODE FORMATTING
3526 cleanHtml: function(code)
3529 codeLength = code.length,
3537 this.cleanlevel = 0;
3539 for (; i < codeLength; i++)
3543 // if no more tags, copy and exit
3544 if (-1 == code.substr(i).indexOf( '<' ))
3546 out += code.substr(i);
3548 return this.cleanFinish(out);
3551 // copy verbatim until a tag
3552 while (point < codeLength && code.charAt(point) != '<')
3559 cont = code.substr(i, point - i);
3560 if (!cont.match(/^\s{2,}$/g))
3562 if ('\n' == out.charAt(out.length - 1)) out += this.cleanGetTabs();
3563 else if ('\n' == cont.charAt(0))
3565 out += '\n' + this.cleanGetTabs();
3566 cont = cont.replace(/^\s+/, '');
3572 if (cont.match(/\n/)) out += '\n' + this.cleanGetTabs();
3577 // find the end of the tag
3578 while (point < codeLength && '>' != code.charAt(point))
3583 tag = code.substr(start, point - start);
3588 if ('!--' == tag.substr(1, 3))
3590 if (!tag.match(/--$/))
3592 while ('-->' != code.substr(point, 3))
3597 tag = code.substr(start, point - start);
3601 if ('\n' != out.charAt(out.length - 1)) out += '\n';
3603 out += this.cleanGetTabs();
3606 else if ('!' == tag[1])
3608 out = this.placeTag(tag + '>', out);
3610 else if ('?' == tag[1])
3614 else if (t = tag.match(/^<(script|style|pre)/i))
3616 t[1] = t[1].toLowerCase();
3617 tag = this.cleanTag(tag);
3618 out = this.placeTag(tag, out);
3619 end = String(code.substr(i + 1)).toLowerCase().indexOf('</' + t[1]);
3623 cont = code.substr(i + 1, end);
3630 tag = this.cleanTag(tag);
3631 out = this.placeTag(tag, out);
3635 return this.cleanFinish( out );
3637 cleanGetTabs: function()
3640 for ( var j = 0; j < this.cleanlevel; j++ )
3647 cleanFinish: function(code)
3649 code = code.replace( /\n\s*\n/g, '\n' );
3650 code = code.replace( /^[\s\n]*/, '' );
3651 code = code.replace( /[\s\n]*$/, '' );
3652 code = code.replace( /<script(.*?)>\n<\/script>/gi, '<script$1></script>' );
3654 this.cleanlevel = 0;
3658 cleanTag: function (tag)
3661 tag = tag.replace(/\n/g, ' ');
3662 tag = tag.replace(/\s{2,}/g, ' ');
3663 tag = tag.replace(/^\s+|\s+$/g, ' ');
3666 if (tag.match(/\/$/))
3669 tag = tag.replace(/\/+$/, '');
3673 while (m = /\s*([^= ]+)(?:=((['"']).*?\3|[^ ]+))?/.exec(tag))
3675 if (m[2]) tagout += m[1].toLowerCase() + '=' + m[2];
3676 else if (m[1]) tagout += m[1].toLowerCase();
3679 tag = tag.substr(m[0].length);
3682 return tagout.replace(/\s*$/, '') + suffix + '>';
3684 placeTag: function (tag, out)
3686 var nl = tag.match(this.cleannewLevel);
3687 if (tag.match(this.cleanlineBefore) || nl)
3689 out = out.replace(/\s*$/, '');
3693 if (nl && '/' == tag.charAt(1)) this.cleanlevel--;
3694 if ('\n' == out.charAt(out.length - 1)) out += this.cleanGetTabs();
3695 if (nl && '/' != tag.charAt(1)) this.cleanlevel++;
3699 if (tag.match(this.cleanlineAfter) || tag.match(this.cleannewLevel))
3701 out = out.replace(/ *$/, '');
3709 formatEmpty: function(e)
3711 var html = $.trim(this.$editor.html());
3713 if (this.opts.linebreaks)
3718 this.$editor.html('');
3724 html = html.replace(/<br\s?\/?>/i, '');
3725 var thtml = html.replace(/<p>\s?<\/p>/gi, '');
3727 if (html === '' || thtml === '')
3731 var node = $(this.opts.emptyHtml).get(0);
3732 this.$editor.html(node);
3739 formatBlocks: function(tag)
3743 var nodes = this.getBlocks();
3744 this.selectionSave();
3746 $.each(nodes, $.proxy(function(i, node)
3748 if (node.tagName !== 'LI
')
3750 var parent = $(node).parent();
3754 if ((node.tagName === 'P
'
3755 && parent.size() != 0
3756 && parent[0].tagName === 'BLOCKQUOTE
')
3758 node.tagName === 'BLOCKQUOTE
')
3763 else if (this.opts.linebreaks)
3765 if (node && node.tagName.search(/H[1-6]/) == 0)
3767 $(node).replaceWith(node.innerHTML + '<br
>');
3773 this.formatBlock(tag, node);
3778 this.formatBlock(tag, node);
3784 this.selectionRestore();
3787 formatBlock: function(tag, block)
3789 if (block === false) block = this.getBlock();
3790 if (block === false && this.opts.linebreaks === true)
3792 this.execCommand('formatblock
', tag);
3799 contents = $(block).contents();
3803 //contents = this.cleanEncodeEntities($(block).text());
3804 contents = $(block).html();
3805 if ($.trim(contents) === '')
3807 contents = '<span id
="selection-marker-1"></span
>';
3811 if (block.tagName === 'PRE
') tag = 'p
';
3813 if (this.opts.linebreaks === true && tag === 'p
')
3815 $(block).replaceWith($('<div
>').append(contents).html() + '<br
>');
3819 var parent = this.getParent();
3821 var node = $('<' + tag + '>').append(contents);
3822 $(block).replaceWith(node);
3824 if (parent && parent.tagName == 'TD
')
3826 $(node).wrapAll('<td
>');
3830 formatChangeTag: function(fromElement, toTagName, save)
3832 if (save !== false) this.selectionSave();
3834 var newElement = $('<' + toTagName + '/>');
3835 $(fromElement).replaceWith(function() { return newElement.append($(this).contents()); });
3837 if (save !== false) this.selectionRestore();
3843 formatQuote: function()
3848 if (this.opts.linebreaks === false)
3850 this.selectionSave();
3852 var blocks = this.getBlocks();
3854 var blockquote = false;
3855 var blocksLen = blocks.length;
3860 var replace = false;
3861 var paragraphsOnly = true;
3863 $.each(blocks, function(i,s)
3865 if (s.tagName !== 'P
') paragraphsOnly = false;
3868 $.each(blocks, $.proxy(function(i,s)
3870 if (s.tagName === 'BLOCKQUOTE
')
3872 this.formatBlock('p
', s, false);
3874 else if (s.tagName === 'P
')
3876 blockquote = $(s).parent();
3878 if (blockquote[0].tagName == 'BLOCKQUOTE
')
3880 var count = $(blockquote).children('p
').size();
3885 $(blockquote).replaceWith(s);
3888 else if (count == blocksLen)
3890 replace = 'blockquote
';
3891 data += this.outerHtml(s);
3897 data += this.outerHtml(s);
3901 $(s).addClass('redactor
-replaced
').empty();
3902 replaced = this.outerHtml(s);
3910 if (paragraphsOnly === false || blocks.length == 1)
3912 this.formatBlock('blockquote
', s, false);
3916 replace = 'paragraphs
';
3917 data += this.outerHtml(s);
3922 else if (s.tagName !== 'LI
')
3924 this.formatBlock('blockquote
', s, false);
3931 if (replace == 'paragraphs
')
3933 $(blocks[0]).replaceWith('<blockquote
>' + data + '</blockquote
>');
3936 else if (replace == 'blockquote
')
3938 $(blockquote).replaceWith(data);
3940 else if (replace == 'html
')
3942 var html = this.$editor.html().replace(replaced, '</blockquote
>' + data + '<blockquote
>');
3944 this.$editor.html(html);
3945 this.$editor.find('blockquote
').each(function()
3947 if ($.trim($(this).html()) == '') $(this).remove();
3953 this.selectionRestore();
3958 var block = this.getBlock();
3959 if (block.tagName === 'BLOCKQUOTE
')
3961 this.selectionSave();
3963 var html = $.trim($(block).html());
3964 var selection = $.trim(this.getSelectionHtml());
3966 html = html.replace(/<span(.*?)id="selection-marker(.*?)<\/span>/gi, '');
3968 if (html == selection)
3970 $(block).replaceWith($(block).html() + '<br
>');
3975 this.inlineFormat('tmp
');
3976 var tmp = this.$editor.find('tmp
');
3979 var newhtml = this.$editor.html().replace('<tmp
></tmp>', '</blockquote
><span id
="selection-marker-1">' + this.opts.invisibleSpace + '</span
>' + selection + '<blockquote
>');
3981 this.$editor.html(newhtml);
3983 this.$editor.find('blockquote
').each(function()
3985 if ($.trim($(this).html()) == '') $(this).remove();
3989 this.selectionRestore();
3990 this.$editor.find('span
#selection
-marker
-1').attr('id
', false);
3994 var wrapper = this.selectionWrap('blockquote
');
3995 var html = $(wrapper).html();
3997 var blocksElemsRemove = ['ul
', 'ol
', 'table
', 'tr
', 'tbody
', 'thead
', 'tfoot
', 'dl
'];
3998 $.each(blocksElemsRemove, function(i,s)
4000 html = html.replace(new RegExp('<' + s + '(.*?)>', 'gi
'), '');
4001 html = html.replace(new RegExp('</' + s + '>', 'gi
'), '');
4004 var blocksElems = this.opts.blockLevelElements;
4005 $.each(blocksElems, function(i,s)
4007 html = html.replace(new RegExp('<' + s + '(.*?)>', 'gi
'), '');
4008 html = html.replace(new RegExp('</' + s + '>', 'gi
'), '<br
>');
4011 $(wrapper).html(html);
4012 this.selectionElement(wrapper);
4013 var next = $(wrapper).next();
4014 if (next.size() != 0 && next[0].tagName === 'BR
')
4025 blockRemoveAttr: function(attr, value)
4027 var nodes = this.getBlocks();
4028 $(nodes).removeAttr(attr);
4032 blockSetAttr: function(attr, value)
4034 var nodes = this.getBlocks();
4035 $(nodes).attr(attr, value);
4039 blockRemoveStyle: function(rule)
4041 var nodes = this.getBlocks();
4042 $(nodes).css(rule, '');
4043 this.removeEmptyAttr(nodes, 'style
');
4047 blockSetStyle: function (rule, value)
4049 var nodes = this.getBlocks();
4050 $(nodes).css(rule, value);
4054 blockRemoveClass: function(className)
4056 var nodes = this.getBlocks();
4057 $(nodes).removeClass(className);
4058 this.removeEmptyAttr(nodes, 'class');
4062 blockSetClass: function(className)
4064 var nodes = this.getBlocks();
4065 $(nodes).addClass(className);
4071 inlineRemoveClass: function(className)
4073 this.selectionSave();
4075 this.inlineEachNodes(function(node)
4077 $(node).removeClass(className);
4078 this.removeEmptyAttr(node, 'class');
4081 this.selectionRestore();
4085 inlineSetClass: function(className)
4087 var current = this.getCurrent();
4088 if (!$(current).hasClass(className)) this.inlineMethods('addClass
', className);
4090 inlineRemoveStyle: function (rule)
4092 this.selectionSave();
4094 this.inlineEachNodes(function(node)
4096 $(node).css(rule, '');
4097 this.removeEmptyAttr(node, 'style
');
4100 this.selectionRestore();
4103 inlineSetStyle: function(rule, value)
4105 this.inlineMethods('css
', rule, value);
4107 inlineRemoveAttr: function (attr)
4109 this.selectionSave();
4111 var range = this.getRange(), node = this.getElement(), nodes = this.getNodes();
4113 if (range.collapsed || range.startContainer === range.endContainer && node)
4118 $(nodes).removeAttr(attr);
4120 this.inlineUnwrapSpan();
4122 this.selectionRestore();
4125 inlineSetAttr: function(attr, value)
4127 this.inlineMethods('attr
', attr, value );
4129 inlineMethods: function(type, attr, value)
4132 this.selectionSave();
4134 var range = this.getRange()
4135 var el = this.getElement();
4137 if ((range.collapsed || range.startContainer === range.endContainer) && el && !this.nodeTestBlocks(el))
4140 $(el)[type](attr, value);
4145 this.document.execCommand('fontSize
', false, 4 );
4147 var fonts = this.$editor.find('font
');
4148 $.each(fonts, $.proxy(function(i, s)
4150 this.inlineSetMethods(type, s, attr, value);
4156 this.selectionRestore();
4159 inlineSetMethods: function(type, s, attr, value)
4161 var parent = $(s).parent(), el;
4163 var selectionHtml = this.getSelectionText();
4164 var parentHtml = $(parent).text();
4165 var selected = selectionHtml == parentHtml;
4167 if (selected && parent && parent[0].tagName === 'INLINE
' && parent[0].attributes.length != 0)
4170 $(s).replaceWith($(s).html());
4174 el = $('<inline
>').append($(s).contents());
4175 $(s).replaceWith(el);
4179 $(el)[type](attr, value);
4183 // Sort elements and execute callback
4184 inlineEachNodes: function(callback)
4186 var range = this.getRange(),
4187 node = this.getElement(),
4188 nodes = this.getNodes(),
4191 if (range.collapsed || range.startContainer === range.endContainer && node)
4197 $.each(nodes, $.proxy(function(i, node)
4199 if (!collapsed && node.tagName !== 'INLINE
')
4201 var selectionHtml = this.getSelectionText();
4202 var parentHtml = $(node).parent().text();
4203 var selected = selectionHtml == parentHtml;
4205 if (selected && node.parentNode.tagName === 'INLINE
' && !$(node.parentNode).hasClass('redactor_editor
'))
4207 node = node.parentNode;
4211 callback.call(this, node);
4215 inlineUnwrapSpan: function()
4217 var $spans = this.$editor.find('inline
');
4219 $.each($spans, $.proxy(function(i, span)
4221 var $span = $(span);
4223 if ($span.attr('class') === undefined && $span.attr('style
') === undefined)
4225 $span.contents().unwrap();
4230 inlineFormat: function(tag)
4232 this.selectionSave();
4234 this.document.execCommand('fontSize
', false, 4 );
4236 var fonts = this.$editor.find('font
');
4238 $.each(fonts, function(i, s)
4240 var el = $('<' + tag + '/>').append($(s).contents());
4241 $(s).replaceWith(el);
4245 this.selectionRestore();
4249 inlineRemoveFormat: function(tag)
4251 this.selectionSave();
4253 var utag = tag.toUpperCase();
4254 var nodes = this.getNodes();
4255 var parent = $(this.getParent()).parent();
4257 $.each(nodes, function(i, s)
4259 if (s.tagName === utag) this.inlineRemoveFormatReplace(s);
4262 if (parent && parent[0].tagName === utag) this.inlineRemoveFormatReplace(parent);
4264 this.selectionRestore();
4267 inlineRemoveFormatReplace: function(el)
4269 $(el).replaceWith($(el).contents());
4273 insertHtml: function (html, sync)
4275 var current = this.getCurrent();
4276 var parent = current.parentNode;
4278 this.focusWithSaveScroll();
4282 var $html = $('<div
>').append($.parseHTML(html));
4283 html = $html.html();
4285 html = this.cleanRemoveEmptyTags(html);
4288 $html = $('<div
>').append($.parseHTML(html));
4289 var currBlock = this.getBlock();
4291 if ($html.contents().length == 1)
4293 var htmlTagName = $html.contents()[0].tagName;
4295 // If the inserted and received text tags match
4296 if (htmlTagName != 'P
' && htmlTagName == currBlock.tagName || htmlTagName == 'PRE
')
4298 //html = $html.html();
4299 $html = $('<div
>').append(html);
4303 if (this.opts.linebreaks)
4305 html = html.replace(/<p(.*?)>([\w\W]*?)<\/p>/gi, '$2<br
>');
4308 // add text in a paragraph
4309 if (!this.opts.linebreaks && $html.contents().length == 1 && $html.contents()[0].nodeType == 3
4310 && (this.getRangeSelectedNodes().length > 2 || (!current || current.tagName == 'BODY
' && !parent || parent.tagName == 'HTML
')))
4312 html = '<p
>' + html + '</p
>';
4315 html = this.setSpansVerifiedHtml(html);
4317 if ($html.contents().length > 1 && currBlock
4318 || $html.contents().is('p
, :header
, ul
, ol
, li
, div
, table
, td
, blockquote
, pre
, address
, section
, header
, footer
, aside
, article
'))
4320 if (this.browser('msie
'))
4324 this.document.selection.createRange().pasteHTML(html);
4328 this.execPasteFrag(html);
4333 this.document.execCommand('inserthtml
', false, html);
4336 else this.insertHtmlAdvanced(html, false);
4340 this.window.setTimeout($.proxy(function()
4342 if (!this.opts.linebreaks) this.selectionEnd(this.$editor.contents().last());
4343 else this.focusEnd();
4348 this.observeStart();
4351 this.setNonEditable();
4353 if (sync !== false) this.sync();
4355 insertHtmlAdvanced: function(html, sync)
4357 html = this.setSpansVerifiedHtml(html);
4359 var sel = this.getSelection();
4361 if (sel.getRangeAt && sel.rangeCount)
4363 var range = sel.getRangeAt(0);
4364 range.deleteContents();
4366 var el = this.document.createElement('div
');
4367 el.innerHTML = html;
4368 var frag = this.document.createDocumentFragment(), node, lastNode;
4369 while ((node = el.firstChild))
4371 lastNode = frag.appendChild(node);
4374 range.insertNode(frag);
4378 range = range.cloneRange();
4379 range.setStartAfter(lastNode);
4380 range.collapse(true);
4381 sel.removeAllRanges();
4382 sel.addRange(range);
4392 insertBeforeCursor: function(html)
4394 html = this.setSpansVerifiedHtml(html);
4398 var space = document.createElement("span");
4399 space.innerHTML = "\u200B";
4401 var range = this.getRange();
4402 range.insertNode(space);
4403 range.insertNode(node[0]);
4404 range.collapse(false);
4406 var sel = this.getSelection();
4407 sel.removeAllRanges();
4408 sel.addRange(range);
4412 insertText: function(html)
4414 var $html = $($.parseHTML(html));
4416 if ($html.length) html = $html.text();
4418 this.focusWithSaveScroll();
4420 if (this.browser('msie
') && !this.isIe11()) this.document.selection.createRange().pasteHTML(html);
4421 else this.document.execCommand('inserthtml
', false, html);
4425 insertNode: function(node)
4427 node = node[0] || node;
4429 if (node.tagName == 'SPAN
')
4431 var replacementTag = 'inline
';
4433 var outer = node.outerHTML;
4435 // Replace opening tag
4436 var regex = new RegExp('<' + node.tagName, 'i
');
4437 var newTag = outer.replace(regex, '<' + replacementTag);
4439 // Replace closing tag
4440 regex = new RegExp('</' + node.tagName, 'i
');
4441 newTag = newTag.replace(regex, '</' + replacementTag);
4442 node = $(newTag)[0];
4445 var sel = this.getSelection();
4446 if (sel.getRangeAt && sel.rangeCount)
4448 // with delete contents
4449 range = sel.getRangeAt(0);
4450 range.deleteContents();
4451 range.insertNode(node);
4452 range.setEndAfter(node);
4453 range.setStartAfter(node);
4454 sel.removeAllRanges();
4455 sel.addRange(range);
4458 insertNodeToCaretPositionFromPoint: function(e, node)
4461 var x = e.clientX, y = e.clientY;
4462 if (this.document.caretPositionFromPoint)
4464 var pos = this.document.caretPositionFromPoint(x, y);
4465 range = this.getRange();
4466 range.setStart(pos.offsetNode, pos.offset);
4467 range.collapse(true);
4468 range.insertNode(node);
4470 else if (this.document.caretRangeFromPoint)
4472 range = this.document.caretRangeFromPoint(x, y);
4473 range.insertNode(node);
4475 else if (typeof document.body.createTextRange != "undefined")
4477 range = this.document.body.createTextRange();
4478 range.moveToPoint(x, y);
4479 var endRange = range.duplicate();
4480 endRange.moveToPoint(x, y);
4481 range.setEndPoint("EndToEnd", endRange);
4486 insertAfterLastElement: function(element, parent)
4488 if (typeof(parent) != 'undefined') element = parent;
4490 if (this.isEndOfElement())
4492 if (this.opts.linebreaks)
4494 var contents = $('<div
>').append($.trim(this.$editor.html())).contents();
4495 var last = contents.last()[0];
4496 if (last.tagName == 'SPAN
' && last.innerHTML == '')
4498 last = contents.prev()[0];
4501 if (this.outerHtml(last) != this.outerHtml(element))
4508 if (this.$editor.contents().last()[0] !== element)
4514 this.insertingAfterLastElement(element);
4517 insertingAfterLastElement: function(element)
4521 if (this.opts.linebreaks === false)
4523 var node = $(this.opts.emptyHtml);
4524 $(element).after(node);
4525 this.selectionStart(node);
4529 var node = $('<span id
="selection-marker-1">' + this.opts.invisibleSpace + '</span
>', this.document)[0];
4530 $(element).after(node);
4531 $(node).after(this.opts.invisibleSpace);
4532 this.selectionRestore();
4533 this.$editor.find('span
#selection
-marker
-1').removeAttr('id
');
4536 insertLineBreak: function(twice)
4538 this.selectionSave();
4546 if (this.browser('mozilla
'))
4548 var span = $('<span
>').html(this.opts.invisibleSpace);
4549 this.$editor.find('#selection
-marker
-1').before(br).before(span).before(this.opts.invisibleSpace);
4551 this.setCaretAfter(span[0]);
4554 this.selectionRemoveMarkers();
4558 var parent = this.getParent();
4559 if (parent && parent.tagName === 'A
')
4561 var offset = this.getCaretOffset(parent);
4563 var text = $.trim($(parent).text()).replace(/\n\r\n/g, '');
4564 var len = text.length;
4568 this.selectionRemoveMarkers();
4570 var node = $('<span id
="selection-marker-1">' + this.opts.invisibleSpace + '</span
>', this.document)[0];
4571 $(parent).after(node);
4572 $(node).before(br + (this.browser('webkit
') ? this.opts.invisibleSpace : ''));
4573 this.selectionRestore();
4580 this.$editor.find('#selection
-marker
-1').before(br + (this.browser('webkit
') ? this.opts.invisibleSpace : ''));
4581 this.selectionRestore();
4584 insertDoubleLineBreak: function()
4586 this.insertLineBreak(true);
4588 replaceLineBreak: function(element)
4590 var node = $('<br
>' + this.opts.invisibleSpace);
4591 $(element).replaceWith(node);
4592 this.selectionStart(node);
4596 pasteClean: function(html)
4598 html = this.callback('pasteBefore
', false, html);
4600 // ie10 fix paste links
4601 if (this.browser('msie
'))
4603 var tmp = $.trim(html);
4604 if (tmp.search(/^<a(.*?)>(.*?)<\/a>$/i) == 0)
4606 html = html.replace(/^<a(.*?)>(.*?)<\/a>$/i, "$2");
4610 if (this.opts.pastePlainText)
4612 var tmp = this.document.createElement('div
');
4614 html = html.replace(/<br>|<\/H[1-6]>|<\/p>|<\/div>/gi, '\n');
4616 tmp.innerHTML = html;
4617 html = tmp.textContent || tmp.innerText;
4619 html = $.trim(html);
4620 html = html.replace('\n', '<br
>');
4621 html = this.cleanParagraphy(html);
4623 this.pasteInsert(html);
4628 var tablePaste = false;
4629 if (this.currentOrParentIs('TD
'))
4632 var blocksElems = this.opts.blockLevelElements;
4633 blocksElems.push('tr
');
4634 blocksElems.push('table
');
4635 $.each(blocksElems, function(i,s)
4637 html = html.replace(new RegExp('<' + s + '(.*?)>', 'gi
'), '');
4638 html = html.replace(new RegExp('</' + s + '>', 'gi
'), '<br
>');
4643 if (this.currentOrParentIs('PRE
'))
4645 html = this.pastePre(html);
4646 this.pasteInsert(html);
4651 html = html.replace(/<img(.*?)v:shapes=(.*?)>/gi, '');
4654 html = html.replace(/<p(.*?)class="MsoListParagraphCxSpFirst"([\w\W]*?)<\/p>/gi, '<ul
><li
$2</li
>');
4655 html = html.replace(/<p(.*?)class="MsoListParagraphCxSpMiddle"([\w\W]*?)<\/p>/gi, '<li
$2</li
>');
4656 html = html.replace(/<p(.*?)class="MsoListParagraphCxSpLast"([\w\W]*?)<\/p>/gi, '<li
$2</li></ul>');
4658 html = html.replace(/<p(.*?)class="MsoListParagraph"([\w\W]*?)<\/p>/gi, '<ul
><li
$2</li></ul>');
4659 // remove ms word's bullet
4660 html
= html
.replace(/·/g, '');
4662 // remove comments and php tags
4663 html
= html
.replace(/<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi, '');
4666 if (this.opts
.cleanSpaces
=== true)
4668 html
= html
.replace(/( ){2,}/gi, ' ');
4669 html
= html
.replace(/ /gi, ' ');
4672 // remove google docs marker
4673 html
= html
.replace(/<b\sid="internal-source-marker(.*?)">([\w\W]*?)<\/b>/gi, "$2");
4674 html
= html
.replace(/<b(.*?)id="docs-internal-guid(.*?)">([\w\W]*?)<\/b>/gi, "$3");
4677 html
= this.cleanStripTags(html
);
4680 html
= html
.replace(/<td>\u200b*<\/td>/gi, '[td]');
4681 html
= html
.replace(/<td> <\/td>/gi, '[td]');
4682 html
= html
.replace(/<td><br><\/td>/gi, '[td]');
4683 html
= html
.replace(/<td(.*?)colspan="(.*?)"(.*?)>([\w\W]*?)<\/td>/gi, '[td colspan="$2"]$4[/td]');
4684 html
= html
.replace(/<td(.*?)rowspan="(.*?)"(.*?)>([\w\W]*?)<\/td>/gi, '[td rowspan="$2"]$4[/td]');
4685 html
= html
.replace(/<a(.*?)href="(.*?)"(.*?)>([\w\W]*?)<\/a>/gi, '[a href="$2"]$4[/a]');
4686 html
= html
.replace(/<iframe(.*?)>([\w\W]*?)<\/iframe>/gi, '[iframe$1]$2[/iframe]');
4687 html
= html
.replace(/<video(.*?)>([\w\W]*?)<\/video>/gi, '[video$1]$2[/video]');
4688 html
= html
.replace(/<audio(.*?)>([\w\W]*?)<\/audio>/gi, '[audio$1]$2[/audio]');
4689 html
= html
.replace(/<embed(.*?)>([\w\W]*?)<\/embed>/gi, '[embed$1]$2[/embed]');
4690 html
= html
.replace(/<object(.*?)>([\w\W]*?)<\/object>/gi, '[object$1]$2[/object]');
4691 html
= html
.replace(/<param(.*?)>/gi, '[param$1]');
4692 html
= html
.replace(/<img(.*?)>/gi, '[img$1]');
4695 html
= html
.replace(/ class="(.*?)"/gi
, '');
4697 // remove all attributes
4698 html
= html
.replace(/<(\w+)([\w\W]*?)>/gi, '<$1>');
4701 if (this.opts
.linebreaks
)
4703 // prevent double linebreaks when an empty line in RTF has bold or underlined formatting associated with it
4704 html
= html
.replace(/<strong><\/strong>/gi, '');
4705 html
= html
.replace(/<u><\/u>/gi, '');
4707 if (this.opts
.cleanFontTag
)
4709 html
= html
.replace(/<font(.*?)>([\w\W]*?)<\/font>/gi, '$2');
4712 html
= html
.replace(/<[^\/>][^>]*>(\s*|\t*|\n*| |<br>)<\/[^>]+>/gi, '<br>');
4716 html
= html
.replace(/<[^\/>][^>]*>(\s*|\t*|\n*| |<br>)<\/[^>]+>/gi, '');
4719 html
= html
.replace(/<div>\s*?\t*?\n*?(<ul>|<ol>|<p>)/gi, '$1');
4722 html
= html
.replace(/\[td colspan="(.*?)"\]([\w\W]*?)\[\/td\]/gi, '<td colspan="$1">$2</td>');
4723 html
= html
.replace(/\[td rowspan="(.*?)"\]([\w\W]*?)\[\/td\]/gi, '<td rowspan="$1">$2</td>');
4724 html
= html
.replace(/\[td\]/gi, '<td> </td>');
4725 html
= html
.replace(/\[a href="(.*?)"\]([\w\W]*?)\[\/a\]/gi, '<a href="$1">$2</a>');
4726 html
= html
.replace(/\[iframe(.*?)\]([\w\W]*?)\[\/iframe\]/gi, '<iframe$1>$2</iframe>');
4727 html
= html
.replace(/\[video(.*?)\]([\w\W]*?)\[\/video\]/gi, '<video$1>$2</video>');
4728 html
= html
.replace(/\[audio(.*?)\]([\w\W]*?)\[\/audio\]/gi, '<audio$1>$2</audio>');
4729 html
= html
.replace(/\[embed(.*?)\]([\w\W]*?)\[\/embed\]/gi, '<embed$1>$2</embed>');
4730 html
= html
.replace(/\[object(.*?)\]([\w\W]*?)\[\/object\]/gi, '<object$1>$2</object>');
4731 html
= html
.replace(/\[param(.*?)\]/gi, '<param$1>');
4732 html
= html
.replace(/\[img(.*?)\]/gi, '<img$1>');
4735 if (this.opts
.convertDivs
)
4737 html
= html
.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '<p>$2</p>');
4738 html
= html
.replace(/<\/div><p>/gi, '<p>');
4739 html
= html
.replace(/<\/p><\/div>/gi, '</p>');
4740 html
= html
.replace(/<p><\/p>/gi, '<br />');
4744 html
= html
.replace(/<div><\/div>/gi, '<br />');
4747 if (this.currentOrParentIs('LI'))
4749 html
= html
.replace(/<p>([\w\W]*?)<\/p>/gi, '$1<br>');
4751 else if (tablePaste
=== false)
4753 html
= this.cleanParagraphy(html
);
4757 html
= html
.replace(/<span(.*?)>([\w\W]*?)<\/span>/gi, '$2');
4760 html
= html
.replace(/<img>/gi, '');
4761 html
= html
.replace(/<[^\/>][^>][^img|param|source|td][^<]*>(\s*|\t*|\n*| |<br>)<\/[^>]+>/gi, '');
4763 html
= html
.replace(/\n{3,}/gi, '\n');
4766 html
= html
.replace(/<p><p>/gi, '<p>');
4767 html
= html
.replace(/<\/p><\/p>/gi, '</p>');
4769 html
= html
.replace(/<li>(\s*|\t*|\n*)<p>/gi, '<li>');
4770 html
= html
.replace(/<\/p>(\s*|\t*|\n*)<\/li>/gi, '</li>');
4772 if (this.opts
.linebreaks
=== true)
4774 html
= html
.replace(/<p(.*?)>([\w\W]*?)<\/p>/gi, '$2<br>');
4777 // remove empty finally
4778 html
= html
.replace(/<[^\/>][^>][^img|param|source|td][^<]*>(\s*|\t*|\n*| |<br>)<\/[^>]+>/gi, '');
4780 // remove safari local images
4781 html
= html
.replace(/<img src="webkit-fake-url\:\/\/(.*?)"(.*?)>/gi, '');
4784 html
= html
.replace(/<td(.*?)>(\s*|\t*|\n*)<p>([\w\W]*?)<\/p>(\s*|\t*|\n*)<\/td>/gi, '<td$1>$3</td>');
4787 if (this.opts
.convertDivs
)
4789 html
= html
.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '$2');
4790 html
= html
.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '$2');
4794 this.pasteClipboardMozilla
= false;
4795 if (this.browser('mozilla'))
4797 if (this.opts
.clipboardUpload
)
4799 var matches
= html
.match(/<img src="data:image(.*?)"(.*?)>/gi);
4800 if (matches
!== null)
4802 this.pasteClipboardMozilla
= matches
;
4805 var img
= matches
[k
].replace('<img', '<img data-mozilla-paste-image="' + k
+ '" ');
4806 html
= html
.replace(matches
[k
], img
);
4812 while (/<br>$/gi.test(html
))
4814 html
= html
.replace(/<br>$/gi, '');
4819 html
= html
.replace(/<p>•([\w\W]*?)<\/p>/gi, '<li>$1</li>');
4821 // ie inserts a blank font tags when pasting
4822 if (this.browser('msie'))
4824 while (/<font>([\w\W]*?)<\/font>/gi.test(html
))
4826 html
= html
.replace(/<font>([\w\W]*?)<\/font>/gi, '$1');
4830 // remove table paragraphs
4831 if (tablePaste
=== false)
4833 html
= html
.replace(/<td(.*?)>([\w\W]*?)<p(.*?)>([\w\W]*?)<\/td>/gi, '<td$1>$2$4</td>');
4834 html
= html
.replace(/<td(.*?)>([\w\W]*?)<\/p>([\w\W]*?)<\/td>/gi, '<td$1>$2$3</td>');
4835 html
= html
.replace(/<td(.*?)>([\w\W]*?)<p(.*?)>([\w\W]*?)<\/td>/gi, '<td$1>$2$4</td>');
4836 html
= html
.replace(/<td(.*?)>([\w\W]*?)<\/p>([\w\W]*?)<\/td>/gi, '<td$1>$2$3</td>');
4839 // ms word break lines
4840 html
= html
.replace(/\n/g, ' ');
4842 // ms word lists break lines
4843 html
= html
.replace(/<p>\n?<li>/gi, '<li>');
4845 this.pasteInsert(html
);
4848 pastePre: function(s
)
4850 s
= s
.replace(/<br>|<\/H[1-6]>|<\/p>|<\/div>/gi, '\n');
4852 var tmp
= this.document
.createElement('div');
4854 return this.cleanEncodeEntities(tmp
.textContent
|| tmp
.innerText
);
4856 pasteInsert: function(html
)
4858 html
= this.callback('pasteAfter', false, html
);
4862 this.$editor
.html(html
);
4863 this.selectionRemove();
4869 this.insertHtml(html
);
4872 this.selectall
= false;
4874 setTimeout($.proxy(function()
4876 this.rtePaste
= false;
4879 if (this.browser('mozilla'))
4881 this.$editor
.find('p:empty').remove()
4883 if (this.pasteClipboardMozilla
!== false)
4885 this.pasteClipboardUploadMozilla();
4890 if (this.opts
.autoresize
&& this.fullscreen
!== true)
4892 $(this.document
.body
).scrollTop(this.saveScroll
);
4896 this.$editor
.scrollTop(this.saveScroll
);
4899 pasteClipboardAppendFields: function(postData
)
4901 // append hidden fields
4902 if (this.opts
.uploadFields
!== false && typeof this.opts
.uploadFields
=== 'object')
4904 $.each(this.opts
.uploadFields
, $.proxy(function(k
, v
)
4906 if (v
!= null && v
.toString().indexOf('#') === 0) v
= $(v
).val();
4914 pasteClipboardUploadMozilla: function()
4916 var imgs
= this.$editor
.find('img[data-mozilla-paste-image]');
4917 $.each(imgs
, $.proxy(function(i
,s
)
4920 var arr
= s
.src
.split(",");
4922 'contentType': arr
[0].split(";")[0].split(":")[1],
4923 'data': arr
[1] // raw base64
4926 // append hidden fields
4927 postData
= this.pasteClipboardAppendFields(postData
);
4929 $.post(this.opts
.clipboardUploadUrl
, postData
,
4930 $.proxy(function(data
)
4932 var json
= (typeof data
=== 'string' ? $.parseJSON(data
) : data
);
4933 $s
.attr('src', json
.filelink
);
4934 $s
.removeAttr('data-mozilla-paste-image');
4939 this.callback('imageUpload', $s
, json
);
4945 pasteClipboardUpload: function(e
)
4947 var result
= e
.target
.result
;
4948 var arr
= result
.split(",");
4950 'contentType': arr
[0].split(";")[0].split(":")[1],
4951 'data': arr
[1] // raw base64
4955 if (this.opts
.clipboardUpload
)
4957 // append hidden fields
4958 postData
= this.pasteClipboardAppendFields(postData
);
4960 $.post(this.opts
.clipboardUploadUrl
, postData
,
4961 $.proxy(function(data
)
4963 var json
= (typeof data
=== 'string' ? $.parseJSON(data
) : data
);
4965 var html
= '<img src="' + json
.filelink
+ '" id="clipboard-image-marker" />';
4966 this.execCommand('inserthtml', html
, false);
4968 var image
= $(this.$editor
.find('img#clipboard-image-marker'));
4970 if (image
.length
) image
.removeAttr('id');
4978 this.callback('imageUpload', image
, json
);
4986 this.insertHtml('<img src="' + result
+ '" />');
4991 bufferSet: function(html
, selectionSave
)
4993 if (html
!== undefined || html
=== false) this.opts
.buffer
.push(html
);
4996 if (selectionSave
!== false)
4998 this.selectionSave();
5001 this.opts
.buffer
.push(this.$editor
.html());
5002 this.selectionRemoveMarkers('buffer');
5005 bufferUndo: function()
5007 if (this.opts
.buffer
.length
=== 0)
5009 this.focusWithSaveScroll();
5014 this.selectionSave();
5015 this.opts
.rebuffer
.push(this.$editor
.html());
5016 this.selectionRestore(false, true);
5018 this.$editor
.html(this.opts
.buffer
.pop());
5020 this.selectionRestore();
5021 setTimeout($.proxy(this.observeStart
, this), 100);
5023 bufferRedo: function()
5025 if (this.opts
.rebuffer
.length
=== 0)
5027 this.focusWithSaveScroll();
5032 this.selectionSave();
5033 this.opts
.buffer
.push(this.$editor
.html());
5034 this.selectionRestore(false, true);
5036 this.$editor
.html(this.opts
.rebuffer
.pop());
5037 this.selectionRestore(true);
5038 setTimeout($.proxy(this.observeStart
, this), 4);
5042 observeStart: function()
5044 this.observeImages();
5046 if (this.opts
.observeLinks
) this.observeLinks();
5048 observeLinks: function()
5050 this.$editor
.find('a').on('click', $.proxy(this.linkObserver
, this));
5051 this.$editor
.on('click.redactor', $.proxy(function(e
)
5053 this.linkObserverTooltipClose(e
);
5056 $(document
).on('click.redactor', $.proxy(function(e
)
5058 this.linkObserverTooltipClose(e
);
5062 observeImages: function()
5064 if (this.opts
.observeImages
=== false) return false;
5066 this.$editor
.find('img').each($.proxy(function(i
, elem
)
5068 if (this.browser('msie')) $(elem
).attr('unselectable', 'on');
5069 this.imageResize(elem
);
5073 linkObserver: function(e
)
5075 var $link
= $(e
.target
);
5077 if ($link
.size() == 0 || $link
[0].tagName
!== 'A') return;
5079 var pos
= $link
.offset();
5080 if (this.opts
.iframe
)
5082 var posFrame
= this.$frame
.offset();
5083 pos
.top
= posFrame
.top
+ (pos
.top
- $(this.document
).scrollTop());
5084 pos
.left
+= posFrame
.left
;
5087 var tooltip
= $('<span class="redactor-link-tooltip"></span>');
5089 var href
= $link
.attr('href');
5090 if (href
=== undefined)
5095 if (href
.length
> 24) href
= href
.substring(0, 24) + '...';
5097 var aLink
= $('<a href="' + $link
.attr('href') + '" target="_blank">' + href
+ '</a>').on('click', $.proxy(function(e
)
5099 this.linkObserverTooltipClose(false);
5102 var aEdit
= $('<a href="#">' + this.opts
.curLang
.edit
+ '</a>').on('click', $.proxy(function(e
)
5106 this.linkObserverTooltipClose(false);
5110 var aUnlink
= $('<a href="#">' + this.opts
.curLang
.unlink
+ '</a>').on('click', $.proxy(function(e
)
5113 this.execCommand('unlink');
5114 this.linkObserverTooltipClose(false);
5119 tooltip
.append(aLink
);
5120 tooltip
.append(' | ');
5121 tooltip
.append(aEdit
);
5122 tooltip
.append(' | ');
5123 tooltip
.append(aUnlink
);
5125 top
: (pos
.top
+ 20) + 'px',
5126 left
: pos
.left
+ 'px'
5129 $('.redactor-link-tooltip').remove();
5130 $('body').append(tooltip
);
5132 linkObserverTooltipClose: function(e
)
5134 if (e
!== false && e
.target
.tagName
== 'A') return false;
5135 $('.redactor-link-tooltip').remove();
5139 getSelection: function()
5141 if (!this.opts
.rangy
) return this.document
.getSelection();
5144 if (!this.opts
.iframe
) return rangy
.getSelection();
5145 else return rangy
.getSelection(this.$frame
[0]);
5148 getRange: function()
5150 if (!this.opts
.rangy
)
5152 if (this.document
.getSelection
)
5154 var sel
= this.getSelection();
5155 if (sel
.getRangeAt
&& sel
.rangeCount
) return sel
.getRangeAt(0);
5158 return this.document
.createRange();
5162 if (!this.opts
.iframe
) return rangy
.createRange();
5163 else return rangy
.createRange(this.iframeDoc());
5166 selectionElement: function(node
)
5168 this.setCaret(node
);
5170 selectionStart: function(node
)
5172 this.selectionSet(node
[0] || node
, 0, null, 0);
5174 selectionEnd: function(node
)
5176 this.selectionSet(node
[0] || node
, 1, null, 1);
5178 selectionSet: function(orgn
, orgo
, focn
, foco
)
5180 if (focn
== null) focn
= orgn
;
5181 if (foco
== null) foco
= orgo
;
5183 var sel
= this.getSelection();
5186 if (orgn
.tagName
== 'P' && orgn
.innerHTML
== '')
5188 orgn
.innerHTML
= this.opts
.invisibleSpace
;
5191 if (orgn
.tagName
== 'BR' && this.opts
.linebreaks
=== false)
5193 var par
= $(this.opts
.emptyHtml
)[0];
5194 $(orgn
).replaceWith(par
);
5199 var range
= this.getRange();
5200 range
.setStart(orgn
, orgo
);
5201 range
.setEnd(focn
, foco
);
5204 sel
.removeAllRanges();
5207 sel
.addRange(range
);
5209 selectionWrap: function(tag
)
5211 tag
= tag
.toLowerCase();
5213 var block
= this.getBlock();
5216 var wrapper
= this.formatChangeTag(block
, tag
);
5221 var sel
= this.getSelection();
5222 var range
= sel
.getRangeAt(0);
5223 var wrapper
= document
.createElement(tag
);
5224 wrapper
.appendChild(range
.extractContents());
5225 range
.insertNode(wrapper
);
5227 this.selectionElement(wrapper
);
5231 selectionAll: function()
5233 var range
= this.getRange();
5234 range
.selectNodeContents(this.$editor
[0]);
5236 var sel
= this.getSelection();
5237 sel
.removeAllRanges();
5238 sel
.addRange(range
);
5240 selectionRemove: function()
5242 this.getSelection().removeAllRanges();
5244 getCaretOffset: function (element
)
5246 var caretOffset
= 0;
5248 var range
= this.getRange();
5249 var preCaretRange
= range
.cloneRange();
5250 preCaretRange
.selectNodeContents(element
);
5251 preCaretRange
.setEnd(range
.endContainer
, range
.endOffset
);
5252 caretOffset
= $.trim(preCaretRange
.toString()).length
;
5256 getCaretOffsetRange: function()
5258 return new Range(this.getSelection().getRangeAt(0));
5260 setCaret: function (el
, start
, end
)
5262 if (typeof end
=== 'undefined') end
= start
;
5265 var range
= this.getRange();
5266 range
.selectNodeContents(el
);
5268 var textNodes
= this.getTextNodesIn(el
);
5269 var foundStart
= false;
5270 var charCount
= 0, endCharCount
;
5272 if (textNodes
.length
== 1 && start
)
5274 range
.setStart(textNodes
[0], start
);
5275 range
.setEnd(textNodes
[0], end
);
5279 for (var i
= 0, textNode
; textNode
= textNodes
[i
++];)
5281 endCharCount
= charCount
+ textNode
.length
;
5282 if (!foundStart
&& start
>= charCount
&& (start
< endCharCount
|| (start
== endCharCount
&& i
< textNodes
.length
)))
5284 range
.setStart(textNode
, start
- charCount
);
5288 if (foundStart
&& end
<= endCharCount
)
5290 range
.setEnd( textNode
, end
- charCount
);
5294 charCount
= endCharCount
;
5298 var sel
= this.getSelection();
5299 sel
.removeAllRanges();
5300 sel
.addRange( range
);
5302 setCaretAfter: function(node
)
5304 this.$editor
.focus();
5306 node
= node
[0] || node
;
5308 var range
= this.document
.createRange()
5313 range
.setStart(node
, start
)
5314 range
.setEnd(node
, end
+ 2)
5317 var selection
= this.window
.getSelection()
5318 var cursorRange
= this.document
.createRange()
5320 var emptyElement
= this.document
.createTextNode('\u200B')
5321 $(node
).after(emptyElement
)
5323 cursorRange
.setStartAfter(emptyElement
)
5325 selection
.removeAllRanges()
5326 selection
.addRange(cursorRange
)
5327 $(emptyElement
).remove();
5329 getTextNodesIn: function (node
)
5333 if (node
.nodeType
== 3) textNodes
.push(node
);
5336 var children
= node
.childNodes
;
5337 for (var i
= 0, len
= children
.length
; i
< len
; ++i
)
5339 textNodes
.push
.apply(textNodes
, this.getTextNodesIn(children
[i
]));
5347 getCurrent: function()
5350 var sel
= this.getSelection();
5352 if (sel
&& sel
.rangeCount
> 0)
5354 el
= sel
.getRangeAt(0).startContainer
;
5355 //el = sel.getRangeAt(0).commonAncestorContainer;
5358 return this.isParentRedactor(el
);
5360 getParent: function(elem
)
5362 elem
= elem
|| this.getCurrent();
5363 if (elem
) return this.isParentRedactor( $( elem
).parent()[0] );
5366 getBlock: function(node
)
5368 if (typeof node
=== 'undefined') node
= this.getCurrent();
5372 if (this.nodeTestBlocks(node
))
5374 if ($(node
).hasClass('redactor_editor')) return false;
5378 node
= node
.parentNode
;
5383 getBlocks: function(nodes
)
5386 if (typeof nodes
== 'undefined')
5388 var range
= this.getRange();
5389 if (range
&& range
.collapsed
=== true) return [this.getBlock()];
5390 var nodes
= this.getNodes(range
);
5393 $.each(nodes
, $.proxy(function(i
,node
)
5395 if (this.opts
.iframe
=== false && $(node
).parents('div.redactor_editor').size() == 0) return false;
5396 if (this.nodeTestBlocks(node
)) newnodes
.push(node
);
5400 if (newnodes
.length
=== 0) newnodes
= [this.getBlock()];
5404 nodeTestBlocks: function(node
)
5406 return node
.nodeType
== 1 && this.rTestBlock
.test(node
.nodeName
);
5408 tagTestBlock: function(tag
)
5410 return this.rTestBlock
.test(tag
);
5412 getNodes: function(range
, tag
)
5414 if (typeof range
== 'undefined' || range
== false) var range
= this.getRange();
5415 if (range
&& range
.collapsed
=== true)
5417 if (typeof tag
=== 'undefined' && this.tagTestBlock(tag
))
5419 var block
= this.getBlock();
5420 if (block
.tagName
== tag
) return [block
];
5425 return [this.getCurrent()];
5429 var nodes
= [], finalnodes
= [];
5431 var sel
= this.document
.getSelection();
5432 if (!sel
.isCollapsed
) nodes
= this.getRangeSelectedNodes(sel
.getRangeAt(0));
5434 $.each(nodes
, $.proxy(function(i
,node
)
5436 if (this.opts
.iframe
=== false && $(node
).parents('div.redactor_editor').size() == 0) return false;
5438 if (typeof tag
=== 'undefined')
5440 if ($.trim(node
.textContent
) != '')
5442 finalnodes
.push(node
);
5445 else if (node
.tagName
== tag
)
5447 finalnodes
.push(node
);
5452 if (finalnodes
.length
== 0)
5454 if (typeof tag
=== 'undefined' && this.tagTestBlock(tag
))
5456 var block
= this.getBlock();
5457 if (block
.tagName
== tag
) return finalnodes
.push(block
);
5462 finalnodes
.push(this.getCurrent());
5466 // last element filtering
5467 var last
= finalnodes
[finalnodes
.length
-1];
5468 if (this.nodeTestBlocks(last
))
5470 finalnodes
= finalnodes
.slice(0, -1);
5475 getElement: function(node
)
5477 if (!node
) node
= this.getCurrent();
5480 if (node
.nodeType
== 1)
5482 if ($(node
).hasClass('redactor_editor')) return false;
5486 node
= node
.parentNode
;
5491 getRangeSelectedNodes: function(range
)
5493 range
= range
|| this.getRange();
5494 var node
= range
.startContainer
;
5495 var endNode
= range
.endContainer
;
5497 if (node
== endNode
) return [node
];
5499 var rangeNodes
= [];
5500 while (node
&& node
!= endNode
)
5502 rangeNodes
.push(node
= this.nextNode(node
));
5505 node
= range
.startContainer
;
5506 while (node
&& node
!= range
.commonAncestorContainer
)
5508 rangeNodes
.unshift(node
);
5509 node
= node
.parentNode
;
5514 nextNode: function(node
)
5516 if (node
.hasChildNodes()) return node
.firstChild
;
5519 while (node
&& !node
.nextSibling
)
5521 node
= node
.parentNode
;
5524 if (!node
) return null;
5525 return node
.nextSibling
;
5529 // GET SELECTION HTML OR TEXT
5530 getSelectionText: function()
5532 return this.getSelection().toString();
5534 getSelectionHtml: function()
5538 var sel
= this.getSelection();
5541 var container
= this.document
.createElement( "div" );
5542 var len
= sel
.rangeCount
;
5543 for (var i
= 0; i
< len
; ++i
)
5545 container
.appendChild(sel
.getRangeAt(i
).cloneContents());
5548 html
= container
.innerHTML
;
5551 return this.syncClean(html
);
5555 selectionSave: function()
5557 if (!this.isFocused()) this.focusWithSaveScroll();
5559 if (!this.opts
.rangy
)
5561 this.selectionCreateMarker(this.getRange());
5566 this.savedSel
= rangy
.saveSelection();
5569 selectionCreateMarker: function(range
, remove
)
5573 var node1
= $('<span id="selection-marker-1" class="redactor-selection-marker">' + this.opts
.invisibleSpace
+ '</span>', this.document
)[0];
5574 var node2
= $('<span id="selection-marker-2" class="redactor-selection-marker">' + this.opts
.invisibleSpace
+ '</span>', this.document
)[0];
5576 if (range
.collapsed
=== true)
5578 this.selectionSetMarker(range
, node1
, true);
5582 this.selectionSetMarker(range
, node1
, true);
5583 this.selectionSetMarker(range
, node2
, false);
5586 this.savedSel
= this.$editor
.html();
5588 this.selectionRestore(false, false);
5590 selectionSetMarker: function(range
, node
, type
)
5592 var boundaryRange
= range
.cloneRange();
5594 boundaryRange
.collapse(type
);
5596 boundaryRange
.insertNode(node
);
5597 boundaryRange
.detach();
5599 selectionRestore: function(replace
, remove
)
5601 if (!this.opts
.rangy
)
5603 if (replace
=== true && this.savedSel
)
5605 this.$editor
.html(this.savedSel
);
5608 var node1
= this.$editor
.find('span#selection-marker-1');
5609 var node2
= this.$editor
.find('span#selection-marker-2');
5611 if (this.browser('mozilla'))
5613 this.$editor
.focus();
5615 else if (!this.isFocused())
5617 this.focusWithSaveScroll();
5620 if (node1
.length
!= 0 && node2
.length
!= 0)
5622 this.selectionSet(node1
[0], 0, node2
[0], 0);
5624 else if (node1
.length
!= 0)
5626 this.selectionSet(node1
[0], 0, null, 0);
5629 if (remove
!== false)
5631 this.selectionRemoveMarkers();
5632 this.savedSel
= false;
5638 rangy
.restoreSelection(this.savedSel
);
5641 selectionRemoveMarkers: function(type
)
5643 if (!this.opts
.rangy
)
5645 $.each(this.$editor
.find('span.redactor-selection-marker'), function()
5647 var html
= $.trim($(this).html().replace(/[^\u0000-\u1C7F]/g, ''));
5654 $(this).removeAttr('class').removeAttr('id');
5661 rangy
.removeMarkers(this.savedSel
);
5666 tableShow: function()
5668 this.selectionSave();
5670 this.modalInit(this.opts
.curLang
.table
, this.opts
.modal_table
, 300, $.proxy(function()
5672 $('#redactor_insert_table_btn').click($.proxy(this.tableInsert
, this));
5674 setTimeout(function()
5676 $('#redactor_table_rows').focus();
5682 tableInsert: function()
5684 this.bufferSet(false, false);
5686 var rows
= $('#redactor_table_rows').val(),
5687 columns
= $('#redactor_table_columns').val(),
5688 $table_box
= $('<div></div>'),
5689 tableId
= Math
.floor(Math
.random() * 99999),
5690 $table
= $('<table id="table' + tableId
+ '"><tbody></tbody></table>'),
5691 i
, $row
, z
, $column
;
5693 for (i
= 0; i
< rows
; i
++)
5695 $row
= $('<tr></tr>');
5697 for (z
= 0; z
< columns
; z
++)
5699 $column
= $('<td>' + this.opts
.invisibleSpace
+ '</td>');
5701 // set the focus to the first td
5702 if (i
=== 0 && z
=== 0)
5704 $column
.append('<span id="selection-marker-1">' + this.opts
.invisibleSpace
+ '</span>');
5707 $($row
).append($column
);
5710 $table
.append($row
);
5713 $table_box
.append($table
);
5714 var html
= $table_box
.html();
5717 this.selectionRestore();
5719 var current
= this.getBlock() || this.getCurrent();
5721 if (current
&& current
.tagName
!= 'BODY')
5723 if (current
.tagName
== 'LI')
5725 var current
= $(current
).closest('ul, ol');
5728 $(current
).after(html
)
5732 this.insertHtmlAdvanced(html
, false);
5735 this.selectionRestore();
5737 var table
= this.$editor
.find('#table' + tableId
);
5738 this.buttonActiveObserver();
5740 table
.find('span#selection-marker-1, inline#selection-marker-1').remove();
5741 table
.removeAttr('id');
5745 tableDeleteTable: function()
5747 var $table
= $(this.getParent()).closest('table');
5748 if (!this.isParentRedactor($table
)) return false;
5749 if ($table
.size() == 0) return false;
5756 tableDeleteRow: function()
5758 var parent
= this.getParent();
5759 var $table
= $(parent
).closest('table');
5762 if (!this.isParentRedactor($table
)) return false;
5763 if ($table
.size() == 0) return false;
5767 var $current_tr
= $(parent
).closest('tr');
5768 var $focus_tr
= $current_tr
.prev().length
? $current_tr
.prev() : $current_tr
.next();
5769 if ($focus_tr
.length
)
5771 var $focus_td
= $focus_tr
.children('td' ).first();
5772 if ($focus_td
.length
)
5774 $focus_td
.prepend('<span id="selection-marker-1">' + this.opts
.invisibleSpace
+ '</span>');
5778 $current_tr
.remove();
5779 this.selectionRestore();
5782 tableDeleteColumn: function()
5784 var parent
= this.getParent();
5785 var $table
= $(parent
).closest('table');
5787 if (!this.isParentRedactor($table
)) return false;
5788 if ($table
.size() == 0) return false;
5792 var $current_td
= $(parent
).closest('td');
5793 if (!($current_td
.is('td')))
5795 $current_td
= $current_td
.closest('td');
5798 var index
= $current_td
.get(0).cellIndex
;
5800 // Set the focus correctly
5801 $table
.find('tr').each($.proxy(function(i
, elem
)
5803 var focusIndex
= index
- 1 < 0 ? index
+ 1 : index
- 1;
5806 $(elem
).find('td').eq(focusIndex
).prepend('<span id="selection-marker-1">' + this.opts
.invisibleSpace
+ '</span>');
5809 $(elem
).find('td').eq(index
).remove();
5813 this.selectionRestore();
5816 tableAddHead: function()
5818 var $table
= $(this.getParent()).closest('table');
5819 if (!this.isParentRedactor($table
)) return false;
5820 if ($table
.size() == 0) return false;
5824 if ($table
.find('thead').size() !== 0) this.tableDeleteHead();
5827 var tr
= $table
.find('tr').first().clone();
5828 tr
.find('td').html(this.opts
.invisibleSpace
);
5829 $thead
= $('<thead></thead>');
5831 $table
.prepend($thead
);
5836 tableDeleteHead: function()
5838 var $table
= $(this.getParent()).closest('table');
5839 if (!this.isParentRedactor($table
)) return false;
5840 var $thead
= $table
.find('thead');
5842 if ($thead
.size() == 0) return false;
5849 tableAddRowAbove: function()
5851 this.tableAddRow('before');
5853 tableAddRowBelow: function()
5855 this.tableAddRow('after');
5857 tableAddColumnLeft: function()
5859 this.tableAddColumn('before');
5861 tableAddColumnRight: function()
5863 this.tableAddColumn('after');
5865 tableAddRow: function(type
)
5867 var $table
= $(this.getParent()).closest('table');
5868 if (!this.isParentRedactor($table
)) return false;
5869 if ($table
.size() == 0) return false;
5873 var $current_tr
= $(this.getParent()).closest('tr');
5874 var new_tr
= $current_tr
.clone();
5875 new_tr
.find('td').html(this.opts
.invisibleSpace
);
5877 if (type
=== 'after') $current_tr
.after(new_tr
);
5878 else $current_tr
.before(new_tr
);
5882 tableAddColumn: function (type
)
5884 var parent
= this.getParent();
5885 var $table
= $(parent
).closest('table');
5887 if (!this.isParentRedactor($table
)) return false;
5888 if ($table
.size() == 0) return false;
5894 var current
= this.getCurrent();
5895 var $current_tr
= $(current
).closest('tr');
5896 var $current_td
= $(current
).closest('td');
5898 $current_tr
.find('td').each($.proxy(function(i
, elem
)
5900 if ($(elem
)[0] === $current_td
[0]) index
= i
;
5904 $table
.find('tr').each($.proxy(function(i
, elem
)
5906 var $current
= $(elem
).find('td').eq(index
);
5908 var td
= $current
.clone();
5909 td
.html(this.opts
.invisibleSpace
);
5911 type
=== 'after' ? $current
.after(td
) : $current
.before(td
);
5919 videoShow: function()
5921 this.selectionSave();
5923 this.modalInit(this.opts
.curLang
.video
, this.opts
.modal_video
, 600, $.proxy(function()
5925 $('#redactor_insert_video_btn').click($.proxy(this.videoInsert
, this));
5927 setTimeout(function()
5929 $('#redactor_insert_video_area').focus();
5935 videoInsert: function ()
5937 var data
= $('#redactor_insert_video_area').val();
5938 data
= this.cleanStripTags(data
);
5940 this.selectionRestore();
5942 var current
= this.getBlock() || this.getCurrent();
5944 if (current
) $(current
).after(data
)
5945 else this.insertHtmlAdvanced(data
, false);
5953 linkShow: function()
5955 this.selectionSave();
5957 var callback
= $.proxy(function()
5959 this.insert_link_node
= false;
5961 var sel
= this.getSelection();
5962 var url
= '', text
= '', target
= '';
5964 var elem
= this.getParent();
5965 var par
= $(elem
).parent().get(0);
5966 if (par
&& par
.tagName
=== 'A')
5971 if (elem
&& elem
.tagName
=== 'A')
5974 text
= $(elem
).text();
5975 target
= elem
.target
;
5977 this.insert_link_node
= elem
;
5979 else text
= sel
.toString();
5981 $('#redactor_link_url_text').val(text
);
5983 var thref
= self
.location
.href
.replace(/\/$/i, '');
5984 url
= url
.replace(thref
, '');
5985 url
= url
.replace(/^\/#/, '#');
5986 url
= url
.replace('mailto:', '');
5988 // remove host from href
5989 if (this.opts
.linkProtocol
=== false)
5991 var re
= new RegExp('^(http|ftp|https)://' + self
.location
.host
, 'i');
5992 url
= url
.replace(re
, '');
5996 $('#redactor_link_url').val(url
);
5998 if (target
=== '_blank')
6000 $('#redactor_link_blank').prop('checked', true);
6003 this.linkInsertPressed
= false;
6004 $('#redactor_insert_link_btn').click($.proxy(this.linkProcess
, this));
6006 setTimeout(function()
6008 $('#redactor_link_url').focus();
6014 this.modalInit(this.opts
.curLang
.link
, this.opts
.modal_link
, 460, callback
);
6017 linkProcess: function()
6019 if (this.linkInsertPressed
)
6024 this.linkInsertPressed
= true;
6025 var target
= '', targetBlank
= '';
6027 var link
= $('#redactor_link_url').val();
6028 var text
= $('#redactor_link_url_text').val();
6031 if (link
.search('@') != -1)
6033 link
= 'mailto:' + link
;
6036 else if (link
.search('#') != 0)
6038 if ($('#redactor_link_blank').prop('checked'))
6040 target
= ' target="_blank"';
6041 targetBlank
= '_blank';
6044 // test url (add protocol)
6045 var pattern
= '((xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}';
6046 var re
= new RegExp('^(http|ftp|https)://' + pattern
, 'i');
6047 var re2
= new RegExp('^' + pattern
, 'i');
6049 if (link
.search(re
) == -1 && link
.search(re2
) == 0 && this.opts
.linkProtocol
)
6051 link
= this.opts
.linkProtocol
+ link
;
6055 text
= text
.replace(/<|>/g, '');
6056 var extra
= ' ';
6057 if (this.browser('mozilla'))
6062 this.linkInsert('<a href="' + link
+ '"' + target
+ '>' + text
+ '</a>' + extra
, $.trim(text
), link
, targetBlank
);
6065 linkInsert: function (a
, text
, link
, target
)
6067 this.selectionRestore();
6071 if (this.insert_link_node
)
6075 $(this.insert_link_node
).text(text
).attr('href', link
);
6079 $(this.insert_link_node
).attr('target', target
);
6083 $(this.insert_link_node
).removeAttr('target');
6088 var $a
= $(a
).addClass('redactor-added-link');
6089 this.exec('inserthtml', this.outerHtml($a
), false);
6091 var link
= this.$editor
.find('a.redactor-added-link');
6093 link
.removeAttr('style').removeClass('redactor-added-link').each(function()
6095 if (this.className
== '') $(this).removeAttr('class');
6104 setTimeout($.proxy(function()
6106 if (this.opts
.observeLinks
) this.observeLinks();
6114 fileShow: function ()
6117 this.selectionSave();
6119 var callback
= $.proxy(function()
6121 var sel
= this.getSelection();
6124 if (this.oldIE()) text
= sel
.text
;
6125 else text
= sel
.toString();
6127 $('#redactor_filename').val(text
);
6130 if (!this.isMobile() && !this.isIPad())
6132 this.draguploadInit('#redactor_file', {
6133 url
: this.opts
.fileUpload
,
6134 uploadFields
: this.opts
.uploadFields
,
6135 success
: $.proxy(this.fileCallback
, this),
6136 error
: $.proxy( function(obj
, json
)
6138 this.callback('fileUploadError', json
);
6141 uploadParam
: this.opts
.fileUploadParam
6145 this.uploadInit('redactor_file', {
6147 url
: this.opts
.fileUpload
,
6148 success
: $.proxy(this.fileCallback
, this),
6149 error
: $.proxy(function(obj
, json
)
6151 this.callback('fileUploadError', json
);
6158 this.modalInit(this.opts
.curLang
.file
, this.opts
.modal_file
, 500, callback
);
6160 fileCallback: function(json
)
6163 this.selectionRestore();
6168 var text
= $('#redactor_filename').val();
6169 if (text
=== '') text
= json
.filename
;
6171 var link
= '<a href="' + json
.filelink
+ '" id="filelink-marker">' + text
+ '</a>';
6174 if (this.browser('webkit') && !!this.window
.chrome
)
6176 link
= link
+ ' ';
6179 this.execCommand('inserthtml', link
, false);
6181 var linkmarker
= $(this.$editor
.find('a#filelink-marker'));
6182 if (linkmarker
.size() != 0) linkmarker
.removeAttr('id');
6183 else linkmarker
= false;
6187 // file upload callback
6188 this.callback('fileUpload', linkmarker
, json
);
6195 imageShow: function()
6198 this.selectionSave();
6200 var callback
= $.proxy(function()
6203 if (this.opts
.imageGetJson
)
6205 $.getJSON(this.opts
.imageGetJson
, $.proxy(function(data
)
6207 var folders
= {}, count
= 0;
6210 $.each(data
, $.proxy(function(key
, val
)
6212 if (typeof val
.folder
!== 'undefined')
6215 folders
[val
.folder
] = count
;
6220 var folderclass
= false;
6221 $.each(data
, $.proxy(function(key
, val
)
6224 var thumbtitle
= '';
6225 if (typeof val
.title
!== 'undefined') thumbtitle
= val
.title
;
6228 if (!$.isEmptyObject(folders
) && typeof val
.folder
!== 'undefined')
6230 folderkey
= folders
[val
.folder
];
6231 if (folderclass
=== false) folderclass
= '.redactorfolder' + folderkey
;
6234 var img
= $('<img src="' + val
.thumb
+ '" class="redactorfolder redactorfolder' + folderkey
+ '" rel="' + val
.image
+ '" title="' + thumbtitle
+ '" />');
6235 $('#redactor_image_box').append(img
);
6236 $(img
).click($.proxy(this.imageThumbClick
, this));
6241 if (!$.isEmptyObject(folders
))
6243 $('.redactorfolder').hide();
6244 $(folderclass
).show();
6246 var onchangeFunc = function(e
)
6248 $('.redactorfolder').hide();
6249 $('.redactorfolder' + $(e
.target
).val()).show();
6252 var select
= $('<select id="redactor_image_box_select">');
6253 $.each( folders
, function(k
, v
)
6255 select
.append( $('<option value="' + v
+ '">' + k
+ '</option>'));
6258 $('#redactor_image_box').before(select
);
6259 select
.change(onchangeFunc
);
6266 $('#redactor-modal-tab-2').remove();
6269 if (this.opts
.imageUpload
|| this.opts
.s3
)
6272 if (!this.isMobile() && !this.isIPad() && this.opts
.s3
=== false)
6274 if ($('#redactor_file' ).length
)
6276 this.draguploadInit('#redactor_file', {
6277 url
: this.opts
.imageUpload
,
6278 uploadFields
: this.opts
.uploadFields
,
6279 success
: $.proxy(this.imageCallback
, this),
6280 error
: $.proxy(function(obj
, json
)
6282 this.callback('imageUploadError', json
);
6285 uploadParam
: this.opts
.imageUploadParam
6290 if (this.opts
.s3
=== false)
6293 this.uploadInit('redactor_file', {
6295 url
: this.opts
.imageUpload
,
6296 success
: $.proxy(this.imageCallback
, this),
6297 error
: $.proxy(function(obj
, json
)
6299 this.callback('imageUploadError', json
);
6307 $('#redactor_file').on('change.redactor', $.proxy(this.s3handleFileSelect
, this));
6313 $('.redactor_tab').hide();
6314 if (!this.opts
.imageGetJson
)
6316 $('#redactor_tabs').remove();
6317 $('#redactor_tab3').show();
6321 $('#redactor-modal-tab-1').remove();
6322 $('#redactor-modal-tab-2').addClass('redactor_tabs_act');
6323 $('#redactor_tab2').show();
6327 if (!this.opts
.imageTabLink
&& (this.opts
.imageUpload
|| this.opts
.imageGetJson
))
6329 $('#redactor-tab-control-3').hide();
6332 $('#redactor_upload_btn').click($.proxy(this.imageCallbackLink
, this));
6334 if (!this.opts
.imageUpload
&& !this.opts
.imageGetJson
)
6336 setTimeout(function()
6338 $('#redactor_file_link').focus();
6345 this.modalInit(this.opts
.curLang
.image
, this.opts
.modal_image
, 610, callback
);
6348 imageEdit: function(image
)
6351 var parent
= $el
.parent().parent();
6353 var callback
= $.proxy(function()
6355 $('#redactor_file_alt').val($el
.attr('alt'));
6356 $('#redactor_image_edit_src').attr('href', $el
.attr('src'));
6358 if ($el
.css('display') == 'block' && $el
.css('float') == 'none')
6360 $('#redactor_form_image_align').val('center');
6364 $('#redactor_form_image_align').val($el
.css('float'));
6367 if ($(parent
).get(0).tagName
=== 'A')
6369 $('#redactor_file_link').val($(parent
).attr('href'));
6371 if ($(parent
).attr('target') == '_blank')
6373 $('#redactor_link_blank').prop('checked', true);
6377 $('#redactor_image_delete_btn').click($.proxy(function()
6379 this.imageRemove($el
);
6383 $('#redactorSaveBtn').click($.proxy(function()
6385 this.imageSave($el
);
6391 this.modalInit(this.opts
.curLang
.edit
, this.opts
.modal_image_edit
, 380, callback
);
6394 imageRemove: function(el
)
6396 var parentLink
= $(el
).parent().parent();
6397 var parent
= $(el
).parent();
6398 var parentEl
= false;
6400 if (parentLink
.length
&& parentLink
[0].tagName
=== 'A')
6403 $(parentLink
).remove();
6405 else if (parent
.length
&& parent
[0].tagName
=== 'A')
6415 if (parent
.length
&& parent
[0].tagName
=== 'P')
6417 this.focusWithSaveScroll();
6419 if (parentEl
=== false) this.selectionStart(parent
);
6423 this.callback('imageDelete', el
);
6428 imageSave: function(el
)
6431 var parent
= $el
.parent();
6433 $el
.attr('alt', $('#redactor_file_alt').val());
6435 var floating
= $('#redactor_form_image_align').val();
6438 this.imageResizeHide(false);
6440 if (floating
=== 'left')
6442 margin
= '0 ' + this.opts
.imageFloatMargin
+ ' ' + this.opts
.imageFloatMargin
+ ' 0';
6443 $el
.css({ 'float': 'left', 'margin': margin
});
6445 else if (floating
=== 'right')
6447 margin
= '0 0 ' + this.opts
.imageFloatMargin
+ ' ' + this.opts
.imageFloatMargin
+ '';
6448 $el
.css({ 'float': 'right', 'margin': margin
});
6450 else if (floating
=== 'center')
6452 $el
.css({ 'float': '', 'display': 'block', 'margin': 'auto' });
6456 $el
.css({ 'float': '', 'display': '', 'margin': '' });
6460 var link
= $.trim($('#redactor_file_link').val());
6464 if ($('#redactor_link_blank').prop('checked'))
6469 if (parent
.get(0).tagName
!== 'A')
6471 var a
= $('<a href="' + link
+ '">' + this.outerHtml(el
) + '</a>');
6475 a
.attr('target', '_blank');
6482 parent
.attr('href', link
);
6485 parent
.attr('target', '_blank');
6489 parent
.removeAttr('target');
6495 if (parent
.get(0).tagName
=== 'A')
6497 parent
.replaceWith(this.outerHtml(el
));
6502 this.observeImages();
6506 imageResizeHide: function(e
)
6508 if (e
!== false && $(e
.target
).parent().size() != 0 && $(e
.target
).parent()[0].id
=== 'redactor-image-box')
6513 var imageBox
= this.$editor
.find('#redactor-image-box');
6514 if (imageBox
.size() == 0)
6519 this.$editor
.find('#redactor-image-editter, #redactor-image-resizer').remove();
6521 imageBox
.find('img').css({
6522 marginTop
: imageBox
[0].style
.marginTop
,
6523 marginBottom
: imageBox
[0].style
.marginBottom
,
6524 marginLeft
: imageBox
[0].style
.marginLeft
,
6525 marginRight
: imageBox
[0].style
.marginRight
6528 imageBox
.css('margin', '');
6531 imageBox
.find('img').css('opacity', '');
6532 imageBox
.replaceWith(function()
6534 return $(this).contents();
6537 $(document
).off('click.redactor-image-resize-hide');
6538 this.$editor
.off('click.redactor-image-resize-hide');
6539 this.$editor
.off('keydown.redactor-image-delete');
6544 imageResize: function(image
)
6546 var $image
= $(image
);
6548 $image
.on('mousedown', $.proxy(function()
6550 this.imageResizeHide(false);
6553 $image
.on('dragstart', $.proxy(function()
6555 this.$editor
.on('drop.redactor-image-inside-drop', $.proxy(function()
6557 setTimeout($.proxy(function()
6559 this.observeImages();
6560 this.$editor
.off('drop.redactor-image-inside-drop');
6568 $image
.on('click', $.proxy(function(e
)
6570 if (this.$editor
.find('#redactor-image-box').size() != 0)
6575 var clicked
= false,
6578 ratio
= $image
.width() / $image
.height(),
6582 var imageResizer
= this.imageResizeControls($image
);
6585 var isResizing
= false;
6586 imageResizer
.on('mousedown', function(e
)
6591 ratio
= $image
.width() / $image
.height();
6593 start_x
= Math
.round(e
.pageX
- $image
.eq(0).offset().left
);
6594 start_y
= Math
.round(e
.pageY
- $image
.eq(0).offset().top
);
6598 $(this.document
.body
).on('mousemove', $.proxy(function(e
)
6602 var mouse_x
= Math
.round(e
.pageX
- $image
.eq(0).offset().left
) - start_x
;
6603 var mouse_y
= Math
.round(e
.pageY
- $image
.eq(0).offset().top
) - start_y
;
6605 var div_h
= $image
.height();
6607 var new_h
= parseInt(div_h
, 10) + mouse_y
;
6608 var new_w
= Math
.round(new_h
* ratio
);
6612 $image
.width(new_w
);
6616 this.imageEditter
.css({
6618 marginLeft
: '-13px',
6625 this.imageEditter
.css({
6627 marginLeft
: '-18px',
6634 start_x
= Math
.round(e
.pageX
- $image
.eq(0).offset().left
);
6635 start_y
= Math
.round(e
.pageY
- $image
.eq(0).offset().top
);
6639 }, this)).on('mouseup', function()
6645 this.$editor
.on('keydown.redactor-image-delete', $.proxy(function(e
)
6649 if (this.keyCode
.BACKSPACE
== key
|| this.keyCode
.DELETE
== key
)
6651 this.bufferSet(false, false);
6652 this.imageResizeHide(false);
6653 this.imageRemove($image
);
6658 $(document
).on('click.redactor-image-resize-hide', $.proxy(this.imageResizeHide
, this));
6659 this.$editor
.on('click.redactor-image-resize-hide', $.proxy(this.imageResizeHide
, this));
6664 imageResizeControls: function($image
)
6666 var imageBox
= $('<span id="redactor-image-box" data-redactor="verified">');
6668 position
: 'relative',
6669 display
: 'inline-block',
6671 outline
: '1px dashed rgba(0, 0, 0, .6)',
6672 'float': $image
.css('float')
6674 imageBox
.attr('contenteditable', false);
6676 if ($image
[0].style
.margin
!= 'auto')
6679 marginTop
: $image
[0].style
.marginTop
,
6680 marginBottom
: $image
[0].style
.marginBottom
,
6681 marginLeft
: $image
[0].style
.marginLeft
,
6682 marginRight
: $image
[0].style
.marginRight
6685 $image
.css('margin', '');
6689 imageBox
.css({ 'display': 'block', 'margin': 'auto' });
6692 $image
.css('opacity', .5).after(imageBox
);
6695 this.imageEditter
= $('<span id="redactor-image-editter" data-redactor="verified">' + this.opts
.curLang
.edit
+ '</span>');
6696 this.imageEditter
.css({
6697 position
: 'absolute',
6702 marginLeft
: '-18px',
6704 backgroundColor
: '#000',
6707 padding
: '7px 10px',
6710 this.imageEditter
.attr('contenteditable', false);
6711 this.imageEditter
.on('click', $.proxy(function()
6713 this.imageEdit($image
);
6715 imageBox
.append(this.imageEditter
);
6718 var imageResizer
= $('<span id="redactor-image-resizer" data-redactor="verified"></span>');
6720 position
: 'absolute',
6723 cursor
: 'nw-resize',
6726 border
: '1px solid #fff',
6727 backgroundColor
: '#000',
6731 imageResizer
.attr('contenteditable', false);
6732 imageBox
.append(imageResizer
);
6734 imageBox
.append($image
);
6736 return imageResizer
;
6738 imageThumbClick: function(e
)
6740 var img
= '<img id="image-marker" src="' + $(e
.target
).attr('rel') + '" alt="' + $(e
.target
).attr('title') + '" />';
6742 var parent
= this.getParent();
6743 if (this.opts
.paragraphy
&& $(parent
).closest('li').size() == 0) img
= '<p>' + img
+ '</p>';
6745 this.imageInsert(img
, true);
6747 imageCallbackLink: function()
6749 var val
= $('#redactor_file_link').val();
6753 var data
= '<img id="image-marker" src="' + val
+ '" />';
6754 if (this.opts
.linebreaks
=== false) data
= '<p>' + data
+ '</p>';
6756 this.imageInsert(data
, true);
6759 else this.modalClose();
6761 imageCallback: function(data
)
6763 this.imageInsert(data
);
6765 imageInsert: function(json
, link
)
6767 this.selectionRestore();
6774 html
= '<img id="image-marker" src="' + json
.filelink
+ '" />';
6776 var parent
= this.getParent();
6777 if (this.opts
.paragraphy
&& $(parent
).closest('li').size() == 0)
6779 html
= '<p>' + html
+ '</p>';
6787 this.execCommand('inserthtml', html
, false);
6789 var image
= $(this.$editor
.find('img#image-marker'));
6791 if (image
.length
) image
.removeAttr('id');
6796 // upload image callback
6797 link
!== true && this.callback('imageUpload', image
, json
);
6801 this.observeImages();
6805 modalTemplatesInit: function()
6807 $.extend( this.opts
,
6809 modal_file
: String()
6810 + '<section id="redactor-modal-file-insert">'
6811 + '<div id="redactor-progress" class="redactor-progress-inline" style="display: none;"><span></span></div>'
6812 + '<form id="redactorUploadFileForm" method="post" action="" enctype="multipart/form-data">'
6813 + '<label>' + this.opts
.curLang
.filename
+ '</label>'
6814 + '<input type="text" id="redactor_filename" class="redactor_input" />'
6815 + '<div style="margin-top: 7px;">'
6816 + '<input type="file" id="redactor_file" name="' + this.opts
.fileUploadParam
+ '" />'
6821 modal_image_edit
: String()
6822 + '<section id="redactor-modal-image-edit">'
6823 + '<label>' + this.opts
.curLang
.title
+ '</label>'
6824 + '<input type="text" id="redactor_file_alt" class="redactor_input" />'
6825 + '<label>' + this.opts
.curLang
.link
+ '</label>'
6826 + '<input type="text" id="redactor_file_link" class="redactor_input" />'
6827 + '<label><input type="checkbox" id="redactor_link_blank"> ' + this.opts
.curLang
.link_new_tab
+ '</label>'
6828 + '<label>' + this.opts
.curLang
.image_position
+ '</label>'
6829 + '<select id="redactor_form_image_align">'
6830 + '<option value="none">' + this.opts
.curLang
.none
+ '</option>'
6831 + '<option value="left">' + this.opts
.curLang
.left
+ '</option>'
6832 + '<option value="center">' + this.opts
.curLang
.center
+ '</option>'
6833 + '<option value="right">' + this.opts
.curLang
.right
+ '</option>'
6837 + '<button id="redactor_image_delete_btn" class="redactor_modal_btn redactor_modal_delete_btn">' + this.opts
.curLang
._delete
+ '</button>'
6838 + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts
.curLang
.cancel
+ '</button>'
6839 + '<button id="redactorSaveBtn" class="redactor_modal_btn redactor_modal_action_btn">' + this.opts
.curLang
.save
+ '</button>'
6842 modal_image
: String()
6843 + '<section id="redactor-modal-image-insert">'
6844 + '<div id="redactor_tabs">'
6845 + '<a href="#" id="redactor-tab-control-1" class="redactor_tabs_act">' + this.opts
.curLang
.upload
+ '</a>'
6846 + '<a href="#" id="redactor-tab-control-2">' + this.opts
.curLang
.choose
+ '</a>'
6847 + '<a href="#" id="redactor-tab-control-3">' + this.opts
.curLang
.link
+ '</a>'
6849 + '<div id="redactor-progress" class="redactor-progress-inline" style="display: none;"><span></span></div>'
6850 + '<form id="redactorInsertImageForm" method="post" action="" enctype="multipart/form-data">'
6851 + '<div id="redactor_tab1" class="redactor_tab">'
6852 + '<input type="file" id="redactor_file" name="' + this.opts
.imageUploadParam
+ '" />'
6854 + '<div id="redactor_tab2" class="redactor_tab" style="display: none;">'
6855 + '<div id="redactor_image_box"></div>'
6858 + '<div id="redactor_tab3" class="redactor_tab" style="display: none;">'
6859 + '<label>' + this.opts
.curLang
.image_web_link
+ '</label>'
6860 + '<input type="text" name="redactor_file_link" id="redactor_file_link" class="redactor_input" /><br><br>'
6864 + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts
.curLang
.cancel
+ '</button>'
6865 + '<button class="redactor_modal_btn redactor_modal_action_btn" id="redactor_upload_btn">' + this.opts
.curLang
.insert
+ '</button>'
6868 modal_link
: String()
6869 + '<section id="redactor-modal-link-insert">'
6870 + '<label>URL</label>'
6871 + '<input type="text" class="redactor_input" id="redactor_link_url" />'
6872 + '<label>' + this.opts
.curLang
.text
+ '</label>'
6873 + '<input type="text" class="redactor_input" id="redactor_link_url_text" />'
6874 + '<label><input type="checkbox" id="redactor_link_blank"> ' + this.opts
.curLang
.link_new_tab
+ '</label>'
6877 + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts
.curLang
.cancel
+ '</button>'
6878 + '<button id="redactor_insert_link_btn" class="redactor_modal_btn redactor_modal_action_btn">' + this.opts
.curLang
.insert
+ '</button>'
6881 modal_table
: String()
6882 + '<section id="redactor-modal-table-insert">'
6883 + '<label>' + this.opts
.curLang
.rows
+ '</label>'
6884 + '<input type="text" size="5" value="2" id="redactor_table_rows" />'
6885 + '<label>' + this.opts
.curLang
.columns
+ '</label>'
6886 + '<input type="text" size="5" value="3" id="redactor_table_columns" />'
6889 + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts
.curLang
.cancel
+ '</button>'
6890 + '<button id="redactor_insert_table_btn" class="redactor_modal_btn redactor_modal_action_btn">' + this.opts
.curLang
.insert
+ '</button>'
6893 modal_video
: String()
6894 + '<section id="redactor-modal-video-insert">'
6895 + '<form id="redactorInsertVideoForm">'
6896 + '<label>' + this.opts
.curLang
.video_html_code
+ '</label>'
6897 + '<textarea id="redactor_insert_video_area" style="width: 99%; height: 160px;"></textarea>'
6901 + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts
.curLang
.cancel
+ '</button>'
6902 + '<button id="redactor_insert_video_btn" class="redactor_modal_btn redactor_modal_action_btn">' + this.opts
.curLang
.insert
+ '</button>'
6907 modalInit: function(title
, content
, width
, callback
)
6909 var $redactorModalOverlay
= $('#redactor_modal_overlay');
6912 if (!$redactorModalOverlay
.length
)
6914 this.$overlay
= $redactorModalOverlay
= $('<div id="redactor_modal_overlay" style="display: none;"></div>');
6915 $('body').prepend(this.$overlay
);
6918 if (this.opts
.modalOverlay
)
6920 $redactorModalOverlay
.show().on('click', $.proxy(this.modalClose
, this));
6923 var $redactorModal
= $('#redactor_modal');
6925 if (!$redactorModal
.length
)
6927 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>');
6928 $('body').append(this.$modal
);
6931 $('#redactor_modal_close').on('click', $.proxy(this.modalClose
, this));
6933 this.hdlModalClose
= $.proxy(function(e
)
6935 if (e
.keyCode
=== this.keyCode
.ESC
)
6943 $(document
).keyup(this.hdlModalClose
);
6944 this.$editor
.keyup(this.hdlModalClose
);
6947 this.modalcontent
= false;
6948 if (content
.indexOf('#') == 0)
6950 this.modalcontent
= $(content
);
6951 $('#redactor_modal_inner').empty().append(this.modalcontent
.html());
6952 this.modalcontent
.html('');
6957 $('#redactor_modal_inner').empty().append(content
);
6960 $redactorModal
.find('#redactor_modal_header').html(title
);
6963 if (typeof $.fn
.draggable
!== 'undefined')
6965 $redactorModal
.draggable({ handle
: '#redactor_modal_header' });
6966 $redactorModal
.find('#redactor_modal_header').css('cursor', 'move');
6969 var $redactor_tabs
= $('#redactor_tabs');
6972 if ($redactor_tabs
.length
)
6975 $redactor_tabs
.find('a').each(function(i
, s
)
6978 $(s
).on('click', function(e
)
6982 $redactor_tabs
.find('a').removeClass('redactor_tabs_act');
6983 $(this).addClass('redactor_tabs_act');
6984 $('.redactor_tab').hide();
6985 $('#redactor_tab' + i
).show();
6986 $('#redactor_tab_selected').val(i
);
6988 if (that
.isMobile() === false)
6990 var height
= $redactorModal
.outerHeight();
6991 $redactorModal
.css('margin-top', '-' + (height
+ 10) / 2 + 'px');
6997 $redactorModal
.find('.redactor_btn_modal_close').on('click', $.proxy(this.modalClose
, this));
6999 var buttons
= $redactorModal
.find('footer button');
7000 var buttonsSize
= buttons
.size();
7001 if (buttonsSize
> 0)
7003 $(buttons
).css('width', (width
/buttonsSize
) + 'px')
7007 if (this.opts
.autoresize
=== true)
7009 this.saveModalScroll
= this.document
.body
.scrollTop
;
7013 this.saveModalScroll
= this.$editor
.scrollTop();
7016 if (this.isMobile() === false)
7018 $redactorModal
.css({
7022 width
: width
+ 'px',
7023 marginLeft
: '-' + (width
/ 2) + 'px'
7026 this.modalSaveBodyOveflow
= $(document
.body
).css('overflow');
7027 $(document
.body
).css('overflow', 'hidden');
7032 $redactorModal
.css({
7043 // modal actions callback
7044 if (typeof callback
=== 'function')
7049 // modal shown callback
7050 setTimeout($.proxy(function()
7052 this.callback('modalOpened');
7056 // fix bootstrap modal focus
7057 $(document
).off('focusin.modal');
7059 if (this.isMobile() === false)
7061 setTimeout(function()
7063 var height
= $redactorModal
.outerHeight();
7064 $redactorModal
.css({
7068 marginTop
: '-' + (height
+ 10) / 2 + 'px'
7073 $redactorModal
.find('input[type=text]').keypress(function(e
)
7075 if (e
.which
=== 13 )
7077 $redactorModal
.find('.redactor_modal_action_btn').click();
7083 modalClose: function()
7085 $('#redactor_modal_close').off('click', this.modalClose
);
7086 $('#redactor_modal').fadeOut('fast', $.proxy(function()
7088 var redactorModalInner
= $('#redactor_modal_inner');
7090 if (this.modalcontent
!== false)
7092 this.modalcontent
.html(redactorModalInner
.html());
7093 this.modalcontent
= false;
7096 redactorModalInner
.html('');
7098 if (this.opts
.modalOverlay
)
7100 $('#redactor_modal_overlay').hide().off('click', this.modalClose
);
7103 $(document
).unbind('keyup', this.hdlModalClose
);
7104 this.$editor
.unbind('keyup', this.hdlModalClose
);
7106 this.selectionRestore();
7109 if (this.opts
.autoresize
&& this.saveModalScroll
)
7111 $(this.document
.body
).scrollTop(this.saveModalScroll
);
7113 else if (this.opts
.autoresize
=== false && this.saveModalScroll
)
7115 this.$editor
.scrollTop(this.saveModalScroll
);
7118 this.callback('modalClosed');
7123 if (this.isMobile() === false)
7125 $(document
.body
).css('overflow', this.modalSaveBodyOveflow
? this.modalSaveBodyOveflow
: 'visible');
7130 modalSetTab: function(num
)
7132 $('.redactor_tab').hide();
7133 $('#redactor_tabs').find('a').removeClass('redactor_tabs_act').eq(num
- 1).addClass('redactor_tabs_act');
7134 $('#redactor_tab' + num
).show();
7138 s3handleFileSelect: function(e
)
7140 var files
= e
.target
.files
;
7142 for (var i
= 0, f
; f
= files
[i
]; i
++)
7144 this.s3uploadFile(f
);
7147 s3uploadFile: function(file
)
7149 this.s3executeOnSignedUrl(file
, $.proxy(function(signedURL
)
7151 this.s3uploadToS3(file
, signedURL
);
7154 s3executeOnSignedUrl: function(file
, callback
)
7156 var xhr
= new XMLHttpRequest();
7159 if (this.opts
.s3
.search(/\?/) != '-1') mark
= '&';
7161 xhr
.open('GET', this.opts
.s3
+ mark
+ 'name=' + file
.name
+ '&type=' + file
.type
, true);
7163 // Hack to pass bytes through unprocessed.
7164 if (xhr
.overrideMimeType
) xhr
.overrideMimeType('text/plain; charset=x-user-defined');
7166 xhr
.onreadystatechange = function(e
)
7168 if (this.readyState
== 4 && this.status
== 200)
7170 $('#redactor-progress').fadeIn();
7171 callback(decodeURIComponent(this.responseText
));
7173 else if(this.readyState
== 4 && this.status
!= 200)
7175 //setProgress(0, 'Could not contact signing script. Status = ' + this.status);
7181 s3createCORSRequest: function(method
, url
)
7183 var xhr
= new XMLHttpRequest();
7184 if ("withCredentials" in xhr
)
7186 xhr
.open(method
, url
, true);
7188 else if (typeof XDomainRequest
!= "undefined")
7190 xhr
= new XDomainRequest();
7191 xhr
.open(method
, url
);
7200 s3uploadToS3: function(file
, url
)
7202 var xhr
= this.s3createCORSRequest('PUT', url
);
7205 //setProgress(0, 'CORS not supported');
7209 xhr
.onload
= $.proxy(function()
7211 if (xhr
.status
== 200)
7213 //setProgress(100, 'Upload completed.');
7215 $('#redactor-progress, #redactor-progress-drag').hide();
7217 var s3image
= url
.split('?');
7221 // url parsing is fail
7225 this.selectionRestore();
7228 html
= '<img id="image-marker" src="' + s3image
[0] + '" />';
7229 if (this.opts
.paragraphy
) html
= '<p>' + html
+ '</p>';
7231 this.execCommand('inserthtml', html
, false);
7233 var image
= $(this.$editor
.find('img#image-marker'));
7235 if (image
.length
) image
.removeAttr('id');
7240 // upload image callback
7241 this.callback('imageUpload', image
, false);
7244 this.observeImages();
7249 //setProgress(0, 'Upload error: ' + xhr.status);
7253 xhr
.onerror = function()
7255 //setProgress(0, 'XHR error.');
7258 xhr
.upload
.onprogress = function(e
)
7261 if (e.lengthComputable)
7263 var percentLoaded = Math.round((e.loaded / e.total) * 100);
7264 setProgress(percentLoaded, percentLoaded == 100 ? 'Finalizing.' : 'Uploading.');
7269 xhr
.setRequestHeader('Content-Type', file
.type
);
7270 xhr
.setRequestHeader('x-amz-acl', 'public-read');
7277 uploadInit: function(el
, options
)
7279 this.uploadOptions
= {
7289 $.extend(this.uploadOptions
, options
);
7291 var $el
= $('#' + el
);
7293 // Test input or form
7294 if ($el
.length
&& $el
[0].tagName
=== 'INPUT')
7296 this.uploadOptions
.input
= $el
;
7297 this.el
= $($el
[0].form
);
7301 this.element_action
= this.el
.attr('action');
7304 if (this.uploadOptions
.auto
)
7306 $(this.uploadOptions
.input
).change($.proxy(function(e
)
7308 this.el
.submit(function(e
)
7313 this.uploadSubmit(e
);
7318 else if (this.uploadOptions
.trigger
)
7320 $('#' + this.uploadOptions
.trigger
).click($.proxy(this.uploadSubmit
, this));
7323 uploadSubmit: function(e
)
7325 $('#redactor-progress').fadeIn();
7326 this.uploadForm(this.element
, this.uploadFrame());
7328 uploadFrame: function()
7330 this.id
= 'f' + Math
.floor(Math
.random() * 99999);
7332 var d
= this.document
.createElement('div');
7333 var iframe
= '<iframe style="display:none" id="' + this.id
+ '" name="' + this.id
+ '"></iframe>';
7335 d
.innerHTML
= iframe
;
7336 $(d
).appendTo("body");
7339 if (this.uploadOptions
.start
) this.uploadOptions
.start();
7341 $( '#' + this.id
).load($.proxy(this.uploadLoaded
, this));
7345 uploadForm: function(f
, name
)
7347 if (this.uploadOptions
.input
)
7349 var formId
= 'redactorUploadForm' + this.id
,
7350 fileId
= 'redactorUploadFile' + this.id
;
7352 this.form
= $('<form action="' + this.uploadOptions
.url
+ '" method="POST" target="' + name
+ '" name="' + formId
+ '" id="' + formId
+ '" enctype="multipart/form-data" />');
7354 // append hidden fields
7355 if (this.opts
.uploadFields
!== false && typeof this.opts
.uploadFields
=== 'object')
7357 $.each(this.opts
.uploadFields
, $.proxy(function(k
, v
)
7359 if (v
!= null && v
.toString().indexOf('#') === 0) v
= $(v
).val();
7361 var hidden
= $('<input/>', {
7367 $(this.form
).append(hidden
);
7372 var oldElement
= this.uploadOptions
.input
;
7373 var newElement
= $(oldElement
).clone();
7375 $(oldElement
).attr('id', fileId
).before(newElement
).appendTo(this.form
);
7377 $(this.form
).css('position', 'absolute')
7378 .css('top', '-2000px')
7379 .css('left', '-2000px')
7387 f
.attr('target', name
)
7388 .attr('method', 'POST')
7389 .attr('enctype', 'multipart/form-data')
7390 .attr('action', this.uploadOptions
.url
);
7392 this.element
.submit();
7395 uploadLoaded: function()
7397 var i
= $( '#' + this.id
)[0], d
;
7399 if (i
.contentDocument
) d
= i
.contentDocument
;
7400 else if (i
.contentWindow
) d
= i
.contentWindow
.document
;
7401 else d
= window
.frames
[this.id
].document
;
7404 if (this.uploadOptions
.success
)
7406 $('#redactor-progress').hide();
7408 if (typeof d
!== 'undefined')
7410 // Remove bizarre <pre> tag wrappers around our json data:
7411 var rawString
= d
.body
.innerHTML
;
7412 var jsonString
= rawString
.match(/\{(.|\n)*\}/)[0];
7414 jsonString
= jsonString
.replace(/^\[/, '');
7415 jsonString
= jsonString
.replace(/\]$/, '');
7417 var json
= $.parseJSON(jsonString
);
7419 if (typeof json
.error
== 'undefined') this.uploadOptions
.success(json
);
7422 this.uploadOptions
.error(this, json
);
7429 alert('Upload failed!');
7433 this.el
.attr('action', this.element_action
);
7434 this.el
.attr('target', '');
7438 draguploadInit: function (el
, options
)
7440 this.draguploadOptions
= $.extend({
7445 uploadFields
: false,
7446 text
: this.opts
.curLang
.drop_file_here
,
7447 atext
: this.opts
.curLang
.or_choose
,
7451 if (window
.FormData
=== undefined) return false;
7453 this.droparea
= $('<div class="redactor_droparea"></div>');
7454 this.dropareabox
= $('<div class="redactor_dropareabox">' + this.draguploadOptions
.text
+ '</div>');
7455 this.dropalternative
= $('<div class="redactor_dropalternative">' + this.draguploadOptions
.atext
+ '</div>');
7457 this.droparea
.append(this.dropareabox
);
7459 $(el
).before(this.droparea
);
7460 $(el
).before(this.dropalternative
);
7463 this.dropareabox
.on('dragover', $.proxy(function()
7465 return this.draguploadOndrag();
7470 this.dropareabox
.on('dragleave', $.proxy(function()
7472 return this.draguploadOndragleave();
7477 this.dropareabox
.get(0).ondrop
= $.proxy(function(e
)
7481 this.dropareabox
.removeClass('hover').addClass('drop');
7483 this.dragUploadAjax(this.draguploadOptions
.url
, e
.dataTransfer
.files
[0], false, false, false, this.draguploadOptions
.uploadParam
);
7487 dragUploadAjax: function(url
, file
, directupload
, progress
, e
, uploadParam
)
7491 var xhr
= $.ajaxSettings
.xhr();
7494 xhr
.upload
.addEventListener('progress', $.proxy(this.uploadProgress
, this), false);
7498 xhr: function () { return xhr
; }
7503 this.callback('drop', e
);
7505 var fd
= new FormData();
7508 if (uploadParam
!== false)
7510 fd
.append(uploadParam
, file
);
7514 fd
.append('file', file
);
7517 // append hidden fields
7518 if (this.opts
.uploadFields
!== false && typeof this.opts
.uploadFields
=== 'object')
7520 $.each(this.opts
.uploadFields
, $.proxy(function(k
, v
)
7522 if (v
!= null && v
.toString().indexOf('#') === 0) v
= $(v
).val();
7536 success
: $.proxy(function(data
)
7538 data
= data
.replace(/^\[/, '');
7539 data
= data
.replace(/\]$/, '');
7541 var json
= (typeof data
=== 'string' ? $.parseJSON(data
) : data
);
7545 progress
.fadeOut('slow', function()
7550 var $img
= $('<img>');
7551 $img
.attr('src', json
.filelink
).attr('id', 'drag-image-marker');
7553 this.insertNodeToCaretPositionFromPoint(e
, $img
[0]);
7555 var image
= $(this.$editor
.find('img#drag-image-marker'));
7556 if (image
.length
) image
.removeAttr('id');
7560 this.observeImages();
7563 if (image
) this.callback('imageUpload', image
, json
);
7566 if (typeof json
.error
!== 'undefined') this.callback('imageUploadError', json
);
7570 if (typeof json
.error
== 'undefined')
7572 this.draguploadOptions
.success(json
);
7576 this.draguploadOptions
.error(this, json
);
7577 this.draguploadOptions
.success(false);
7584 draguploadOndrag: function()
7586 this.dropareabox
.addClass('hover');
7589 draguploadOndragleave: function()
7591 this.dropareabox
.removeClass('hover');
7594 uploadProgress: function(e
, text
)
7596 var percent
= e
.loaded
? parseInt(e
.loaded
/ e
.total
* 100, 10) : e
;
7597 this.dropareabox
.text('Loading ' + percent
+ '% ' + (text
|| ''));
7601 isMobile: function()
7603 return /(iPhone|iPod|BlackBerry|Android)/.test(navigator
.userAgent
);
7607 return /iPad/.test(navigator
.userAgent
);
7609 normalize: function(str
)
7611 if (typeof(str
) === 'undefined') return 0;
7612 return parseInt(str
.replace('px',''), 10);
7614 outerHtml: function(el
)
7616 return $('<div>').append($(el
).eq(0).clone()).html();
7618 stripHtml: function(html
)
7620 var tmp
= document
.createElement("DIV");
7621 tmp
.innerHTML
= html
;
7622 return tmp
.textContent
|| tmp
.innerText
|| "";
7624 isString: function(obj
)
7626 return Object
.prototype.toString
.call(obj
) == '[object String]';
7628 isEmpty: function(html
)
7630 html
= html
.replace(/​|<br>|<br\/>| /gi, '');
7631 html
= html
.replace(/\s/g, '');
7632 html
= html
.replace(/^<p>[^\W\w\D\d]*?<\/p>$/i, '');
7638 return !!navigator
.userAgent
.match(/Trident\/7\./);
7640 browser: function(browser
)
7642 var ua
= navigator
.userAgent
.toLowerCase();
7643 var match
= /(opr)[\/]([\w.]+)/.exec( ua
) ||
7644 /(chrome)[ \/]([\w.]+)/.exec( ua
) ||
7645 /(webkit)[ \/]([\w.]+).*(safari)[ \/]([\w.]+)/.exec(ua
) ||
7646 /(webkit)[ \/]([\w.]+)/.exec( ua
) ||
7647 /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua
) ||
7648 /(msie) ([\w.]+)/.exec( ua
) ||
7649 ua
.indexOf("trident") >= 0 && /(rv)(?::| )([\w.]+)/.exec( ua
) ||
7650 ua
.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua
) ||
7653 if (browser
== 'version') return match
[2];
7654 if (browser
== 'webkit') return (match
[1] == 'chrome' || match
[1] == 'webkit');
7655 if (match
[1] == 'rv') return browser
== 'msie';
7656 if (match
[1] == 'opr') return browser
== 'webkit';
7658 return browser
== match
[1];
7663 if (this.browser('msie') && parseInt(this.browser('version'), 10) < 9) return true;
7666 getFragmentHtml: function (fragment
)
7668 var cloned
= fragment
.cloneNode(true);
7669 var div
= this.document
.createElement('div');
7671 div
.appendChild(cloned
);
7672 return div
.innerHTML
;
7674 extractContent: function()
7676 var node
= this.$editor
[0];
7677 var frag
= this.document
.createDocumentFragment();
7680 while ((child
= node
.firstChild
))
7682 frag
.appendChild(child
);
7687 isParentRedactor: function(el
)
7689 if (!el
) return false;
7690 if (this.opts
.iframe
) return el
;
7692 if ($(el
).parents('div.redactor_editor').length
== 0 || $(el
).hasClass('redactor_editor')) return false;
7695 currentOrParentIs: function(tagName
)
7697 var parent
= this.getParent(), current
= this.getCurrent();
7698 return parent
&& parent
.tagName
=== tagName
? parent
: current
&& current
.tagName
=== tagName
? current
: false;
7700 isEndOfElement: function()
7702 var current
= this.getBlock();
7703 var offset
= this.getCaretOffset(current
);
7705 var text
= $.trim($(current
).text()).replace(/\n\r\n/g, '');
7707 var len
= text
.length
;
7709 if (offset
== len
) return true;
7712 isFocused: function()
7714 var el
, sel
= this.getSelection();
7716 if (sel
&& sel
.rangeCount
&& sel
.rangeCount
> 0) el
= sel
.getRangeAt(0).startContainer
;
7717 if (!el
) return false;
7718 if (this.opts
.iframe
)
7720 if (this.getCaretOffsetRange().equals()) return !this.$editor
.is(el
);
7724 return $(el
).closest('div.redactor_editor').length
!= 0;
7726 removeEmptyAttr: function (el
, attr
)
7728 if ($(el
).attr(attr
) == '') $(el
).removeAttr(attr
);
7730 removeFromArrayByValue: function(array
, value
)
7734 while ((index
= array
.indexOf(value
)) !== -1)
7736 array
.splice(index
, 1);
7745 Redactor
.prototype.init
.prototype = Redactor
.prototype;
7748 $.Redactor
.fn
.formatLinkify = function(protocol
, convertLinks
, convertImageLinks
, convertVideoLinks
, linkSize
)
7750 var url1
= /(^|<|\s)(www\..+?\..+?)([.),]?)(\s|\.\s+|\)|>|$)/,
7751 url2
= /(^|<|\s)(((https?|ftp):\/\/|mailto:).+?)([.),]?)(\s|\.\s+|\)|>|$)/,
7752 urlImage
= /(https?:\/\/.*\.(?:png|jpg|jpeg|gif))/gi,
7753 urlYoutube
= /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w.-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig,
7754 urlVimeo
= /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/;
7756 var childNodes
= (this.$editor
? this.$editor
.get(0) : this).childNodes
, i
= childNodes
.length
;
7759 var n
= childNodes
[i
];
7760 if (n
.nodeType
=== 3)
7762 var html
= n
.nodeValue
;
7765 if (convertVideoLinks
&& html
)
7767 var iframeStart
= '<iframe width="500" height="281" src="',
7768 iframeEnd
= '" frameborder="0" allowfullscreen></iframe>';
7770 if (html
.match(urlYoutube
))
7772 html
= html
.replace(urlYoutube
, iframeStart
+ '//www.youtube.com/embed/$1' + iframeEnd
);
7773 $(n
).after(html
).remove();
7775 else if (html
.match(urlVimeo
))
7777 html
= html
.replace(urlVimeo
, iframeStart
+ '//player.vimeo.com/video/$2' + iframeEnd
);
7778 $(n
).after(html
).remove();
7783 if (convertImageLinks
&& html
&& html
.match(urlImage
))
7785 html
= html
.replace(urlImage
, '<img src="$1">');
7787 $(n
).after(html
).remove();
7791 if (convertLinks
&& html
&& (html
.match(url1
) || html
.match(url2
)))
7800 var href1
= url1
.exec(html
);
7801 var href2
= url2
.exec(html
);
7803 if (href1
&& href1
[2] && href2
&& href2
[2])
7805 //process whichever came first sequentially *first*
7806 var index1
= html
.indexOf(href1
[2]);
7807 var index2
= html
.indexOf(href2
[2]);
7808 if (index1
< index2
)
7819 else if (href1
&& href1
[2])
7824 else if (href2
&& href2
[2])
7830 found
= (href
&& href
.length
);
7836 if (found
&& href
&& href
.length
> linkSize
)
7838 href
= href
.substring(0, linkSize
) + '...';
7843 html
= html
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g
, '>');
7850 html
= html
.replace(url1
, '$1<a href="' + protocol
+ '$2">' + $.trim(href
) + '</a>$3$4')
7854 html
= html
.replace(url2
, '$1<a href="$2">' + $.trim(href
) + '</a>$5$6');
7861 $(n
).after(html
).remove();
7864 else if (n
.nodeType
=== 1 && !/^(a|button|textarea)$/i.test(n
.tagName
))
7866 $.Redactor
.fn
.formatLinkify
.call(n
, protocol
, convertLinks
, convertImageLinks
, convertVideoLinks
, linkSize
);