UNPKG

bower-redactor

Version:
2,000 lines (1,693 loc) 232 kB
/* Redactor v10.1.3 Updated: June 4, 2015 http://imperavi.com/redactor/ Copyright (c) 2009-2015, Imperavi LLC. License: http://imperavi.com/redactor/license/ Usage: $('#content').redactor(); */ (function($) { 'use strict'; if (!Function.prototype.bind) { Function.prototype.bind = function(scope) { var fn = this; return function() { return fn.apply(scope); }; }; } var uuid = 0; // Plugin $.fn.redactor = function(options) { var val = []; var args = Array.prototype.slice.call(arguments, 1); if (typeof options === 'string') { this.each(function() { var instance = $.data(this, 'redactor'); var func; if (options.search(/\./) != '-1') { func = options.split('.'); if (typeof instance[func[0]] != 'undefined') { func = instance[func[0]][func[1]]; } } else { func = instance[options]; } if (typeof instance !== 'undefined' && $.isFunction(func)) { var methodVal = func.apply(instance, args); if (methodVal !== undefined && methodVal !== instance) { val.push(methodVal); } } else { $.error('No such method "' + options + '" for Redactor'); } }); } else { this.each(function() { $.data(this, 'redactor', {}); $.data(this, 'redactor', Redactor(this, options)); }); } if (val.length === 0) return this; else if (val.length === 1) return val[0]; else return val; }; // Initialization function Redactor(el, options) { return new Redactor.prototype.init(el, options); } // Functionality $.Redactor = Redactor; $.Redactor.VERSION = '10.1.3'; $.Redactor.modules = ['alignment', 'autosave', 'block', 'buffer', 'build', 'button', 'caret', 'clean', 'code', 'core', 'dropdown', 'file', 'focus', 'image', 'indent', 'inline', 'insert', 'keydown', 'keyup', 'lang', 'line', 'link', 'list', 'modal', 'observe', 'paragraphize', 'paste', 'placeholder', 'progress', 'selection', 'shortcuts', 'tabifier', 'tidy', 'toolbar', 'upload', 'utils', 'linkify']; $.Redactor.opts = { // settings lang: 'en', direction: 'ltr', // ltr or rtl plugins: false, // array focus: false, focusEnd: false, placeholder: false, visual: true, tabindex: false, minHeight: false, maxHeight: false, linebreaks: false, replaceDivs: true, paragraphize: true, cleanStyleOnEnter: false, enterKey: true, cleanOnPaste: true, cleanSpaces: true, pastePlainText: false, autosave: false, // false or url autosaveName: false, autosaveInterval: 60, // seconds autosaveOnChange: false, autosaveFields: false, linkTooltip: true, linkProtocol: 'http', linkNofollow: false, linkSize: 50, imageEditable: true, imageLink: true, imagePosition: true, imageFloatMargin: '10px', imageResizable: true, imageUpload: null, imageUploadParam: 'file', uploadImageField: false, dragImageUpload: true, fileUpload: null, fileUploadParam: 'file', dragFileUpload: true, s3: false, convertLinks: true, convertUrlLinks: true, convertImageLinks: true, convertVideoLinks: true, preSpaces: 4, // or false tabAsSpaces: false, // true or number of spaces tabKey: true, scrollTarget: false, toolbar: true, toolbarFixed: true, toolbarFixedTarget: document, toolbarFixedTopOffset: 0, // pixels toolbarExternal: false, // ID selector toolbarOverflow: false, source: true, buttons: ['html', 'formatting', 'bold', 'italic', 'deleted', 'unorderedlist', 'orderedlist', 'outdent', 'indent', 'image', 'file', 'link', 'alignment', 'horizontalrule'], // + 'underline' buttonsHide: [], buttonsHideOnMobile: [], formatting: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'], formattingAdd: false, tabifier: true, deniedTags: ['script', 'style'], allowedTags: false, // or array paragraphizeBlocks: ['table', 'div', 'pre', 'form', 'ul', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'dl', 'blockquote', 'figcaption', 'address', 'section', 'header', 'footer', 'aside', 'article', 'object', 'style', 'script', 'iframe', 'select', 'input', 'textarea', 'button', 'option', 'map', 'area', 'math', 'hr', 'fieldset', 'legend', 'hgroup', 'nav', 'figure', 'details', 'menu', 'summary', 'p'], removeComments: false, replaceTags: [ ['strike', 'del'], ['b', 'strong'] ], replaceStyles: [ ['font-weight:\\s?bold', "strong"], ['font-style:\\s?italic', "em"], ['text-decoration:\\s?underline', "u"], ['text-decoration:\\s?line-through', 'del'] ], removeDataAttr: false, removeAttr: false, // or multi array allowedAttr: false, // or multi array removeWithoutAttr: ['span'], // or false removeEmpty: ['p'], // or false; activeButtons: ['deleted', 'italic', 'bold', 'underline', 'unorderedlist', 'orderedlist', 'alignleft', 'aligncenter', 'alignright', 'justify'], activeButtonsStates: { b: 'bold', strong: 'bold', i: 'italic', em: 'italic', del: 'deleted', strike: 'deleted', ul: 'unorderedlist', ol: 'orderedlist', u: 'underline' }, shortcuts: { 'ctrl+shift+m, meta+shift+m': { func: 'inline.removeFormat' }, 'ctrl+b, meta+b': { func: 'inline.format', params: ['bold'] }, 'ctrl+i, meta+i': { func: 'inline.format', params: ['italic'] }, 'ctrl+h, meta+h': { func: 'inline.format', params: ['superscript'] }, 'ctrl+l, meta+l': { func: 'inline.format', params: ['subscript'] }, 'ctrl+k, meta+k': { func: 'link.show' }, 'ctrl+shift+7': { func: 'list.toggle', params: ['orderedlist'] }, 'ctrl+shift+8': { func: 'list.toggle', params: ['unorderedlist'] } }, shortcutsAdd: false, // private buffer: [], rebuffer: [], emptyHtml: '<p>&#x200b;</p>', invisibleSpace: '&#x200b;', imageTypes: ['image/png', 'image/jpeg', 'image/gif'], indentValue: 20, verifiedTags: ['a', 'img', 'b', 'strong', 'sub', 'sup', 'i', 'em', 'u', 'small', 'strike', 'del', 'cite', 'ul', 'ol', 'li'], // and for span tag special rule inlineTags: ['strong', 'b', 'u', 'em', 'i', 'code', 'del', 'ins', 'samp', 'kbd', 'sup', 'sub', 'mark', 'var', 'cite', 'small'], alignmentTags: ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DL', 'DT', 'DD', 'DIV', 'TD', 'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'ADDRESS', 'SECTION', 'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE'], blockLevelElements: ['PRE', 'UL', 'OL', 'LI'], // lang langs: { en: { html: 'HTML', video: 'Insert Video', image: 'Insert Image', table: 'Table', link: 'Link', link_insert: 'Insert link', link_edit: 'Edit link', unlink: 'Unlink', formatting: 'Formatting', paragraph: 'Normal text', quote: 'Quote', code: 'Code', header1: 'Header 1', header2: 'Header 2', header3: 'Header 3', header4: 'Header 4', header5: 'Header 5', bold: 'Bold', italic: 'Italic', fontcolor: 'Font Color', backcolor: 'Back Color', unorderedlist: 'Unordered List', orderedlist: 'Ordered List', outdent: 'Outdent', indent: 'Indent', cancel: 'Cancel', insert: 'Insert', save: 'Save', _delete: 'Delete', insert_table: 'Insert Table', insert_row_above: 'Add Row Above', insert_row_below: 'Add Row Below', insert_column_left: 'Add Column Left', insert_column_right: 'Add Column Right', delete_column: 'Delete Column', delete_row: 'Delete Row', delete_table: 'Delete Table', rows: 'Rows', columns: 'Columns', add_head: 'Add Head', delete_head: 'Delete Head', title: 'Title', image_position: 'Position', none: 'None', left: 'Left', right: 'Right', center: 'Center', image_web_link: 'Image Web Link', text: 'Text', mailto: 'Email', web: 'URL', video_html_code: 'Video Embed Code or Youtube/Vimeo Link', file: 'Insert File', upload: 'Upload', download: 'Download', choose: 'Choose', or_choose: 'Or choose', drop_file_here: 'Drop file here', align_left: 'Align text to the left', align_center: 'Center text', align_right: 'Align text to the right', align_justify: 'Justify text', horizontalrule: 'Insert Horizontal Rule', deleted: 'Deleted', anchor: 'Anchor', link_new_tab: 'Open link in new tab', underline: 'Underline', alignment: 'Alignment', filename: 'Name (optional)', edit: 'Edit', upload_label: 'Drop file here or ' } }, linkify: { regexps: { youtube: /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w.\-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig, vimeo: /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/, image: /((https?|www)[^\s]+\.)(jpe?g|png|gif)(\?[^\s-]+)?/ig, url: /(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/ig, } }, codemirror: false }; // Functionality Redactor.fn = $.Redactor.prototype = { keyCode: { BACKSPACE: 8, DELETE: 46, DOWN: 40, ENTER: 13, SPACE: 32, ESC: 27, TAB: 9, CTRL: 17, META: 91, SHIFT: 16, ALT: 18, RIGHT: 39, LEFT: 37, LEFT_WIN: 91 }, // Initialization init: function(el, options) { this.$element = $(el); this.uuid = uuid++; // if paste event detected = true this.rtePaste = false; this.$pasteBox = false; this.loadOptions(options); this.loadModules(); // formatting storage this.formatting = {}; // block level tags $.merge(this.opts.blockLevelElements, this.opts.alignmentTags); this.reIsBlock = new RegExp('^(' + this.opts.blockLevelElements.join('|' ) + ')$', 'i'); // setup allowed and denied tags this.tidy.setupAllowed(); // setup denied tags if (this.opts.deniedTags !== false) { var tags = ['html', 'head', 'link', 'body', 'meta', 'applet']; for (var i = 0; i < tags.length; i++) { this.opts.deniedTags.push(tags[i]); } } // load lang this.lang.load(); // extend shortcuts $.extend(this.opts.shortcuts, this.opts.shortcutsAdd); // start callback this.core.setCallback('start'); // build this.start = true; this.build.run(); }, loadOptions: function(options) { this.opts = $.extend( {}, $.extend(true, {}, $.Redactor.opts), this.$element.data(), options ); }, getModuleMethods: function(object) { return Object.getOwnPropertyNames(object).filter(function(property) { return typeof object[property] == 'function'; }); }, loadModules: function() { var len = $.Redactor.modules.length; for (var i = 0; i < len; i++) { this.bindModuleMethods($.Redactor.modules[i]); } }, bindModuleMethods: function(module) { if (typeof this[module] == 'undefined') return; // init module this[module] = this[module](); var methods = this.getModuleMethods(this[module]); var len = methods.length; // bind methods for (var z = 0; z < len; z++) { this[module][methods[z]] = this[module][methods[z]].bind(this); } }, alignment: function() { return { left: function() { this.alignment.set(''); }, right: function() { this.alignment.set('right'); }, center: function() { this.alignment.set('center'); }, justify: function() { this.alignment.set('justify'); }, set: function(type) { // focus if (!this.utils.browser('msie')) this.$editor.focus(); this.buffer.set(); this.selection.save(); // get blocks this.alignment.blocks = this.selection.getBlocks(); this.alignment.type = type; // set alignment if (this.alignment.isLinebreaksOrNoBlocks()) { this.alignment.setText(); } else { this.alignment.setBlocks(); } // sync this.selection.restore(); this.code.sync(); }, setText: function() { var wrapper = this.selection.wrap('div'); $(wrapper).attr('data-tagblock', 'redactor').css('text-align', this.alignment.type); }, setBlocks: function() { $.each(this.alignment.blocks, $.proxy(function(i, el) { var $el = this.utils.getAlignmentElement(el); if (!$el) return; if (this.alignment.isNeedReplaceElement($el)) { this.alignment.replaceElement($el); } else { this.alignment.alignElement($el); } }, this)); }, isLinebreaksOrNoBlocks: function() { return (this.opts.linebreaks && this.alignment.blocks[0] === false); }, isNeedReplaceElement: function($el) { return (this.alignment.type === '' && typeof($el.data('tagblock')) !== 'undefined'); }, replaceElement: function($el) { $el.replaceWith($el.html()); }, alignElement: function($el) { $el.css('text-align', this.alignment.type); this.utils.removeEmptyAttr($el, 'style'); } }; }, autosave: function() { return { html: false, enable: function() { if (!this.opts.autosave) return; this.autosave.name = (this.opts.autosaveName) ? this.opts.autosaveName : this.$textarea.attr('name'); if (this.opts.autosaveOnChange) return; this.autosaveInterval = setInterval(this.autosave.load, this.opts.autosaveInterval * 1000); }, onChange: function() { if (!this.opts.autosaveOnChange) return; this.autosave.load(); }, load: function() { this.autosave.source = this.code.get(); if (this.autosave.html === this.autosave.source) return; //if (this.utils.isEmpty(this.autosave.source)) return; // data var data = {}; data['name'] = this.autosave.name; data[this.autosave.name] = this.autosave.source; data = this.autosave.getHiddenFields(data); // ajax var jsxhr = $.ajax({ url: this.opts.autosave, type: 'post', data: data }); jsxhr.done(this.autosave.success); }, getHiddenFields: function(data) { if (this.opts.autosaveFields === false || typeof this.opts.autosaveFields !== 'object') { return data; } $.each(this.opts.autosaveFields, $.proxy(function(k, v) { if (v !== null && v.toString().indexOf('#') === 0) v = $(v).val(); data[k] = v; }, this)); return data; }, success: function(data) { var json; try { json = $.parseJSON(data); } catch(e) { //data has already been parsed json = data; } var callbackName = (typeof json.error == 'undefined') ? 'autosave' : 'autosaveError'; this.core.setCallback(callbackName, this.autosave.name, json); this.autosave.html = this.autosave.source; }, disable: function() { clearInterval(this.autosaveInterval); } }; }, block: function() { return { formatting: function(name) { this.block.clearStyle = false; var type, value; if (typeof this.formatting[name].data != 'undefined') type = 'data'; else if (typeof this.formatting[name].attr != 'undefined') type = 'attr'; else if (typeof this.formatting[name]['class'] != 'undefined') type = 'class'; if (typeof this.formatting[name].clear != 'undefined') { this.block.clearStyle = true; } if (type) value = this.formatting[name][type]; this.block.format(this.formatting[name].tag, type, value); }, format: function(tag, type, value) { if (tag == 'quote') tag = 'blockquote'; var formatTags = ['p', 'pre', 'blockquote', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']; if ($.inArray(tag, formatTags) == -1) return; this.block.isRemoveInline = (tag == 'pre' || tag.search(/h[1-6]/i) != -1); // focus if (!this.utils.browser('msie')) this.$editor.focus(); this.block.blocks = this.selection.getBlocks(); this.block.blocksSize = this.block.blocks.length; this.block.type = type; this.block.value = value; this.buffer.set(); this.selection.save(); this.block.set(tag); this.selection.restore(); this.code.sync(); }, set: function(tag) { this.selection.get(); this.block.containerTag = this.range.commonAncestorContainer.tagName; if (this.range.collapsed) { this.block.setCollapsed(tag); } else { this.block.setMultiple(tag); } }, setCollapsed: function(tag) { var block = this.block.blocks[0]; if (block === false) return; if (block.tagName == 'LI') { if (tag != 'blockquote') return; this.block.formatListToBlockquote(); return; } var isContainerTable = (this.block.containerTag == 'TD' || this.block.containerTag == 'TH'); if (isContainerTable && !this.opts.linebreaks) { document.execCommand('formatblock', false, '<' + tag + '>'); block = this.selection.getBlock(); this.block.toggle($(block)); } else if (block.tagName.toLowerCase() != tag) { if (this.opts.linebreaks && tag == 'p') { $(block).prepend('<br>').append('<br>'); this.utils.replaceWithContents(block); } else { var $formatted = this.utils.replaceToTag(block, tag); this.block.toggle($formatted); if (tag != 'p' && tag != 'blockquote') $formatted.find('img').remove(); if (this.block.isRemoveInline) this.utils.removeInlineTags($formatted); if (tag == 'p' || this.block.headTag) $formatted.find('p').contents().unwrap(); this.block.formatTableWrapping($formatted); } } else if (tag == 'blockquote' && block.tagName.toLowerCase() == tag) { // blockquote off if (this.opts.linebreaks) { $(block).prepend('<br>').append('<br>'); this.utils.replaceWithContents(block); } else { var $el = this.utils.replaceToTag(block, 'p'); this.block.toggle($el); } } else if (block.tagName.toLowerCase() == tag) { this.block.toggle($(block)); } if (typeof this.block.type == 'undefined' && typeof this.block.value == 'undefined') { $(block).removeAttr('class').removeAttr('style'); } }, setMultiple: function(tag) { var block = this.block.blocks[0]; var isContainerTable = (this.block.containerTag == 'TD' || this.block.containerTag == 'TH'); if (block !== false && this.block.blocksSize === 1) { if (block.tagName.toLowerCase() == tag && tag == 'blockquote') { // blockquote off if (this.opts.linebreaks) { $(block).prepend('<br>').append('<br>'); this.utils.replaceWithContents(block); } else { var $el = this.utils.replaceToTag(block, 'p'); this.block.toggle($el); } } else if (block.tagName == 'LI') { if (tag != 'blockquote') return; this.block.formatListToBlockquote(); } else if (this.block.containerTag == 'BLOCKQUOTE') { this.block.formatBlockquote(tag); } else if (this.opts.linebreaks && ((isContainerTable) || (this.range.commonAncestorContainer != block))) { this.block.formatWrap(tag); } else { if (this.opts.linebreaks && tag == 'p') { $(block).prepend('<br>').append('<br>'); this.utils.replaceWithContents(block); } else if (block.tagName === 'TD') { this.block.formatWrap(tag); } else { var $formatted = this.utils.replaceToTag(block, tag); this.block.toggle($formatted); if (this.block.isRemoveInline) this.utils.removeInlineTags($formatted); if (tag == 'p' || this.block.headTag) $formatted.find('p').contents().unwrap(); } } } else { if (this.opts.linebreaks || tag != 'p') { if (tag == 'blockquote') { var count = 0; for (var i = 0; i < this.block.blocksSize; i++) { if (this.block.blocks[i].tagName == 'BLOCKQUOTE') count++; } // only blockquote selected if (count == this.block.blocksSize) { $.each(this.block.blocks, $.proxy(function(i,s) { var $formatted = false; if (this.opts.linebreaks) { $(s).prepend('<br>').append('<br>'); $formatted = this.utils.replaceWithContents(s); } else { $formatted = this.utils.replaceToTag(s, 'p'); } if ($formatted && typeof this.block.type == 'undefined' && typeof this.block.value == 'undefined') { $formatted.removeAttr('class').removeAttr('style'); } }, this)); return; } } this.block.formatWrap(tag); } else { var classSize = 0; var toggleType = false; if (this.block.type == 'class') { toggleType = 'toggle'; classSize = $(this.block.blocks).filter('.' + this.block.value).length; if (this.block.blocksSize == classSize) toggleType = 'toggle'; else if (this.block.blocksSize > classSize) toggleType = 'set'; else if (classSize === 0) toggleType = 'set'; } var exceptTags = ['ul', 'ol', 'li', 'td', 'th', 'dl', 'dt', 'dd']; $.each(this.block.blocks, $.proxy(function(i,s) { if ($.inArray(s.tagName.toLowerCase(), exceptTags) != -1) return; var $formatted = this.utils.replaceToTag(s, tag); if (toggleType) { if (toggleType == 'toggle') this.block.toggle($formatted); else if (toggleType == 'remove') this.block.remove($formatted); else if (toggleType == 'set') this.block.setForce($formatted); } else this.block.toggle($formatted); if (tag != 'p' && tag != 'blockquote') $formatted.find('img').remove(); if (this.block.isRemoveInline) this.utils.removeInlineTags($formatted); if (tag == 'p' || this.block.headTag) $formatted.find('p').contents().unwrap(); if (typeof this.block.type == 'undefined' && typeof this.block.value == 'undefined') { $formatted.removeAttr('class').removeAttr('style'); } }, this)); } } }, setForce: function($el) { // remove style and class if the specified setting if (this.block.clearStyle) { $el.removeAttr('class').removeAttr('style'); } if (this.block.type == 'class') { $el.addClass(this.block.value); return; } else if (this.block.type == 'attr' || this.block.type == 'data') { $el.attr(this.block.value.name, this.block.value.value); return; } }, toggle: function($el) { // remove style and class if the specified setting if (this.block.clearStyle) { $el.removeAttr('class').removeAttr('style'); } if (this.block.type == 'class') { $el.toggleClass(this.block.value); return; } else if (this.block.type == 'attr' || this.block.type == 'data') { if ($el.attr(this.block.value.name) == this.block.value.value) { $el.removeAttr(this.block.value.name); } else { $el.attr(this.block.value.name, this.block.value.value); } return; } else { $el.removeAttr('style class'); return; } }, remove: function($el) { $el.removeClass(this.block.value); }, formatListToBlockquote: function() { var block = $(this.block.blocks[0]).closest('ul, ol', this.$editor[0]); $(block).find('ul, ol').contents().unwrap(); $(block).find('li').append($('<br>')).contents().unwrap(); var $el = this.utils.replaceToTag(block, 'blockquote'); this.block.toggle($el); }, formatBlockquote: function(tag) { document.execCommand('outdent'); document.execCommand('formatblock', false, tag); this.clean.clearUnverified(); this.$editor.find('p:empty').remove(); var formatted = this.selection.getBlock(); if (tag != 'p') { $(formatted).find('img').remove(); } if (!this.opts.linebreaks) { this.block.toggle($(formatted)); } this.$editor.find('ul, ol, tr, blockquote, p').each($.proxy(this.utils.removeEmpty, this)); if (this.opts.linebreaks && tag == 'p') { this.utils.replaceWithContents(formatted); } }, formatWrap: function(tag) { if (this.block.containerTag == 'UL' || this.block.containerTag == 'OL') { if (tag == 'blockquote') { this.block.formatListToBlockquote(); } else { return; } } var formatted = this.selection.wrap(tag); if (formatted === false) return; var $formatted = $(formatted); this.block.formatTableWrapping($formatted); var $elements = $formatted.find(this.opts.blockLevelElements.join(',') + ', td, table, thead, tbody, tfoot, th, tr'); if ((this.opts.linebreaks && tag == 'p') || tag == 'pre' || tag == 'blockquote') { $elements.append('<br />'); } $elements.contents().unwrap(); if (tag != 'p' && tag != 'blockquote') $formatted.find('img').remove(); $.each(this.block.blocks, $.proxy(this.utils.removeEmpty, this)); $formatted.append(this.selection.getMarker(2)); if (!this.opts.linebreaks) { this.block.toggle($formatted); } this.$editor.find('ul, ol, tr, blockquote, p').each($.proxy(this.utils.removeEmpty, this)); $formatted.find('blockquote:empty').remove(); if (this.block.isRemoveInline) { this.utils.removeInlineTags($formatted); } if (this.opts.linebreaks && tag == 'p') { this.utils.replaceWithContents($formatted); } }, formatTableWrapping: function($formatted) { if ($formatted.closest('table', this.$editor[0]).length === 0) return; if ($formatted.closest('tr', this.$editor[0]).length === 0) $formatted.wrap('<tr>'); if ($formatted.closest('td', this.$editor[0]).length === 0 && $formatted.closest('th').length === 0) { $formatted.wrap('<td>'); } }, removeData: function(name, value) { var blocks = this.selection.getBlocks(); $(blocks).removeAttr('data-' + name); this.code.sync(); }, setData: function(name, value) { var blocks = this.selection.getBlocks(); $(blocks).attr('data-' + name, value); this.code.sync(); }, toggleData: function(name, value) { var blocks = this.selection.getBlocks(); $.each(blocks, function() { if ($(this).attr('data-' + name)) { $(this).removeAttr('data-' + name); } else { $(this).attr('data-' + name, value); } }); }, removeAttr: function(attr, value) { var blocks = this.selection.getBlocks(); $(blocks).removeAttr(attr); this.code.sync(); }, setAttr: function(attr, value) { var blocks = this.selection.getBlocks(); $(blocks).attr(attr, value); this.code.sync(); }, toggleAttr: function(attr, value) { var blocks = this.selection.getBlocks(); $.each(blocks, function() { if ($(this).attr(name)) { $(this).removeAttr(name); } else { $(this).attr(name, value); } }); }, removeClass: function(className) { var blocks = this.selection.getBlocks(); $(blocks).removeClass(className); this.utils.removeEmptyAttr(blocks, 'class'); this.code.sync(); }, setClass: function(className) { var blocks = this.selection.getBlocks(); $(blocks).addClass(className); this.code.sync(); }, toggleClass: function(className) { var blocks = this.selection.getBlocks(); $(blocks).toggleClass(className); this.code.sync(); } }; }, buffer: function() { return { set: function(type) { if (typeof type == 'undefined' || type == 'undo') { this.buffer.setUndo(); } else { this.buffer.setRedo(); } }, setUndo: function() { this.selection.save(); this.opts.buffer.push(this.$editor.html()); this.selection.restore(); }, setRedo: function() { this.selection.save(); this.opts.rebuffer.push(this.$editor.html()); this.selection.restore(); }, getUndo: function() { this.$editor.html(this.opts.buffer.pop()); }, getRedo: function() { this.$editor.html(this.opts.rebuffer.pop()); }, add: function() { this.opts.buffer.push(this.$editor.html()); }, undo: function() { if (this.opts.buffer.length === 0) return; this.buffer.set('redo'); this.buffer.getUndo(); this.selection.restore(); setTimeout($.proxy(this.observe.load, this), 50); }, redo: function() { if (this.opts.rebuffer.length === 0) return; this.buffer.set('undo'); this.buffer.getRedo(); this.selection.restore(); setTimeout($.proxy(this.observe.load, this), 50); } }; }, build: function() { return { run: function() { this.build.createContainerBox(); this.build.loadContent(); this.build.loadEditor(); this.build.enableEditor(); this.build.setCodeAndCall(); }, isTextarea: function() { return (this.$element[0].tagName === 'TEXTAREA'); }, createContainerBox: function() { this.$box = $('<div class="redactor-box" />'); }, createTextarea: function() { this.$textarea = $('<textarea />').attr('name', this.build.getTextareaName()); }, getTextareaName: function() { return ((typeof(name) == 'undefined')) ? 'content-' + this.uuid : this.$element.attr('id'); }, loadContent: function() { var func = (this.build.isTextarea()) ? 'val' : 'html'; this.content = $.trim(this.$element[func]()); }, enableEditor: function() { this.$editor.attr({ 'contenteditable': true, 'dir': this.opts.direction }); }, loadEditor: function() { var func = (this.build.isTextarea()) ? 'fromTextarea' : 'fromElement'; this.build[func](); }, fromTextarea: function() { this.$editor = $('<div />'); this.$textarea = this.$element; this.$box.insertAfter(this.$element).append(this.$editor).append(this.$element); this.$editor.addClass('redactor-editor'); this.$element.hide(); }, fromElement: function() { this.$editor = this.$element; this.build.createTextarea(); this.$box.insertAfter(this.$editor).append(this.$editor).append(this.$textarea); this.$editor.addClass('redactor-editor'); this.$textarea.hide(); }, setCodeAndCall: function() { // set code this.code.set(this.content); this.build.setOptions(); this.build.callEditor(); // code mode if (this.opts.visual) return; setTimeout($.proxy(this.code.showCode, this), 200); }, callEditor: function() { this.build.disableMozillaEditing(); this.build.disableIeLinks(); this.build.setEvents(); this.build.setHelpers(); // load toolbar if (this.opts.toolbar) { this.opts.toolbar = this.toolbar.init(); this.toolbar.build(); } // modal templates init this.modal.loadTemplates(); // plugins this.build.plugins(); // observers setTimeout($.proxy(this.observe.load, this), 4); // init callback this.core.setCallback('init'); }, setOptions: function() { // textarea direction $(this.$textarea).attr('dir', this.opts.direction); if (this.opts.linebreaks) this.$editor.addClass('redactor-linebreaks'); if (this.opts.tabindex) this.$editor.attr('tabindex', this.opts.tabindex); if (this.opts.minHeight) this.$editor.css('minHeight', this.opts.minHeight); if (this.opts.maxHeight) this.$editor.css('maxHeight', this.opts.maxHeight); }, setEventDropUpload: function(e) { e.preventDefault(); if (!this.opts.dragImageUpload || !this.opts.dragFileUpload) return; var files = e.dataTransfer.files; this.upload.directUpload(files[0], e); }, setEventDrop: function(e) { this.code.sync(); setTimeout(this.clean.clearUnverified, 1); this.core.setCallback('drop', e); }, setEvents: function() { // drop this.$editor.on('drop.redactor', $.proxy(function(e) { e = e.originalEvent || e; if (window.FormData === undefined || !e.dataTransfer) return true; if (e.dataTransfer.files.length === 0) { return this.build.setEventDrop(e); } else { this.build.setEventDropUpload(e); } setTimeout(this.clean.clearUnverified, 1); this.core.setCallback('drop', e); }, this)); // click this.$editor.on('click.redactor', $.proxy(function(e) { var event = this.core.getEvent(); var type = (event == 'click' || event == 'arrow') ? false : 'click'; this.core.addEvent(type); this.utils.disableSelectAll(); this.core.setCallback('click', e); }, this)); // paste this.$editor.on('paste.redactor', $.proxy(this.paste.init, this)); // cut this.$editor.on('cut.redactor', $.proxy(this.code.sync, this)); // keydown this.$editor.on('keydown.redactor', $.proxy(this.keydown.init, this)); // keyup this.$editor.on('keyup.redactor', $.proxy(this.keyup.init, this)); // textarea keydown if ($.isFunction(this.opts.codeKeydownCallback)) { this.$textarea.on('keydown.redactor-textarea', $.proxy(this.opts.codeKeydownCallback, this)); } // textarea keyup if ($.isFunction(this.opts.codeKeyupCallback)) { this.$textarea.on('keyup.redactor-textarea', $.proxy(this.opts.codeKeyupCallback, this)); } // focus if ($.isFunction(this.opts.focusCallback)) { this.$editor.on('focus.redactor', $.proxy(this.opts.focusCallback, this)); } var clickedElement; $(document).on('mousedown', function(e) { clickedElement = e.target; }); // blur this.$editor.on('blur.redactor', $.proxy(function(e) { if (this.rtePaste) return; if (!this.build.isBlured(clickedElement)) return; this.utils.disableSelectAll(); if ($.isFunction(this.opts.blurCallback)) this.core.setCallback('blur', e); }, this)); }, isBlured: function(clickedElement) { var $el = $(clickedElement); return (!$el.hasClass('redactor-toolbar, redactor-dropdown') && !$el.is('#redactor-modal') && $el.parents('.redactor-toolbar, .redactor-dropdown, #redactor-modal').length === 0); }, setHelpers: function() { // linkify if (this.linkify.isEnabled()) { this.linkify.format(); } // placeholder this.placeholder.enable(); // focus if (this.opts.focus) setTimeout(this.focus.setStart, 100); if (this.opts.focusEnd) setTimeout(this.focus.setEnd, 100); }, plugins: function() { if (!this.opts.plugins) return; if (!RedactorPlugins) return; $.each(this.opts.plugins, $.proxy(function(i, s) { if (typeof RedactorPlugins[s] === 'undefined') return; if ($.inArray(s, $.Redactor.modules) !== -1) { $.error('Plugin name "' + s + '" matches the name of the Redactor\'s module.'); return; } if (!$.isFunction(RedactorPlugins[s])) return; this[s] = RedactorPlugins[s](); // get methods var methods = this.getModuleMethods(this[s]); var len = methods.length; // bind methods for (var z = 0; z < len; z++) { this[s][methods[z]] = this[s][methods[z]].bind(this); } if ($.isFunction(this[s].init)) this[s].init(); }, this)); }, disableMozillaEditing: function() { if (!this.utils.browser('mozilla')) return; // FF fix try { document.execCommand('enableObjectResizing', false, false); document.execCommand('enableInlineTableEditing', false, false); } catch (e) {} }, disableIeLinks: function() { if (!this.utils.browser('ie')) return; // IE prevent converting links document.execCommand("AutoUrlDetect", false, false); } }; }, button: function() { return { build: function(btnName, btnObject) { var $button = $('<a href="#" class="re-icon re-' + btnName + '" rel="' + btnName + '" />').attr('tabindex', '-1'); // click if (btnObject.func || btnObject.command || btnObject.dropdown) { this.button.setEvent($button, btnName, btnObject); } // dropdown if (btnObject.dropdown) { var $dropdown = $('<div class="redactor-dropdown redactor-dropdown-' + this.uuid + ' redactor-dropdown-box-' + btnName + '" style="display: none;">'); $button.data('dropdown', $dropdown); this.dropdown.build(btnName, $dropdown, btnObject.dropdown); } // tooltip if (this.utils.isDesktop()) { this.button.createTooltip($button, btnName, btnObject.title); } return $button; }, setEvent: function($button, btnName, btnObject) { $button.on('touchstart click', $.proxy(function(e) { if ($button.hasClass('redactor-button-disabled')) return false; var type = 'func'; var callback = btnObject.func; if (btnObject.command) { type = 'command'; callback = btnObject.command; } else if (btnObject.dropdown) { type = 'dropdown'; callback = false; } this.button.onClick(e, btnName, type, callback); }, this)); }, createTooltip: function($button, name, title) { var $tooltip = $('<span>').addClass('redactor-toolbar-tooltip redactor-toolbar-tooltip-' + this.uuid + ' redactor-toolbar-tooltip-' + name).hide().html(title); $tooltip.appendTo('body'); $button.on('mouseover', function() { if ($(this).hasClass('redactor-button-disabled')) return; var pos = $button.offset(); $tooltip.show(); $tooltip.css({ top: (pos.top + $button.innerHeight()) + 'px', left: (pos.left + $button.innerWidth()/2 - $tooltip.innerWidth()/2) + 'px' }); }); $button.on('mouseout', function() { $tooltip.hide(); }); }, onClick: function(e, btnName, type, callback) { this.button.caretOffset = this.caret.getOffset(); e.preventDefault(); if (this.utils.browser('msie')) e.returnValue = false; if (type == 'command') this.inline.format(callback); else if (type == 'dropdown') this.dropdown.show(e, btnName); else this.button.onClickCallback(e, callback, btnName); }, onClickCallback: function(e, callback, btnName) { var func; if ($.isFunction(callback)) callback.call(this, btnName); else if (callback.search(/\./) != '-1') { func = callback.split('.'); if (typeof this[func[0]] == 'undefined') return; this[func[0]][func[1]](btnName); } else this[callback](btnName); this.observe.buttons(e, btnName); }, get: function(key) { return this.$toolbar.find('a.re-' + key); }, setActive: function(key) { this.button.get(key).addClass('redactor-act'); }, setInactive: function(key) { this.button.get(key).removeClass('redactor-act'); }, setInactiveAll: function(key) { if (typeof key === 'undefined') { this.$toolbar.find('a.re-icon').removeClass('redactor-act'); } else { this.$toolbar.find('a.re-icon').not('.re-' + key).removeClass('redactor-act'); } }, setActiveInVisual: function() { this.$toolbar.find('a.re-icon').not('a.re-html').removeClass('redactor-button-disabled'); }, setInactiveInCode: function() { this.$toolbar.find('a.re-icon').not('a.re-html').addClass('redactor-button-disabled'); }, changeIcon: function(key, classname) { this.button.get(key).addClass('re-' + classname); }, removeIcon: function(key, classname) { this.button.get(key).removeClass('re-' + classname); }, setAwesome: function(key, name) { var $button = this.button.get(key); $button.removeClass('redactor-btn-image').addClass('fa-redactor-btn'); $button.html('<i class="fa ' + name + '"></i>'); }, addCallback: function($btn, callback) { if ($btn == "buffer") return; var type = (callback == 'dropdown') ? 'dropdown' : 'func'; var key = $btn.attr('rel'); $btn.on('touchstart click', $.proxy(function(e) { if ($btn.hasClass('redactor-button-disabled')) return false; this.button.onClick(e, key, type, callback); }, this)); }, addDropdown: function($btn, dropdown) { var key = $btn.attr('rel'); this.button.addCallback($btn, 'dropdown'); var $dropdown = $('<div class="redactor-dropdown redactor-dropdown-' + this.uuid + ' redactor-dropdown-box-' + key + '" style="display: none;">'); $btn.data('dropdown', $dropdown); // build dropdown if (dropdown) this.dropdown.build(key, $dropdown, dropdown); return $dropdown; }, add: function(key, title) { if (!this.opts.toolbar) return; if (this.button.isMobileUndoRedo(key)) return "buffer"; var btn = this.button.build(key, { title: title }); btn.addClass('redactor-btn-image'); this.$toolbar.append($('<li>').append(btn)); return btn; }, addFirst: function(key, title) { if (!this.opts.toolbar) return; if (this.button.isMobileUndoRedo(key)) return "buffer"; var btn = this.button.build(key, { title: title }); btn.addClass('redactor-btn-image'); this.$toolbar.prepend($('<li>').append(btn)); return btn; }, addAfter: function(afterkey, key, title) { if (!this.opts.toolbar) return; if (this.button.isMobileUndoRedo(key)) return "buffer"; var btn = this.button.build(key, { title: title }); btn.addClass('redactor-btn-image'); var $btn = this.button.get(afterkey); if ($btn.length !== 0) $btn.parent().after($('<li>').append(btn)); else this.$toolbar.append($('<li>').append(btn)); return btn; }, addBefore: function(beforekey, key, title) { if (!this.opts.toolbar) return; if (this.button.isMobileUndoRedo(key)) return "buffer"; var btn = this.button.build(key, { title: title }); btn.addClass('redactor-btn-image'); var $btn = this.button.get(beforekey); if ($btn.length !== 0) $btn.parent().before($('<li>').append(btn)); else this.$toolbar.append($('<li>').append(btn)); return btn; }, remove: function(key) { this.button.get(key).remove(); }, isMobileUndoRedo: function(key) { return (key == "undo" || key == "redo") && !this.utils.isDesktop(); } }; }, caret: function() { return { setStart: function(node) { // inline tag if (!this.utils.isBlock(node)) { var space = this.utils.createSpaceElement(); $(node).prepend(space); this.caret.setEnd(space); } else { this.caret.set(node, 0, node, 0); } }, setEnd: function(node) { this.caret.set(node, 1, node, 1); }, set: function(orgn, orgo, focn, foco) { // focus // disabled in 10.0.7 // if (!this.utils.browser('msie')) this.$editor.focus(); orgn = orgn[0] || orgn; focn = focn[0] || focn; if (this.utils.isBlockTag(orgn.tagName) && orgn.innerHTML === '') { orgn.innerHTML = this.opts.invisibleSpace; } if (orgn.tagName == 'BR' && this.opts.linebreaks === false) { var parent = $(this.opts.emptyHtml)[0]; $(orgn).replaceWith(parent); orgn = parent; focn = orgn; } this.selection.get(); try { this.range.setStart(orgn, orgo); this.range.setEnd(focn, foco); } catch (e) {} this.selection.addRange(); }, setAfter: function(node) { try { var tag = $(node)[0].tagName; // inline tag if (tag != 'BR' && !this.utils.isBlock(node)) { var space = this.utils.createSpaceElement(); $(node).after(space); this.caret.setEnd(space); } else { if (tag != 'BR' && this.utils.browser('msie')) { this.caret.setStart($(node).next()); } else { this.caret.setAfterOrBefore(node, 'after'); } } } catch (e) { var space = this.utils.createSpaceElement(); $(node).after(space); this.caret.setEnd(space); } }, setBefore: function(node) { // block tag if (this.utils.isBlock(node)) { this.caret.setEnd($(node).prev()); } else { this.caret.setAfterOrBefore(node, 'before'); } }, setAfterOrBefore: function(node, type) { // focus if (!this.utils.browser('msie')) this.$editor.focus(); node = node[0] || node; this.selection.get(); if (type == 'after') { try { this.range.setStartAfter(node); this.range.setEndAfter(node); } catch (e) {} } else { try { this.range.setStartBefore(node); this.range.setEndBefore(node); } catch (e) {} } this.range.collapse(false); this.selection.addRange(); }, getOffsetOfElement: function(node) { node = node[0] || node; this.selection.get(); var cloned = this.range.cloneRange(); cloned.selectNodeContents(node); cloned.setEnd(this.range.endContainer, this.range.endOffset); return $.trim(cloned.toString()).length; }, getOffset: function() { var offset = 0; var sel = window.getSelection(); if (sel.rangeCount > 0) { var range = window.getSelection().getRangeAt(0); var caretRange = range.cloneRange(); caretRange.selectNodeContents(this.$editor[0]); caretRange.setEnd(range.endContainer, range.endOffset); offset = caretRange.toString().length; } return offset; }, setOffset: function(start, end) { if (typeof end == 'undefined') end = start; if (!this.focus.isFocused()) this.focus.setStart(); var sel = this.selection.get(); var node, offset = 0; var walker = document.createTreeWalker(this.$editor[0], NodeFilter.SHOW_TEXT, null, null); while (node = walker.nextNode()) { offset += node.nodeValue.length; if (offset > start) { this.range.setStart(node, node.nodeValue.length + start - offset); start = Infinity; } if (offset >= end) { this.range.setEnd(node, node.nodeValue.length + end - offset); break; } } this.range.collapse(false); this.selection.addRange(); }, // deprecated setToPoint: function(start, end) { this.caret.setOffset(start, end); }, getCoords: function() { return this.caret.getOffset(); } }; }, clean: function() { return { onSet: function(html) { html = this.clean.savePreCode(html); // convert script tag html = html.replace(/<script(.*?[^>]?)>([\w\W]*?)<\/script>/gi, '<pre class="redactor-script-tag" style="display: none;" $1>$2</pre>'); // replace dollar sign to entity html = html.replace(/\$/g, '&#36;'); // replace special characters in links html = html.replace(/<a href="(.*?[^>]?)®(.*?[^>]?)">/gi, '<a href="$1&reg$2">'); if (this.opts.replaceDivs) html = this.clean.replaceDivs(html); if (this.opts.linebreaks) html = this.clean.replaceParagraphsToBr(html); // save form tag html = this.clean.saveFormTags(html); // convert font tag to span var $div = $('<div>'); $div.html(html); var fonts = $div.find('font[style]'); if (fonts.length !== 0) { fonts.replaceWith(function() { var $el = $(this); var $span = $('<span>').attr('style', $el.attr('style')); return $span.append($el.contents()); }); html = $div.html(); } $div.remove(); // remove font tag html = html.replace(/<font(.*?[^<])>/gi, ''); html = html.replace(/<\/font>/gi, ''); // tidy html html = this.tidy.load(html); // paragraphize if (this.opts.paragraphize) html = this.paragraphize.load(html); // verified html = this.clean.setVerified(html); // convert inline tags html = this.clean.convertInline(html); return html; }, onSync: function(html) { // remove spaces html = html.replace(/[\u200B-\u200D\uFEFF]/g, ''); html = html.replace(/&#x200b;/gi, ''); if (this.opts.cleanSpaces) { html = html.replace(/&nbsp;/gi, ' '); } if (html.search(/^<p>(||\s||<br\s?\/?>||&nbsp;)<\/p>$/i) != -1) { return ''; } // reconvert script tag html = html.replace(/<pre class="redactor-script-tag" style="display: none;"(.*?[^>]?)>([\w\W]*?)<\/pre>/gi, '<script$1>$2</script>'); // restore form tag html = this.clean.restoreFormTags(html); var chars = {