|
@@ -0,0 +1,9542 @@
|
|
|
+/*
|
|
|
+ Redactor 10.2.5
|
|
|
+ Updated: October 1, 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.2.5';
|
|
|
+ $.Redactor.modules = ['alignment', 'autosave', 'block', 'buffer', 'build', 'button',
|
|
|
+ 'caret', 'clean', 'code', 'core', 'dropdown', 'file', 'focus',
|
|
|
+ 'image', 'indent', 'inline', 'insert', 'keydown', 'keyup',
|
|
|
+ 'lang', 'line', 'link', 'linkify', 'list', 'modal', 'observe', 'paragraphize',
|
|
|
+ 'paste', 'placeholder', 'progress', 'selection', 'shortcuts',
|
|
|
+ 'tabifier', 'tidy', 'toolbar', 'upload', 'utils'];
|
|
|
+
|
|
|
+ $.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>​</p>',
|
|
|
+ invisibleSpace: '​',
|
|
|
+ imageTypes: ['image/png', 'image/jpeg', 'image/gif'],
|
|
|
+ indentValue: 20,
|
|
|
+ verifiedTags: ['a', 'img', 'b', 'strong', 'sub', 'sup', 'i', 'em', 'u', 'small', 'strike', 'del', 'cite', 'ul', 'ol', 'li'], // and for span tag special rule
|
|
|
+ inlineTags: ['strong', 'b', 'u', 'em', 'i', 'code', 'del', 'ins', 'samp', 'kbd', 'sup', 'sub', 'mark', 'var', 'cite', 'small'],
|
|
|
+ alignmentTags: ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DL', 'DT', 'DD', 'DIV', 'TD', 'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'ADDRESS', 'SECTION', 'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE'],
|
|
|
+ blockLevelElements: ['PRE', 'UL', 'OL', 'LI'],
|
|
|
+ highContrast: false,
|
|
|
+ observe: {
|
|
|
+ dropdowns: []
|
|
|
+ },
|
|
|
+
|
|
|
+ // 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,
|
|
|
+ UP: 38,
|
|
|
+ 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.opts.linebreaks)
|
|
|
+ {
|
|
|
+ this.$editor.focus();
|
|
|
+ }
|
|
|
+
|
|
|
+ // get blocks
|
|
|
+ this.alignment.blocks = this.selection.getBlocks();
|
|
|
+ this.alignment.type = type;
|
|
|
+
|
|
|
+ this.buffer.set();
|
|
|
+ this.selection.save();
|
|
|
+
|
|
|
+ // 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()
|
|
|
+ {
|
|
|
+ if (!this.opts.autosave) return;
|
|
|
+
|
|
|
+ this.autosave.source = this.code.get();
|
|
|
+
|
|
|
+ if (this.autosave.html === 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();
|
|
|
+
|
|
|
+ var html = $.trim(this.$editor.html());
|
|
|
+ this.block.isEmpty = this.utils.isEmpty(html);
|
|
|
+
|
|
|
+ // FF focus
|
|
|
+ if (this.utils.browser('mozilla') && !this.focus.isFocused())
|
|
|
+ {
|
|
|
+ if (this.block.isEmpty)
|
|
|
+ {
|
|
|
+ var $first;
|
|
|
+ if (!this.opts.linebreaks)
|
|
|
+ {
|
|
|
+ $first = this.$editor.children().first();
|
|
|
+ this.caret.setEnd($first);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ 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();
|
|
|
+ this.observe.load();
|
|
|
+
|
|
|
+ },
|
|
|
+ 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)
|
|
|
+ {
|
|
|
+ if (this.opts.linebreaks && this.block.isEmpty && tag != 'p')
|
|
|
+ {
|
|
|
+ var node = document.createElement(tag);
|
|
|
+ this.$editor.html(node);
|
|
|
+ this.caret.setEnd(node);
|
|
|
+
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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).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).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).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');
|
|
|
+
|
|
|
+ $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);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.opts.linebreaks)
|
|
|
+ {
|
|
|
+ var $next = $formatted.next().next();
|
|
|
+ if ($next.size() != 0 && $next[0].tagName === 'BR')
|
|
|
+ {
|
|
|
+ $next.remove();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ },
|
|
|
+ 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 {
|
|
|
+ focused: false,
|
|
|
+ blured: true,
|
|
|
+ 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" role="application" />');
|
|
|
+ },
|
|
|
+ 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('dragover.redactor dragenter.redactor', function(e)
|
|
|
+ {
|
|
|
+ e.preventDefault();
|
|
|
+ e.stopPropagation();
|
|
|
+ });
|
|
|
+
|
|
|
+ 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
|
|
|
+ this.$editor.on('focus.redactor', $.proxy(function(e)
|
|
|
+ {
|
|
|
+ if ($.isFunction(this.opts.focusCallback))
|
|
|
+ {
|
|
|
+ this.core.setCallback('focus', e);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.build.focused = true;
|
|
|
+ this.build.blured = false;
|
|
|
+
|
|
|
+ if (this.selection.getCurrent() === false)
|
|
|
+ {
|
|
|
+ this.selection.get();
|
|
|
+ this.range.setStart(this.$editor[0], 0);
|
|
|
+ this.range.setEnd(this.$editor[0], 0);
|
|
|
+ this.selection.addRange();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ }, this));
|
|
|
+
|
|
|
+
|
|
|
+ // blur
|
|
|
+ $(document).on('mousedown.redactor-blur.' + this.uuid, $.proxy(function(e)
|
|
|
+ {
|
|
|
+ if (this.start) return;
|
|
|
+ if (this.rtePaste) return;
|
|
|
+
|
|
|
+ if ($(e.target).closest('.redactor-editor, .redactor-toolbar, .redactor-dropdown').size() !== 0)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.utils.disableSelectAll();
|
|
|
+ if (!this.build.blured && $.isFunction(this.opts.blurCallback))
|
|
|
+ {
|
|
|
+ this.core.setCallback('blur', e);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.build.focused = false;
|
|
|
+ this.build.blured = true;
|
|
|
+
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ },
|
|
|
+ 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;
|
|
|
+
|
|
|
+ $.each(this.opts.plugins, $.proxy(function(i, s)
|
|
|
+ {
|
|
|
+ var func = (typeof RedactorPlugins !== 'undefined' && typeof RedactorPlugins[s] !== 'undefined') ? RedactorPlugins : Redactor.fn;
|
|
|
+
|
|
|
+ if (!$.isFunction(func[s]))
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this[s] = func[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('msie')) 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({'role': 'button', 'aria-label': btnObject.title, 'tabindex': '-1'});
|
|
|
+
|
|
|
+ // click
|
|
|
+ if (btnObject.func || btnObject.command || btnObject.dropdown)
|
|
|
+ {
|
|
|
+ this.button.setEvent($button, btnName, btnObject);
|
|
|
+ }
|
|
|
+
|
|
|
+ // dropdown
|
|
|
+ if (btnObject.dropdown)
|
|
|
+ {
|
|
|
+ $button.addClass('redactor-toolbar-link-dropdown').attr('aria-haspopup', true);
|
|
|
+
|
|
|
+ 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.css({
|
|
|
+ top: (pos.top + $button.innerHeight()) + 'px',
|
|
|
+ left: (pos.left + $button.innerWidth()/2 - $tooltip.innerWidth()/2) + 'px'
|
|
|
+ });
|
|
|
+ $tooltip.show();
|
|
|
+
|
|
|
+ });
|
|
|
+
|
|
|
+ $button.on('mouseout', function()
|
|
|
+ {
|
|
|
+ $tooltip.hide();
|
|
|
+ });
|
|
|
+
|
|
|
+ },
|
|
|
+ onClick: function(e, btnName, type, callback)
|
|
|
+ {
|
|
|
+ this.button.caretOffset = this.caret.getOffset();
|
|
|
+
|
|
|
+ e.preventDefault();
|
|
|
+
|
|
|
+ $(document).find('.redactor-toolbar-tooltip').hide();
|
|
|
+
|
|
|
+ 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, a.re-fullscreen').removeClass('redactor-button-disabled');
|
|
|
+ },
|
|
|
+ setInactiveInCode: function()
|
|
|
+ {
|
|
|
+ this.$toolbar.find('a.re-icon').not('a.re-html, a.re-fullscreen').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)
|
|
|
+ {
|
|
|
+ $btn.addClass('redactor-toolbar-link-dropdown').attr('aria-haspopup', true);
|
|
|
+
|
|
|
+ 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)
|
|
|
+ {
|
|
|
+ node = node[0] || node;
|
|
|
+ if (node.lastChild.nodeType == 1)
|
|
|
+ {
|
|
|
+ return this.caret.setAfter(node.lastChild);
|
|
|
+ }
|
|
|
+
|
|
|
+ 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, '$');
|
|
|
+
|
|
|
+ // replace special characters in links
|
|
|
+ html = html.replace(/<a href="(.*?[^>]?)®(.*?[^>]?)">/gi, '<a href="$1®$2">');
|
|
|
+
|
|
|
+ if (this.opts.replaceDivs && !this.opts.linebreaks) 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);
|
|
|
+
|
|
|
+ html = html.replace(/&/g, '&');
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ onSync: function(html)
|
|
|
+ {
|
|
|
+ // remove spaces
|
|
|
+ html = html.replace(/\u200B/g, '');
|
|
|
+ html = html.replace(/​/gi, '');
|
|
|
+
|
|
|
+ if (this.opts.cleanSpaces)
|
|
|
+ {
|
|
|
+ html = html.replace(/ /gi, ' ');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (html.search(/^<p>(||\s||<br\s?\/?>|| )<\/p>$/i) != -1)
|
|
|
+ {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+
|
|
|
+ // reconvert script tag
|
|
|
+ html = html.replace(/<pre class="redactor-script-tag" style="display: none;"(.*?[^>]?)>([\w\W]*?)<\/pre>/gi, '<script$1>$2</script>');
|
|
|
+
|
|
|
+ // restore form tag
|
|
|
+ html = this.clean.restoreFormTags(html);
|
|
|
+
|
|
|
+ var chars = {
|
|
|
+ '\u2122': '™',
|
|
|
+ '\u00a9': '©',
|
|
|
+ '\u2026': '…',
|
|
|
+ '\u2014': '—',
|
|
|
+ '\u2010': '‐'
|
|
|
+ };
|
|
|
+ // replace special characters
|
|
|
+ $.each(chars, function(i,s)
|
|
|
+ {
|
|
|
+ html = html.replace(new RegExp(i, 'g'), s);
|
|
|
+ });
|
|
|
+
|
|
|
+ // remove last br in FF
|
|
|
+ if (this.utils.browser('mozilla'))
|
|
|
+ {
|
|
|
+ html = html.replace(/<br\s?\/?>$/gi, '');
|
|
|
+ }
|
|
|
+
|
|
|
+ // remove br in|of li tags
|
|
|
+ html = html.replace(new RegExp('<br\\s?/?></li>', 'gi'), '</li>');
|
|
|
+ html = html.replace(new RegExp('</li><br\\s?/?>', 'gi'), '</li>');
|
|
|
+
|
|
|
+ // remove empty attributes
|
|
|
+ html = html.replace(/<(.*?)rel="\s*?"(.*?[^>]?)>/gi, '<$1$2">');
|
|
|
+ html = html.replace(/<(.*?)style="\s*?"(.*?[^>]?)>/gi, '<$1$2">');
|
|
|
+ html = html.replace(/="">/gi, '>');
|
|
|
+ html = html.replace(/""">/gi, '">');
|
|
|
+ html = html.replace(/"">/gi, '">');
|
|
|
+
|
|
|
+ // remove verified
|
|
|
+ html = html.replace(/<div(.*?)data-tagblock="redactor"(.*?[^>])>/gi, '<div$1$2>');
|
|
|
+ html = html.replace(/<(.*?) data-verified="redactor"(.*?[^>])>/gi, '<$1$2>');
|
|
|
+
|
|
|
+ var $div = $("<div/>").html($.parseHTML(html, document, true));
|
|
|
+ $div.find("span").removeAttr("rel");
|
|
|
+
|
|
|
+ $div.find('pre .redactor-invisible-space').each(function()
|
|
|
+ {
|
|
|
+ $(this).contents().unwrap();
|
|
|
+ });
|
|
|
+
|
|
|
+ html = $div.html();
|
|
|
+
|
|
|
+ // remove rel attribute from img
|
|
|
+ html = html.replace(/<img(.*?[^>])rel="(.*?[^>])"(.*?[^>])>/gi, '<img$1$3>');
|
|
|
+ html = html.replace(/<span class="redactor-invisible-space">(.*?)<\/span>/gi, '$1');
|
|
|
+
|
|
|
+ html = html.replace(/ data-save-url="(.*?[^>])"/gi, '');
|
|
|
+
|
|
|
+ // remove image resize
|
|
|
+ html = html.replace(/<span(.*?)id="redactor-image-box"(.*?[^>])>([\w\W]*?)<img(.*?)><\/span>/gi, '$3<img$4>');
|
|
|
+ html = html.replace(/<span(.*?)id="redactor-image-resizer"(.*?[^>])>(.*?)<\/span>/gi, '');
|
|
|
+ html = html.replace(/<span(.*?)id="redactor-image-editter"(.*?[^>])>(.*?)<\/span>/gi, '');
|
|
|
+
|
|
|
+ // remove font tag
|
|
|
+ html = html.replace(/<font(.*?)>/gi, '');
|
|
|
+ html = html.replace(/<\/font>/gi, '');
|
|
|
+
|
|
|
+ // tidy html
|
|
|
+ html = this.tidy.load(html);
|
|
|
+
|
|
|
+ // link nofollow
|
|
|
+ if (this.opts.linkNofollow)
|
|
|
+ {
|
|
|
+ html = html.replace(/<a(.*?)rel="nofollow"(.*?[^>])>/gi, '<a$1$2>');
|
|
|
+ html = html.replace(/<a(.*?[^>])>/gi, '<a$1 rel="nofollow">');
|
|
|
+ }
|
|
|
+
|
|
|
+ // reconvert inline
|
|
|
+ html = html.replace(/\sdata-redactor-(tag|class|style)="(.*?[^>])"/gi, '');
|
|
|
+ html = html.replace(new RegExp('<(.*?) data-verified="redactor"(.*?[^>])>', 'gi'), '<$1$2>');
|
|
|
+ html = html.replace(new RegExp('<(.*?) data-verified="redactor">', 'gi'), '<$1>');
|
|
|
+
|
|
|
+ html = html.replace(/&/g, '&');
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ onPaste: function(html, setMode)
|
|
|
+ {
|
|
|
+ html = $.trim(html);
|
|
|
+ html = html.replace(/\$/g, '$');
|
|
|
+
|
|
|
+ // convert dirty spaces
|
|
|
+ html = html.replace(/<span class="s[0-9]">/gi, '<span>');
|
|
|
+ html = html.replace(/<span class="Apple-converted-space"> <\/span>/gi, ' ');
|
|
|
+ html = html.replace(/<span class="Apple-tab-span"[^>]*>\t<\/span>/gi, '\t');
|
|
|
+ html = html.replace(/<span[^>]*>(\s| )<\/span>/gi, ' ');
|
|
|
+
|
|
|
+ if (this.opts.pastePlainText)
|
|
|
+ {
|
|
|
+ return this.clean.getPlainText(html);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!this.utils.isSelectAll() && typeof setMode == 'undefined')
|
|
|
+ {
|
|
|
+ if (this.utils.isCurrentOrParent(['FIGCAPTION', 'A']))
|
|
|
+ {
|
|
|
+ return this.clean.getPlainText(html, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.utils.isCurrentOrParent('PRE'))
|
|
|
+ {
|
|
|
+ html = html.replace(/”/g, '"');
|
|
|
+ html = html.replace(/“/g, '"');
|
|
|
+ html = html.replace(/‘/g, '\'');
|
|
|
+ html = html.replace(/’/g, '\'');
|
|
|
+
|
|
|
+ return this.clean.getPreCode(html);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.utils.isCurrentOrParent(['BLOCKQUOTE', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6']))
|
|
|
+ {
|
|
|
+ html = this.clean.getOnlyImages(html);
|
|
|
+
|
|
|
+ if (!this.utils.browser('msie'))
|
|
|
+ {
|
|
|
+ var block = this.selection.getBlock();
|
|
|
+ if (block && block.tagName == 'P')
|
|
|
+ {
|
|
|
+ html = html.replace(/<img(.*?)>/gi, '<p><img$1></p>');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return html;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.utils.isCurrentOrParent(['TD']))
|
|
|
+ {
|
|
|
+ html = this.clean.onPasteTidy(html, 'td');
|
|
|
+
|
|
|
+ if (this.opts.linebreaks) html = this.clean.replaceParagraphsToBr(html);
|
|
|
+
|
|
|
+ html = this.clean.replaceDivsToBr(html);
|
|
|
+
|
|
|
+ return html;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if (this.utils.isCurrentOrParent(['LI']))
|
|
|
+ {
|
|
|
+ return this.clean.onPasteTidy(html, 'li');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ html = this.clean.isSingleLine(html, setMode);
|
|
|
+
|
|
|
+ if (!this.clean.singleLine)
|
|
|
+ {
|
|
|
+ if (this.opts.linebreaks) html = this.clean.replaceParagraphsToBr(html);
|
|
|
+ if (this.opts.replaceDivs) html = this.clean.replaceDivs(html);
|
|
|
+
|
|
|
+ html = this.clean.saveFormTags(html);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ html = this.clean.onPasteWord(html);
|
|
|
+ html = this.clean.onPasteExtra(html);
|
|
|
+
|
|
|
+ html = this.clean.onPasteTidy(html, 'all');
|
|
|
+
|
|
|
+
|
|
|
+ // paragraphize
|
|
|
+ if (!this.clean.singleLine && this.opts.paragraphize)
|
|
|
+ {
|
|
|
+ html = this.paragraphize.load(html);
|
|
|
+ }
|
|
|
+
|
|
|
+ html = this.clean.removeDirtyStyles(html);
|
|
|
+ html = this.clean.onPasteRemoveSpans(html);
|
|
|
+ html = this.clean.onPasteRemoveEmpty(html);
|
|
|
+
|
|
|
+
|
|
|
+ html = this.clean.convertInline(html);
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ onPasteWord: function(html)
|
|
|
+ {
|
|
|
+ // comments
|
|
|
+ html = html.replace(/<!--[\s\S]*?-->/gi, '');
|
|
|
+
|
|
|
+ // style
|
|
|
+ html = html.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '');
|
|
|
+
|
|
|
+ // op
|
|
|
+ html = html.replace(/<o\:p[^>]*>[\s\S]*?<\/o\:p>/gi, '');
|
|
|
+
|
|
|
+ if (html.match(/class="?Mso|style="[^"]*\bmso-|style='[^'']*\bmso-|w:WordDocument/i))
|
|
|
+ {
|
|
|
+ // comments
|
|
|
+ html = html.replace(/<!--[\s\S]+?-->/gi, '');
|
|
|
+
|
|
|
+ // scripts
|
|
|
+ html = html.replace(/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi, '');
|
|
|
+
|
|
|
+ // Convert <s> into <strike>
|
|
|
+ html = html.replace(/<(\/?)s>/gi, "<$1strike>");
|
|
|
+
|
|
|
+ // Replace nbsp entites to char since it's easier to handle
|
|
|
+ html = html.replace(/ /gi, ' ');
|
|
|
+
|
|
|
+ // Convert <span style="mso-spacerun:yes">___</span> to string of alternating
|
|
|
+ // breaking/non-breaking spaces of same length
|
|
|
+ html = html.replace(/<span\s+style\s*=\s*"\s*mso-spacerun\s*:\s*yes\s*;?\s*"\s*>([\s\u00a0]*)<\/span>/gi, function(str, spaces) {
|
|
|
+ return (spaces.length > 0) ? spaces.replace(/./, " ").slice(Math.floor(spaces.length/2)).split("").join("\u00a0") : '';
|
|
|
+ });
|
|
|
+
|
|
|
+ html = this.clean.onPasteIeFixLinks(html);
|
|
|
+
|
|
|
+ // shapes
|
|
|
+ html = html.replace(/<img(.*?)v:shapes=(.*?)>/gi, '');
|
|
|
+ html = html.replace(/src="file\:\/\/(.*?)"/, 'src=""');
|
|
|
+
|
|
|
+ // lists
|
|
|
+ var $div = $("<div/>").html(html);
|
|
|
+
|
|
|
+ var lastList = false;
|
|
|
+ var lastLevel = 1;
|
|
|
+ var listsIds = [];
|
|
|
+
|
|
|
+ $div.find("p[style]").each(function()
|
|
|
+ {
|
|
|
+ var matches = $(this).attr('style').match(/mso\-list\:l([0-9]+)\slevel([0-9]+)/);
|
|
|
+
|
|
|
+ if (matches)
|
|
|
+ {
|
|
|
+ var currentList = parseInt(matches[1]);
|
|
|
+ var currentLevel = parseInt(matches[2]);
|
|
|
+ var listType = $(this).html().match(/^[\w]+\./) ? "ol" : "ul";
|
|
|
+
|
|
|
+ var $li = $("<li/>").html($(this).html());
|
|
|
+
|
|
|
+ $li.html($li.html().replace(/^([\w\.]+)</, '<'));
|
|
|
+ $li.find("span:first").remove();
|
|
|
+
|
|
|
+ if (currentLevel == 1 && $.inArray(currentList, listsIds) == -1)
|
|
|
+ {
|
|
|
+ var $list = $("<" + listType + "/>").attr({"data-level": currentLevel,
|
|
|
+ "data-list": currentList})
|
|
|
+ .html($li);
|
|
|
+
|
|
|
+ $(this).replaceWith($list);
|
|
|
+
|
|
|
+ lastList = currentList;
|
|
|
+ listsIds.push(currentList);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (currentLevel > lastLevel)
|
|
|
+ {
|
|
|
+ var $prevList = $div.find('[data-level="' + lastLevel + '"][data-list="' + lastList + '"]');
|
|
|
+
|
|
|
+ var $lastList = $prevList;
|
|
|
+
|
|
|
+ for(var i = lastLevel; i < currentLevel; i++)
|
|
|
+ {
|
|
|
+ $list = $("<" + listType + "/>");
|
|
|
+
|
|
|
+ $list.appendTo($lastList.find("li").last());
|
|
|
+
|
|
|
+ $lastList = $list;
|
|
|
+ }
|
|
|
+
|
|
|
+ $lastList.attr({"data-level": currentLevel,
|
|
|
+ "data-list": currentList})
|
|
|
+ .html($li);
|
|
|
+
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ var $prevList = $div.find('[data-level="' + currentLevel + '"][data-list="' + currentList + '"]').last();
|
|
|
+
|
|
|
+ $prevList.append($li);
|
|
|
+ }
|
|
|
+
|
|
|
+ lastLevel = currentLevel;
|
|
|
+ lastList = currentList;
|
|
|
+
|
|
|
+ $(this).remove();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ $div.find('[data-level][data-list]').removeAttr('data-level data-list');
|
|
|
+ html = $div.html();
|
|
|
+
|
|
|
+ // remove ms word's bullet
|
|
|
+ html = html.replace(/·/g, '');
|
|
|
+ html = html.replace(/<p class="Mso(.*?)"/gi, '<p');
|
|
|
+
|
|
|
+ // classes
|
|
|
+ html = html.replace(/ class=\"(mso[^\"]*)\"/gi, "");
|
|
|
+ html = html.replace(/ class=(mso\w+)/gi, "");
|
|
|
+
|
|
|
+ // remove ms word tags
|
|
|
+ html = html.replace(/<o:p(.*?)>([\w\W]*?)<\/o:p>/gi, '$2');
|
|
|
+
|
|
|
+ // ms word break lines
|
|
|
+ html = html.replace(/\n/g, ' ');
|
|
|
+
|
|
|
+ // ms word lists break lines
|
|
|
+ html = html.replace(/<p>\n?<li>/gi, '<li>');
|
|
|
+ }
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ onPasteExtra: function(html)
|
|
|
+ {
|
|
|
+ // remove google docs markers
|
|
|
+ html = html.replace(/<b\sid="internal-source-marker(.*?)">([\w\W]*?)<\/b>/gi, "$2");
|
|
|
+ html = html.replace(/<b(.*?)id="docs-internal-guid(.*?)">([\w\W]*?)<\/b>/gi, "$3");
|
|
|
+
|
|
|
+ // google docs styles
|
|
|
+ html = html.replace(/<span[^>]*(font-style: italic; font-weight: bold|font-weight: bold; font-style: italic)[^>]*>/gi, '<span style="font-weight: bold;"><span style="font-style: italic;">');
|
|
|
+ html = html.replace(/<span[^>]*font-style: italic[^>]*>/gi, '<span style="font-style: italic;">');
|
|
|
+ html = html.replace(/<span[^>]*font-weight: bold[^>]*>/gi, '<span style="font-weight: bold;">');
|
|
|
+ html = html.replace(/<span[^>]*text-decoration: underline[^>]*>/gi, '<span style="text-decoration: underline;">');
|
|
|
+
|
|
|
+ html = html.replace(/<img>/gi, '');
|
|
|
+ html = html.replace(/\n{3,}/gi, '\n');
|
|
|
+ html = html.replace(/<font(.*?)>([\w\W]*?)<\/font>/gi, '$2');
|
|
|
+
|
|
|
+ // remove dirty p
|
|
|
+ html = html.replace(/<p><p>/gi, '<p>');
|
|
|
+ html = html.replace(/<\/p><\/p>/gi, '</p>');
|
|
|
+ html = html.replace(/<li>(\s*|\t*|\n*)<p>/gi, '<li>');
|
|
|
+ html = html.replace(/<\/p>(\s*|\t*|\n*)<\/li>/gi, '</li>');
|
|
|
+
|
|
|
+ // remove space between paragraphs
|
|
|
+ html = html.replace(/<\/p>\s<p/gi, '<\/p><p');
|
|
|
+
|
|
|
+ // remove safari local images
|
|
|
+ html = html.replace(/<img src="webkit-fake-url\:\/\/(.*?)"(.*?)>/gi, '');
|
|
|
+
|
|
|
+ // bullets
|
|
|
+ html = html.replace(/<p>•([\w\W]*?)<\/p>/gi, '<li>$1</li>');
|
|
|
+
|
|
|
+ // FF fix
|
|
|
+ if (this.utils.browser('mozilla'))
|
|
|
+ {
|
|
|
+ html = html.replace(/<br\s?\/?>$/gi, '');
|
|
|
+ }
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ onPasteTidy: function(html, type)
|
|
|
+ {
|
|
|
+ // remove all tags except these
|
|
|
+ var tags = ['span', 'a', 'pre', 'blockquote', 'small', 'em', 'strong', 'code', 'kbd', 'mark', 'address', 'cite', 'var', 'samp', 'dfn', 'sup', 'sub', 'b', 'i', 'u', 'del',
|
|
|
+ 'ol', 'ul', 'li', 'dl', 'dt', 'dd', 'p', 'br', 'video', 'audio', 'iframe', 'embed', 'param', 'object', 'img', 'table',
|
|
|
+ 'td', 'th', 'tr', 'tbody', 'tfoot', 'thead', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
|
|
|
+ var tagsEmpty = false;
|
|
|
+ var attrAllowed = [
|
|
|
+ ['a', '*'],
|
|
|
+ ['img', ['src', 'alt']],
|
|
|
+ ['span', ['class', 'rel', 'data-verified']],
|
|
|
+ ['iframe', '*'],
|
|
|
+ ['video', '*'],
|
|
|
+ ['audio', '*'],
|
|
|
+ ['embed', '*'],
|
|
|
+ ['object', '*'],
|
|
|
+ ['param', '*'],
|
|
|
+ ['source', '*']
|
|
|
+ ];
|
|
|
+
|
|
|
+ if (type == 'all')
|
|
|
+ {
|
|
|
+ tagsEmpty = ['p', 'span', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
|
|
|
+ attrAllowed = [
|
|
|
+ ['table', 'class'],
|
|
|
+ ['td', ['colspan', 'rowspan']],
|
|
|
+ ['a', '*'],
|
|
|
+ ['img', ['src', 'alt', 'data-redactor-inserted-image']],
|
|
|
+ ['span', ['class', 'rel', 'data-verified']],
|
|
|
+ ['iframe', '*'],
|
|
|
+ ['video', '*'],
|
|
|
+ ['audio', '*'],
|
|
|
+ ['embed', '*'],
|
|
|
+ ['object', '*'],
|
|
|
+ ['param', '*'],
|
|
|
+ ['source', '*']
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ else if (type == 'td')
|
|
|
+ {
|
|
|
+ // remove all tags except these and remove all table tags: tr, td etc
|
|
|
+ tags = ['ul', 'ol', 'li', 'span', 'a', 'small', 'em', 'strong', 'code', 'kbd', 'mark', 'cite', 'var', 'samp', 'dfn', 'sup', 'sub', 'b', 'i', 'u', 'del',
|
|
|
+ 'ol', 'ul', 'li', 'dl', 'dt', 'dd', 'br', 'iframe', 'video', 'audio', 'embed', 'param', 'object', 'img', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
|
|
|
+
|
|
|
+ }
|
|
|
+ else if (type == 'li')
|
|
|
+ {
|
|
|
+ // only inline tags and ul, ol, li
|
|
|
+ tags = ['ul', 'ol', 'li', 'span', 'a', 'small', 'em', 'strong', 'code', 'kbd', 'mark', 'cite', 'var', 'samp', 'dfn', 'sup', 'sub', 'b', 'i', 'u', 'del', 'br',
|
|
|
+ 'iframe', 'video', 'audio', 'embed', 'param', 'object', 'img'];
|
|
|
+ }
|
|
|
+
|
|
|
+ var options = {
|
|
|
+ deniedTags: (this.opts.deniedTags) ? this.opts.deniedTags : false,
|
|
|
+ allowedTags: (this.opts.allowedTags) ? this.opts.allowedTags : tags,
|
|
|
+ removeComments: true,
|
|
|
+ removePhp: true,
|
|
|
+ removeAttr: (this.opts.removeAttr) ? this.opts.removeAttr : false,
|
|
|
+ allowedAttr: (this.opts.allowedAttr) ? this.opts.allowedAttr : attrAllowed,
|
|
|
+ removeEmpty: tagsEmpty
|
|
|
+ };
|
|
|
+
|
|
|
+ return this.tidy.load(html, options);
|
|
|
+ },
|
|
|
+ onPasteRemoveEmpty: function(html)
|
|
|
+ {
|
|
|
+ html = html.replace(/<(p|h[1-6])>(|\s|\n|\t|<br\s?\/?>)<\/(p|h[1-6])>/gi, '');
|
|
|
+
|
|
|
+ // remove br in the end
|
|
|
+ if (!this.opts.linebreaks) html = html.replace(/<br>$/i, '');
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ onPasteRemoveSpans: function(html)
|
|
|
+ {
|
|
|
+ html = html.replace(/<span>(.*?)<\/span>/gi, '$1');
|
|
|
+ html = html.replace(/<span[^>]*>\s| <\/span>/gi, ' ');
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ onPasteIeFixLinks: function(html)
|
|
|
+ {
|
|
|
+ if (!this.utils.browser('msie')) return html;
|
|
|
+
|
|
|
+ var tmp = $.trim(html);
|
|
|
+ if (tmp.search(/^<a(.*?)>(.*?)<\/a>$/i) === 0)
|
|
|
+ {
|
|
|
+ html = html.replace(/^<a(.*?)>(.*?)<\/a>$/i, "$2");
|
|
|
+ }
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ isSingleLine: function(html, setMode)
|
|
|
+ {
|
|
|
+ this.clean.singleLine = false;
|
|
|
+
|
|
|
+ if (!this.utils.isSelectAll() && typeof setMode == 'undefined')
|
|
|
+ {
|
|
|
+ var blocks = this.opts.blockLevelElements.join('|').replace('P|', '').replace('DIV|', '');
|
|
|
+
|
|
|
+ var matchBlocks = html.match(new RegExp('</(' + blocks + ')>', 'gi'));
|
|
|
+ var matchContainers = html.match(/<\/(p|div)>/gi);
|
|
|
+
|
|
|
+ if (!matchBlocks && (matchContainers === null || (matchContainers && matchContainers.length <= 1)))
|
|
|
+ {
|
|
|
+ var matchBR = html.match(/<br\s?\/?>/gi);
|
|
|
+ //var matchIMG = html.match(/<img(.*?[^>])>/gi);
|
|
|
+ if (!matchBR)
|
|
|
+ {
|
|
|
+ this.clean.singleLine = true;
|
|
|
+ html = html.replace(/<\/?(p|div)(.*?)>/gi, '');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ stripTags: function(input, allowed)
|
|
|
+ {
|
|
|
+ allowed = (((allowed || '') + '').toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join('');
|
|
|
+ var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi;
|
|
|
+
|
|
|
+ return input.replace(tags, function ($0, $1) {
|
|
|
+ return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : '';
|
|
|
+ });
|
|
|
+ },
|
|
|
+ savePreCode: function(html)
|
|
|
+ {
|
|
|
+ html = this.clean.savePreFormatting(html);
|
|
|
+ html = this.clean.saveCodeFormatting(html);
|
|
|
+
|
|
|
+ html = this.clean.restoreSelectionMarker(html);
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ savePreFormatting: function(html)
|
|
|
+ {
|
|
|
+ var pre = html.match(/<pre(.*?)>([\w\W]*?)<\/pre>/gi);
|
|
|
+
|
|
|
+ if (pre !== null)
|
|
|
+ {
|
|
|
+ $.each(pre, $.proxy(function(i,s)
|
|
|
+ {
|
|
|
+ var arr = s.match(/<pre(.*?)>([\w\W]*?)<\/pre>/i);
|
|
|
+
|
|
|
+ arr[2] = arr[2].replace(/<br\s?\/?>/g, '\n');
|
|
|
+ arr[2] = arr[2].replace(/ /g, ' ');
|
|
|
+
|
|
|
+ if (this.opts.preSpaces)
|
|
|
+ {
|
|
|
+ arr[2] = arr[2].replace(/\t/g, Array(this.opts.preSpaces + 1).join(' '));
|
|
|
+ }
|
|
|
+
|
|
|
+ arr[2] = this.clean.encodeEntities(arr[2]);
|
|
|
+
|
|
|
+ // $ fix
|
|
|
+ arr[2] = arr[2].replace(/\$/g, '$');
|
|
|
+
|
|
|
+ html = html.replace(s, '<pre' + arr[1] + '>' + arr[2] + '</pre>');
|
|
|
+
|
|
|
+ }, this));
|
|
|
+ }
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ saveCodeFormatting: function(html)
|
|
|
+ {
|
|
|
+ var code = html.match(/<code(.*?)>([\w\W]*?)<\/code>/gi);
|
|
|
+
|
|
|
+ if (code !== null)
|
|
|
+ {
|
|
|
+ $.each(code, $.proxy(function(i,s)
|
|
|
+ {
|
|
|
+ var arr = s.match(/<code(.*?)>([\w\W]*?)<\/code>/i);
|
|
|
+
|
|
|
+ arr[2] = arr[2].replace(/ /g, ' ');
|
|
|
+ arr[2] = this.clean.encodeEntities(arr[2]);
|
|
|
+ arr[2] = arr[2].replace(/\$/g, '$');
|
|
|
+
|
|
|
+ html = html.replace(s, '<code' + arr[1] + '>' + arr[2] + '</code>');
|
|
|
+ }, this));
|
|
|
+ }
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ restoreSelectionMarker: function(html)
|
|
|
+ {
|
|
|
+ html = html.replace(/<span id="selection-marker-([0-9])" class="redactor-selection-marker" data-verified="redactor"><\/span>/g, '<span id="selection-marker-$1" class="redactor-selection-marker" data-verified="redactor"></span>');
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ getTextFromHtml: function(html)
|
|
|
+ {
|
|
|
+ html = html.replace(/<br\s?\/?>|<\/H[1-6]>|<\/p>|<\/div>|<\/li>|<\/td>/gi, '\n');
|
|
|
+
|
|
|
+ var tmp = document.createElement('div');
|
|
|
+ tmp.innerHTML = html;
|
|
|
+ html = tmp.textContent || tmp.innerText;
|
|
|
+
|
|
|
+ return $.trim(html);
|
|
|
+ },
|
|
|
+ getPlainText: function(html, paragraphize)
|
|
|
+ {
|
|
|
+ html = this.clean.getTextFromHtml(html);
|
|
|
+ html = html.replace(/\n\s*\n/g, "\n");
|
|
|
+ html = html.replace(/\n\n/g, "\n");
|
|
|
+ html = html.replace(/\n/g, '<br />');
|
|
|
+
|
|
|
+ if (this.opts.paragraphize && typeof paragraphize == 'undefined' && !this.utils.browser('mozilla'))
|
|
|
+ {
|
|
|
+ html = this.paragraphize.load(html);
|
|
|
+ }
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ getPreCode: function(html)
|
|
|
+ {
|
|
|
+ html = html.replace(/<img(.*?) style="(.*?)"(.*?[^>])>/gi, '<img$1$3>');
|
|
|
+ html = html.replace(/<img(.*?)>/gi, '<img$1>');
|
|
|
+ html = this.clean.getTextFromHtml(html);
|
|
|
+
|
|
|
+ if (this.opts.preSpaces)
|
|
|
+ {
|
|
|
+ html = html.replace(/\t/g, Array(this.opts.preSpaces + 1).join(' '));
|
|
|
+ }
|
|
|
+
|
|
|
+ html = this.clean.encodeEntities(html);
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ getOnlyImages: function(html)
|
|
|
+ {
|
|
|
+ html = html.replace(/<img(.*?)>/gi, '[img$1]');
|
|
|
+
|
|
|
+ // remove all tags
|
|
|
+ html = html.replace(/<([Ss]*?)>/gi, '');
|
|
|
+
|
|
|
+ html = html.replace(/\[img(.*?)\]/gi, '<img$1>');
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ getOnlyLinksAndImages: function(html)
|
|
|
+ {
|
|
|
+ html = html.replace(/<a(.*?)href="(.*?)"(.*?)>([\w\W]*?)<\/a>/gi, '[a href="$2"]$4[/a]');
|
|
|
+ html = html.replace(/<img(.*?)>/gi, '[img$1]');
|
|
|
+
|
|
|
+ // remove all tags
|
|
|
+ html = html.replace(/<(.*?)>/gi, '');
|
|
|
+
|
|
|
+ html = html.replace(/\[a href="(.*?)"\]([\w\W]*?)\[\/a\]/gi, '<a href="$1">$2</a>');
|
|
|
+ html = html.replace(/\[img(.*?)\]/gi, '<img$1>');
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ encodeEntities: function(str)
|
|
|
+ {
|
|
|
+ str = String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
|
+ return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
|
+ },
|
|
|
+ removeDirtyStyles: function(html)
|
|
|
+ {
|
|
|
+ if (this.utils.browser('msie')) return html;
|
|
|
+
|
|
|
+ var div = document.createElement('div');
|
|
|
+ div.innerHTML = html;
|
|
|
+
|
|
|
+ this.clean.clearUnverifiedRemove($(div));
|
|
|
+
|
|
|
+ html = div.innerHTML;
|
|
|
+ $(div).remove();
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ clearUnverified: function()
|
|
|
+ {
|
|
|
+ if (this.utils.browser('msie')) return;
|
|
|
+
|
|
|
+ this.clean.clearUnverifiedRemove(this.$editor);
|
|
|
+
|
|
|
+ var headers = this.$editor.find('h1, h2, h3, h4, h5, h6');
|
|
|
+ headers.find('span').removeAttr('style');
|
|
|
+ headers.find(this.opts.verifiedTags.join(', ')).removeAttr('style');
|
|
|
+
|
|
|
+ this.code.sync();
|
|
|
+ },
|
|
|
+ clearUnverifiedRemove: function($editor)
|
|
|
+ {
|
|
|
+ $editor.find(this.opts.verifiedTags.join(', ')).removeAttr('style');
|
|
|
+ $editor.find('span').not('[data-verified="redactor"]').removeAttr('style');
|
|
|
+
|
|
|
+ $editor.find('span[data-verified="redactor"], img[data-verified="redactor"]').each(function(i, s)
|
|
|
+ {
|
|
|
+ var $s = $(s);
|
|
|
+ $s.attr('style', $s.attr('rel'));
|
|
|
+ });
|
|
|
+
|
|
|
+ },
|
|
|
+ cleanEmptyParagraph: function()
|
|
|
+ {
|
|
|
+
|
|
|
+ },
|
|
|
+ setVerified: function(html)
|
|
|
+ {
|
|
|
+ if (this.utils.browser('msie')) return html;
|
|
|
+
|
|
|
+ html = html.replace(new RegExp('<img(.*?[^>])>', 'gi'), '<img$1 data-verified="redactor">');
|
|
|
+ html = html.replace(new RegExp('<span(.*?[^>])>', 'gi'), '<span$1 data-verified="redactor">');
|
|
|
+
|
|
|
+ var matches = html.match(new RegExp('<(span|img)(.*?)style="(.*?)"(.*?[^>])>', 'gi'));
|
|
|
+
|
|
|
+ if (matches)
|
|
|
+ {
|
|
|
+ var len = matches.length;
|
|
|
+ for (var i = 0; i < len; i++)
|
|
|
+ {
|
|
|
+ try {
|
|
|
+
|
|
|
+ var newTag = matches[i].replace(/style="(.*?)"/i, 'style="$1" rel="$1"');
|
|
|
+ html = html.replace(matches[i], newTag);
|
|
|
+
|
|
|
+ }
|
|
|
+ catch (e) {}
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ convertInline: function(html)
|
|
|
+ {
|
|
|
+ var $div = $('<div />').html(html);
|
|
|
+
|
|
|
+ var tags = this.opts.inlineTags;
|
|
|
+ tags.push('span');
|
|
|
+
|
|
|
+ $div.find(tags.join(',')).each(function()
|
|
|
+ {
|
|
|
+ var $el = $(this);
|
|
|
+ var tag = this.tagName.toLowerCase();
|
|
|
+ $el.attr('data-redactor-tag', tag);
|
|
|
+
|
|
|
+ if (tag == 'span')
|
|
|
+ {
|
|
|
+ if ($el.attr('style')) $el.attr('data-redactor-style', $el.attr('style'));
|
|
|
+ else if ($el.attr('class')) $el.attr('data-redactor-class', $el.attr('class'));
|
|
|
+ }
|
|
|
+
|
|
|
+ });
|
|
|
+
|
|
|
+ html = $div.html();
|
|
|
+ $div.remove();
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ normalizeLists: function()
|
|
|
+ {
|
|
|
+ this.$editor.find('li').each(function(i,s)
|
|
|
+ {
|
|
|
+ var $next = $(s).next();
|
|
|
+ if ($next.length !== 0 && ($next[0].tagName == 'UL' || $next[0].tagName == 'OL'))
|
|
|
+ {
|
|
|
+ $(s).append($next);
|
|
|
+ }
|
|
|
+
|
|
|
+ });
|
|
|
+ },
|
|
|
+ removeSpaces: function(html)
|
|
|
+ {
|
|
|
+ html = html.replace(/\n/g, '');
|
|
|
+ html = html.replace(/[\t]*/g, '');
|
|
|
+ html = html.replace(/\n\s*\n/g, "\n");
|
|
|
+ html = html.replace(/^[\s\n]*/g, ' ');
|
|
|
+ html = html.replace(/[\s\n]*$/g, ' ');
|
|
|
+ html = html.replace( />\s{2,}</g, '> <'); // between inline tags can be only one space
|
|
|
+ html = html.replace(/\n\n/g, "\n");
|
|
|
+ html = html.replace(/\u200B/g, '');
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ replaceDivs: function(html)
|
|
|
+ {
|
|
|
+ if (this.opts.linebreaks)
|
|
|
+ {
|
|
|
+ html = html.replace(/<div><br\s?\/?><\/div>/gi, '<br />');
|
|
|
+ html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '$2<br />');
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '<p$1>$2</p>');
|
|
|
+ }
|
|
|
+
|
|
|
+ html = html.replace(/<div(.*?[^>])>/gi, '');
|
|
|
+ html = html.replace(/<\/div>/gi, '');
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ replaceDivsToBr: function(html)
|
|
|
+ {
|
|
|
+ html = html.replace(/<div\s(.*?)>/gi, '<p>');
|
|
|
+ html = html.replace(/<div><br\s?\/?><\/div>/gi, '<br /><br />');
|
|
|
+ html = html.replace(/<div>([\w\W]*?)<\/div>/gi, '$1<br /><br />');
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ replaceParagraphsToBr: function(html)
|
|
|
+ {
|
|
|
+ html = html.replace(/<p\s(.*?)>/gi, '<p>');
|
|
|
+ html = html.replace(/<p><br\s?\/?><\/p>/gi, '<br />');
|
|
|
+ html = html.replace(/<p>([\w\W]*?)<\/p>/gi, '$1<br /><br />');
|
|
|
+ html = html.replace(/(<br\s?\/?>){1,}\n?<\/blockquote>/gi, '</blockquote>');
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ saveFormTags: function(html)
|
|
|
+ {
|
|
|
+ return html.replace(/<form(.*?)>([\w\W]*?)<\/form>/gi, '<section$1 rel="redactor-form-tag">$2</section>');
|
|
|
+ },
|
|
|
+ restoreFormTags: function(html)
|
|
|
+ {
|
|
|
+ return html.replace(/<section(.*?) rel="redactor-form-tag"(.*?)>([\w\W]*?)<\/section>/gi, '<form$1$2>$3</form>');
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ code: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ set: function(html)
|
|
|
+ {
|
|
|
+ html = $.trim(html.toString());
|
|
|
+
|
|
|
+ // clean
|
|
|
+ html = this.clean.onSet(html);
|
|
|
+
|
|
|
+
|
|
|
+ if (this.utils.browser('msie'))
|
|
|
+ {
|
|
|
+ html = html.replace(/<span(.*?)id="selection-marker-(1|2)"(.*?)><\/span>/gi, '');
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$editor.html(html);
|
|
|
+ this.code.sync();
|
|
|
+
|
|
|
+ if (html !== '') this.placeholder.remove();
|
|
|
+
|
|
|
+ setTimeout($.proxy(this.buffer.add, this), 15);
|
|
|
+ if (this.start === false) this.observe.load();
|
|
|
+
|
|
|
+ },
|
|
|
+ get: function()
|
|
|
+ {
|
|
|
+ var code = this.$textarea.val();
|
|
|
+
|
|
|
+ if (this.opts.replaceDivs) code = this.clean.replaceDivs(code);
|
|
|
+ if (this.opts.linebreaks) code = this.clean.replaceParagraphsToBr(code);
|
|
|
+
|
|
|
+ // indent code
|
|
|
+ code = this.tabifier.get(code);
|
|
|
+
|
|
|
+ return code;
|
|
|
+ },
|
|
|
+ sync: function()
|
|
|
+ {
|
|
|
+ setTimeout($.proxy(this.code.startSync, this), 10);
|
|
|
+ },
|
|
|
+ startSync: function()
|
|
|
+ {
|
|
|
+ var html = this.$editor.html();
|
|
|
+
|
|
|
+ // is there a need to synchronize
|
|
|
+ if (this.code.syncCode && this.code.syncCode == html || (this.start && html == '' ))
|
|
|
+ {
|
|
|
+ // do not sync
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // save code
|
|
|
+ this.code.syncCode = html;
|
|
|
+
|
|
|
+ // before clean callback
|
|
|
+ html = this.core.setCallback('syncBefore', html);
|
|
|
+
|
|
|
+ // clean
|
|
|
+ html = this.clean.onSync(html);
|
|
|
+
|
|
|
+ // set code
|
|
|
+ this.$textarea.val(html);
|
|
|
+
|
|
|
+ // after sync callback
|
|
|
+ this.core.setCallback('sync', html);
|
|
|
+
|
|
|
+ if (this.start === false)
|
|
|
+ {
|
|
|
+ this.core.setCallback('change', html);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.start = false;
|
|
|
+
|
|
|
+ if (this.autosave.html == false)
|
|
|
+ {
|
|
|
+ this.autosave.html = this.code.get();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.opts.codemirror)
|
|
|
+ {
|
|
|
+ this.$textarea.next('.CodeMirror').each(function(i, el)
|
|
|
+ {
|
|
|
+ el.CodeMirror.setValue(html);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ //autosave
|
|
|
+ this.autosave.onChange();
|
|
|
+ this.autosave.enable();
|
|
|
+ },
|
|
|
+ toggle: function()
|
|
|
+ {
|
|
|
+ if (this.opts.visual)
|
|
|
+ {
|
|
|
+ this.code.showCode();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.code.showVisual();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ showCode: function()
|
|
|
+ {
|
|
|
+ this.selection.save();
|
|
|
+
|
|
|
+ this.code.offset = this.caret.getOffset();
|
|
|
+ var scroll = $(window).scrollTop();
|
|
|
+
|
|
|
+ var width = this.$editor.innerWidth(),
|
|
|
+ height = this.$editor.innerHeight();
|
|
|
+
|
|
|
+ this.$editor.hide();
|
|
|
+
|
|
|
+ var html = this.$textarea.val();
|
|
|
+
|
|
|
+ this.modified = this.clean.removeSpaces(html);
|
|
|
+
|
|
|
+ // indent code
|
|
|
+ html = this.tabifier.get(html);
|
|
|
+
|
|
|
+ // caret position sync
|
|
|
+ var start = 0, end = 0;
|
|
|
+ var $editorDiv = $("<div/>").append($.parseHTML(this.clean.onSync(this.$editor.html()), document, true));
|
|
|
+ var $selectionMarkers = $editorDiv.find("span.redactor-selection-marker");
|
|
|
+
|
|
|
+ if ($selectionMarkers.length > 0)
|
|
|
+ {
|
|
|
+ var editorHtml = this.tabifier.get($editorDiv.html()).replace(/&/g, '&');
|
|
|
+
|
|
|
+ if ($selectionMarkers.length == 1)
|
|
|
+ {
|
|
|
+ start = this.utils.strpos(editorHtml, $editorDiv.find("#selection-marker-1").prop("outerHTML"));
|
|
|
+ end = start;
|
|
|
+ }
|
|
|
+ else if ($selectionMarkers.length == 2)
|
|
|
+ {
|
|
|
+ start = this.utils.strpos(editorHtml, $editorDiv.find("#selection-marker-1").prop("outerHTML"));
|
|
|
+ end = this.utils.strpos(editorHtml, $editorDiv.find("#selection-marker-2").prop("outerHTML")) - $editorDiv.find("#selection-marker-1").prop("outerHTML").toString().length;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this.selection.removeMarkers();
|
|
|
+ this.$textarea.val(html);
|
|
|
+
|
|
|
+ if (this.opts.codemirror)
|
|
|
+ {
|
|
|
+ this.$textarea.next('.CodeMirror').each(function(i, el)
|
|
|
+ {
|
|
|
+ $(el).show();
|
|
|
+ el.CodeMirror.setValue(html);
|
|
|
+ el.CodeMirror.setSize('100%', height);
|
|
|
+ el.CodeMirror.refresh();
|
|
|
+
|
|
|
+ if (start == end)
|
|
|
+ {
|
|
|
+ el.CodeMirror.setCursor(el.CodeMirror.posFromIndex(start).line, el.CodeMirror.posFromIndex(end).ch);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ el.CodeMirror.setSelection({line: el.CodeMirror.posFromIndex(start).line,
|
|
|
+ ch: el.CodeMirror.posFromIndex(start).ch},
|
|
|
+ {line: el.CodeMirror.posFromIndex(end).line,
|
|
|
+ ch: el.CodeMirror.posFromIndex(end).ch});
|
|
|
+ }
|
|
|
+
|
|
|
+ el.CodeMirror.focus();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.$textarea.height(height).show().focus();
|
|
|
+ this.$textarea.on('keydown.redactor-textarea-indenting', this.code.textareaIndenting);
|
|
|
+
|
|
|
+ $(window).scrollTop(scroll);
|
|
|
+
|
|
|
+ if (this.$textarea[0].setSelectionRange)
|
|
|
+ {
|
|
|
+ this.$textarea[0].setSelectionRange(start, end);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$textarea[0].scrollTop = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.opts.visual = false;
|
|
|
+
|
|
|
+ this.button.setInactiveInCode();
|
|
|
+ this.button.setActive('html');
|
|
|
+ this.core.setCallback('source', html);
|
|
|
+ },
|
|
|
+ showVisual: function()
|
|
|
+ {
|
|
|
+ var html;
|
|
|
+
|
|
|
+ if (this.opts.visual) return;
|
|
|
+
|
|
|
+ var start = 0, end = 0;
|
|
|
+
|
|
|
+ if (this.opts.codemirror)
|
|
|
+ {
|
|
|
+ var selection;
|
|
|
+
|
|
|
+ this.$textarea.next('.CodeMirror').each(function(i, el)
|
|
|
+ {
|
|
|
+ selection = el.CodeMirror.listSelections();
|
|
|
+
|
|
|
+ start = el.CodeMirror.indexFromPos(selection[0].anchor);
|
|
|
+ end = el.CodeMirror.indexFromPos(selection[0].head);
|
|
|
+
|
|
|
+ html = el.CodeMirror.getValue();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ start = this.$textarea.get(0).selectionStart;
|
|
|
+ end = this.$textarea.get(0).selectionEnd;
|
|
|
+
|
|
|
+ html = this.$textarea.hide().val();
|
|
|
+ }
|
|
|
+
|
|
|
+ // if selection starts from end
|
|
|
+ if (start > end && end > 0)
|
|
|
+ {
|
|
|
+ var tempStart = end;
|
|
|
+ var tempEnd = start;
|
|
|
+
|
|
|
+ start = tempStart;
|
|
|
+ end = tempEnd;
|
|
|
+ }
|
|
|
+
|
|
|
+ start = this.code.enlargeOffset(html, start);
|
|
|
+ end = this.code.enlargeOffset(html, end);
|
|
|
+
|
|
|
+ html = html.substr(0, start) + this.selection.getMarkerAsHtml(1) + html.substr(start);
|
|
|
+
|
|
|
+ if (end > start)
|
|
|
+ {
|
|
|
+ var markerLength = this.selection.getMarkerAsHtml(1).toString().length;
|
|
|
+
|
|
|
+ html = html.substr(0, end + markerLength) + this.selection.getMarkerAsHtml(2) + html.substr(end + markerLength);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ if (this.modified !== this.clean.removeSpaces(html))
|
|
|
+ {
|
|
|
+ this.code.set(html);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.opts.codemirror)
|
|
|
+ {
|
|
|
+ this.$textarea.next('.CodeMirror').hide();
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$editor.show();
|
|
|
+
|
|
|
+ if (!this.utils.isEmpty(html))
|
|
|
+ {
|
|
|
+ this.placeholder.remove();
|
|
|
+ }
|
|
|
+
|
|
|
+ this.selection.restore();
|
|
|
+
|
|
|
+ this.$textarea.off('keydown.redactor-textarea-indenting');
|
|
|
+
|
|
|
+ this.button.setActiveInVisual();
|
|
|
+ this.button.setInactive('html');
|
|
|
+ this.observe.load();
|
|
|
+ this.opts.visual = true;
|
|
|
+ this.core.setCallback('visual', html);
|
|
|
+ },
|
|
|
+ textareaIndenting: function(e)
|
|
|
+ {
|
|
|
+ if (e.keyCode !== 9) return true;
|
|
|
+
|
|
|
+ var $el = this.$textarea;
|
|
|
+ var start = $el.get(0).selectionStart;
|
|
|
+ $el.val($el.val().substring(0, start) + "\t" + $el.val().substring($el.get(0).selectionEnd));
|
|
|
+ $el.get(0).selectionStart = $el.get(0).selectionEnd = start + 1;
|
|
|
+
|
|
|
+ return false;
|
|
|
+ },
|
|
|
+ enlargeOffset: function(html, offset)
|
|
|
+ {
|
|
|
+ var htmlLength = html.length;
|
|
|
+ var c = 0;
|
|
|
+
|
|
|
+ if (html[offset] == '>')
|
|
|
+ {
|
|
|
+ c++;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ for(var i = offset; i <= htmlLength; i++)
|
|
|
+ {
|
|
|
+ c++;
|
|
|
+
|
|
|
+ if (html[i] == '>')
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ else if (html[i] == '<' || i == htmlLength)
|
|
|
+ {
|
|
|
+ c = 0;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return offset + c;
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ core: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ getObject: function()
|
|
|
+ {
|
|
|
+ return $.extend({}, this);
|
|
|
+ },
|
|
|
+ getEditor: function()
|
|
|
+ {
|
|
|
+ return this.$editor;
|
|
|
+ },
|
|
|
+ getBox: function()
|
|
|
+ {
|
|
|
+ return this.$box;
|
|
|
+ },
|
|
|
+ getElement: function()
|
|
|
+ {
|
|
|
+ return this.$element;
|
|
|
+ },
|
|
|
+ getTextarea: function()
|
|
|
+ {
|
|
|
+ return this.$textarea;
|
|
|
+ },
|
|
|
+ getToolbar: function()
|
|
|
+ {
|
|
|
+ return (this.$toolbar) ? this.$toolbar : false;
|
|
|
+ },
|
|
|
+ addEvent: function(name)
|
|
|
+ {
|
|
|
+ this.core.event = name;
|
|
|
+ },
|
|
|
+ getEvent: function()
|
|
|
+ {
|
|
|
+ return this.core.event;
|
|
|
+ },
|
|
|
+ setCallback: function(type, e, data)
|
|
|
+ {
|
|
|
+ var eventName = type + 'Callback';
|
|
|
+ var eventNamespace = 'redactor';
|
|
|
+ var callback = this.opts[eventName];
|
|
|
+
|
|
|
+ if (this.$textarea)
|
|
|
+ {
|
|
|
+ var returnValue = false;
|
|
|
+ var events = $._data(this.$textarea[0], 'events');
|
|
|
+
|
|
|
+ if (typeof events != 'undefined' && typeof events[eventName] != 'undefined')
|
|
|
+ {
|
|
|
+ $.each(events[eventName], $.proxy(function(key, value)
|
|
|
+ {
|
|
|
+ if (value['namespace'] == eventNamespace)
|
|
|
+ {
|
|
|
+ var data = (typeof data == 'undefined') ? [e] : [e, data];
|
|
|
+
|
|
|
+ returnValue = (typeof data == 'undefined') ? value.handler.call(this, e) : value.handler.call(this, e, data);
|
|
|
+ }
|
|
|
+ }, this));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (returnValue) return returnValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($.isFunction(callback))
|
|
|
+ {
|
|
|
+ return (typeof data == 'undefined') ? callback.call(this, e) : callback.call(this, e, data);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return (typeof data == 'undefined') ? e : data;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ destroy: function()
|
|
|
+ {
|
|
|
+ this.opts.destroyed = true;
|
|
|
+
|
|
|
+ this.core.setCallback('destroy');
|
|
|
+
|
|
|
+ // off events and remove data
|
|
|
+ this.$element.off('.redactor').removeData('redactor');
|
|
|
+ this.$editor.off('.redactor');
|
|
|
+
|
|
|
+ $(document).off('mousedown.redactor-blur.' + this.uuid);
|
|
|
+ $(document).off('mousedown.redactor.' + this.uuid);
|
|
|
+ $(document).off('click.redactor-image-delete.' + this.uuid);
|
|
|
+ $(document).off('click.redactor-image-resize-hide.' + this.uuid);
|
|
|
+ $(document).off('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid);
|
|
|
+ $("body").off('scroll.redactor.' + this.uuid);
|
|
|
+ $(this.opts.toolbarFixedTarget).off('scroll.redactor.' + this.uuid);
|
|
|
+
|
|
|
+ // common
|
|
|
+ this.$editor.removeClass('redactor-editor redactor-linebreaks redactor-placeholder');
|
|
|
+ this.$editor.removeAttr('contenteditable');
|
|
|
+
|
|
|
+ var html = this.code.get();
|
|
|
+
|
|
|
+ if (this.opts.toolbar)
|
|
|
+ {
|
|
|
+ // dropdowns off
|
|
|
+ this.$toolbar.find('a').each(function()
|
|
|
+ {
|
|
|
+ var $el = $(this);
|
|
|
+ if ($el.data('dropdown'))
|
|
|
+ {
|
|
|
+ $el.data('dropdown').remove();
|
|
|
+ $el.data('dropdown', {});
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.build.isTextarea())
|
|
|
+ {
|
|
|
+ this.$box.after(this.$element);
|
|
|
+ this.$box.remove();
|
|
|
+ this.$element.val(html).show();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.$box.after(this.$editor);
|
|
|
+ this.$box.remove();
|
|
|
+ this.$element.html(html).show();
|
|
|
+ }
|
|
|
+
|
|
|
+ // paste box
|
|
|
+ if (this.$pasteBox) this.$pasteBox.remove();
|
|
|
+
|
|
|
+ // modal
|
|
|
+ if (this.$modalBox) this.$modalBox.remove();
|
|
|
+ if (this.$modalOverlay) this.$modalOverlay.remove();
|
|
|
+
|
|
|
+ // buttons tooltip
|
|
|
+ $('.redactor-toolbar-tooltip-' + this.uuid).remove();
|
|
|
+
|
|
|
+ // autosave
|
|
|
+ clearInterval(this.autosaveInterval);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ dropdown: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ build: function(name, $dropdown, dropdownObject)
|
|
|
+ {
|
|
|
+ if (name == 'formatting' && this.opts.formattingAdd)
|
|
|
+ {
|
|
|
+ $.each(this.opts.formattingAdd, $.proxy(function(i,s)
|
|
|
+ {
|
|
|
+ var name = s.tag,
|
|
|
+ func;
|
|
|
+
|
|
|
+ if (typeof s['class'] != 'undefined')
|
|
|
+ {
|
|
|
+ name = name + '-' + s['class'];
|
|
|
+ }
|
|
|
+
|
|
|
+ s.type = (this.utils.isBlockTag(s.tag)) ? 'block' : 'inline';
|
|
|
+
|
|
|
+ if (typeof s.func !== "undefined")
|
|
|
+ {
|
|
|
+ func = s.func;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ func = (s.type == 'inline') ? 'inline.formatting' : 'block.formatting';
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.opts.linebreaks && s.type == 'block' && s.tag == 'p') return;
|
|
|
+
|
|
|
+ this.formatting[name] = {
|
|
|
+ tag: s.tag,
|
|
|
+ style: s.style,
|
|
|
+ 'class': s['class'],
|
|
|
+ attr: s.attr,
|
|
|
+ data: s.data,
|
|
|
+ clear: s.clear
|
|
|
+ };
|
|
|
+
|
|
|
+ dropdownObject[name] = {
|
|
|
+ func: func,
|
|
|
+ title: s.title
|
|
|
+ };
|
|
|
+
|
|
|
+ }, this));
|
|
|
+ }
|
|
|
+
|
|
|
+ $.each(dropdownObject, $.proxy(function(btnName, btnObject)
|
|
|
+ {
|
|
|
+ var $item = $('<a href="#" class="redactor-dropdown-' + btnName + '" role="button">' + btnObject.title + '</a>');
|
|
|
+ if (name == 'formatting') $item.addClass('redactor-formatting-' + btnName);
|
|
|
+
|
|
|
+ $item.on('click', $.proxy(function(e)
|
|
|
+ {
|
|
|
+ e.preventDefault();
|
|
|
+
|
|
|
+ var type = 'func';
|
|
|
+ var callback = btnObject.func;
|
|
|
+ if (btnObject.command)
|
|
|
+ {
|
|
|
+ type = 'command';
|
|
|
+ callback = btnObject.command;
|
|
|
+ }
|
|
|
+ else if (btnObject.dropdown)
|
|
|
+ {
|
|
|
+ type = 'dropdown';
|
|
|
+ callback = btnObject.dropdown;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($(e.target).hasClass('redactor-dropdown-link-inactive')) return;
|
|
|
+
|
|
|
+ this.button.onClick(e, btnName, type, callback);
|
|
|
+ this.dropdown.hideAll();
|
|
|
+
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ this.observe.addDropdown($item, btnName, btnObject);
|
|
|
+
|
|
|
+ $dropdown.append($item);
|
|
|
+
|
|
|
+ }, this));
|
|
|
+ },
|
|
|
+ show: function(e, key)
|
|
|
+ {
|
|
|
+ if (!this.opts.visual)
|
|
|
+ {
|
|
|
+ e.preventDefault();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ var $button = this.button.get(key);
|
|
|
+
|
|
|
+ // Always re-append it to the end of <body> so it always has the highest sub-z-index.
|
|
|
+ var $dropdown = $button.data('dropdown').appendTo(document.body);
|
|
|
+
|
|
|
+ if (this.opts.highContrast)
|
|
|
+ {
|
|
|
+ $dropdown.addClass("redactor-dropdown-contrast");
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($button.hasClass('dropact'))
|
|
|
+ {
|
|
|
+ this.dropdown.hideAll();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.dropdown.hideAll();
|
|
|
+ this.observe.dropdowns();
|
|
|
+
|
|
|
+ this.core.setCallback('dropdownShow', { dropdown: $dropdown, key: key, button: $button });
|
|
|
+
|
|
|
+ this.button.setActive(key);
|
|
|
+
|
|
|
+ $button.addClass('dropact');
|
|
|
+
|
|
|
+ var keyPosition = $button.offset();
|
|
|
+
|
|
|
+ // fix right placement
|
|
|
+ var dropdownWidth = $dropdown.width();
|
|
|
+ if ((keyPosition.left + dropdownWidth) > $(document).width())
|
|
|
+ {
|
|
|
+ keyPosition.left = Math.max(0, keyPosition.left - dropdownWidth);
|
|
|
+ }
|
|
|
+
|
|
|
+ var left = keyPosition.left + 'px';
|
|
|
+ if (this.$toolbar.hasClass('toolbar-fixed-box'))
|
|
|
+ {
|
|
|
+ var top = this.$toolbar.innerHeight() + this.opts.toolbarFixedTopOffset;
|
|
|
+ var position = 'fixed';
|
|
|
+ if (this.opts.toolbarFixedTarget !== document)
|
|
|
+ {
|
|
|
+ top = (this.$toolbar.innerHeight() + this.$toolbar.offset().top) + this.opts.toolbarFixedTopOffset;
|
|
|
+ position = 'absolute';
|
|
|
+ }
|
|
|
+
|
|
|
+ $dropdown.css({ position: position, left: left, top: top + 'px' }).show();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ var top = ($button.innerHeight() + keyPosition.top) + 'px';
|
|
|
+
|
|
|
+ $dropdown.css({ position: 'absolute', left: left, top: top }).show();
|
|
|
+ }
|
|
|
+
|
|
|
+ this.core.setCallback('dropdownShown', { dropdown: $dropdown, key: key, button: $button });
|
|
|
+
|
|
|
+ this.$dropdown = $dropdown;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ $(document).one('click.redactor-dropdown', $.proxy(this.dropdown.hide, this));
|
|
|
+ this.$editor.one('click.redactor-dropdown', $.proxy(this.dropdown.hide, this));
|
|
|
+ $(document).one('keyup.redactor-dropdown', $.proxy(this.dropdown.closeHandler, this));
|
|
|
+
|
|
|
+ // disable scroll whan dropdown scroll
|
|
|
+ $dropdown.on('mouseover.redactor-dropdown', $.proxy(this.utils.disableBodyScroll, this)).on('mouseout.redactor-dropdown', $.proxy(this.utils.enableBodyScroll, this));
|
|
|
+
|
|
|
+ e.stopPropagation();
|
|
|
+ },
|
|
|
+ closeHandler: function(e)
|
|
|
+ {
|
|
|
+ if (e.which != this.keyCode.ESC) return;
|
|
|
+
|
|
|
+ this.dropdown.hideAll();
|
|
|
+ this.$editor.focus();
|
|
|
+ },
|
|
|
+ hideAll: function()
|
|
|
+ {
|
|
|
+ this.$toolbar.find('a.dropact').removeClass('redactor-act').removeClass('dropact');
|
|
|
+
|
|
|
+ this.utils.enableBodyScroll();
|
|
|
+
|
|
|
+ $('.redactor-dropdown-' + this.uuid).hide();
|
|
|
+ $('.redactor-dropdown-link-selected').removeClass('redactor-dropdown-link-selected');
|
|
|
+
|
|
|
+
|
|
|
+ if (this.$dropdown)
|
|
|
+ {
|
|
|
+ this.$dropdown.off('.redactor-dropdown');
|
|
|
+ this.core.setCallback('dropdownHide', this.$dropdown);
|
|
|
+
|
|
|
+ this.$dropdown = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ hide: function (e)
|
|
|
+ {
|
|
|
+ var $dropdown = $(e.target);
|
|
|
+
|
|
|
+ if (!$dropdown.hasClass('dropact') && !$dropdown.hasClass('redactor-dropdown-link-inactive'))
|
|
|
+ {
|
|
|
+ if ($dropdown.hasClass('redactor-dropdown'))
|
|
|
+ {
|
|
|
+ $dropdown.removeClass('dropact');
|
|
|
+ $dropdown.off('mouseover mouseout');
|
|
|
+ }
|
|
|
+
|
|
|
+ this.dropdown.hideAll();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ file: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ show: function()
|
|
|
+ {
|
|
|
+ this.modal.load('file', this.lang.get('file'), 700);
|
|
|
+ this.upload.init('#redactor-modal-file-upload', this.opts.fileUpload, this.file.insert);
|
|
|
+
|
|
|
+ this.selection.save();
|
|
|
+
|
|
|
+ this.selection.get();
|
|
|
+ var text = this.sel.toString();
|
|
|
+
|
|
|
+ $('#redactor-filename').val(text);
|
|
|
+
|
|
|
+ this.modal.show();
|
|
|
+ },
|
|
|
+ insert: function(json, direct, e)
|
|
|
+ {
|
|
|
+ // error callback
|
|
|
+ if (typeof json.error != 'undefined')
|
|
|
+ {
|
|
|
+ this.modal.close();
|
|
|
+ this.selection.restore();
|
|
|
+ this.core.setCallback('fileUploadError', json);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ var link;
|
|
|
+ if (typeof json == 'string')
|
|
|
+ {
|
|
|
+ link = json;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ var text = $('#redactor-filename').val();
|
|
|
+ if (typeof text == 'undefined' || text === '') text = json.filename;
|
|
|
+
|
|
|
+ link = '<a href="' + json.filelink + '" id="filelink-marker">' + text + '</a>';
|
|
|
+ }
|
|
|
+
|
|
|
+ if (direct)
|
|
|
+ {
|
|
|
+ this.selection.removeMarkers();
|
|
|
+ var marker = this.selection.getMarker();
|
|
|
+ this.insert.nodeToCaretPositionFromPoint(e, marker);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.modal.close();
|
|
|
+ }
|
|
|
+
|
|
|
+ this.selection.restore();
|
|
|
+ this.buffer.set();
|
|
|
+
|
|
|
+ this.insert.htmlWithoutClean(link);
|
|
|
+
|
|
|
+ if (typeof json == 'string') return;
|
|
|
+
|
|
|
+ var linkmarker = $(this.$editor.find('a#filelink-marker'));
|
|
|
+ if (linkmarker.length !== 0)
|
|
|
+ {
|
|
|
+ linkmarker.removeAttr('id').removeAttr('style');
|
|
|
+ }
|
|
|
+ else linkmarker = false;
|
|
|
+
|
|
|
+ this.core.setCallback('fileUpload', linkmarker, json);
|
|
|
+
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ focus: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ setStart: function()
|
|
|
+ {
|
|
|
+ this.$editor.focus();
|
|
|
+
|
|
|
+ var first = this.$editor.children().first();
|
|
|
+
|
|
|
+ if (first.length === 0) return;
|
|
|
+ if (first[0].length === 0 || first[0].tagName == 'BR' || first[0].nodeType == 3)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (first[0].tagName == 'UL' || first[0].tagName == 'OL')
|
|
|
+ {
|
|
|
+ var child = first.find('li').first();
|
|
|
+ if (!this.utils.isBlock(child) && child.text() === '')
|
|
|
+ {
|
|
|
+ // empty inline tag in li
|
|
|
+ this.caret.setStart(child);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.opts.linebreaks && !this.utils.isBlockTag(first[0].tagName))
|
|
|
+ {
|
|
|
+ this.selection.get();
|
|
|
+ this.range.setStart(this.$editor[0], 0);
|
|
|
+ this.range.setEnd(this.$editor[0], 0);
|
|
|
+ this.selection.addRange();
|
|
|
+
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // if node is tag
|
|
|
+ this.caret.setStart(first);
|
|
|
+ },
|
|
|
+ setEnd: function()
|
|
|
+ {
|
|
|
+ var last = this.$editor.children().last();
|
|
|
+ this.$editor.focus();
|
|
|
+
|
|
|
+ if (last.size() === 0) return;
|
|
|
+ if (this.utils.isEmpty(this.$editor.html()))
|
|
|
+ {
|
|
|
+
|
|
|
+ this.selection.get();
|
|
|
+ this.range.collapse(true);
|
|
|
+ this.range.setStartAfter(last[0]);
|
|
|
+ this.range.setEnd(last[0], 0);
|
|
|
+ this.selection.addRange();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.selection.get();
|
|
|
+ this.range.selectNodeContents(last[0]);
|
|
|
+ this.range.collapse(false);
|
|
|
+ this.selection.addRange();
|
|
|
+
|
|
|
+ }
|
|
|
+ },
|
|
|
+ isFocused: function()
|
|
|
+ {
|
|
|
+ return this.$editor[0] === document.activeElement;
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ image: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ show: function()
|
|
|
+ {
|
|
|
+ this.modal.load('image', this.lang.get('image'), 700);
|
|
|
+ this.upload.init('#redactor-modal-image-droparea', this.opts.imageUpload, this.image.insert);
|
|
|
+
|
|
|
+ this.selection.save();
|
|
|
+ this.modal.show();
|
|
|
+
|
|
|
+ },
|
|
|
+ showEdit: function($image)
|
|
|
+ {
|
|
|
+ var $link = $image.closest('a', this.$editor[0]);
|
|
|
+
|
|
|
+ this.modal.load('imageEdit', this.lang.get('edit'), 705);
|
|
|
+
|
|
|
+ this.modal.createCancelButton();
|
|
|
+ this.image.buttonDelete = this.modal.createDeleteButton(this.lang.get('_delete'));
|
|
|
+ this.image.buttonSave = this.modal.createActionButton(this.lang.get('save'));
|
|
|
+
|
|
|
+ this.image.buttonDelete.on('click', $.proxy(function()
|
|
|
+ {
|
|
|
+ this.image.remove($image);
|
|
|
+
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ this.image.buttonSave.on('click', $.proxy(function()
|
|
|
+ {
|
|
|
+ this.image.update($image);
|
|
|
+
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ // hide link's tooltip
|
|
|
+ $('.redactor-link-tooltip').remove();
|
|
|
+
|
|
|
+ $('#redactor-image-title').val($image.attr('alt'));
|
|
|
+
|
|
|
+ if (!this.opts.imageLink) $('.redactor-image-link-option').hide();
|
|
|
+ else
|
|
|
+ {
|
|
|
+ var $redactorImageLink = $('#redactor-image-link');
|
|
|
+
|
|
|
+ $redactorImageLink.attr('href', $image.attr('src'));
|
|
|
+ if ($link.length !== 0)
|
|
|
+ {
|
|
|
+ $redactorImageLink.val($link.attr('href'));
|
|
|
+ if ($link.attr('target') == '_blank') $('#redactor-image-link-blank').prop('checked', true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!this.opts.imagePosition) $('.redactor-image-position-option').hide();
|
|
|
+ else
|
|
|
+ {
|
|
|
+ var floatValue = ($image.css('display') == 'block' && $image.css('float') == 'none') ? 'center' : $image.css('float');
|
|
|
+ $('#redactor-image-align').val(floatValue);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.modal.show();
|
|
|
+ $('#redactor-image-title').focus();
|
|
|
+
|
|
|
+ },
|
|
|
+ setFloating: function($image)
|
|
|
+ {
|
|
|
+ var floating = $('#redactor-image-align').val();
|
|
|
+
|
|
|
+ var imageFloat = '';
|
|
|
+ var imageDisplay = '';
|
|
|
+ var imageMargin = '';
|
|
|
+
|
|
|
+ switch (floating)
|
|
|
+ {
|
|
|
+ case 'left':
|
|
|
+ imageFloat = 'left';
|
|
|
+ imageMargin = '0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin + ' 0';
|
|
|
+ break;
|
|
|
+ case 'right':
|
|
|
+ imageFloat = 'right';
|
|
|
+ imageMargin = '0 0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin;
|
|
|
+ break;
|
|
|
+ case 'center':
|
|
|
+ imageDisplay = 'block';
|
|
|
+ imageMargin = 'auto';
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ $image.css({ 'float': imageFloat, display: imageDisplay, margin: imageMargin });
|
|
|
+ $image.attr('rel', $image.attr('style'));
|
|
|
+ },
|
|
|
+ update: function($image)
|
|
|
+ {
|
|
|
+ this.image.hideResize();
|
|
|
+ this.buffer.set();
|
|
|
+
|
|
|
+ var $link = $image.closest('a', this.$editor[0]);
|
|
|
+
|
|
|
+ var title = $('#redactor-image-title').val().replace(/(<([^>]+)>)/ig,"");
|
|
|
+ $image.attr('alt', title);
|
|
|
+
|
|
|
+ this.image.setFloating($image);
|
|
|
+
|
|
|
+ // as link
|
|
|
+ var link = $.trim($('#redactor-image-link').val());
|
|
|
+ var link = link.replace(/(<([^>]+)>)/ig,"");
|
|
|
+ if (link !== '')
|
|
|
+ {
|
|
|
+ // test url (add protocol)
|
|
|
+ var pattern = '((xn--)?[a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,}';
|
|
|
+ var re = new RegExp('^(http|ftp|https)://' + pattern, 'i');
|
|
|
+ var re2 = new RegExp('^' + pattern, 'i');
|
|
|
+
|
|
|
+ if (link.search(re) == -1 && link.search(re2) === 0 && this.opts.linkProtocol)
|
|
|
+ {
|
|
|
+ link = this.opts.linkProtocol + '://' + link;
|
|
|
+ }
|
|
|
+
|
|
|
+ var target = ($('#redactor-image-link-blank').prop('checked')) ? true : false;
|
|
|
+
|
|
|
+ if ($link.length === 0)
|
|
|
+ {
|
|
|
+ var a = $('<a href="' + link + '">' + this.utils.getOuterHtml($image) + '</a>');
|
|
|
+ if (target) a.attr('target', '_blank');
|
|
|
+
|
|
|
+ $image.replaceWith(a);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ $link.attr('href', link);
|
|
|
+ if (target)
|
|
|
+ {
|
|
|
+ $link.attr('target', '_blank');
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ $link.removeAttr('target');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if ($link.length !== 0)
|
|
|
+ {
|
|
|
+ $link.replaceWith(this.utils.getOuterHtml($image));
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ this.modal.close();
|
|
|
+ this.observe.images();
|
|
|
+ this.code.sync();
|
|
|
+
|
|
|
+
|
|
|
+ },
|
|
|
+ setEditable: function($image)
|
|
|
+ {
|
|
|
+ if (this.opts.imageEditable)
|
|
|
+ {
|
|
|
+ $image.on('dragstart', $.proxy(this.image.onDrag, this));
|
|
|
+ }
|
|
|
+
|
|
|
+ var handler = $.proxy(function(e)
|
|
|
+ {
|
|
|
+
|
|
|
+ this.observe.image = $image;
|
|
|
+
|
|
|
+ this.image.resizer = this.image.loadEditableControls($image);
|
|
|
+
|
|
|
+ $(document).on('mousedown.redactor-image-resize-hide.' + this.uuid, $.proxy(this.image.hideResize, this));
|
|
|
+
|
|
|
+ // resize
|
|
|
+ if (!this.opts.imageResizable) return;
|
|
|
+
|
|
|
+ this.image.resizer.on('mousedown.redactor touchstart.redactor', $.proxy(function(e)
|
|
|
+ {
|
|
|
+ this.image.setResizable(e, $image);
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ }, this);
|
|
|
+
|
|
|
+
|
|
|
+ $image.off('mousedown.redactor').on('mousedown.redactor', $.proxy(this.image.hideResize, this));
|
|
|
+ $image.off('click.redactor touchstart.redactor').on('click.redactor touchstart.redactor', handler);
|
|
|
+ },
|
|
|
+ setResizable: function(e, $image)
|
|
|
+ {
|
|
|
+ e.preventDefault();
|
|
|
+
|
|
|
+ this.image.resizeHandle = {
|
|
|
+ x : e.pageX,
|
|
|
+ y : e.pageY,
|
|
|
+ el : $image,
|
|
|
+ ratio: $image.width() / $image.height(),
|
|
|
+ h: $image.height()
|
|
|
+ };
|
|
|
+
|
|
|
+ e = e.originalEvent || e;
|
|
|
+
|
|
|
+ if (e.targetTouches)
|
|
|
+ {
|
|
|
+ this.image.resizeHandle.x = e.targetTouches[0].pageX;
|
|
|
+ this.image.resizeHandle.y = e.targetTouches[0].pageY;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.image.startResize();
|
|
|
+
|
|
|
+
|
|
|
+ },
|
|
|
+ startResize: function()
|
|
|
+ {
|
|
|
+ $(document).on('mousemove.redactor-image-resize touchmove.redactor-image-resize', $.proxy(this.image.moveResize, this));
|
|
|
+ $(document).on('mouseup.redactor-image-resize touchend.redactor-image-resize', $.proxy(this.image.stopResize, this));
|
|
|
+ },
|
|
|
+ moveResize: function(e)
|
|
|
+ {
|
|
|
+ e.preventDefault();
|
|
|
+
|
|
|
+ e = e.originalEvent || e;
|
|
|
+
|
|
|
+ var height = this.image.resizeHandle.h;
|
|
|
+
|
|
|
+ if (e.targetTouches) height += (e.targetTouches[0].pageY - this.image.resizeHandle.y);
|
|
|
+ else height += (e.pageY - this.image.resizeHandle.y);
|
|
|
+
|
|
|
+ var width = Math.round(height * this.image.resizeHandle.ratio);
|
|
|
+
|
|
|
+ if (height < 50 || width < 100) return;
|
|
|
+
|
|
|
+ var height = Math.round(this.image.resizeHandle.el.width() / this.image.resizeHandle.ratio);
|
|
|
+
|
|
|
+ this.image.resizeHandle.el.attr({width: width, height: height});
|
|
|
+ this.image.resizeHandle.el.width(width);
|
|
|
+ this.image.resizeHandle.el.height(height);
|
|
|
+
|
|
|
+ this.code.sync();
|
|
|
+ },
|
|
|
+ stopResize: function()
|
|
|
+ {
|
|
|
+ this.handle = false;
|
|
|
+ $(document).off('.redactor-image-resize');
|
|
|
+
|
|
|
+ this.image.hideResize();
|
|
|
+ },
|
|
|
+ onDrag: function(e)
|
|
|
+ {
|
|
|
+ if (this.$editor.find('#redactor-image-box').length !== 0)
|
|
|
+ {
|
|
|
+ e.preventDefault();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$editor.on('drop.redactor-image-inside-drop', $.proxy(function()
|
|
|
+ {
|
|
|
+ setTimeout($.proxy(this.image.onDrop, this), 1);
|
|
|
+
|
|
|
+ }, this));
|
|
|
+ },
|
|
|
+ onDrop: function()
|
|
|
+ {
|
|
|
+ this.image.fixImageSourceAfterDrop();
|
|
|
+ this.observe.images();
|
|
|
+ this.$editor.off('drop.redactor-image-inside-drop');
|
|
|
+ this.clean.clearUnverified();
|
|
|
+ this.code.sync();
|
|
|
+ },
|
|
|
+ fixImageSourceAfterDrop: function()
|
|
|
+ {
|
|
|
+ this.$editor.find('img[data-save-url]').each(function()
|
|
|
+ {
|
|
|
+ var $el = $(this);
|
|
|
+ $el.attr('src', $el.attr('data-save-url'));
|
|
|
+ $el.removeAttr('data-save-url');
|
|
|
+ });
|
|
|
+ },
|
|
|
+ hideResize: function(e)
|
|
|
+ {
|
|
|
+ if (e && $(e.target).closest('#redactor-image-box', this.$editor[0]).length !== 0) return;
|
|
|
+ if (e && e.target.tagName == 'IMG')
|
|
|
+ {
|
|
|
+ var $image = $(e.target);
|
|
|
+ $image.attr('data-save-url', $image.attr('src'));
|
|
|
+ }
|
|
|
+
|
|
|
+ var imageBox = this.$editor.find('#redactor-image-box');
|
|
|
+ if (imageBox.length === 0) return;
|
|
|
+
|
|
|
+ $('#redactor-image-editter').remove();
|
|
|
+ $('#redactor-image-resizer').remove();
|
|
|
+
|
|
|
+ imageBox.find('img').css({
|
|
|
+ marginTop: imageBox[0].style.marginTop,
|
|
|
+ marginBottom: imageBox[0].style.marginBottom,
|
|
|
+ marginLeft: imageBox[0].style.marginLeft,
|
|
|
+ marginRight: imageBox[0].style.marginRight
|
|
|
+ });
|
|
|
+
|
|
|
+ imageBox.css('margin', '');
|
|
|
+ imageBox.find('img').css('opacity', '');
|
|
|
+ imageBox.replaceWith(function()
|
|
|
+ {
|
|
|
+ return $(this).contents();
|
|
|
+ });
|
|
|
+
|
|
|
+ $(document).off('mousedown.redactor-image-resize-hide.' + this.uuid);
|
|
|
+
|
|
|
+
|
|
|
+ if (typeof this.image.resizeHandle !== 'undefined')
|
|
|
+ {
|
|
|
+ this.image.resizeHandle.el.attr('rel', this.image.resizeHandle.el.attr('style'));
|
|
|
+ }
|
|
|
+
|
|
|
+ this.code.sync();
|
|
|
+
|
|
|
+ },
|
|
|
+ loadResizableControls: function($image, imageBox)
|
|
|
+ {
|
|
|
+ if (this.opts.imageResizable && !this.utils.isMobile())
|
|
|
+ {
|
|
|
+ var imageResizer = $('<span id="redactor-image-resizer" data-redactor="verified"></span>');
|
|
|
+
|
|
|
+ if (!this.utils.isDesktop())
|
|
|
+ {
|
|
|
+ imageResizer.css({ width: '15px', height: '15px' });
|
|
|
+ }
|
|
|
+
|
|
|
+ imageResizer.attr('contenteditable', false);
|
|
|
+ imageBox.append(imageResizer);
|
|
|
+ imageBox.append($image);
|
|
|
+
|
|
|
+ return imageResizer;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ imageBox.append($image);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ loadEditableControls: function($image)
|
|
|
+ {
|
|
|
+ var imageBox = $('<span id="redactor-image-box" data-redactor="verified">');
|
|
|
+ imageBox.css('float', $image.css('float')).attr('contenteditable', false);
|
|
|
+
|
|
|
+ if ($image[0].style.margin != 'auto')
|
|
|
+ {
|
|
|
+ imageBox.css({
|
|
|
+ marginTop: $image[0].style.marginTop,
|
|
|
+ marginBottom: $image[0].style.marginBottom,
|
|
|
+ marginLeft: $image[0].style.marginLeft,
|
|
|
+ marginRight: $image[0].style.marginRight
|
|
|
+ });
|
|
|
+
|
|
|
+ $image.css('margin', '');
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ imageBox.css({ 'display': 'block', 'margin': 'auto' });
|
|
|
+ }
|
|
|
+
|
|
|
+ $image.css('opacity', '.5').after(imageBox);
|
|
|
+
|
|
|
+
|
|
|
+ if (this.opts.imageEditable)
|
|
|
+ {
|
|
|
+ // editter
|
|
|
+ this.image.editter = $('<span id="redactor-image-editter" data-redactor="verified">' + this.lang.get('edit') + '</span>');
|
|
|
+ this.image.editter.attr('contenteditable', false);
|
|
|
+ this.image.editter.on('click', $.proxy(function()
|
|
|
+ {
|
|
|
+ this.image.showEdit($image);
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ imageBox.append(this.image.editter);
|
|
|
+
|
|
|
+ // position correction
|
|
|
+ var editerWidth = this.image.editter.innerWidth();
|
|
|
+ this.image.editter.css('margin-left', '-' + editerWidth/2 + 'px');
|
|
|
+ }
|
|
|
+
|
|
|
+ return this.image.loadResizableControls($image, imageBox);
|
|
|
+
|
|
|
+ },
|
|
|
+ remove: function(image)
|
|
|
+ {
|
|
|
+ var $image = $(image);
|
|
|
+ var $link = $image.closest('a', this.$editor[0]);
|
|
|
+ var $figure = $image.closest('figure', this.$editor[0]);
|
|
|
+ var $parent = $image.parent();
|
|
|
+ if ($('#redactor-image-box').length !== 0)
|
|
|
+ {
|
|
|
+ $parent = $('#redactor-image-box').parent();
|
|
|
+ }
|
|
|
+
|
|
|
+ var $next;
|
|
|
+ if ($figure.length !== 0)
|
|
|
+ {
|
|
|
+ $next = $figure.next();
|
|
|
+ $figure.remove();
|
|
|
+ }
|
|
|
+ else if ($link.length !== 0)
|
|
|
+ {
|
|
|
+ $parent = $link.parent();
|
|
|
+ $link.remove();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ $image.remove();
|
|
|
+ }
|
|
|
+
|
|
|
+ $('#redactor-image-box').remove();
|
|
|
+
|
|
|
+ if ($figure.length !== 0)
|
|
|
+ {
|
|
|
+ this.caret.setStart($next);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.caret.setStart($parent);
|
|
|
+ }
|
|
|
+
|
|
|
+ // delete callback
|
|
|
+ this.core.setCallback('imageDelete', $image[0].src, $image);
|
|
|
+
|
|
|
+ this.modal.close();
|
|
|
+ this.code.sync();
|
|
|
+ },
|
|
|
+ insert: function(json, direct, e)
|
|
|
+ {
|
|
|
+ // error callback
|
|
|
+ if (typeof json.error != 'undefined')
|
|
|
+ {
|
|
|
+ this.modal.close();
|
|
|
+ this.selection.restore();
|
|
|
+ this.core.setCallback('imageUploadError', json);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ var $img;
|
|
|
+ if (typeof json == 'string')
|
|
|
+ {
|
|
|
+ $img = $(json).attr('data-redactor-inserted-image', 'true');
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ $img = $('<img>');
|
|
|
+ $img.attr('src', json.filelink).attr('data-redactor-inserted-image', 'true');
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ var node = $img;
|
|
|
+ var isP = this.utils.isCurrentOrParent('P');
|
|
|
+ if (isP)
|
|
|
+ {
|
|
|
+ // will replace
|
|
|
+ node = $('<blockquote />').append($img);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (direct)
|
|
|
+ {
|
|
|
+ this.selection.removeMarkers();
|
|
|
+ var marker = this.selection.getMarker();
|
|
|
+ this.insert.nodeToCaretPositionFromPoint(e, marker);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.modal.close();
|
|
|
+ }
|
|
|
+
|
|
|
+ this.selection.restore();
|
|
|
+ this.buffer.set();
|
|
|
+
|
|
|
+ this.insert.html(this.utils.getOuterHtml(node), false);
|
|
|
+
|
|
|
+ var $image = this.$editor.find('img[data-redactor-inserted-image=true]').removeAttr('data-redactor-inserted-image');
|
|
|
+
|
|
|
+ if (isP)
|
|
|
+ {
|
|
|
+ $image.parent().contents().unwrap().wrap('<p />');
|
|
|
+ }
|
|
|
+ else if (this.opts.linebreaks)
|
|
|
+ {
|
|
|
+ if (!this.utils.isEmpty(this.code.get()))
|
|
|
+ {
|
|
|
+ $image.before('<br>');
|
|
|
+ }
|
|
|
+
|
|
|
+ $image.after('<br>');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (typeof json == 'string') return;
|
|
|
+
|
|
|
+ this.core.setCallback('imageUpload', $image, json);
|
|
|
+
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ indent: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ increase: function()
|
|
|
+ {
|
|
|
+ // focus
|
|
|
+ if (!this.utils.browser('msie')) this.$editor.focus();
|
|
|
+
|
|
|
+ this.buffer.set();
|
|
|
+ this.selection.save();
|
|
|
+
|
|
|
+ var block = this.selection.getBlock();
|
|
|
+
|
|
|
+ if (block && block.tagName == 'LI')
|
|
|
+ {
|
|
|
+ this.indent.increaseLists();
|
|
|
+ }
|
|
|
+ else if (block === false && this.opts.linebreaks)
|
|
|
+ {
|
|
|
+ this.indent.increaseText();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.indent.increaseBlocks();
|
|
|
+ }
|
|
|
+
|
|
|
+ this.selection.restore();
|
|
|
+ this.code.sync();
|
|
|
+ },
|
|
|
+ increaseLists: function()
|
|
|
+ {
|
|
|
+ document.execCommand('indent');
|
|
|
+
|
|
|
+ this.indent.fixEmptyIndent();
|
|
|
+ this.clean.normalizeLists();
|
|
|
+ this.clean.clearUnverified();
|
|
|
+ },
|
|
|
+ increaseBlocks: function()
|
|
|
+ {
|
|
|
+ $.each(this.selection.getBlocks(), $.proxy(function(i, elem)
|
|
|
+ {
|
|
|
+ if (elem.tagName === 'TD' || elem.tagName === 'TH') return;
|
|
|
+
|
|
|
+ var $el = this.utils.getAlignmentElement(elem);
|
|
|
+
|
|
|
+ var left = this.utils.normalize($el.css('margin-left')) + this.opts.indentValue;
|
|
|
+ $el.css('margin-left', left + 'px');
|
|
|
+
|
|
|
+ }, this));
|
|
|
+ },
|
|
|
+ increaseText: function()
|
|
|
+ {
|
|
|
+ var wrapper = this.selection.wrap('div');
|
|
|
+ $(wrapper).attr('data-tagblock', 'redactor');
|
|
|
+ $(wrapper).css('margin-left', this.opts.indentValue + 'px');
|
|
|
+ },
|
|
|
+ decrease: function()
|
|
|
+ {
|
|
|
+ this.buffer.set();
|
|
|
+ this.selection.save();
|
|
|
+
|
|
|
+ var block = this.selection.getBlock();
|
|
|
+ if (block && block.tagName == 'LI')
|
|
|
+ {
|
|
|
+ this.indent.decreaseLists();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.indent.decreaseBlocks();
|
|
|
+ }
|
|
|
+
|
|
|
+ this.selection.restore();
|
|
|
+ this.code.sync();
|
|
|
+ },
|
|
|
+ decreaseLists: function()
|
|
|
+ {
|
|
|
+ document.execCommand('outdent');
|
|
|
+
|
|
|
+ var current = this.selection.getCurrent();
|
|
|
+ var $item = $(current).closest('li', this.$editor[0]);
|
|
|
+
|
|
|
+ this.indent.fixEmptyIndent();
|
|
|
+
|
|
|
+ if (!this.opts.linebreaks && $item.length === 0)
|
|
|
+ {
|
|
|
+ document.execCommand('formatblock', false, 'p');
|
|
|
+ this.$editor.find('ul, ol, blockquote, p').each($.proxy(this.utils.removeEmpty, this));
|
|
|
+ }
|
|
|
+
|
|
|
+ this.clean.clearUnverified();
|
|
|
+ },
|
|
|
+ decreaseBlocks: function()
|
|
|
+ {
|
|
|
+ $.each(this.selection.getBlocks(), $.proxy(function(i, elem)
|
|
|
+ {
|
|
|
+ var $el = this.utils.getAlignmentElement(elem);
|
|
|
+ var left = this.utils.normalize($el.css('margin-left')) - this.opts.indentValue;
|
|
|
+
|
|
|
+ if (left <= 0)
|
|
|
+ {
|
|
|
+ if (this.opts.linebreaks && typeof($el.data('tagblock')) !== 'undefined')
|
|
|
+ {
|
|
|
+ $el.replaceWith($el.html() + '<br />');
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ $el.css('margin-left', '');
|
|
|
+ this.utils.removeEmptyAttr($el, 'style');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ $el.css('margin-left', left + 'px');
|
|
|
+ }
|
|
|
+
|
|
|
+ }, this));
|
|
|
+ },
|
|
|
+ fixEmptyIndent: function()
|
|
|
+ {
|
|
|
+ var block = this.selection.getBlock();
|
|
|
+
|
|
|
+ if (this.range.collapsed && block && block.tagName == 'LI' && this.utils.isEmpty($(block).text()))
|
|
|
+ {
|
|
|
+ var $block = $(block);
|
|
|
+ $block.find('span').not('.redactor-selection-marker').contents().unwrap();
|
|
|
+ $block.append('<br>');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ inline: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ formatting: function(name)
|
|
|
+ {
|
|
|
+ var type, value;
|
|
|
+
|
|
|
+ if (typeof this.formatting[name].style != 'undefined') type = 'style';
|
|
|
+ else if (typeof this.formatting[name]['class'] != 'undefined') type = 'class';
|
|
|
+
|
|
|
+ if (type) value = this.formatting[name][type];
|
|
|
+
|
|
|
+ this.inline.format(this.formatting[name].tag, type, value);
|
|
|
+
|
|
|
+ },
|
|
|
+ format: function(tag, type, value)
|
|
|
+ {
|
|
|
+ var current = this.selection.getCurrent();
|
|
|
+ if (current && current.tagName === 'TR') return;
|
|
|
+
|
|
|
+ // Stop formatting pre and headers
|
|
|
+ if (this.utils.isCurrentOrParent('PRE') || this.utils.isCurrentOrParentHeader()) return;
|
|
|
+
|
|
|
+ var tags = ['b', 'bold', 'i', 'italic', 'underline', 'strikethrough', 'deleted', 'superscript', 'subscript'];
|
|
|
+ var replaced = ['strong', 'strong', 'em', 'em', 'u', 'del', 'del', 'sup', 'sub'];
|
|
|
+
|
|
|
+ for (var i = 0; i < tags.length; i++)
|
|
|
+ {
|
|
|
+ if (tag == tags[i]) tag = replaced[i];
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if (this.opts.allowedTags)
|
|
|
+ {
|
|
|
+ if ($.inArray(tag, this.opts.allowedTags) == -1) return;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if ($.inArray(tag, this.opts.deniedTags) !== -1) return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.inline.type = type || false;
|
|
|
+ this.inline.value = value || false;
|
|
|
+
|
|
|
+ this.buffer.set();
|
|
|
+
|
|
|
+ if (!this.utils.browser('msie') && !this.opts.linebreaks)
|
|
|
+ {
|
|
|
+ this.$editor.focus();
|
|
|
+ }
|
|
|
+
|
|
|
+ this.selection.get();
|
|
|
+
|
|
|
+ if (this.range.collapsed)
|
|
|
+ {
|
|
|
+ this.inline.formatCollapsed(tag);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.inline.formatMultiple(tag);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ formatCollapsed: function(tag)
|
|
|
+ {
|
|
|
+ var current = this.selection.getCurrent();
|
|
|
+ var $parent = $(current).closest(tag + '[data-redactor-tag=' + tag + ']', this.$editor[0]);
|
|
|
+
|
|
|
+ // inline there is
|
|
|
+ if ($parent.length !== 0 && (this.inline.type != 'style' && $parent[0].tagName != 'SPAN'))
|
|
|
+ {
|
|
|
+ // remove empty
|
|
|
+ if (this.utils.isEmpty($parent.text()))
|
|
|
+ {
|
|
|
+ this.caret.setAfter($parent[0]);
|
|
|
+
|
|
|
+ $parent.remove();
|
|
|
+ this.code.sync();
|
|
|
+ }
|
|
|
+ else if (this.utils.isEndOfElement($parent))
|
|
|
+ {
|
|
|
+ this.caret.setAfter($parent[0]);
|
|
|
+ }
|
|
|
+
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // create empty inline
|
|
|
+ var node = $('<' + tag + '>').attr('data-verified', 'redactor').attr('data-redactor-tag', tag);
|
|
|
+ node.html(this.opts.invisibleSpace);
|
|
|
+
|
|
|
+ node = this.inline.setFormat(node);
|
|
|
+
|
|
|
+ var node = this.insert.node(node);
|
|
|
+ this.caret.setEnd(node);
|
|
|
+
|
|
|
+ this.code.sync();
|
|
|
+ },
|
|
|
+ formatMultiple: function(tag)
|
|
|
+ {
|
|
|
+ this.inline.formatConvert(tag);
|
|
|
+
|
|
|
+ this.selection.save();
|
|
|
+ document.execCommand('strikethrough');
|
|
|
+
|
|
|
+ this.$editor.find('strike').each($.proxy(function(i,s)
|
|
|
+ {
|
|
|
+ var $el = $(s);
|
|
|
+
|
|
|
+ this.inline.formatRemoveSameChildren($el, tag);
|
|
|
+
|
|
|
+ var $span;
|
|
|
+ if (this.inline.type)
|
|
|
+ {
|
|
|
+ $span = $('<span>').attr('data-redactor-tag', tag).attr('data-verified', 'redactor');
|
|
|
+ $span = this.inline.setFormat($span);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ $span = $('<' + tag + '>').attr('data-redactor-tag', tag).attr('data-verified', 'redactor');
|
|
|
+ }
|
|
|
+
|
|
|
+ $el.replaceWith($span.html($el.contents()));
|
|
|
+ var $parent = $span.parent();
|
|
|
+
|
|
|
+ // remove U tag if selected link + node
|
|
|
+ if ($span[0].tagName === 'A' && $parent && $parent[0].tagName === 'U')
|
|
|
+ {
|
|
|
+ $span.parent().replaceWith($span);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (tag == 'span')
|
|
|
+ {
|
|
|
+ if ($parent && $parent[0].tagName === 'SPAN' && this.inline.type === 'style')
|
|
|
+ {
|
|
|
+ var arr = this.inline.value.split(';');
|
|
|
+
|
|
|
+ for (var z = 0; z < arr.length; z++)
|
|
|
+ {
|
|
|
+ if (arr[z] === '') return;
|
|
|
+ var style = arr[z].split(':');
|
|
|
+ $parent.css(style[0], '');
|
|
|
+
|
|
|
+ if (this.utils.removeEmptyAttr($parent, 'style'))
|
|
|
+ {
|
|
|
+ $parent.replaceWith($parent.contents());
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ // clear text decoration
|
|
|
+ if (tag != 'span')
|
|
|
+ {
|
|
|
+ this.$editor.find(this.opts.inlineTags.join(', ')).each($.proxy(function(i,s)
|
|
|
+ {
|
|
|
+ var $el = $(s);
|
|
|
+
|
|
|
+
|
|
|
+ if (s.tagName === 'U' && s.attributes.length === 0)
|
|
|
+ {
|
|
|
+ $el.replaceWith($el.contents());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ var property = $el.css('text-decoration');
|
|
|
+ if (property === 'line-through')
|
|
|
+ {
|
|
|
+ $el.css('text-decoration', '');
|
|
|
+ this.utils.removeEmptyAttr($el, 'style');
|
|
|
+ }
|
|
|
+ }, this));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (tag != 'del')
|
|
|
+ {
|
|
|
+ var _this = this;
|
|
|
+ this.$editor.find('inline').each(function(i,s)
|
|
|
+ {
|
|
|
+ _this.utils.replaceToTag(s, 'del');
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ if (tag != 'u')
|
|
|
+ {
|
|
|
+ var _this = this;
|
|
|
+ this.$editor.find('unline').each(function(i,s)
|
|
|
+ {
|
|
|
+ _this.utils.replaceToTag(s, 'u');
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ this.selection.restore();
|
|
|
+ this.code.sync();
|
|
|
+
|
|
|
+ },
|
|
|
+ formatRemoveSameChildren: function($el, tag)
|
|
|
+ {
|
|
|
+ var self = this;
|
|
|
+ $el.children(tag).each(function()
|
|
|
+ {
|
|
|
+ var $child = $(this);
|
|
|
+
|
|
|
+ if (!$child.hasClass('redactor-selection-marker'))
|
|
|
+ {
|
|
|
+ if (self.inline.type == 'style')
|
|
|
+ {
|
|
|
+ var arr = self.inline.value.split(';');
|
|
|
+
|
|
|
+ for (var z = 0; z < arr.length; z++)
|
|
|
+ {
|
|
|
+ if (arr[z] === '') return;
|
|
|
+
|
|
|
+ var style = arr[z].split(':');
|
|
|
+ $child.css(style[0], '');
|
|
|
+
|
|
|
+ if (self.utils.removeEmptyAttr($child , 'style'))
|
|
|
+ {
|
|
|
+ $child.replaceWith($child.contents());
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ $child.contents().unwrap();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ });
|
|
|
+ },
|
|
|
+ formatConvert: function(tag)
|
|
|
+ {
|
|
|
+ this.selection.save();
|
|
|
+
|
|
|
+ var find = '';
|
|
|
+ if (this.inline.type == 'class') find = '[data-redactor-class=' + this.inline.value + ']';
|
|
|
+ else if (this.inline.type == 'style')
|
|
|
+ {
|
|
|
+ find = '[data-redactor-style="' + this.inline.value + '"]';
|
|
|
+ }
|
|
|
+
|
|
|
+ var self = this;
|
|
|
+ if (tag != 'del')
|
|
|
+ {
|
|
|
+ this.$editor.find('del').each(function(i,s)
|
|
|
+ {
|
|
|
+ self.utils.replaceToTag(s, 'inline');
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ if (tag != 'u')
|
|
|
+ {
|
|
|
+ this.$editor.find('u').each(function(i,s)
|
|
|
+ {
|
|
|
+ self.utils.replaceToTag(s, 'unline');
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ if (tag != 'span')
|
|
|
+ {
|
|
|
+ this.$editor.find(tag).each(function()
|
|
|
+ {
|
|
|
+ var $el = $(this);
|
|
|
+ $el.replaceWith($('<strike />').html($el.contents()));
|
|
|
+
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$editor.find('[data-redactor-tag="' + tag + '"]' + find).each(function()
|
|
|
+ {
|
|
|
+ if (find === '' && tag == 'span' && this.tagName.toLowerCase() == tag) return;
|
|
|
+
|
|
|
+ var $el = $(this);
|
|
|
+ $el.replaceWith($('<strike />').html($el.contents()));
|
|
|
+
|
|
|
+ });
|
|
|
+
|
|
|
+ this.selection.restore();
|
|
|
+ },
|
|
|
+ setFormat: function(node)
|
|
|
+ {
|
|
|
+ switch (this.inline.type)
|
|
|
+ {
|
|
|
+ case 'class':
|
|
|
+
|
|
|
+ if (node.hasClass(this.inline.value))
|
|
|
+ {
|
|
|
+ node.removeClass(this.inline.value);
|
|
|
+ node.removeAttr('data-redactor-class');
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ node.addClass(this.inline.value);
|
|
|
+ node.attr('data-redactor-class', this.inline.value);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ break;
|
|
|
+ case 'style':
|
|
|
+
|
|
|
+ node[0].style.cssText = this.inline.value;
|
|
|
+ node.attr('data-redactor-style', this.inline.value);
|
|
|
+
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ return node;
|
|
|
+ },
|
|
|
+ removeStyle: function()
|
|
|
+ {
|
|
|
+ this.buffer.set();
|
|
|
+ var current = this.selection.getCurrent();
|
|
|
+ var nodes = this.selection.getInlines();
|
|
|
+
|
|
|
+ this.selection.save();
|
|
|
+
|
|
|
+ if (current && current.tagName === 'SPAN')
|
|
|
+ {
|
|
|
+ var $s = $(current);
|
|
|
+
|
|
|
+ $s.removeAttr('style');
|
|
|
+ if ($s[0].attributes.length === 0)
|
|
|
+ {
|
|
|
+ $s.replaceWith($s.contents());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $.each(nodes, $.proxy(function(i,s)
|
|
|
+ {
|
|
|
+ var $s = $(s);
|
|
|
+ if ($.inArray(s.tagName.toLowerCase(), this.opts.inlineTags) != -1 && !$s.hasClass('redactor-selection-marker'))
|
|
|
+ {
|
|
|
+ $s.removeAttr('style');
|
|
|
+ if ($s[0].attributes.length === 0)
|
|
|
+ {
|
|
|
+ $s.replaceWith($s.contents());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ this.selection.restore();
|
|
|
+ this.code.sync();
|
|
|
+
|
|
|
+ },
|
|
|
+ removeStyleRule: function(name)
|
|
|
+ {
|
|
|
+ this.buffer.set();
|
|
|
+ var parent = this.selection.getParent();
|
|
|
+ var nodes = this.selection.getInlines();
|
|
|
+
|
|
|
+ this.selection.save();
|
|
|
+
|
|
|
+ if (parent && parent.tagName === 'SPAN')
|
|
|
+ {
|
|
|
+ var $s = $(parent);
|
|
|
+
|
|
|
+ $s.css(name, '');
|
|
|
+ this.utils.removeEmptyAttr($s, 'style');
|
|
|
+ if ($s[0].attributes.length === 0)
|
|
|
+ {
|
|
|
+ $s.replaceWith($s.contents());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $.each(nodes, $.proxy(function(i,s)
|
|
|
+ {
|
|
|
+ var $s = $(s);
|
|
|
+ if ($.inArray(s.tagName.toLowerCase(), this.opts.inlineTags) != -1 && !$s.hasClass('redactor-selection-marker'))
|
|
|
+ {
|
|
|
+ $s.css(name, '');
|
|
|
+ this.utils.removeEmptyAttr($s, 'style');
|
|
|
+ if ($s[0].attributes.length === 0)
|
|
|
+ {
|
|
|
+ $s.replaceWith($s.contents());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ this.selection.restore();
|
|
|
+ this.code.sync();
|
|
|
+ },
|
|
|
+ removeFormat: function()
|
|
|
+ {
|
|
|
+ this.buffer.set();
|
|
|
+ var current = this.selection.getCurrent();
|
|
|
+
|
|
|
+ this.selection.save();
|
|
|
+
|
|
|
+ document.execCommand('removeFormat');
|
|
|
+
|
|
|
+ if (current && current.tagName === 'SPAN')
|
|
|
+ {
|
|
|
+ $(current).replaceWith($(current).contents());
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ $.each(this.selection.getNodes(), $.proxy(function(i,s)
|
|
|
+ {
|
|
|
+ var $s = $(s);
|
|
|
+ if ($.inArray(s.tagName.toLowerCase(), this.opts.inlineTags) != -1 && !$s.hasClass('redactor-selection-marker'))
|
|
|
+ {
|
|
|
+ $s.replaceWith($s.contents());
|
|
|
+ }
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ this.selection.restore();
|
|
|
+ this.code.sync();
|
|
|
+
|
|
|
+ },
|
|
|
+ toggleClass: function(className)
|
|
|
+ {
|
|
|
+ this.inline.format('span', 'class', className);
|
|
|
+ },
|
|
|
+ toggleStyle: function(value)
|
|
|
+ {
|
|
|
+ this.inline.format('span', 'style', value);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ insert: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ set: function(html, clean)
|
|
|
+ {
|
|
|
+ this.placeholder.remove();
|
|
|
+
|
|
|
+ html = this.clean.setVerified(html);
|
|
|
+
|
|
|
+ if (typeof clean == 'undefined')
|
|
|
+ {
|
|
|
+ html = this.clean.onPaste(html, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$editor.html(html);
|
|
|
+ this.selection.remove();
|
|
|
+ this.focus.setEnd();
|
|
|
+ this.clean.normalizeLists();
|
|
|
+ this.code.sync();
|
|
|
+ this.observe.load();
|
|
|
+
|
|
|
+ if (typeof clean == 'undefined')
|
|
|
+ {
|
|
|
+ setTimeout($.proxy(this.clean.clearUnverified, this), 10);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ text: function(text)
|
|
|
+ {
|
|
|
+ this.placeholder.remove();
|
|
|
+
|
|
|
+ text = text.toString();
|
|
|
+ text = $.trim(text);
|
|
|
+ text = this.clean.getPlainText(text, false);
|
|
|
+
|
|
|
+ this.$editor.focus();
|
|
|
+
|
|
|
+ if (this.utils.browser('msie'))
|
|
|
+ {
|
|
|
+ this.insert.htmlIe(text);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.selection.get();
|
|
|
+
|
|
|
+ this.range.deleteContents();
|
|
|
+ var el = document.createElement("div");
|
|
|
+ el.innerHTML = text;
|
|
|
+ var frag = document.createDocumentFragment(), node, lastNode;
|
|
|
+ while ((node = el.firstChild))
|
|
|
+ {
|
|
|
+ lastNode = frag.appendChild(node);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.range.insertNode(frag);
|
|
|
+
|
|
|
+ if (lastNode)
|
|
|
+ {
|
|
|
+ var range = this.range.cloneRange();
|
|
|
+ range.setStartAfter(lastNode);
|
|
|
+ range.collapse(true);
|
|
|
+ this.sel.removeAllRanges();
|
|
|
+ this.sel.addRange(range);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this.code.sync();
|
|
|
+ this.clean.clearUnverified();
|
|
|
+ },
|
|
|
+ htmlWithoutClean: function(html)
|
|
|
+ {
|
|
|
+ this.insert.html(html, false);
|
|
|
+ },
|
|
|
+ html: function(html, clean)
|
|
|
+ {
|
|
|
+ this.placeholder.remove();
|
|
|
+
|
|
|
+ if (typeof clean == 'undefined') clean = true;
|
|
|
+
|
|
|
+ if (!this.opts.linebreaks)
|
|
|
+ {
|
|
|
+ this.$editor.focus();
|
|
|
+ }
|
|
|
+
|
|
|
+ html = this.clean.setVerified(html);
|
|
|
+
|
|
|
+ if (clean)
|
|
|
+ {
|
|
|
+ html = this.clean.onPaste(html);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.utils.browser('msie'))
|
|
|
+ {
|
|
|
+ this.insert.htmlIe(html);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (this.clean.singleLine) this.insert.execHtml(html);
|
|
|
+ else document.execCommand('insertHTML', false, html);
|
|
|
+
|
|
|
+ this.insert.htmlFixMozilla();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ this.clean.normalizeLists();
|
|
|
+
|
|
|
+ // remove empty paragraphs finaly
|
|
|
+ if (!this.opts.linebreaks)
|
|
|
+ {
|
|
|
+ this.$editor.find('p').each($.proxy(this.utils.removeEmpty, this));
|
|
|
+ }
|
|
|
+
|
|
|
+ this.code.sync();
|
|
|
+ this.observe.load();
|
|
|
+
|
|
|
+ if (clean)
|
|
|
+ {
|
|
|
+ this.clean.clearUnverified();
|
|
|
+ }
|
|
|
+
|
|
|
+ },
|
|
|
+ htmlFixMozilla: function()
|
|
|
+ {
|
|
|
+ // FF inserts empty p when content was selected dblclick
|
|
|
+ if (!this.utils.browser('mozilla')) return;
|
|
|
+
|
|
|
+ var $next = $(this.selection.getBlock()).next();
|
|
|
+ if ($next.length > 0 && $next[0].tagName == 'P' && $next.html() === '')
|
|
|
+ {
|
|
|
+ $next.remove();
|
|
|
+ }
|
|
|
+
|
|
|
+ },
|
|
|
+ htmlIe: function(html)
|
|
|
+ {
|
|
|
+ if (this.utils.isIe11())
|
|
|
+ {
|
|
|
+ var parent = this.utils.isCurrentOrParent('P');
|
|
|
+ var $html = $('<div>').append(html);
|
|
|
+ var blocksMatch = $html.contents().is('p, :header, dl, ul, ol, div, table, td, blockquote, pre, address, section, header, footer, aside, article');
|
|
|
+
|
|
|
+ if (parent && blocksMatch) this.insert.ie11FixInserting(parent, html);
|
|
|
+ else this.insert.ie11PasteFrag(html);
|
|
|
+
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ document.selection.createRange().pasteHTML(html);
|
|
|
+
|
|
|
+ },
|
|
|
+ execHtml: function(html)
|
|
|
+ {
|
|
|
+ html = this.clean.setVerified(html);
|
|
|
+
|
|
|
+ this.selection.get();
|
|
|
+
|
|
|
+ this.range.deleteContents();
|
|
|
+
|
|
|
+ var el = document.createElement('div');
|
|
|
+ el.innerHTML = html;
|
|
|
+
|
|
|
+ var frag = document.createDocumentFragment(), node, lastNode;
|
|
|
+ while ((node = el.firstChild))
|
|
|
+ {
|
|
|
+ lastNode = frag.appendChild(node);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.range.insertNode(frag);
|
|
|
+
|
|
|
+ this.range.collapse(true);
|
|
|
+ this.caret.setAfter(lastNode);
|
|
|
+
|
|
|
+ },
|
|
|
+ node: function(node, deleteContents)
|
|
|
+ {
|
|
|
+ node = node[0] || node;
|
|
|
+
|
|
|
+ var offset = this.caret.getOffset();
|
|
|
+ var html = this.utils.getOuterHtml(node);
|
|
|
+ html = this.clean.setVerified(html);
|
|
|
+
|
|
|
+ if (html.match(/</g) !== null)
|
|
|
+ {
|
|
|
+ node = $(html)[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ this.selection.get();
|
|
|
+
|
|
|
+ if (deleteContents !== false)
|
|
|
+ {
|
|
|
+ this.range.deleteContents();
|
|
|
+ }
|
|
|
+
|
|
|
+ this.range.insertNode(node);
|
|
|
+ this.range.collapse(false);
|
|
|
+ this.selection.addRange();
|
|
|
+
|
|
|
+ this.caret.setOffset(offset);
|
|
|
+
|
|
|
+ return node;
|
|
|
+ },
|
|
|
+ nodeToPoint: function(node, x, y)
|
|
|
+ {
|
|
|
+ node = node[0] || node;
|
|
|
+
|
|
|
+ this.selection.get();
|
|
|
+
|
|
|
+ var range;
|
|
|
+ if (document.caretPositionFromPoint)
|
|
|
+ {
|
|
|
+ var pos = document.caretPositionFromPoint(x, y);
|
|
|
+
|
|
|
+ this.range.setStart(pos.offsetNode, pos.offset);
|
|
|
+ this.range.collapse(true);
|
|
|
+ this.range.insertNode(node);
|
|
|
+ }
|
|
|
+ else if (document.caretRangeFromPoint)
|
|
|
+ {
|
|
|
+ range = document.caretRangeFromPoint(x, y);
|
|
|
+ range.insertNode(node);
|
|
|
+ }
|
|
|
+ else if (typeof document.body.createTextRange != "undefined")
|
|
|
+ {
|
|
|
+ range = document.body.createTextRange();
|
|
|
+ range.moveToPoint(x, y);
|
|
|
+ var endRange = range.duplicate();
|
|
|
+ endRange.moveToPoint(x, y);
|
|
|
+ range.setEndPoint("EndToEnd", endRange);
|
|
|
+ range.select();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ nodeToCaretPositionFromPoint: function(e, node)
|
|
|
+ {
|
|
|
+ node = node[0] || node;
|
|
|
+
|
|
|
+ var range;
|
|
|
+ var x = e.clientX, y = e.clientY;
|
|
|
+ if (document.caretPositionFromPoint)
|
|
|
+ {
|
|
|
+ var pos = document.caretPositionFromPoint(x, y);
|
|
|
+ var sel = document.getSelection();
|
|
|
+ range = sel.getRangeAt(0);
|
|
|
+ range.setStart(pos.offsetNode, pos.offset);
|
|
|
+ range.collapse(true);
|
|
|
+ range.insertNode(node);
|
|
|
+ }
|
|
|
+ else if (document.caretRangeFromPoint)
|
|
|
+ {
|
|
|
+ range = document.caretRangeFromPoint(x, y);
|
|
|
+ range.insertNode(node);
|
|
|
+ }
|
|
|
+ else if (typeof document.body.createTextRange != "undefined")
|
|
|
+ {
|
|
|
+ range = document.body.createTextRange();
|
|
|
+ range.moveToPoint(x, y);
|
|
|
+ var endRange = range.duplicate();
|
|
|
+ endRange.moveToPoint(x, y);
|
|
|
+ range.setEndPoint("EndToEnd", endRange);
|
|
|
+ range.select();
|
|
|
+ }
|
|
|
+
|
|
|
+ },
|
|
|
+ ie11FixInserting: function(parent, html)
|
|
|
+ {
|
|
|
+ var node = document.createElement('span');
|
|
|
+ node.className = 'redactor-ie-paste';
|
|
|
+ this.insert.node(node);
|
|
|
+
|
|
|
+ var parHtml = $(parent).html();
|
|
|
+
|
|
|
+ parHtml = '<p>' + parHtml.replace(/<span class="redactor-ie-paste"><\/span>/gi, '</p>' + html + '<p>') + '</p>';
|
|
|
+ parHtml = parHtml.replace(/<p><\/p>/gi, '');
|
|
|
+ $(parent).replaceWith(parHtml);
|
|
|
+ },
|
|
|
+ ie11PasteFrag: function(html)
|
|
|
+ {
|
|
|
+ this.selection.get();
|
|
|
+ this.range.deleteContents();
|
|
|
+
|
|
|
+ var el = document.createElement("div");
|
|
|
+ el.innerHTML = html;
|
|
|
+
|
|
|
+ var frag = document.createDocumentFragment(), node, lastNode;
|
|
|
+ while ((node = el.firstChild))
|
|
|
+ {
|
|
|
+ lastNode = frag.appendChild(node);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.range.insertNode(frag);
|
|
|
+ this.range.collapse(false);
|
|
|
+ this.selection.addRange();
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ keydown: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ init: function(e)
|
|
|
+ {
|
|
|
+ if (this.rtePaste) return;
|
|
|
+
|
|
|
+ var key = e.which;
|
|
|
+ var arrow = (key >= 37 && key <= 40);
|
|
|
+
|
|
|
+ this.keydown.ctrl = e.ctrlKey || e.metaKey;
|
|
|
+ this.keydown.current = this.selection.getCurrent();
|
|
|
+ this.keydown.parent = this.selection.getParent();
|
|
|
+ this.keydown.block = this.selection.getBlock();
|
|
|
+
|
|
|
+ // detect tags
|
|
|
+ this.keydown.pre = this.utils.isTag(this.keydown.current, 'pre');
|
|
|
+ this.keydown.blockquote = this.utils.isTag(this.keydown.current, 'blockquote');
|
|
|
+ this.keydown.figcaption = this.utils.isTag(this.keydown.current, 'figcaption');
|
|
|
+
|
|
|
+ // shortcuts setup
|
|
|
+ this.shortcuts.init(e, key);
|
|
|
+
|
|
|
+ if (this.utils.isDesktop())
|
|
|
+ {
|
|
|
+ this.keydown.checkEvents(arrow, key);
|
|
|
+ this.keydown.setupBuffer(e, key);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.keydown.addArrowsEvent(arrow);
|
|
|
+ this.keydown.setupSelectAll(e, key);
|
|
|
+
|
|
|
+ // callback
|
|
|
+ var keydownStop = this.core.setCallback('keydown', e);
|
|
|
+ if (keydownStop === false)
|
|
|
+ {
|
|
|
+ e.preventDefault();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ie and ff exit from table
|
|
|
+ if (this.opts.enterKey && (this.utils.browser('msie') || this.utils.browser('mozilla')) && (key === this.keyCode.DOWN || key === this.keyCode.RIGHT))
|
|
|
+ {
|
|
|
+ var isEndOfTable = false;
|
|
|
+ var $table = false;
|
|
|
+ if (this.keydown.block && this.keydown.block.tagName === 'TD')
|
|
|
+ {
|
|
|
+ $table = $(this.keydown.block).closest('table', this.$editor[0]);
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($table && $table.find('td').last()[0] === this.keydown.block)
|
|
|
+ {
|
|
|
+ isEndOfTable = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.utils.isEndOfElement() && isEndOfTable)
|
|
|
+ {
|
|
|
+ var node = $(this.opts.emptyHtml);
|
|
|
+ $table.after(node);
|
|
|
+ this.caret.setStart(node);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // down
|
|
|
+ if (this.opts.enterKey && key === this.keyCode.DOWN)
|
|
|
+ {
|
|
|
+ this.keydown.onArrowDown();
|
|
|
+ }
|
|
|
+
|
|
|
+ // turn off enter key
|
|
|
+ if (!this.opts.enterKey && key === this.keyCode.ENTER)
|
|
|
+ {
|
|
|
+ e.preventDefault();
|
|
|
+ // remove selected
|
|
|
+ if (!this.range.collapsed) this.range.deleteContents();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // on enter
|
|
|
+ if (key == this.keyCode.ENTER && !e.shiftKey && !e.ctrlKey && !e.metaKey)
|
|
|
+ {
|
|
|
+ var stop = this.core.setCallback('enter', e);
|
|
|
+ if (stop === false)
|
|
|
+ {
|
|
|
+ e.preventDefault();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.keydown.blockquote && this.keydown.exitFromBlockquote(e) === true)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ var current, $next;
|
|
|
+ if (this.keydown.pre)
|
|
|
+ {
|
|
|
+ return this.keydown.insertNewLine(e);
|
|
|
+ }
|
|
|
+ else if (this.keydown.blockquote || this.keydown.figcaption)
|
|
|
+ {
|
|
|
+ current = this.selection.getCurrent();
|
|
|
+ $next = $(current).next();
|
|
|
+
|
|
|
+ if ($next.length !== 0 && $next[0].tagName == 'BR')
|
|
|
+ {
|
|
|
+ return this.keydown.insertBreakLine(e);
|
|
|
+ }
|
|
|
+ else if (this.utils.isEndOfElement() && (current && current != 'SPAN'))
|
|
|
+ {
|
|
|
+ return this.keydown.insertDblBreakLine(e);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return this.keydown.insertBreakLine(e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (this.opts.linebreaks && !this.keydown.block)
|
|
|
+ {
|
|
|
+ current = this.selection.getCurrent();
|
|
|
+ $next = $(this.keydown.current).next();
|
|
|
+
|
|
|
+ if ($next.length !== 0 && $next[0].tagName == 'BR')
|
|
|
+ {
|
|
|
+ return this.keydown.insertBreakLine(e);
|
|
|
+ }
|
|
|
+ else if (current !== false && $(current).hasClass('redactor-invisible-space'))
|
|
|
+ {
|
|
|
+ this.caret.setAfter(current);
|
|
|
+ $(current).contents().unwrap();
|
|
|
+
|
|
|
+ return this.keydown.insertDblBreakLine(e);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (this.utils.isEndOfEditor())
|
|
|
+ {
|
|
|
+ return this.keydown.insertDblBreakLine(e);
|
|
|
+ }
|
|
|
+ else if ($next.length === 0 && current === false && typeof $next.context != 'undefined')
|
|
|
+ {
|
|
|
+ return this.keydown.insertBreakLine(e);
|
|
|
+ }
|
|
|
+
|
|
|
+ return this.keydown.insertBreakLine(e);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ else if (this.opts.linebreaks && this.keydown.block)
|
|
|
+ {
|
|
|
+ setTimeout($.proxy(this.keydown.replaceDivToBreakLine, this), 1);
|
|
|
+ }
|
|
|
+ // paragraphs
|
|
|
+ else if (!this.opts.linebreaks && this.keydown.block)
|
|
|
+ {
|
|
|
+ setTimeout($.proxy(this.keydown.replaceDivToParagraph, this), 1);
|
|
|
+
|
|
|
+ if (this.keydown.block.tagName === 'LI')
|
|
|
+ {
|
|
|
+ current = this.selection.getCurrent();
|
|
|
+ var $parent = $(current).closest('li', this.$editor[0]);
|
|
|
+ var $list = $parent.closest('ul,ol', this.$editor[0]);
|
|
|
+
|
|
|
+ if ($parent.length !== 0 && this.utils.isEmpty($parent.html()) && $list.next().length === 0 && this.utils.isEmpty($list.find("li").last().html()))
|
|
|
+ {
|
|
|
+ $list.find("li").last().remove();
|
|
|
+
|
|
|
+ var node = $(this.opts.emptyHtml);
|
|
|
+ $list.after(node);
|
|
|
+ this.caret.setStart(node);
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (!this.opts.linebreaks && !this.keydown.block)
|
|
|
+ {
|
|
|
+ return this.keydown.insertParagraph(e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Shift+Enter or Ctrl+Enter
|
|
|
+ if (key === this.keyCode.ENTER && (e.ctrlKey || e.shiftKey))
|
|
|
+ {
|
|
|
+ return this.keydown.onShiftEnter(e);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // tab or cmd + [
|
|
|
+ if (key === this.keyCode.TAB || e.metaKey && key === 221 || e.metaKey && key === 219)
|
|
|
+ {
|
|
|
+ return this.keydown.onTab(e, key);
|
|
|
+ }
|
|
|
+
|
|
|
+ // image delete and backspace
|
|
|
+ if (key === this.keyCode.BACKSPACE || key === this.keyCode.DELETE)
|
|
|
+ {
|
|
|
+ var nodes = this.selection.getNodes();
|
|
|
+
|
|
|
+ if (nodes)
|
|
|
+ {
|
|
|
+ var len = nodes.length;
|
|
|
+ var last;
|
|
|
+ for (var i = 0; i < len; i++)
|
|
|
+ {
|
|
|
+ var children = $(nodes[i]).children('img');
|
|
|
+ if (children.length !== 0)
|
|
|
+ {
|
|
|
+ var self = this;
|
|
|
+ $.each(children, function(z,s)
|
|
|
+ {
|
|
|
+ var $s = $(s);
|
|
|
+ if ($s.css('float') != 'none') return;
|
|
|
+
|
|
|
+ // image delete callback
|
|
|
+ self.core.setCallback('imageDelete', s.src, $s);
|
|
|
+ last = s;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ else if (nodes[i].tagName == 'IMG')
|
|
|
+ {
|
|
|
+ if (last != nodes[i])
|
|
|
+ {
|
|
|
+ // image delete callback
|
|
|
+ this.core.setCallback('imageDelete', nodes[i].src, $(nodes[i]));
|
|
|
+ last = nodes[i];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // backspace
|
|
|
+ if (key === this.keyCode.BACKSPACE)
|
|
|
+ {
|
|
|
+ // backspace as outdent
|
|
|
+ var block = this.selection.getBlock();
|
|
|
+ var indented = ($(block).css('margin-left') !== '0px');
|
|
|
+ if (block && indented && this.range.collapsed && this.utils.isStartOfElement())
|
|
|
+ {
|
|
|
+ this.indent.decrease();
|
|
|
+ e.preventDefault();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // remove hr in FF
|
|
|
+ if (this.utils.browser('mozilla'))
|
|
|
+ {
|
|
|
+ var prev = this.selection.getPrev();
|
|
|
+ var prev2 = $(prev).prev()[0];
|
|
|
+ if (prev && prev.tagName === 'HR') $(prev).remove();
|
|
|
+ if (prev2 && prev2.tagName === 'HR') $(prev2).remove();
|
|
|
+ }
|
|
|
+
|
|
|
+ this.keydown.removeInvisibleSpace();
|
|
|
+ this.keydown.removeEmptyListInTable(e);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.code.sync();
|
|
|
+ },
|
|
|
+ checkEvents: function(arrow, key)
|
|
|
+ {
|
|
|
+ if (!arrow && (this.core.getEvent() == 'click' || this.core.getEvent() == 'arrow'))
|
|
|
+ {
|
|
|
+ this.core.addEvent(false);
|
|
|
+
|
|
|
+ if (this.keydown.checkKeyEvents(key))
|
|
|
+ {
|
|
|
+ this.buffer.set();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ checkKeyEvents: function(key)
|
|
|
+ {
|
|
|
+ var k = this.keyCode;
|
|
|
+ var keys = [k.BACKSPACE, k.DELETE, k.ENTER, k.ESC, k.TAB, k.CTRL, k.META, k.ALT, k.SHIFT];
|
|
|
+
|
|
|
+ return ($.inArray(key, keys) == -1) ? true : false;
|
|
|
+
|
|
|
+ },
|
|
|
+ addArrowsEvent: function(arrow)
|
|
|
+ {
|
|
|
+ if (!arrow) return;
|
|
|
+
|
|
|
+ if ((this.core.getEvent() == 'click' || this.core.getEvent() == 'arrow'))
|
|
|
+ {
|
|
|
+ this.core.addEvent(false);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.core.addEvent('arrow');
|
|
|
+ },
|
|
|
+ setupBuffer: function(e, key)
|
|
|
+ {
|
|
|
+ if (this.keydown.ctrl && key === 90 && !e.shiftKey && !e.altKey && this.opts.buffer.length) // z key
|
|
|
+ {
|
|
|
+ e.preventDefault();
|
|
|
+ this.buffer.undo();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // undo
|
|
|
+ else if (this.keydown.ctrl && key === 90 && e.shiftKey && !e.altKey && this.opts.rebuffer.length !== 0)
|
|
|
+ {
|
|
|
+ e.preventDefault();
|
|
|
+ this.buffer.redo();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ else if (!this.keydown.ctrl)
|
|
|
+ {
|
|
|
+ if (key == this.keyCode.BACKSPACE || key == this.keyCode.DELETE || (key == this.keyCode.ENTER && !e.ctrlKey && !e.shiftKey))
|
|
|
+ {
|
|
|
+ this.buffer.set();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ setupSelectAll: function(e, key)
|
|
|
+ {
|
|
|
+ if (this.keydown.ctrl && key === 65)
|
|
|
+ {
|
|
|
+ this.utils.enableSelectAll();
|
|
|
+ }
|
|
|
+ else if (key != this.keyCode.LEFT_WIN && !this.keydown.ctrl)
|
|
|
+ {
|
|
|
+ this.utils.disableSelectAll();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onArrowDown: function()
|
|
|
+ {
|
|
|
+ var tags = [this.keydown.blockquote, this.keydown.pre, this.keydown.figcaption];
|
|
|
+
|
|
|
+ for (var i = 0; i < tags.length; i++)
|
|
|
+ {
|
|
|
+ if (tags[i])
|
|
|
+ {
|
|
|
+ this.keydown.insertAfterLastElement(tags[i]);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onShiftEnter: function(e)
|
|
|
+ {
|
|
|
+ this.buffer.set();
|
|
|
+
|
|
|
+ if (this.utils.isEndOfElement())
|
|
|
+ {
|
|
|
+ return this.keydown.insertDblBreakLine(e);
|
|
|
+ }
|
|
|
+
|
|
|
+ return this.keydown.insertBreakLine(e);
|
|
|
+ },
|
|
|
+ onTab: function(e, key)
|
|
|
+ {
|
|
|
+ if (!this.opts.tabKey) return true;
|
|
|
+ if (this.utils.isEmpty(this.code.get()) && this.opts.tabAsSpaces === false) return true;
|
|
|
+
|
|
|
+ e.preventDefault();
|
|
|
+
|
|
|
+ var node;
|
|
|
+ if (this.keydown.pre && !e.shiftKey)
|
|
|
+ {
|
|
|
+ node = (this.opts.preSpaces) ? document.createTextNode(Array(this.opts.preSpaces + 1).join('\u00a0')) : document.createTextNode('\t');
|
|
|
+ this.insert.node(node);
|
|
|
+ this.code.sync();
|
|
|
+ }
|
|
|
+ else if (this.opts.tabAsSpaces !== false)
|
|
|
+ {
|
|
|
+ node = document.createTextNode(Array(this.opts.tabAsSpaces + 1).join('\u00a0'));
|
|
|
+ this.insert.node(node);
|
|
|
+ this.code.sync();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (e.metaKey && key === 219) this.indent.decrease();
|
|
|
+ else if (e.metaKey && key === 221) this.indent.increase();
|
|
|
+ else if (!e.shiftKey) this.indent.increase();
|
|
|
+ else this.indent.decrease();
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ },
|
|
|
+ replaceDivToBreakLine: function()
|
|
|
+ {
|
|
|
+ var blockElem = this.selection.getBlock();
|
|
|
+ var blockHtml = blockElem.innerHTML.replace(/<br\s?\/?>/gi, '');
|
|
|
+ if ((blockElem.tagName === 'DIV' || blockElem.tagName === 'P') && blockHtml === '' && !$(blockElem).hasClass('redactor-editor'))
|
|
|
+ {
|
|
|
+ var br = document.createElement('br');
|
|
|
+
|
|
|
+ $(blockElem).replaceWith(br);
|
|
|
+ this.caret.setBefore(br);
|
|
|
+
|
|
|
+ this.code.sync();
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ replaceDivToParagraph: function()
|
|
|
+ {
|
|
|
+ var blockElem = this.selection.getBlock();
|
|
|
+ var blockHtml = blockElem.innerHTML.replace(/<br\s?\/?>/gi, '');
|
|
|
+ if (blockElem.tagName === 'DIV' && this.utils.isEmpty(blockHtml) && !$(blockElem).hasClass('redactor-editor'))
|
|
|
+ {
|
|
|
+ var p = document.createElement('p');
|
|
|
+ p.innerHTML = this.opts.invisibleSpace;
|
|
|
+
|
|
|
+ $(blockElem).replaceWith(p);
|
|
|
+ this.caret.setStart(p);
|
|
|
+
|
|
|
+ this.code.sync();
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ else if (this.opts.cleanStyleOnEnter && blockElem.tagName == 'P')
|
|
|
+ {
|
|
|
+ $(blockElem).removeAttr('class').removeAttr('style');
|
|
|
+ }
|
|
|
+ },
|
|
|
+ insertParagraph: function(e)
|
|
|
+ {
|
|
|
+ e.preventDefault();
|
|
|
+
|
|
|
+ this.selection.get();
|
|
|
+
|
|
|
+ var p = document.createElement('p');
|
|
|
+ p.innerHTML = this.opts.invisibleSpace;
|
|
|
+
|
|
|
+ this.range.deleteContents();
|
|
|
+ this.range.insertNode(p);
|
|
|
+
|
|
|
+ this.caret.setStart(p);
|
|
|
+
|
|
|
+ this.code.sync();
|
|
|
+
|
|
|
+ return false;
|
|
|
+ },
|
|
|
+ exitFromBlockquote: function(e)
|
|
|
+ {
|
|
|
+ if (!this.utils.isEndOfElement()) return;
|
|
|
+
|
|
|
+ var tmp = $.trim($(this.keydown.block).html());
|
|
|
+ if (tmp.search(/(<br\s?\/?>){2}$/i) != -1)
|
|
|
+ {
|
|
|
+ e.preventDefault();
|
|
|
+
|
|
|
+ if (this.opts.linebreaks)
|
|
|
+ {
|
|
|
+ var br = document.createElement('br');
|
|
|
+ $(this.keydown.blockquote).after(br);
|
|
|
+
|
|
|
+ this.caret.setBefore(br);
|
|
|
+ $(this.keydown.block).html(tmp.replace(/<br\s?\/?>$/i, ''));
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ var node = $(this.opts.emptyHtml);
|
|
|
+ $(this.keydown.blockquote).after(node);
|
|
|
+ this.caret.setStart(node);
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ return;
|
|
|
+
|
|
|
+ },
|
|
|
+ insertAfterLastElement: function(element)
|
|
|
+ {
|
|
|
+ if (!this.utils.isEndOfElement()) return;
|
|
|
+
|
|
|
+ this.buffer.set();
|
|
|
+
|
|
|
+ if (this.opts.linebreaks)
|
|
|
+ {
|
|
|
+ var contents = $('<div>').append($.trim(this.$editor.html())).contents();
|
|
|
+ var last = contents.last()[0];
|
|
|
+ if (last.tagName == 'SPAN' && last.innerHTML === '')
|
|
|
+ {
|
|
|
+ last = contents.prev()[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.utils.getOuterHtml(last) != this.utils.getOuterHtml(element)) return;
|
|
|
+
|
|
|
+ var br = document.createElement('br');
|
|
|
+ $(element).after(br);
|
|
|
+ this.caret.setAfter(br);
|
|
|
+
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (this.$editor.contents().last()[0] !== element) return;
|
|
|
+
|
|
|
+ var node = $(this.opts.emptyHtml);
|
|
|
+ $(element).after(node);
|
|
|
+ this.caret.setStart(node);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ insertNewLine: function(e)
|
|
|
+ {
|
|
|
+ e.preventDefault();
|
|
|
+
|
|
|
+ var node = document.createTextNode('\n');
|
|
|
+
|
|
|
+ this.selection.get();
|
|
|
+
|
|
|
+ this.range.deleteContents();
|
|
|
+ this.range.insertNode(node);
|
|
|
+
|
|
|
+ this.caret.setAfter(node);
|
|
|
+
|
|
|
+ this.code.sync();
|
|
|
+
|
|
|
+ return false;
|
|
|
+ },
|
|
|
+ insertBreakLine: function(e)
|
|
|
+ {
|
|
|
+ return this.keydown.insertBreakLineProcessing(e);
|
|
|
+ },
|
|
|
+ insertDblBreakLine: function(e)
|
|
|
+ {
|
|
|
+ return this.keydown.insertBreakLineProcessing(e, true);
|
|
|
+ },
|
|
|
+ insertBreakLineProcessing: function(e, dbl)
|
|
|
+ {
|
|
|
+ e.stopPropagation();
|
|
|
+
|
|
|
+ this.selection.get();
|
|
|
+
|
|
|
+ var br1 = document.createElement('br');
|
|
|
+
|
|
|
+ if (this.utils.browser('msie'))
|
|
|
+ {
|
|
|
+ this.range.collapse(false);
|
|
|
+ this.range.setEnd(this.range.endContainer, this.range.endOffset);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.range.deleteContents();
|
|
|
+ }
|
|
|
+
|
|
|
+ this.range.insertNode(br1);
|
|
|
+
|
|
|
+ // move br outside A tag
|
|
|
+ var $parentA = $(br1).parent("a");
|
|
|
+
|
|
|
+ if ($parentA.length > 0)
|
|
|
+ {
|
|
|
+ $parentA.find(br1).remove();
|
|
|
+ $parentA.after(br1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (dbl === true)
|
|
|
+ {
|
|
|
+ var $next = $(br1).next();
|
|
|
+ if ($next.length !== 0 && $next[0].tagName === 'BR' && this.utils.isEndOfEditor())
|
|
|
+ {
|
|
|
+ this.caret.setAfter(br1);
|
|
|
+ this.code.sync();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ var br2 = document.createElement('br');
|
|
|
+
|
|
|
+ this.range.insertNode(br2);
|
|
|
+ this.caret.setAfter(br2);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // caret does not move after the br visual
|
|
|
+ if (this.utils.browser('msie'))
|
|
|
+ {
|
|
|
+ var space = document.createElement('span');
|
|
|
+ space.innerHTML = '​';
|
|
|
+
|
|
|
+ $(br1).after(space);
|
|
|
+
|
|
|
+ this.range.setStartAfter(space);
|
|
|
+ this.range.setEndAfter(space);
|
|
|
+ $(space).remove();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ var range = document.createRange();
|
|
|
+ range.setStartAfter(br1);
|
|
|
+ range.collapse(true);
|
|
|
+ var selection = window.getSelection();
|
|
|
+ selection.removeAllRanges();
|
|
|
+ selection.addRange(range);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this.code.sync();
|
|
|
+ return false;
|
|
|
+ },
|
|
|
+ removeInvisibleSpace: function()
|
|
|
+ {
|
|
|
+ var $current = $(this.keydown.current);
|
|
|
+ if ($current.text().search(/^\u200B$/g) === 0)
|
|
|
+ {
|
|
|
+ $current.remove();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ removeEmptyListInTable: function(e)
|
|
|
+ {
|
|
|
+ var $current = $(this.keydown.current);
|
|
|
+ var $parent = $(this.keydown.parent);
|
|
|
+ var td = $current.closest('td', this.$editor[0]);
|
|
|
+
|
|
|
+ if (td.length !== 0 && $current.closest('li', this.$editor[0]) && $parent.children('li').length === 1)
|
|
|
+ {
|
|
|
+ if (!this.utils.isEmpty($current.text())) return;
|
|
|
+
|
|
|
+ e.preventDefault();
|
|
|
+
|
|
|
+ $current.remove();
|
|
|
+ $parent.remove();
|
|
|
+
|
|
|
+ this.caret.setStart(td);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ keyup: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ init: function(e)
|
|
|
+ {
|
|
|
+
|
|
|
+ if (this.rtePaste) return;
|
|
|
+
|
|
|
+ var key = e.which;
|
|
|
+
|
|
|
+ this.keyup.current = this.selection.getCurrent();
|
|
|
+ this.keyup.parent = this.selection.getParent();
|
|
|
+ var $parent = this.utils.isRedactorParent($(this.keyup.parent).parent());
|
|
|
+
|
|
|
+ // callback
|
|
|
+ var keyupStop = this.core.setCallback('keyup', e);
|
|
|
+ if (keyupStop === false)
|
|
|
+ {
|
|
|
+ e.preventDefault();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // replace to p before / after the table or body
|
|
|
+ if (!this.opts.linebreaks && this.keyup.current.nodeType === 3 && this.keyup.current.length <= 1 && (this.keyup.parent === false || this.keyup.parent.tagName == 'BODY'))
|
|
|
+ {
|
|
|
+ this.keyup.replaceToParagraph();
|
|
|
+ }
|
|
|
+
|
|
|
+ // replace div after lists
|
|
|
+ if (!this.opts.linebreaks && this.utils.isRedactorParent(this.keyup.current) && this.keyup.current.tagName === 'DIV')
|
|
|
+ {
|
|
|
+ this.keyup.replaceToParagraph(false);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if (!this.opts.linebreaks && $(this.keyup.parent).hasClass('redactor-invisible-space') && ($parent === false || $parent[0].tagName == 'BODY'))
|
|
|
+ {
|
|
|
+ $(this.keyup.parent).contents().unwrap();
|
|
|
+ this.keyup.replaceToParagraph();
|
|
|
+ }
|
|
|
+
|
|
|
+ // linkify
|
|
|
+ if (this.linkify.isEnabled() && this.linkify.isKey(key)) this.linkify.format();
|
|
|
+
|
|
|
+ if (key === this.keyCode.DELETE || key === this.keyCode.BACKSPACE)
|
|
|
+ {
|
|
|
+ if (this.utils.browser('mozilla'))
|
|
|
+ {
|
|
|
+ var td = $(this.keydown.current).closest('td', this.$editor[0]);
|
|
|
+ if (td.size() !== 0 && td.text() !== '')
|
|
|
+ {
|
|
|
+ e.preventDefault();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // clear unverified
|
|
|
+ this.clean.clearUnverified();
|
|
|
+
|
|
|
+ if (this.observe.image)
|
|
|
+ {
|
|
|
+ e.preventDefault();
|
|
|
+
|
|
|
+ this.image.hideResize();
|
|
|
+
|
|
|
+ this.buffer.set();
|
|
|
+ this.image.remove(this.observe.image);
|
|
|
+ this.observe.image = false;
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // remove empty paragraphs
|
|
|
+ this.$editor.find('p').each($.proxy(function(i, s)
|
|
|
+ {
|
|
|
+ this.utils.removeEmpty(i, $(s).html());
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ // remove invisible space
|
|
|
+ if (this.opts.linebreaks && this.keyup.current && this.keyup.current.tagName == 'DIV' && this.utils.isEmpty(this.keyup.current.innerHTML))
|
|
|
+ {
|
|
|
+ $(this.keyup.current).after(this.selection.getMarkerAsHtml());
|
|
|
+ this.selection.restore();
|
|
|
+ $(this.keyup.current).remove();
|
|
|
+ }
|
|
|
+
|
|
|
+ this.keyup.removeEmptyLists();
|
|
|
+
|
|
|
+ // if empty
|
|
|
+ return this.keyup.formatEmpty(e);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ replaceToParagraph: function(clone)
|
|
|
+ {
|
|
|
+ var $current = $(this.keyup.current);
|
|
|
+
|
|
|
+ var node;
|
|
|
+ if (clone === false)
|
|
|
+ {
|
|
|
+ node = $('<p>').append($current.html());
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ node = $('<p>').append($current.clone());
|
|
|
+ }
|
|
|
+
|
|
|
+ $current.replaceWith(node);
|
|
|
+ var next = $(node).next();
|
|
|
+ if (typeof(next[0]) !== 'undefined' && next[0].tagName == 'BR')
|
|
|
+ {
|
|
|
+ next.remove();
|
|
|
+ }
|
|
|
+
|
|
|
+ this.caret.setEnd(node);
|
|
|
+ },
|
|
|
+ removeEmptyLists: function()
|
|
|
+ {
|
|
|
+ var removeIt = function()
|
|
|
+ {
|
|
|
+ var html = $.trim(this.innerHTML).replace(/\/t\/n/g, '');
|
|
|
+ if (html === '')
|
|
|
+ {
|
|
|
+ $(this).remove();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ this.$editor.find('li').each(removeIt);
|
|
|
+ this.$editor.find('ul, ol').each(removeIt);
|
|
|
+ },
|
|
|
+ formatEmpty: function(e)
|
|
|
+ {
|
|
|
+ var html = $.trim(this.$editor.html());
|
|
|
+
|
|
|
+ if (!this.utils.isEmpty(html)) return;
|
|
|
+
|
|
|
+ e.preventDefault();
|
|
|
+
|
|
|
+ if (this.opts.linebreaks)
|
|
|
+ {
|
|
|
+ this.$editor.html(this.selection.getMarkerAsHtml());
|
|
|
+ this.selection.restore();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.$editor.html(this.opts.emptyHtml);
|
|
|
+ this.focus.setStart();
|
|
|
+ }
|
|
|
+
|
|
|
+ this.code.sync();
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ lang: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ load: function()
|
|
|
+ {
|
|
|
+ this.opts.curLang = this.opts.langs[this.opts.lang];
|
|
|
+ },
|
|
|
+ get: function(name)
|
|
|
+ {
|
|
|
+ return (typeof this.opts.curLang[name] != 'undefined') ? this.opts.curLang[name] : '';
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ line: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ insert: function()
|
|
|
+ {
|
|
|
+ this.buffer.set();
|
|
|
+
|
|
|
+ var blocks = this.selection.getBlocks();
|
|
|
+ if (blocks[0] !== false && this.line.isExceptLastOrFirst(blocks))
|
|
|
+ {
|
|
|
+ if (!this.utils.browser('msie')) this.$editor.focus();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.utils.browser('msie'))
|
|
|
+ {
|
|
|
+ this.line.insertInIe();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.line.insertInOthersBrowsers();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ isExceptLastOrFirst: function(blocks)
|
|
|
+ {
|
|
|
+ var exceptTags = ['li', 'td', 'th', 'blockquote', 'figcaption', 'pre', 'dl', 'dt', 'dd'];
|
|
|
+
|
|
|
+ var first = blocks[0].tagName.toLowerCase();
|
|
|
+ var last = this.selection.getLastBlock();
|
|
|
+
|
|
|
+ last = (typeof last == 'undefined') ? first : last.tagName.toLowerCase();
|
|
|
+
|
|
|
+ var firstFound = $.inArray(first, exceptTags) != -1;
|
|
|
+ var lastFound = $.inArray(last, exceptTags) != -1;
|
|
|
+
|
|
|
+ if ((firstFound && lastFound) || firstFound)
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ insertInIe: function()
|
|
|
+ {
|
|
|
+ this.utils.saveScroll();
|
|
|
+ this.buffer.set();
|
|
|
+
|
|
|
+ this.insert.node(document.createElement('hr'));
|
|
|
+
|
|
|
+ this.utils.restoreScroll();
|
|
|
+ this.code.sync();
|
|
|
+ },
|
|
|
+ insertInOthersBrowsers: function()
|
|
|
+ {
|
|
|
+ this.buffer.set();
|
|
|
+
|
|
|
+ var extra = '<p id="redactor-insert-line"><br /></p>';
|
|
|
+ if (this.opts.linebreaks) extra = '<br id="redactor-insert-line">';
|
|
|
+
|
|
|
+ document.execCommand('insertHtml', false, '<hr>' + extra);
|
|
|
+
|
|
|
+ this.line.setFocus();
|
|
|
+ this.code.sync();
|
|
|
+ },
|
|
|
+ setFocus: function()
|
|
|
+ {
|
|
|
+ var node = this.$editor.find('#redactor-insert-line');
|
|
|
+ var next = $(node).next()[0];
|
|
|
+ var target = next;
|
|
|
+ if (this.utils.browser('mozilla') && next && next.innerHTML === '')
|
|
|
+ {
|
|
|
+ target = $(next).next()[0];
|
|
|
+ $(next).remove();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (target)
|
|
|
+ {
|
|
|
+ node.remove();
|
|
|
+
|
|
|
+ if (!this.opts.linebreaks)
|
|
|
+ {
|
|
|
+ this.$editor.focus();
|
|
|
+ this.line.setStart(target);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+
|
|
|
+ node.removeAttr('id');
|
|
|
+ this.line.setStart(node[0]);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ setStart: function(node)
|
|
|
+ {
|
|
|
+ if (typeof node === 'undefined') return;
|
|
|
+
|
|
|
+ var textNode = document.createTextNode('\u200B');
|
|
|
+
|
|
|
+ this.selection.get();
|
|
|
+ this.range.setStart(node, 0);
|
|
|
+ this.range.insertNode(textNode);
|
|
|
+ this.range.collapse(true);
|
|
|
+ this.selection.addRange();
|
|
|
+
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ link: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ show: function(e)
|
|
|
+ {
|
|
|
+ if (typeof e != 'undefined' && e.preventDefault) e.preventDefault();
|
|
|
+
|
|
|
+ if (!this.observe.isCurrent('a'))
|
|
|
+ {
|
|
|
+ this.modal.load('link', this.lang.get('link_insert'), 600);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.modal.load('link', this.lang.get('link_edit'), 600);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.modal.createCancelButton();
|
|
|
+
|
|
|
+ var buttonText = !this.observe.isCurrent('a') ? this.lang.get('insert') : this.lang.get('edit');
|
|
|
+
|
|
|
+ this.link.buttonInsert = this.modal.createActionButton(buttonText);
|
|
|
+
|
|
|
+ this.selection.get();
|
|
|
+
|
|
|
+ this.link.getData();
|
|
|
+ this.link.cleanUrl();
|
|
|
+
|
|
|
+ if (this.link.target == '_blank') $('#redactor-link-blank').prop('checked', true);
|
|
|
+
|
|
|
+ this.link.$inputUrl = $('#redactor-link-url');
|
|
|
+ this.link.$inputText = $('#redactor-link-url-text');
|
|
|
+
|
|
|
+ this.link.$inputText.val(this.link.text);
|
|
|
+ this.link.$inputUrl.val(this.link.url);
|
|
|
+
|
|
|
+ this.link.buttonInsert.on('click', $.proxy(this.link.insert, this));
|
|
|
+
|
|
|
+ // hide link's tooltip
|
|
|
+ $('.redactor-link-tooltip').remove();
|
|
|
+
|
|
|
+ // show modal
|
|
|
+ this.selection.save();
|
|
|
+ this.modal.show();
|
|
|
+ this.link.$inputUrl.focus();
|
|
|
+ },
|
|
|
+ cleanUrl: function()
|
|
|
+ {
|
|
|
+ var thref = self.location.href.replace(/\/$/i, '');
|
|
|
+
|
|
|
+ if (typeof this.link.url !== "undefined")
|
|
|
+ {
|
|
|
+ this.link.url = this.link.url.replace(thref, '');
|
|
|
+ this.link.url = this.link.url.replace(/^\/#/, '#');
|
|
|
+ this.link.url = this.link.url.replace('mailto:', '');
|
|
|
+
|
|
|
+ // remove host from href
|
|
|
+ if (!this.opts.linkProtocol)
|
|
|
+ {
|
|
|
+ var re = new RegExp('^(http|ftp|https)://' + self.location.host, 'i');
|
|
|
+ this.link.url = this.link.url.replace(re, '');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ getData: function()
|
|
|
+ {
|
|
|
+ this.link.$node = false;
|
|
|
+
|
|
|
+ var $el = $(this.selection.getCurrent()).closest('a', this.$editor[0]);
|
|
|
+ if ($el.length !== 0 && $el[0].tagName === 'A')
|
|
|
+ {
|
|
|
+ this.link.$node = $el;
|
|
|
+
|
|
|
+ this.link.url = $el.attr('href');
|
|
|
+ this.link.text = $el.text();
|
|
|
+ this.link.target = $el.attr('target');
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.link.text = this.sel.toString();
|
|
|
+ this.link.url = '';
|
|
|
+ this.link.target = '';
|
|
|
+ }
|
|
|
+
|
|
|
+ },
|
|
|
+ insert: function()
|
|
|
+ {
|
|
|
+ this.placeholder.remove();
|
|
|
+
|
|
|
+ var target = '';
|
|
|
+ var link = this.link.$inputUrl.val();
|
|
|
+ var text = this.link.$inputText.val().replace(/(<([^>]+)>)/ig,"");
|
|
|
+
|
|
|
+ if ($.trim(link) === '')
|
|
|
+ {
|
|
|
+ this.link.$inputUrl.addClass('redactor-input-error').on('keyup', function()
|
|
|
+ {
|
|
|
+ $(this).removeClass('redactor-input-error');
|
|
|
+ $(this).off('keyup');
|
|
|
+
|
|
|
+ });
|
|
|
+
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // mailto
|
|
|
+ if (link.search('@') != -1 && /(http|ftp|https):\/\//i.test(link) === false)
|
|
|
+ {
|
|
|
+ link = link.replace('mailto:', '');
|
|
|
+ link = 'mailto:' + link;
|
|
|
+ }
|
|
|
+ // url, not anchor
|
|
|
+ else if (link.search('#') !== 0)
|
|
|
+ {
|
|
|
+ if ($('#redactor-link-blank').prop('checked'))
|
|
|
+ {
|
|
|
+ target = '_blank';
|
|
|
+ }
|
|
|
+
|
|
|
+ // test url (add protocol)
|
|
|
+ var pattern = '((xn--)?[a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,}';
|
|
|
+ var re = new RegExp('^(http|ftp|https)://' + pattern, 'i');
|
|
|
+ var re2 = new RegExp('^' + pattern, 'i');
|
|
|
+ var re3 = new RegExp('\.(html|php)$', 'i');
|
|
|
+ if (link.search(re) == -1 && link.search(re3) == -1 && link.search(re2) === 0 && this.opts.linkProtocol)
|
|
|
+ {
|
|
|
+ link = this.opts.linkProtocol + '://' + link;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this.link.set(text, link, target);
|
|
|
+ this.modal.close();
|
|
|
+ },
|
|
|
+ set: function(text, link, target)
|
|
|
+ {
|
|
|
+ text = $.trim(text.replace(/<|>/g, ''));
|
|
|
+
|
|
|
+ this.selection.restore();
|
|
|
+ var blocks = this.selection.getBlocks();
|
|
|
+
|
|
|
+ if (text === '' && link === '') return;
|
|
|
+ if (text === '' && link !== '') text = link;
|
|
|
+
|
|
|
+ if (this.link.$node)
|
|
|
+ {
|
|
|
+ this.buffer.set();
|
|
|
+
|
|
|
+ var $link = this.link.$node,
|
|
|
+ $el = $link.children();
|
|
|
+
|
|
|
+ if ($el.length > 0)
|
|
|
+ {
|
|
|
+ while ($el.length)
|
|
|
+ {
|
|
|
+ $el = $el.children();
|
|
|
+ }
|
|
|
+
|
|
|
+ $el = $el.end();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ $el = $link;
|
|
|
+ }
|
|
|
+
|
|
|
+ $link.attr('href', link);
|
|
|
+ $el.text(text);
|
|
|
+
|
|
|
+ if (target !== '')
|
|
|
+ {
|
|
|
+ $link.attr('target', target);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ $link.removeAttr('target');
|
|
|
+ }
|
|
|
+
|
|
|
+ this.selection.selectElement($link);
|
|
|
+
|
|
|
+ this.code.sync();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (this.utils.browser('mozilla') && this.link.text === '')
|
|
|
+ {
|
|
|
+ var $a = $('<a />').attr('href', link).text(text);
|
|
|
+ if (target !== '') $a.attr('target', target);
|
|
|
+
|
|
|
+ $a = $(this.insert.node($a));
|
|
|
+
|
|
|
+ if (this.opts.linebreaks)
|
|
|
+ {
|
|
|
+ $a.after(' ');
|
|
|
+ }
|
|
|
+
|
|
|
+ this.selection.selectElement($a);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ var $a;
|
|
|
+ if (this.utils.browser('msie'))
|
|
|
+ {
|
|
|
+ $a = $('<a href="' + link + '">').text(text);
|
|
|
+ if (target !== '') $a.attr('target', target);
|
|
|
+
|
|
|
+ $a = $(this.insert.node($a));
|
|
|
+
|
|
|
+ if (this.selection.getText().match(/\s$/))
|
|
|
+ {
|
|
|
+ $a.after(" ");
|
|
|
+ }
|
|
|
+
|
|
|
+ this.selection.selectElement($a);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ document.execCommand('createLink', false, link);
|
|
|
+
|
|
|
+ $a = $(this.selection.getCurrent()).closest('a', this.$editor[0]);
|
|
|
+ if (this.utils.browser('mozilla'))
|
|
|
+ {
|
|
|
+ $a = $('a[_moz_dirty=""]');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (target !== '') $a.attr('target', target);
|
|
|
+ $a.removeAttr('style').removeAttr('_moz_dirty');
|
|
|
+
|
|
|
+ if (this.selection.getText().match(/\s$/))
|
|
|
+ {
|
|
|
+ $a.after(" ");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.link.text !== '' || this.link.text != text)
|
|
|
+ {
|
|
|
+ if (!this.opts.linebreaks && blocks && blocks.length <= 1)
|
|
|
+ {
|
|
|
+ $a.text(text);
|
|
|
+ }
|
|
|
+ else if (this.opts.linebreaks)
|
|
|
+ {
|
|
|
+ $a.text(text);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.selection.selectElement($a);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this.code.sync();
|
|
|
+ this.core.setCallback('insertedLink', $a);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // link tooltip
|
|
|
+ setTimeout($.proxy(function()
|
|
|
+ {
|
|
|
+ this.observe.links();
|
|
|
+
|
|
|
+ }, this), 5);
|
|
|
+ },
|
|
|
+ unlink: function(e)
|
|
|
+ {
|
|
|
+ if (typeof e != 'undefined' && e.preventDefault)
|
|
|
+ {
|
|
|
+ e.preventDefault();
|
|
|
+ }
|
|
|
+
|
|
|
+ var nodes = this.selection.getNodes();
|
|
|
+ if (!nodes) return;
|
|
|
+
|
|
|
+ this.buffer.set();
|
|
|
+
|
|
|
+ var len = nodes.length;
|
|
|
+ var links = [];
|
|
|
+ for (var i = 0; i < len; i++)
|
|
|
+ {
|
|
|
+ if (nodes[i].tagName === 'A')
|
|
|
+ {
|
|
|
+ links.push(nodes[i]);
|
|
|
+ }
|
|
|
+
|
|
|
+ var $node = $(nodes[i]).closest('a', this.$editor[0]);
|
|
|
+ $node.replaceWith($node.contents());
|
|
|
+ }
|
|
|
+
|
|
|
+ this.core.setCallback('deletedLink', links);
|
|
|
+
|
|
|
+ // hide link's tooltip
|
|
|
+ $('.redactor-link-tooltip').remove();
|
|
|
+
|
|
|
+ this.code.sync();
|
|
|
+
|
|
|
+ },
|
|
|
+ toggleClass: function(className)
|
|
|
+ {
|
|
|
+ this.link.setClass(className, 'toggleClass');
|
|
|
+ },
|
|
|
+ addClass: function(className)
|
|
|
+ {
|
|
|
+ this.link.setClass(className, 'addClass');
|
|
|
+ },
|
|
|
+ removeClass: function(className)
|
|
|
+ {
|
|
|
+ this.link.setClass(className, 'removeClass');
|
|
|
+ },
|
|
|
+ setClass: function(className, func)
|
|
|
+ {
|
|
|
+ var links = this.selection.getInlinesTags(['a']);
|
|
|
+ if (links === false) return;
|
|
|
+
|
|
|
+ $.each(links, function()
|
|
|
+ {
|
|
|
+ $(this)[func](className);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ linkify: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ isKey: function(key)
|
|
|
+ {
|
|
|
+ return key == this.keyCode.ENTER || key == this.keyCode.SPACE;
|
|
|
+ },
|
|
|
+ isEnabled: function()
|
|
|
+ {
|
|
|
+ return this.opts.convertLinks && (this.opts.convertUrlLinks || this.opts.convertImageLinks || this.opts.convertVideoLinks) && !this.utils.isCurrentOrParent('PRE');
|
|
|
+ },
|
|
|
+ format: function()
|
|
|
+ {
|
|
|
+ var linkify = this.linkify,
|
|
|
+ opts = this.opts;
|
|
|
+
|
|
|
+ this.$editor
|
|
|
+ .find(":not(iframe,img,a,pre)")
|
|
|
+ .addBack()
|
|
|
+ .contents()
|
|
|
+ .filter(function()
|
|
|
+ {
|
|
|
+ return this.nodeType === 3 && $.trim(this.nodeValue) != "" && !$(this).parent().is("pre") && (this.nodeValue.match(opts.linkify.regexps.youtube) || this.nodeValue.match(opts.linkify.regexps.vimeo) || this.nodeValue.match(opts.linkify.regexps.image) || this.nodeValue.match(opts.linkify.regexps.url));
|
|
|
+ })
|
|
|
+ .each(function()
|
|
|
+ {
|
|
|
+ var text = $(this).text(),
|
|
|
+ html = text;
|
|
|
+
|
|
|
+ if (opts.convertVideoLinks && (html.match(opts.linkify.regexps.youtube) || html.match(opts.linkify.regexps.vimeo)) )
|
|
|
+ {
|
|
|
+ html = linkify.convertVideoLinks(html);
|
|
|
+ }
|
|
|
+ else if (opts.convertImageLinks && html.match(opts.linkify.regexps.image))
|
|
|
+ {
|
|
|
+ html = linkify.convertImages(html);
|
|
|
+ }
|
|
|
+ else if (opts.convertUrlLinks)
|
|
|
+ {
|
|
|
+ html = linkify.convertLinks(html);
|
|
|
+ }
|
|
|
+
|
|
|
+ $(this).before(text.replace(text, html))
|
|
|
+ .remove();
|
|
|
+ });
|
|
|
+
|
|
|
+
|
|
|
+ var objects = this.$editor.find('.redactor-linkify-object').each(function()
|
|
|
+ {
|
|
|
+ var $el = $(this);
|
|
|
+ $el.removeClass('redactor-linkify-object');
|
|
|
+ if ($el.attr('class') === '') $el.removeAttr('class');
|
|
|
+
|
|
|
+ return $el[0];
|
|
|
+
|
|
|
+ });
|
|
|
+
|
|
|
+ // callback
|
|
|
+ setTimeout($.proxy(function()
|
|
|
+ {
|
|
|
+ this.observe.load();
|
|
|
+ this.core.setCallback('linkify', objects);
|
|
|
+ }, this), 100);
|
|
|
+
|
|
|
+ // sync
|
|
|
+ this.code.sync();
|
|
|
+ },
|
|
|
+ convertVideoLinks: function(html)
|
|
|
+ {
|
|
|
+ var iframeStart = '<iframe class="redactor-linkify-object" width="500" height="281" src="',
|
|
|
+ iframeEnd = '" frameborder="0" allowfullscreen></iframe>';
|
|
|
+
|
|
|
+ if (html.match(this.opts.linkify.regexps.youtube))
|
|
|
+ {
|
|
|
+ html = html.replace(this.opts.linkify.regexps.youtube, iframeStart + '//www.youtube.com/embed/$1' + iframeEnd);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (html.match(this.opts.linkify.regexps.vimeo))
|
|
|
+ {
|
|
|
+ html = html.replace(this.opts.linkify.regexps.vimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd);
|
|
|
+ }
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ convertImages: function(html)
|
|
|
+ {
|
|
|
+ var matches = html.match(this.opts.linkify.regexps.image);
|
|
|
+
|
|
|
+ if (matches)
|
|
|
+ {
|
|
|
+ html = html.replace(html, '<img src="' + matches + '" class="redactor-linkify-object" />');
|
|
|
+
|
|
|
+ if (this.opts.linebreaks)
|
|
|
+ {
|
|
|
+ if (!this.utils.isEmpty(this.code.get()))
|
|
|
+ {
|
|
|
+ html = '<br>' + html;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ html += '<br>';
|
|
|
+ }
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ convertLinks: function(html)
|
|
|
+ {
|
|
|
+ var matches = html.match(this.opts.linkify.regexps.url);
|
|
|
+
|
|
|
+ if (matches)
|
|
|
+ {
|
|
|
+ matches = $.grep(matches, function(v, k) { return $.inArray(v, matches) === k; });
|
|
|
+
|
|
|
+ var length = matches.length;
|
|
|
+
|
|
|
+ for (var i = 0; i < length; i++)
|
|
|
+ {
|
|
|
+ var href = matches[i],
|
|
|
+ text = href,
|
|
|
+ linkProtocol = this.opts.linkProtocol + '://';
|
|
|
+
|
|
|
+ if (href.match(/(https?|ftp):\/\//i) !== null)
|
|
|
+ {
|
|
|
+ linkProtocol = "";
|
|
|
+ }
|
|
|
+
|
|
|
+ if (text.length > this.opts.linkSize)
|
|
|
+ {
|
|
|
+ text = text.substring(0, this.opts.linkSize) + '...';
|
|
|
+ }
|
|
|
+
|
|
|
+ if (text.search('%') === -1)
|
|
|
+ {
|
|
|
+ text = decodeURIComponent(text);
|
|
|
+ }
|
|
|
+
|
|
|
+ var regexB = "\\b";
|
|
|
+
|
|
|
+ if ($.inArray(href.slice(-1), ["/", "&", "="]) != -1)
|
|
|
+ {
|
|
|
+ regexB = "";
|
|
|
+ }
|
|
|
+
|
|
|
+ // escaping url
|
|
|
+ var regexp = new RegExp('(' + href.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") + regexB + ')', 'g');
|
|
|
+
|
|
|
+ html = html.replace(regexp, '<a href="' + linkProtocol + $.trim(href) + '" class="redactor-linkify-object">' + $.trim(text) + '</a>');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return html;
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ list: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ toggle: function(cmd)
|
|
|
+ {
|
|
|
+ this.placeholder.remove();
|
|
|
+
|
|
|
+ if (!this.utils.browser('msie') && !this.opts.linebreaks)
|
|
|
+ {
|
|
|
+ this.$editor.focus();
|
|
|
+ }
|
|
|
+
|
|
|
+ this.buffer.set();
|
|
|
+ this.selection.save();
|
|
|
+
|
|
|
+ var parent = this.selection.getParent();
|
|
|
+ var $list = $(parent).closest('ol, ul', this.$editor[0]);
|
|
|
+
|
|
|
+ if (!this.utils.isRedactorParent($list) && $list.length !== 0)
|
|
|
+ {
|
|
|
+ $list = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ var isUnorderedCmdOrdered, isOrderedCmdUnordered;
|
|
|
+ var remove = false;
|
|
|
+ if ($list && $list.length)
|
|
|
+ {
|
|
|
+ remove = true;
|
|
|
+ var listTag = $list[0].tagName;
|
|
|
+
|
|
|
+ isUnorderedCmdOrdered = (cmd === 'orderedlist' && listTag === 'UL');
|
|
|
+ isOrderedCmdUnordered = (cmd === 'unorderedlist' && listTag === 'OL');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isUnorderedCmdOrdered)
|
|
|
+ {
|
|
|
+ this.utils.replaceToTag($list, 'ol');
|
|
|
+ }
|
|
|
+ else if (isOrderedCmdUnordered)
|
|
|
+ {
|
|
|
+ this.utils.replaceToTag($list, 'ul');
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (remove)
|
|
|
+ {
|
|
|
+ this.list.remove(cmd, $list);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.list.insert(cmd);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this.selection.restore();
|
|
|
+ this.code.sync();
|
|
|
+
|
|
|
+ },
|
|
|
+ insert: function(cmd)
|
|
|
+ {
|
|
|
+ var current = this.selection.getCurrent();
|
|
|
+ var $td = $(current).closest('td, th', this.$editor[0]);
|
|
|
+
|
|
|
+ if (this.utils.browser('msie') && this.opts.linebreaks)
|
|
|
+ {
|
|
|
+ this.list.insertInIe(cmd);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ document.execCommand('insert' + cmd);
|
|
|
+ }
|
|
|
+
|
|
|
+ var parent = this.selection.getParent();
|
|
|
+ var $list = $(parent).closest('ol, ul', this.$editor[0]);
|
|
|
+ if ($td.length !== 0)
|
|
|
+ {
|
|
|
+ var newTd = $td.clone();
|
|
|
+ $td.after(newTd).remove('');
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if (this.utils.isEmpty($list.find('li').text()))
|
|
|
+ {
|
|
|
+ var $children = $list.children('li');
|
|
|
+ $children.find('br').remove();
|
|
|
+ $children.append(this.selection.getMarkerAsHtml());
|
|
|
+
|
|
|
+ if (this.opts.linebreaks && this.utils.browser('mozilla') && $children.size() == 2 && this.utils.isEmpty($children.eq(1).text()))
|
|
|
+ {
|
|
|
+ $children.eq(1).remove();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($list.length)
|
|
|
+ {
|
|
|
+ // remove block-element list wrapper
|
|
|
+ var $listParent = $list.parent();
|
|
|
+ if (this.utils.isRedactorParent($listParent) && $listParent[0].tagName != 'LI' && this.utils.isBlock($listParent[0]))
|
|
|
+ {
|
|
|
+ $listParent.replaceWith($listParent.contents());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!this.utils.browser('msie'))
|
|
|
+ {
|
|
|
+ this.$editor.focus();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ this.clean.clearUnverified();
|
|
|
+ },
|
|
|
+ insertInIe: function(cmd)
|
|
|
+ {
|
|
|
+ var wrapper = this.selection.wrap('div');
|
|
|
+ var wrapperHtml = $(wrapper).html();
|
|
|
+
|
|
|
+ var tmpList = (cmd == 'orderedlist') ? $('<ol>') : $('<ul>');
|
|
|
+ var tmpLi = $('<li>');
|
|
|
+
|
|
|
+ if ($.trim(wrapperHtml) === '')
|
|
|
+ {
|
|
|
+ tmpLi.append(this.selection.getMarkerAsHtml());
|
|
|
+ tmpList.append(tmpLi);
|
|
|
+ this.$editor.find('#selection-marker-1').replaceWith(tmpList);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ var items = wrapperHtml.split(/<br\s?\/?>/gi);
|
|
|
+ if (items)
|
|
|
+ {
|
|
|
+ for (var i = 0; i < items.length; i++)
|
|
|
+ {
|
|
|
+ if ($.trim(items[i]) !== '')
|
|
|
+ {
|
|
|
+ tmpList.append($('<li>').html(items[i]));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ tmpLi.append(wrapperHtml);
|
|
|
+ tmpList.append(tmpLi);
|
|
|
+ }
|
|
|
+
|
|
|
+ $(wrapper).replaceWith(tmpList);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ remove: function(cmd, $list)
|
|
|
+ {
|
|
|
+ if ($.inArray('ul', this.selection.getBlocks())) cmd = 'unorderedlist';
|
|
|
+
|
|
|
+ document.execCommand('insert' + cmd);
|
|
|
+
|
|
|
+ var $current = $(this.selection.getCurrent());
|
|
|
+ this.indent.fixEmptyIndent();
|
|
|
+
|
|
|
+ if (!this.opts.linebreaks && $current.closest('li, th, td', this.$editor[0]).length === 0)
|
|
|
+ {
|
|
|
+ document.execCommand('formatblock', false, 'p');
|
|
|
+ this.$editor.find('ul, ol, blockquote').each($.proxy(this.utils.removeEmpty, this));
|
|
|
+ }
|
|
|
+
|
|
|
+ var $table = $(this.selection.getCurrent()).closest('table', this.$editor[0]);
|
|
|
+ var $prev = $table.prev();
|
|
|
+ if (!this.opts.linebreaks && $table.length !== 0 && $prev.length !== 0 && $prev[0].tagName == 'BR')
|
|
|
+ {
|
|
|
+ $prev.remove();
|
|
|
+ }
|
|
|
+
|
|
|
+ this.clean.clearUnverified();
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ modal: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ callbacks: {},
|
|
|
+ loadTemplates: function()
|
|
|
+ {
|
|
|
+ this.opts.modal = {
|
|
|
+ imageEdit: String()
|
|
|
+ + '<section id="redactor-modal-image-edit">'
|
|
|
+ + '<label>' + this.lang.get('title') + '</label>'
|
|
|
+ + '<input type="text" id="redactor-image-title" />'
|
|
|
+ + '<label class="redactor-image-link-option">' + this.lang.get('link') + '</label>'
|
|
|
+ + '<input type="text" id="redactor-image-link" class="redactor-image-link-option" aria-label="' + this.lang.get('link') + '" />'
|
|
|
+ + '<label class="redactor-image-link-option"><input type="checkbox" id="redactor-image-link-blank" aria-label="' + this.lang.get('link_new_tab') + '"> ' + this.lang.get('link_new_tab') + '</label>'
|
|
|
+ + '<label class="redactor-image-position-option">' + this.lang.get('image_position') + '</label>'
|
|
|
+ + '<select class="redactor-image-position-option" id="redactor-image-align" aria-label="' + this.lang.get('image_position') + '">'
|
|
|
+ + '<option value="none">' + this.lang.get('none') + '</option>'
|
|
|
+ + '<option value="left">' + this.lang.get('left') + '</option>'
|
|
|
+ + '<option value="center">' + this.lang.get('center') + '</option>'
|
|
|
+ + '<option value="right">' + this.lang.get('right') + '</option>'
|
|
|
+ + '</select>'
|
|
|
+ + '</section>',
|
|
|
+
|
|
|
+ image: String()
|
|
|
+ + '<section id="redactor-modal-image-insert">'
|
|
|
+ + '<div id="redactor-modal-image-droparea"></div>'
|
|
|
+ + '</section>',
|
|
|
+
|
|
|
+ file: String()
|
|
|
+ + '<section id="redactor-modal-file-insert">'
|
|
|
+ + '<div id="redactor-modal-file-upload-box">'
|
|
|
+ + '<label>' + this.lang.get('filename') + '</label>'
|
|
|
+ + '<input type="text" id="redactor-filename" aria-label="' + this.lang.get('filename') + '" /><br><br>'
|
|
|
+ + '<div id="redactor-modal-file-upload"></div>'
|
|
|
+ + '</div>'
|
|
|
+ + '</section>',
|
|
|
+
|
|
|
+ link: String()
|
|
|
+ + '<section id="redactor-modal-link-insert">'
|
|
|
+ + '<label>URL</label>'
|
|
|
+ + '<input type="url" id="redactor-link-url" aria-label="URL" />'
|
|
|
+ + '<label>' + this.lang.get('text') + '</label>'
|
|
|
+ + '<input type="text" id="redactor-link-url-text" aria-label="' + this.lang.get('text') + '" />'
|
|
|
+ + '<label><input type="checkbox" id="redactor-link-blank"> ' + this.lang.get('link_new_tab') + '</label>'
|
|
|
+ + '</section>'
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ $.extend(this.opts, this.opts.modal);
|
|
|
+
|
|
|
+ },
|
|
|
+ addCallback: function(name, callback)
|
|
|
+ {
|
|
|
+ this.modal.callbacks[name] = callback;
|
|
|
+ },
|
|
|
+ createTabber: function($modal)
|
|
|
+ {
|
|
|
+ this.modal.$tabber = $('<div>').attr('id', 'redactor-modal-tabber');
|
|
|
+
|
|
|
+ $modal.prepend(this.modal.$tabber);
|
|
|
+ },
|
|
|
+ addTab: function(id, name, active)
|
|
|
+ {
|
|
|
+ var $tab = $('<a href="#" rel="tab' + id + '">').text(name);
|
|
|
+ if (active)
|
|
|
+ {
|
|
|
+ $tab.addClass('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ var self = this;
|
|
|
+ $tab.on('click', function(e)
|
|
|
+ {
|
|
|
+ e.preventDefault();
|
|
|
+ $('.redactor-tab').hide();
|
|
|
+ $('.redactor-' + $(this).attr('rel')).show();
|
|
|
+
|
|
|
+ self.modal.$tabber.find('a').removeClass('active');
|
|
|
+ $(this).addClass('active');
|
|
|
+
|
|
|
+ });
|
|
|
+
|
|
|
+ this.modal.$tabber.append($tab);
|
|
|
+ },
|
|
|
+ addTemplate: function(name, template)
|
|
|
+ {
|
|
|
+ this.opts.modal[name] = template;
|
|
|
+ },
|
|
|
+ getTemplate: function(name)
|
|
|
+ {
|
|
|
+ return this.opts.modal[name];
|
|
|
+ },
|
|
|
+ getModal: function()
|
|
|
+ {
|
|
|
+ return this.$modalBody.find('section');
|
|
|
+ },
|
|
|
+ load: function(templateName, title, width)
|
|
|
+ {
|
|
|
+ this.modal.templateName = templateName;
|
|
|
+ this.modal.width = width;
|
|
|
+
|
|
|
+ this.modal.build();
|
|
|
+ this.modal.enableEvents();
|
|
|
+ this.modal.setTitle(title);
|
|
|
+ this.modal.setDraggable();
|
|
|
+ this.modal.setContent();
|
|
|
+
|
|
|
+ // callbacks
|
|
|
+ if (typeof this.modal.callbacks[templateName] != 'undefined')
|
|
|
+ {
|
|
|
+ this.modal.callbacks[templateName].call(this);
|
|
|
+ }
|
|
|
+
|
|
|
+ },
|
|
|
+ show: function()
|
|
|
+ {
|
|
|
+ this.utils.disableBodyScroll();
|
|
|
+
|
|
|
+ if (this.utils.isMobile())
|
|
|
+ {
|
|
|
+ this.modal.showOnMobile();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.modal.showOnDesktop();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.opts.highContrast)
|
|
|
+ {
|
|
|
+ this.$modalBox.addClass("redactor-modal-contrast");
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$modalOverlay.show();
|
|
|
+ this.$modalBox.show();
|
|
|
+
|
|
|
+ this.$modal.attr('tabindex', '-1');
|
|
|
+ this.$modal.focus();
|
|
|
+
|
|
|
+ this.modal.setButtonsWidth();
|
|
|
+
|
|
|
+ this.utils.saveScroll();
|
|
|
+
|
|
|
+ // resize
|
|
|
+ if (!this.utils.isMobile())
|
|
|
+ {
|
|
|
+ setTimeout($.proxy(this.modal.showOnDesktop, this), 0);
|
|
|
+ $(window).on('resize.redactor-modal', $.proxy(this.modal.resize, this));
|
|
|
+ }
|
|
|
+
|
|
|
+ // modal shown callback
|
|
|
+ this.core.setCallback('modalOpened', this.modal.templateName, this.$modal);
|
|
|
+
|
|
|
+ // fix bootstrap modal focus
|
|
|
+ $(document).off('focusin.modal');
|
|
|
+
|
|
|
+ // enter
|
|
|
+ this.$modal.find('input[type=text],input[type=url],input[type=email]').on('keydown.redactor-modal', $.proxy(this.modal.setEnter, this));
|
|
|
+ },
|
|
|
+ showOnDesktop: function()
|
|
|
+ {
|
|
|
+ var height = this.$modal.outerHeight();
|
|
|
+ var windowHeight = $(window).height();
|
|
|
+ var windowWidth = $(window).width();
|
|
|
+
|
|
|
+ if (this.modal.width > windowWidth)
|
|
|
+ {
|
|
|
+ this.$modal.css({
|
|
|
+ width: '96%',
|
|
|
+ marginTop: (windowHeight/2 - height/2) + 'px'
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (height > windowHeight)
|
|
|
+ {
|
|
|
+ this.$modal.css({
|
|
|
+ width: this.modal.width + 'px',
|
|
|
+ marginTop: '20px'
|
|
|
+ });
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.$modal.css({
|
|
|
+ width: this.modal.width + 'px',
|
|
|
+ marginTop: (windowHeight/2 - height/2) + 'px'
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ showOnMobile: function()
|
|
|
+ {
|
|
|
+ this.$modal.css({
|
|
|
+ width: '96%',
|
|
|
+ marginTop: '2%'
|
|
|
+ });
|
|
|
+
|
|
|
+ },
|
|
|
+ resize: function()
|
|
|
+ {
|
|
|
+ if (this.utils.isMobile())
|
|
|
+ {
|
|
|
+ this.modal.showOnMobile();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.modal.showOnDesktop();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ setTitle: function(title)
|
|
|
+ {
|
|
|
+ this.$modalHeader.html(title);
|
|
|
+ },
|
|
|
+ setContent: function()
|
|
|
+ {
|
|
|
+ this.$modalBody.html(this.modal.getTemplate(this.modal.templateName));
|
|
|
+ },
|
|
|
+ setDraggable: function()
|
|
|
+ {
|
|
|
+ if (typeof $.fn.draggable === 'undefined') return;
|
|
|
+
|
|
|
+ this.$modal.draggable({ handle: this.$modalHeader });
|
|
|
+ this.$modalHeader.css('cursor', 'move');
|
|
|
+ },
|
|
|
+ setEnter: function(e)
|
|
|
+ {
|
|
|
+ if (e.which != 13) return;
|
|
|
+
|
|
|
+ e.preventDefault();
|
|
|
+ this.$modal.find('button.redactor-modal-action-btn').click();
|
|
|
+ },
|
|
|
+ createCancelButton: function()
|
|
|
+ {
|
|
|
+ var button = $('<button>').addClass('redactor-modal-btn redactor-modal-close-btn').html(this.lang.get('cancel'));
|
|
|
+ button.on('click', $.proxy(this.modal.close, this));
|
|
|
+
|
|
|
+ this.$modalFooter.append(button);
|
|
|
+ },
|
|
|
+ createDeleteButton: function(label)
|
|
|
+ {
|
|
|
+ return this.modal.createButton(label, 'delete');
|
|
|
+ },
|
|
|
+ createActionButton: function(label)
|
|
|
+ {
|
|
|
+ return this.modal.createButton(label, 'action');
|
|
|
+ },
|
|
|
+ createButton: function(label, className)
|
|
|
+ {
|
|
|
+ var button = $('<button>').addClass('redactor-modal-btn').addClass('redactor-modal-' + className + '-btn').html(label);
|
|
|
+ this.$modalFooter.append(button);
|
|
|
+
|
|
|
+ return button;
|
|
|
+ },
|
|
|
+ setButtonsWidth: function()
|
|
|
+ {
|
|
|
+ var buttons = this.$modalFooter.find('button');
|
|
|
+ var buttonsSize = buttons.length;
|
|
|
+ if (buttonsSize === 0) return;
|
|
|
+
|
|
|
+ buttons.css('width', (100/buttonsSize) + '%');
|
|
|
+ },
|
|
|
+ build: function()
|
|
|
+ {
|
|
|
+ this.modal.buildOverlay();
|
|
|
+
|
|
|
+ this.$modalBox = $('<div id="redactor-modal-box"/>').hide();
|
|
|
+ this.$modal = $('<div id="redactor-modal" role="dialog" aria-labelledby="redactor-modal-header" />');
|
|
|
+ this.$modalHeader = $('<header id="redactor-modal-header"/>');
|
|
|
+ this.$modalClose = $('<button type="button" id="redactor-modal-close" tabindex="1" aria-label="Close" />').html('×');
|
|
|
+ this.$modalBody = $('<div id="redactor-modal-body" />');
|
|
|
+ this.$modalFooter = $('<footer />');
|
|
|
+
|
|
|
+ this.$modal.append(this.$modalHeader);
|
|
|
+ this.$modal.append(this.$modalClose);
|
|
|
+ this.$modal.append(this.$modalBody);
|
|
|
+ this.$modal.append(this.$modalFooter);
|
|
|
+ this.$modalBox.append(this.$modal);
|
|
|
+ this.$modalBox.appendTo(document.body);
|
|
|
+ },
|
|
|
+ buildOverlay: function()
|
|
|
+ {
|
|
|
+ this.$modalOverlay = $('<div id="redactor-modal-overlay">').hide();
|
|
|
+ $('body').prepend(this.$modalOverlay);
|
|
|
+ },
|
|
|
+ enableEvents: function()
|
|
|
+ {
|
|
|
+ this.$modalClose.on('click.redactor-modal', $.proxy(this.modal.close, this));
|
|
|
+ $(document).on('keyup.redactor-modal', $.proxy(this.modal.closeHandler, this));
|
|
|
+ this.$editor.on('keyup.redactor-modal', $.proxy(this.modal.closeHandler, this));
|
|
|
+ this.$modalBox.on('click.redactor-modal', $.proxy(this.modal.close, this));
|
|
|
+ },
|
|
|
+ disableEvents: function()
|
|
|
+ {
|
|
|
+ this.$modalClose.off('click.redactor-modal');
|
|
|
+ $(document).off('keyup.redactor-modal');
|
|
|
+ this.$editor.off('keyup.redactor-modal');
|
|
|
+ this.$modalBox.off('click.redactor-modal');
|
|
|
+ $(window).off('resize.redactor-modal');
|
|
|
+ },
|
|
|
+ closeHandler: function(e)
|
|
|
+ {
|
|
|
+ if (e.which != this.keyCode.ESC) return;
|
|
|
+
|
|
|
+ this.modal.close(false);
|
|
|
+ },
|
|
|
+ close: function(e)
|
|
|
+ {
|
|
|
+ if (e)
|
|
|
+ {
|
|
|
+ if (!$(e.target).hasClass('redactor-modal-close-btn') && e.target != this.$modalClose[0] && e.target != this.$modalBox[0])
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ e.preventDefault();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!this.$modalBox) return;
|
|
|
+
|
|
|
+ this.modal.disableEvents();
|
|
|
+ this.utils.enableBodyScroll();
|
|
|
+
|
|
|
+ this.$modalOverlay.remove();
|
|
|
+
|
|
|
+ this.$modalBox.fadeOut('fast', $.proxy(function()
|
|
|
+ {
|
|
|
+ this.$modalBox.remove();
|
|
|
+
|
|
|
+ setTimeout($.proxy(this.utils.restoreScroll, this), 0);
|
|
|
+
|
|
|
+ if (e !== undefined) this.selection.restore();
|
|
|
+
|
|
|
+ $(document.body).css('overflow', this.modal.bodyOveflow);
|
|
|
+ this.core.setCallback('modalClosed', this.modal.templateName);
|
|
|
+
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ observe: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ load: function()
|
|
|
+ {
|
|
|
+ if (typeof this.opts.destroyed != "undefined") return;
|
|
|
+
|
|
|
+ if (this.utils.browser('msie'))
|
|
|
+ {
|
|
|
+ var self = this;
|
|
|
+ this.$editor.find('pre, code').on('mouseover',function()
|
|
|
+ {
|
|
|
+ self.$editor.attr('contenteditable', false);
|
|
|
+ $(this).attr('contenteditable', true);
|
|
|
+
|
|
|
+ }).on('mouseout',function()
|
|
|
+ {
|
|
|
+ self.$editor.attr('contenteditable', true);
|
|
|
+ $(this).removeAttr('contenteditable');
|
|
|
+
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ this.observe.images();
|
|
|
+ this.observe.links();
|
|
|
+ },
|
|
|
+ toolbar: function(e, btnName)
|
|
|
+ {
|
|
|
+ this.observe.buttons(e, btnName);
|
|
|
+ this.observe.dropdowns();
|
|
|
+ },
|
|
|
+ isCurrent: function($el, $current)
|
|
|
+ {
|
|
|
+ if (typeof $current == 'undefined')
|
|
|
+ {
|
|
|
+ var $current = $(this.selection.getCurrent());
|
|
|
+ }
|
|
|
+
|
|
|
+ return $current.is($el) || $current.parents($el).length > 0;
|
|
|
+ },
|
|
|
+ dropdowns: function()
|
|
|
+ {
|
|
|
+ var $current = $(this.selection.getCurrent());
|
|
|
+
|
|
|
+ $.each(this.opts.observe.dropdowns, $.proxy(function(key, value)
|
|
|
+ {
|
|
|
+ var observe = value.observe,
|
|
|
+ element = observe.element,
|
|
|
+ $item = value.item,
|
|
|
+ inValues = typeof observe['in'] != 'undefined' ? observe['in'] : false,
|
|
|
+ outValues = typeof observe['out'] != 'undefined' ? observe['out'] : false;
|
|
|
+
|
|
|
+ if ($current.closest(element).size() > 0)
|
|
|
+ {
|
|
|
+ this.observe.setDropdownProperties($item, inValues, outValues);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.observe.setDropdownProperties($item, outValues, inValues);
|
|
|
+ }
|
|
|
+ }, this));
|
|
|
+ },
|
|
|
+ setDropdownProperties: function($item, addProperties, deleteProperties)
|
|
|
+ {
|
|
|
+ if (deleteProperties && typeof deleteProperties['attr'] != 'undefined')
|
|
|
+ {
|
|
|
+ this.observe.setDropdownAttr($item, deleteProperties.attr, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (typeof addProperties['attr'] != 'undefined')
|
|
|
+ {
|
|
|
+ this.observe.setDropdownAttr($item, addProperties.attr);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (typeof addProperties['title'] != 'undefined')
|
|
|
+ {
|
|
|
+ $item.text(addProperties['title']);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ setDropdownAttr: function($item, properties, isDelete)
|
|
|
+ {
|
|
|
+ $.each(properties, function(key, value)
|
|
|
+ {
|
|
|
+ if (key == 'class')
|
|
|
+ {
|
|
|
+ if (!isDelete)
|
|
|
+ {
|
|
|
+ $item.addClass(value);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ $item.removeClass(value);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (!isDelete)
|
|
|
+ {
|
|
|
+ $item.attr(key, value);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ $item.removeAttr(key);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ addDropdown: function($item, btnName, btnObject)
|
|
|
+ {
|
|
|
+ if (typeof btnObject.observe == "undefined") return;
|
|
|
+
|
|
|
+ btnObject.item = $item;
|
|
|
+
|
|
|
+ this.opts.observe.dropdowns.push(btnObject);
|
|
|
+ },
|
|
|
+ buttons: function(e, btnName)
|
|
|
+ {
|
|
|
+ var current = this.selection.getCurrent();
|
|
|
+ var parent = this.selection.getParent();
|
|
|
+
|
|
|
+ if (e !== false)
|
|
|
+ {
|
|
|
+ this.button.setInactiveAll();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.button.setInactiveAll(btnName);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (e === false && btnName !== 'html')
|
|
|
+ {
|
|
|
+ if ($.inArray(btnName, this.opts.activeButtons) != -1) this.button.toggleActive(btnName);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ //var linkButtonName = (this.utils.isCurrentOrParent('A')) ? this.lang.get('link_edit') : this.lang.get('link_insert');
|
|
|
+ //$('body').find('a.redactor-dropdown-link').text(linkButtonName);
|
|
|
+
|
|
|
+ $.each(this.opts.activeButtonsStates, $.proxy(function(key, value)
|
|
|
+ {
|
|
|
+ var parentEl = $(parent).closest(key, this.$editor[0]);
|
|
|
+ var currentEl = $(current).closest(key, this.$editor[0]);
|
|
|
+
|
|
|
+ if (parentEl.length !== 0 && !this.utils.isRedactorParent(parentEl)) return;
|
|
|
+ if (!this.utils.isRedactorParent(currentEl)) return;
|
|
|
+ if (parentEl.length !== 0 || currentEl.closest(key, this.$editor[0]).length !== 0)
|
|
|
+ {
|
|
|
+ this.button.setActive(value);
|
|
|
+ }
|
|
|
+
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ var $parent = $(parent).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
|
|
|
+ if (this.utils.isRedactorParent(parent) && $parent.length)
|
|
|
+ {
|
|
|
+ var align = ($parent.css('text-align') === '') ? 'left' : $parent.css('text-align');
|
|
|
+ this.button.setActive('align' + align);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ addButton: function(tag, btnName)
|
|
|
+ {
|
|
|
+ this.opts.activeButtons.push(btnName);
|
|
|
+ this.opts.activeButtonsStates[tag] = btnName;
|
|
|
+ },
|
|
|
+ images: function()
|
|
|
+ {
|
|
|
+ this.$editor.find('img').each($.proxy(function(i, img)
|
|
|
+ {
|
|
|
+ var $img = $(img);
|
|
|
+
|
|
|
+ // IE fix (when we clicked on an image and then press backspace IE does goes to image's url)
|
|
|
+ $img.closest('a', this.$editor[0]).on('click', function(e) { e.preventDefault(); });
|
|
|
+
|
|
|
+ if (this.utils.browser('msie')) $img.attr('unselectable', 'on');
|
|
|
+
|
|
|
+ this.image.setEditable($img);
|
|
|
+
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ $(document).on('click.redactor-image-delete.' + this.uuid, $.proxy(function(e)
|
|
|
+ {
|
|
|
+ this.observe.image = false;
|
|
|
+ if (e.target.tagName == 'IMG' && this.utils.isRedactorParent(e.target))
|
|
|
+ {
|
|
|
+ this.observe.image = (this.observe.image && this.observe.image == e.target) ? false : e.target;
|
|
|
+ }
|
|
|
+
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ },
|
|
|
+ links: function()
|
|
|
+ {
|
|
|
+ if (!this.opts.linkTooltip) return;
|
|
|
+
|
|
|
+ this.$editor.find('a').on('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid, $.proxy(this.observe.showTooltip, this));
|
|
|
+ this.$editor.on('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid, $.proxy(this.observe.closeTooltip, this));
|
|
|
+ $(document).on('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid, $.proxy(this.observe.closeTooltip, this));
|
|
|
+ },
|
|
|
+ getTooltipPosition: function($link)
|
|
|
+ {
|
|
|
+ return $link.offset();
|
|
|
+ },
|
|
|
+ showTooltip: function(e)
|
|
|
+ {
|
|
|
+ var $el = $(e.target);
|
|
|
+
|
|
|
+ if ($el[0].tagName == 'IMG')
|
|
|
+ return;
|
|
|
+
|
|
|
+ if ($el[0].tagName !== 'A')
|
|
|
+ $el = $el.closest('a', this.$editor[0]);
|
|
|
+
|
|
|
+ if ($el[0].tagName !== 'A')
|
|
|
+ return;
|
|
|
+
|
|
|
+ var $link = $el;
|
|
|
+
|
|
|
+ var pos = this.observe.getTooltipPosition($link);
|
|
|
+ var tooltip = $('<span class="redactor-link-tooltip"></span>');
|
|
|
+
|
|
|
+ var href = $link.attr('href');
|
|
|
+ if (href === undefined)
|
|
|
+ {
|
|
|
+ href = '';
|
|
|
+ }
|
|
|
+
|
|
|
+ if (href.length > 24) href = href.substring(0, 24) + '...';
|
|
|
+
|
|
|
+ var aLink = $('<a href="' + $link.attr('href') + '" target="_blank" />').html(href).addClass('redactor-link-tooltip-action');
|
|
|
+ var aEdit = $('<a href="#" />').html(this.lang.get('edit')).on('click', $.proxy(this.link.show, this)).addClass('redactor-link-tooltip-action');
|
|
|
+ var aUnlink = $('<a href="#" />').html(this.lang.get('unlink')).on('click', $.proxy(this.link.unlink, this)).addClass('redactor-link-tooltip-action');
|
|
|
+
|
|
|
+ tooltip.append(aLink).append(' | ').append(aEdit).append(' | ').append(aUnlink);
|
|
|
+ tooltip.css({
|
|
|
+ top: (pos.top + parseInt($link.css('line-height'), 10)) + 'px',
|
|
|
+ left: pos.left + 'px'
|
|
|
+ });
|
|
|
+
|
|
|
+ $('.redactor-link-tooltip').remove();
|
|
|
+ $('body').append(tooltip);
|
|
|
+ },
|
|
|
+ closeTooltip: function(e)
|
|
|
+ {
|
|
|
+ e = e.originalEvent || e;
|
|
|
+
|
|
|
+ var target = e.target;
|
|
|
+ var $parent = $(target).closest('a', this.$editor[0]);
|
|
|
+ if ($parent.length !== 0 && $parent[0].tagName === 'A' && target.tagName !== 'A')
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ else if ((target.tagName === 'A' && this.utils.isRedactorParent(target)) || $(target).hasClass('redactor-link-tooltip-action'))
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ $('.redactor-link-tooltip').remove();
|
|
|
+ }
|
|
|
+
|
|
|
+ };
|
|
|
+ },
|
|
|
+ paragraphize: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ load: function(html)
|
|
|
+ {
|
|
|
+ if (this.opts.linebreaks) return html;
|
|
|
+ if (html === '' || html === '<p></p>') return this.opts.emptyHtml;
|
|
|
+
|
|
|
+ html = html + "\n";
|
|
|
+
|
|
|
+ this.paragraphize.safes = [];
|
|
|
+ this.paragraphize.z = 0;
|
|
|
+
|
|
|
+ html = html.replace(/(<br\s?\/?>){1,}\n?<\/blockquote>/gi, '</blockquote>');
|
|
|
+
|
|
|
+ html = this.paragraphize.getSafes(html);
|
|
|
+ html = this.paragraphize.getSafesComments(html);
|
|
|
+ html = this.paragraphize.replaceBreaksToNewLines(html);
|
|
|
+ html = this.paragraphize.replaceBreaksToParagraphs(html);
|
|
|
+ html = this.paragraphize.clear(html);
|
|
|
+ html = this.paragraphize.restoreSafes(html);
|
|
|
+
|
|
|
+ html = html.replace(new RegExp('<br\\s?/?>\n?<(' + this.opts.paragraphizeBlocks.join('|') + ')(.*?[^>])>', 'gi'), '<p><br /></p>\n<$1$2>');
|
|
|
+
|
|
|
+ return $.trim(html);
|
|
|
+ },
|
|
|
+ getSafes: function(html)
|
|
|
+ {
|
|
|
+ var $div = $('<div />').append(html);
|
|
|
+
|
|
|
+ // remove paragraphs in blockquotes
|
|
|
+ $div.find('blockquote p').replaceWith(function()
|
|
|
+ {
|
|
|
+ return $(this).append('<br />').contents();
|
|
|
+ });
|
|
|
+
|
|
|
+ html = $div.html();
|
|
|
+
|
|
|
+ $div.find(this.opts.paragraphizeBlocks.join(', ')).each($.proxy(function(i,s)
|
|
|
+ {
|
|
|
+ this.paragraphize.z++;
|
|
|
+ this.paragraphize.safes[this.paragraphize.z] = s.outerHTML;
|
|
|
+ html = html.replace(s.outerHTML, '\n{replace' + this.paragraphize.z + '}');
|
|
|
+
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ getSafesComments: function(html)
|
|
|
+ {
|
|
|
+ var commentsMatches = html.match(/<!--([\w\W]*?)-->/gi);
|
|
|
+
|
|
|
+ if (!commentsMatches) return html;
|
|
|
+
|
|
|
+ $.each(commentsMatches, $.proxy(function(i,s)
|
|
|
+ {
|
|
|
+ this.paragraphize.z++;
|
|
|
+ this.paragraphize.safes[this.paragraphize.z] = s;
|
|
|
+ html = html.replace(s, '\n{replace' + this.paragraphize.z + '}');
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ restoreSafes: function(html)
|
|
|
+ {
|
|
|
+ $.each(this.paragraphize.safes, function(i,s)
|
|
|
+ {
|
|
|
+ s = (typeof s !== 'undefined') ? s.replace(/\$/g, '$') : s;
|
|
|
+ html = html.replace('{replace' + i + '}', s);
|
|
|
+
|
|
|
+ });
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ replaceBreaksToParagraphs: function(html)
|
|
|
+ {
|
|
|
+ var htmls = html.split(new RegExp('\n', 'g'), -1);
|
|
|
+
|
|
|
+ html = '';
|
|
|
+ if (htmls)
|
|
|
+ {
|
|
|
+ var len = htmls.length;
|
|
|
+ for (var i = 0; i < len; i++)
|
|
|
+ {
|
|
|
+ if (!htmls.hasOwnProperty(i)) return;
|
|
|
+
|
|
|
+ if (htmls[i].search('{replace') == -1)
|
|
|
+ {
|
|
|
+ htmls[i] = htmls[i].replace(/<p>\n\t?<\/p>/gi, '');
|
|
|
+ htmls[i] = htmls[i].replace(/<p><\/p>/gi, '');
|
|
|
+
|
|
|
+ if (htmls[i] !== '')
|
|
|
+ {
|
|
|
+ html += '<p>' + htmls[i].replace(/^\n+|\n+$/g, "") + "</p>";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else html += htmls[i];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ replaceBreaksToNewLines: function(html)
|
|
|
+ {
|
|
|
+ html = html.replace(/<br \/>\s*<br \/>/gi, "\n\n");
|
|
|
+ html = html.replace(/<br\s?\/?>\n?<br\s?\/?>/gi, "\n<br /><br />");
|
|
|
+
|
|
|
+ html = html.replace(new RegExp("\r\n", 'g'), "\n");
|
|
|
+ html = html.replace(new RegExp("\r", 'g'), "\n");
|
|
|
+ html = html.replace(new RegExp("/\n\n+/"), 'g', "\n\n");
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ clear: function(html)
|
|
|
+ {
|
|
|
+ html = html.replace(new RegExp('</blockquote></p>', 'gi'), '</blockquote>');
|
|
|
+ html = html.replace(new RegExp('<p></blockquote>', 'gi'), '</blockquote>');
|
|
|
+ html = html.replace(new RegExp('<p><blockquote>', 'gi'), '<blockquote>');
|
|
|
+ html = html.replace(new RegExp('<blockquote></p>', 'gi'), '<blockquote>');
|
|
|
+
|
|
|
+ html = html.replace(new RegExp('<p><p ', 'gi'), '<p ');
|
|
|
+ html = html.replace(new RegExp('<p><p>', 'gi'), '<p>');
|
|
|
+ html = html.replace(new RegExp('</p></p>', 'gi'), '</p>');
|
|
|
+ html = html.replace(new RegExp('<p>\\s?</p>', 'gi'), '');
|
|
|
+ html = html.replace(new RegExp("\n</p>", 'gi'), '</p>');
|
|
|
+ html = html.replace(new RegExp('<p>\t?\t?\n?<p>', 'gi'), '<p>');
|
|
|
+ html = html.replace(new RegExp('<p>\t*</p>', 'gi'), '');
|
|
|
+
|
|
|
+ return html;
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ paste: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ init: function(e)
|
|
|
+ {
|
|
|
+ if (!this.opts.cleanOnPaste)
|
|
|
+ {
|
|
|
+ setTimeout($.proxy(this.code.sync, this), 1);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.rtePaste = true;
|
|
|
+
|
|
|
+ this.buffer.set();
|
|
|
+ this.selection.save();
|
|
|
+ this.utils.saveScroll();
|
|
|
+
|
|
|
+ this.paste.createPasteBox();
|
|
|
+
|
|
|
+ $(window).on('scroll.redactor-freeze', $.proxy(function()
|
|
|
+ {
|
|
|
+ $(window).scrollTop(this.saveBodyScroll);
|
|
|
+
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ setTimeout($.proxy(function()
|
|
|
+ {
|
|
|
+ var html = this.$pasteBox.html();
|
|
|
+
|
|
|
+ this.$pasteBox.remove();
|
|
|
+
|
|
|
+ this.selection.restore();
|
|
|
+ this.utils.restoreScroll();
|
|
|
+
|
|
|
+ this.paste.insert(html);
|
|
|
+
|
|
|
+ $(window).off('scroll.redactor-freeze');
|
|
|
+
|
|
|
+ if (this.linkify.isEnabled())
|
|
|
+ {
|
|
|
+ this.linkify.format();
|
|
|
+ }
|
|
|
+
|
|
|
+ }, this), 1);
|
|
|
+ },
|
|
|
+ createPasteBox: function()
|
|
|
+ {
|
|
|
+ this.$pasteBox = $('<div>').html('').attr('contenteditable', 'true').css({ position: 'fixed', width: 0, top: 0, left: '-9999px' });
|
|
|
+
|
|
|
+ if (this.utils.browser('msie'))
|
|
|
+ {
|
|
|
+ this.$box.append(this.$pasteBox);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // bootstrap modal
|
|
|
+ var $visibleModals = $('.modal-body:visible');
|
|
|
+ if ($visibleModals.length > 0)
|
|
|
+ {
|
|
|
+ $visibleModals.append(this.$pasteBox);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ $('body').append(this.$pasteBox);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$pasteBox.get(0).focus();
|
|
|
+ },
|
|
|
+ insert: function(html)
|
|
|
+ {
|
|
|
+ html = this.core.setCallback('pasteBefore', html);
|
|
|
+
|
|
|
+ // clean
|
|
|
+ html = (this.utils.isSelectAll()) ? this.clean.onPaste(html, false) : this.clean.onPaste(html);
|
|
|
+
|
|
|
+ html = this.core.setCallback('paste', html);
|
|
|
+
|
|
|
+ if (this.utils.isSelectAll())
|
|
|
+ {
|
|
|
+ this.insert.set(html, false);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.insert.html(html, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.utils.disableSelectAll();
|
|
|
+ this.rtePaste = false;
|
|
|
+
|
|
|
+ setTimeout($.proxy(this.clean.clearUnverified, this), 10);
|
|
|
+
|
|
|
+ // clean empty spans
|
|
|
+ setTimeout($.proxy(function()
|
|
|
+ {
|
|
|
+ var spans = this.$editor.find('span');
|
|
|
+ $.each(spans, function(i,s)
|
|
|
+ {
|
|
|
+ var html = s.innerHTML.replace(/\u200B/, '');
|
|
|
+ if (html === '' && s.attributes.length === 0) $(s).remove();
|
|
|
+
|
|
|
+ });
|
|
|
+
|
|
|
+ }, this), 10);
|
|
|
+
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ placeholder: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ enable: function()
|
|
|
+ {
|
|
|
+ if (!this.placeholder.is()) return;
|
|
|
+
|
|
|
+ this.$editor.attr('placeholder', this.$element.attr('placeholder'));
|
|
|
+
|
|
|
+ this.placeholder.toggle();
|
|
|
+ this.$editor.on('keydown.redactor-placeholder', $.proxy(this.placeholder.toggle, this));
|
|
|
+ },
|
|
|
+ toggle: function()
|
|
|
+ {
|
|
|
+ setTimeout($.proxy(function()
|
|
|
+ {
|
|
|
+ var func = this.utils.isEmpty(this.$editor.html(), false) ? 'addClass' : 'removeClass';
|
|
|
+ this.$editor[func]('redactor-placeholder');
|
|
|
+
|
|
|
+ }, this), 5);
|
|
|
+ },
|
|
|
+ remove: function()
|
|
|
+ {
|
|
|
+ this.$editor.removeClass('redactor-placeholder');
|
|
|
+ },
|
|
|
+ is: function()
|
|
|
+ {
|
|
|
+ if (this.opts.placeholder)
|
|
|
+ {
|
|
|
+ return this.$element.attr('placeholder', this.opts.placeholder);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return !(typeof this.$element.attr('placeholder') == 'undefined' || this.$element.attr('placeholder') === '');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ progress: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ show: function()
|
|
|
+ {
|
|
|
+ $(document.body).append($('<div id="redactor-progress"><span></span></div>'));
|
|
|
+ $('#redactor-progress').fadeIn();
|
|
|
+ },
|
|
|
+ hide: function()
|
|
|
+ {
|
|
|
+ $('#redactor-progress').fadeOut(1500, function()
|
|
|
+ {
|
|
|
+ $(this).remove();
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ };
|
|
|
+ },
|
|
|
+ selection: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ get: function()
|
|
|
+ {
|
|
|
+ this.sel = document.getSelection();
|
|
|
+
|
|
|
+ if (document.getSelection && this.sel.getRangeAt && this.sel.rangeCount)
|
|
|
+ {
|
|
|
+ this.range = this.sel.getRangeAt(0);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.range = document.createRange();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ addRange: function()
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ this.sel.removeAllRanges();
|
|
|
+ } catch (e) {}
|
|
|
+
|
|
|
+ this.sel.addRange(this.range);
|
|
|
+ },
|
|
|
+ getCurrent: function()
|
|
|
+ {
|
|
|
+ var el = false;
|
|
|
+
|
|
|
+ this.selection.get();
|
|
|
+
|
|
|
+ if (this.sel && this.sel.rangeCount > 0)
|
|
|
+ {
|
|
|
+ el = this.sel.getRangeAt(0).startContainer;
|
|
|
+ }
|
|
|
+
|
|
|
+ return this.utils.isRedactorParent(el);
|
|
|
+ },
|
|
|
+ getParent: function(elem)
|
|
|
+ {
|
|
|
+ elem = elem || this.selection.getCurrent();
|
|
|
+ if (elem)
|
|
|
+ {
|
|
|
+ return this.utils.isRedactorParent($(elem).parent()[0]);
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ },
|
|
|
+ getPrev: function()
|
|
|
+ {
|
|
|
+ return window.getSelection().anchorNode.previousSibling;
|
|
|
+ },
|
|
|
+ getNext: function()
|
|
|
+ {
|
|
|
+ return window.getSelection().anchorNode.nextSibling;
|
|
|
+ },
|
|
|
+ getBlock: function(node)
|
|
|
+ {
|
|
|
+ node = node || this.selection.getCurrent();
|
|
|
+
|
|
|
+ while (node)
|
|
|
+ {
|
|
|
+ if (this.utils.isBlockTag(node.tagName))
|
|
|
+ {
|
|
|
+ return ($(node).hasClass('redactor-editor')) ? false : node;
|
|
|
+ }
|
|
|
+
|
|
|
+ node = node.parentNode;
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ },
|
|
|
+ getInlines: function(nodes, tags)
|
|
|
+ {
|
|
|
+ this.selection.get();
|
|
|
+
|
|
|
+ if (this.range && this.range.collapsed)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ var inlines = [];
|
|
|
+ nodes = (typeof nodes == 'undefined' || nodes === false) ? this.selection.getNodes() : nodes;
|
|
|
+ var inlineTags = this.opts.inlineTags;
|
|
|
+ inlineTags.push('span');
|
|
|
+
|
|
|
+ if (typeof tags !== 'undefined')
|
|
|
+ {
|
|
|
+ for (var i = 0; i < tags.length; i++)
|
|
|
+ {
|
|
|
+ inlineTags.push(tags[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $.each(nodes, $.proxy(function(i,node)
|
|
|
+ {
|
|
|
+ if ($.inArray(node.tagName.toLowerCase(), inlineTags) != -1)
|
|
|
+ {
|
|
|
+ inlines.push(node);
|
|
|
+ }
|
|
|
+
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ return (inlines.length === 0) ? false : inlines;
|
|
|
+ },
|
|
|
+ getInlinesTags: function(tags)
|
|
|
+ {
|
|
|
+ this.selection.get();
|
|
|
+
|
|
|
+ if (this.range && this.range.collapsed)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ var inlines = [];
|
|
|
+ var nodes = this.selection.getNodes();
|
|
|
+ $.each(nodes, $.proxy(function(i,node)
|
|
|
+ {
|
|
|
+ if ($.inArray(node.tagName.toLowerCase(), tags) != -1)
|
|
|
+ {
|
|
|
+ inlines.push(node);
|
|
|
+ }
|
|
|
+
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ return (inlines.length === 0) ? false : inlines;
|
|
|
+ },
|
|
|
+ getBlocks: function(nodes)
|
|
|
+ {
|
|
|
+ this.selection.get();
|
|
|
+
|
|
|
+ if (this.range && this.range.collapsed)
|
|
|
+ {
|
|
|
+ return [this.selection.getBlock()];
|
|
|
+ }
|
|
|
+
|
|
|
+ var blocks = [];
|
|
|
+ nodes = (typeof nodes == 'undefined') ? this.selection.getNodes() : nodes;
|
|
|
+
|
|
|
+ $.each(nodes, $.proxy(function(i,node)
|
|
|
+ {
|
|
|
+ if (this.utils.isBlock(node))
|
|
|
+ {
|
|
|
+ blocks.push(node);
|
|
|
+ }
|
|
|
+
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ return (blocks.length === 0) ? [this.selection.getBlock()] : blocks;
|
|
|
+ },
|
|
|
+ getLastBlock: function()
|
|
|
+ {
|
|
|
+ return this.selection.lastBlock;
|
|
|
+ },
|
|
|
+ getNodes: function()
|
|
|
+ {
|
|
|
+ this.selection.get();
|
|
|
+
|
|
|
+ var startNode = this.selection.getNodesMarker(1);
|
|
|
+ var endNode = this.selection.getNodesMarker(2);
|
|
|
+
|
|
|
+ if (this.range.collapsed === false)
|
|
|
+ {
|
|
|
+ if (window.getSelection) {
|
|
|
+ var sel = window.getSelection();
|
|
|
+ if (sel.rangeCount > 0) {
|
|
|
+
|
|
|
+ var range = sel.getRangeAt(0);
|
|
|
+ var startPointNode = range.startContainer, startOffset = range.startOffset;
|
|
|
+
|
|
|
+ var boundaryRange = range.cloneRange();
|
|
|
+ boundaryRange.collapse(false);
|
|
|
+ boundaryRange.insertNode(endNode);
|
|
|
+ boundaryRange.setStart(startPointNode, startOffset);
|
|
|
+ boundaryRange.collapse(true);
|
|
|
+ boundaryRange.insertNode(startNode);
|
|
|
+
|
|
|
+ // Reselect the original text
|
|
|
+ range.setStartAfter(startNode);
|
|
|
+ range.setEndBefore(endNode);
|
|
|
+ sel.removeAllRanges();
|
|
|
+ sel.addRange(range);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.selection.setNodesMarker(this.range, startNode, true);
|
|
|
+ endNode = startNode;
|
|
|
+ }
|
|
|
+
|
|
|
+ var nodes = [];
|
|
|
+ var counter = 0;
|
|
|
+
|
|
|
+ var self = this;
|
|
|
+ this.$editor.find('*').each(function()
|
|
|
+ {
|
|
|
+ if (this == startNode)
|
|
|
+ {
|
|
|
+ var parent = $(this).parent();
|
|
|
+ if (parent.length !== 0 && parent[0].tagName != 'BODY' && self.utils.isRedactorParent(parent[0]))
|
|
|
+ {
|
|
|
+ nodes.push(parent[0]);
|
|
|
+ }
|
|
|
+
|
|
|
+ nodes.push(this);
|
|
|
+ counter = 1;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (counter > 0)
|
|
|
+ {
|
|
|
+ nodes.push(this);
|
|
|
+ counter = counter + 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this == endNode)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ });
|
|
|
+
|
|
|
+ var finalNodes = [];
|
|
|
+ var len = nodes.length;
|
|
|
+ for (var i = 0; i < len; i++)
|
|
|
+ {
|
|
|
+ if (nodes[i].id != 'nodes-marker-1' && nodes[i].id != 'nodes-marker-2')
|
|
|
+ {
|
|
|
+ finalNodes.push(nodes[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this.selection.removeNodesMarkers();
|
|
|
+
|
|
|
+ return finalNodes;
|
|
|
+
|
|
|
+ },
|
|
|
+ getNodesMarker: function(num)
|
|
|
+ {
|
|
|
+ return $('<span id="nodes-marker-' + num + '" class="redactor-nodes-marker" data-verified="redactor">' + this.opts.invisibleSpace + '</span>')[0];
|
|
|
+ },
|
|
|
+ setNodesMarker: function(range, node, type)
|
|
|
+ {
|
|
|
+ var range = range.cloneRange();
|
|
|
+
|
|
|
+ try {
|
|
|
+ range.collapse(type);
|
|
|
+ range.insertNode(node);
|
|
|
+ }
|
|
|
+ catch (e) {}
|
|
|
+ },
|
|
|
+ removeNodesMarkers: function()
|
|
|
+ {
|
|
|
+ $(document).find('span.redactor-nodes-marker').remove();
|
|
|
+ this.$editor.find('span.redactor-nodes-marker').remove();
|
|
|
+ },
|
|
|
+ fromPoint: function(start, end)
|
|
|
+ {
|
|
|
+ this.caret.setOffset(start, end);
|
|
|
+ },
|
|
|
+ wrap: function(tag)
|
|
|
+ {
|
|
|
+ this.selection.get();
|
|
|
+
|
|
|
+ if (this.range.collapsed) return false;
|
|
|
+
|
|
|
+ var wrapper = document.createElement(tag);
|
|
|
+ wrapper.appendChild(this.range.extractContents());
|
|
|
+ this.range.insertNode(wrapper);
|
|
|
+
|
|
|
+ return wrapper;
|
|
|
+ },
|
|
|
+ selectElement: function(node)
|
|
|
+ {
|
|
|
+ if (this.utils.browser('mozilla'))
|
|
|
+ {
|
|
|
+ node = node[0] || node;
|
|
|
+
|
|
|
+ var range = document.createRange();
|
|
|
+ range.selectNodeContents(node);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.caret.set(node, 0, node, 1);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ selectAll: function()
|
|
|
+ {
|
|
|
+ this.selection.get();
|
|
|
+ this.range.selectNodeContents(this.$editor[0]);
|
|
|
+ this.selection.addRange();
|
|
|
+ },
|
|
|
+ remove: function()
|
|
|
+ {
|
|
|
+ this.selection.get();
|
|
|
+ this.sel.removeAllRanges();
|
|
|
+ },
|
|
|
+ save: function()
|
|
|
+ {
|
|
|
+ this.selection.createMarkers();
|
|
|
+ },
|
|
|
+ createMarkers: function()
|
|
|
+ {
|
|
|
+ this.selection.get();
|
|
|
+
|
|
|
+ var node1 = this.selection.getMarker(1);
|
|
|
+
|
|
|
+ this.selection.setMarker(this.range, node1, true);
|
|
|
+ if (this.range.collapsed === false)
|
|
|
+ {
|
|
|
+ var node2 = this.selection.getMarker(2);
|
|
|
+ this.selection.setMarker(this.range, node2, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.savedSel = this.$editor.html();
|
|
|
+ },
|
|
|
+ getMarker: function(num)
|
|
|
+ {
|
|
|
+ if (typeof num == 'undefined') num = 1;
|
|
|
+
|
|
|
+ return $('<span id="selection-marker-' + num + '" class="redactor-selection-marker" data-verified="redactor">' + this.opts.invisibleSpace + '</span>')[0];
|
|
|
+ },
|
|
|
+ getMarkerAsHtml: function(num)
|
|
|
+ {
|
|
|
+ return this.utils.getOuterHtml(this.selection.getMarker(num));
|
|
|
+ },
|
|
|
+ setMarker: function(range, node, type)
|
|
|
+ {
|
|
|
+ range = range.cloneRange();
|
|
|
+
|
|
|
+ try {
|
|
|
+ range.collapse(type);
|
|
|
+ range.insertNode(node);
|
|
|
+
|
|
|
+ }
|
|
|
+ catch (e)
|
|
|
+ {
|
|
|
+ this.focus.setStart();
|
|
|
+ }
|
|
|
+
|
|
|
+ },
|
|
|
+ restore: function()
|
|
|
+ {
|
|
|
+ var node1 = this.$editor.find('span#selection-marker-1');
|
|
|
+ var node2 = this.$editor.find('span#selection-marker-2');
|
|
|
+
|
|
|
+ if (this.utils.browser('mozilla'))
|
|
|
+ {
|
|
|
+ this.$editor.focus();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (node1.length !== 0 && node2.length !== 0)
|
|
|
+ {
|
|
|
+ this.caret.set(node1, 0, node2, 0);
|
|
|
+ }
|
|
|
+ else if (node1.length !== 0)
|
|
|
+ {
|
|
|
+ this.caret.set(node1, 0, node1, 0);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.$editor.focus();
|
|
|
+ }
|
|
|
+
|
|
|
+ this.selection.removeMarkers();
|
|
|
+ this.savedSel = false;
|
|
|
+
|
|
|
+ },
|
|
|
+ removeMarkers: function()
|
|
|
+ {
|
|
|
+ this.$editor.find('span.redactor-selection-marker').each(function(i,s)
|
|
|
+ {
|
|
|
+ var text = $(s).text().replace(/\u200B/g, '');
|
|
|
+ if (text === '') $(s).remove();
|
|
|
+ else $(s).replaceWith(function() { return $(this).contents(); });
|
|
|
+ });
|
|
|
+ },
|
|
|
+ getText: function()
|
|
|
+ {
|
|
|
+ this.selection.get();
|
|
|
+
|
|
|
+ return this.sel.toString();
|
|
|
+ },
|
|
|
+ getHtml: function()
|
|
|
+ {
|
|
|
+ var html = '';
|
|
|
+
|
|
|
+ this.selection.get();
|
|
|
+ if (this.sel.rangeCount)
|
|
|
+ {
|
|
|
+ var container = document.createElement('div');
|
|
|
+ var len = this.sel.rangeCount;
|
|
|
+ for (var i = 0; i < len; ++i)
|
|
|
+ {
|
|
|
+ container.appendChild(this.sel.getRangeAt(i).cloneContents());
|
|
|
+ }
|
|
|
+
|
|
|
+ html = container.innerHTML;
|
|
|
+ }
|
|
|
+
|
|
|
+ return this.clean.onSync(html);
|
|
|
+ },
|
|
|
+ replaceSelection: function(html)
|
|
|
+ {
|
|
|
+ this.selection.get();
|
|
|
+ this.range.deleteContents();
|
|
|
+ var div = document.createElement("div");
|
|
|
+ div.innerHTML = html;
|
|
|
+ var frag = document.createDocumentFragment(), child;
|
|
|
+ while ((child = div.firstChild)) {
|
|
|
+ frag.appendChild(child);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.range.insertNode(frag);
|
|
|
+ },
|
|
|
+ replaceWithHtml: function(html)
|
|
|
+ {
|
|
|
+ html = this.selection.getMarkerAsHtml(1) + html + this.selection.getMarkerAsHtml(2);
|
|
|
+
|
|
|
+ this.selection.get();
|
|
|
+
|
|
|
+ if (window.getSelection && window.getSelection().getRangeAt)
|
|
|
+ {
|
|
|
+ this.selection.replaceSelection(html);
|
|
|
+ }
|
|
|
+ else if (document.selection && document.selection.createRange)
|
|
|
+ {
|
|
|
+ this.range.pasteHTML(html);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.selection.restore();
|
|
|
+ this.code.sync();
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ shortcuts: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ init: function(e, key)
|
|
|
+ {
|
|
|
+ // disable browser's hot keys for bold and italic
|
|
|
+ if (!this.opts.shortcuts)
|
|
|
+ {
|
|
|
+ if ((e.ctrlKey || e.metaKey) && (key === 66 || key === 73)) e.preventDefault();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ $.each(this.opts.shortcuts, $.proxy(function(str, command)
|
|
|
+ {
|
|
|
+ var keys = str.split(',');
|
|
|
+ var len = keys.length;
|
|
|
+ for (var i = 0; i < len; i++)
|
|
|
+ {
|
|
|
+ if (typeof keys[i] === 'string')
|
|
|
+ {
|
|
|
+ this.shortcuts.handler(e, $.trim(keys[i]), $.proxy(function()
|
|
|
+ {
|
|
|
+ var func;
|
|
|
+ if (command.func.search(/\./) != '-1')
|
|
|
+ {
|
|
|
+ func = command.func.split('.');
|
|
|
+ if (typeof this[func[0]] != 'undefined')
|
|
|
+ {
|
|
|
+ this[func[0]][func[1]].apply(this, command.params);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this[command.func].apply(this, command.params);
|
|
|
+ }
|
|
|
+
|
|
|
+ }, this));
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }, this));
|
|
|
+ },
|
|
|
+ handler: function(e, keys, origHandler)
|
|
|
+ {
|
|
|
+ // based on https://github.com/jeresig/jquery.hotkeys
|
|
|
+ var hotkeysSpecialKeys =
|
|
|
+ {
|
|
|
+ 8: "backspace", 9: "tab", 10: "return", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
|
|
|
+ 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
|
|
|
+ 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 59: ";", 61: "=",
|
|
|
+ 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
|
|
|
+ 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
|
|
|
+ 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
|
|
|
+ 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 173: "-", 186: ";", 187: "=",
|
|
|
+ 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'"
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ var hotkeysShiftNums =
|
|
|
+ {
|
|
|
+ "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
|
|
|
+ "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
|
|
|
+ ".": ">", "/": "?", "\\": "|"
|
|
|
+ };
|
|
|
+
|
|
|
+ keys = keys.toLowerCase().split(" ");
|
|
|
+ var special = hotkeysSpecialKeys[e.keyCode],
|
|
|
+ character = String.fromCharCode( e.which ).toLowerCase(),
|
|
|
+ modif = "", possible = {};
|
|
|
+
|
|
|
+ $.each([ "alt", "ctrl", "meta", "shift"], function(index, specialKey)
|
|
|
+ {
|
|
|
+ if (e[specialKey + 'Key'] && special !== specialKey)
|
|
|
+ {
|
|
|
+ modif += specialKey + '+';
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+
|
|
|
+ if (special) possible[modif + special] = true;
|
|
|
+ if (character)
|
|
|
+ {
|
|
|
+ possible[modif + character] = true;
|
|
|
+ possible[modif + hotkeysShiftNums[character]] = true;
|
|
|
+
|
|
|
+ // "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
|
|
|
+ if (modif === "shift+")
|
|
|
+ {
|
|
|
+ possible[hotkeysShiftNums[character]] = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for (var i = 0, len = keys.length; i < len; i++)
|
|
|
+ {
|
|
|
+ if (possible[keys[i]])
|
|
|
+ {
|
|
|
+ e.preventDefault();
|
|
|
+ return origHandler.apply(this, arguments);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ tabifier: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ get: function(code)
|
|
|
+ {
|
|
|
+ if (!this.opts.tabifier) return code;
|
|
|
+
|
|
|
+ // clean setup
|
|
|
+ var ownLine = ['area', 'body', 'head', 'hr', 'i?frame', 'link', 'meta', 'noscript', 'style', 'script', 'table', 'tbody', 'thead', 'tfoot'];
|
|
|
+ var contOwnLine = ['li', 'dt', 'dt', 'h[1-6]', 'option', 'script'];
|
|
|
+ var newLevel = ['p', 'blockquote', 'div', 'dl', 'fieldset', 'form', 'frameset', 'map', 'ol', 'pre', 'select', 'td', 'th', 'tr', 'ul'];
|
|
|
+
|
|
|
+ this.tabifier.lineBefore = new RegExp('^<(/?' + ownLine.join('|/?' ) + '|' + contOwnLine.join('|') + ')[ >]');
|
|
|
+ this.tabifier.lineAfter = new RegExp('^<(br|/?' + ownLine.join('|/?' ) + '|/' + contOwnLine.join('|/') + ')[ >]');
|
|
|
+ this.tabifier.newLevel = new RegExp('^</?(' + newLevel.join('|' ) + ')[ >]');
|
|
|
+
|
|
|
+ var i = 0,
|
|
|
+ codeLength = code.length,
|
|
|
+ point = 0,
|
|
|
+ start = null,
|
|
|
+ end = null,
|
|
|
+ tag = '',
|
|
|
+ out = '',
|
|
|
+ cont = '';
|
|
|
+
|
|
|
+ this.tabifier.cleanlevel = 0;
|
|
|
+
|
|
|
+ for (; i < codeLength; i++)
|
|
|
+ {
|
|
|
+ point = i;
|
|
|
+
|
|
|
+ // if no more tags, copy and exit
|
|
|
+ if (-1 == code.substr(i).indexOf( '<' ))
|
|
|
+ {
|
|
|
+ out += code.substr(i);
|
|
|
+
|
|
|
+ return this.tabifier.finish(out);
|
|
|
+ }
|
|
|
+
|
|
|
+ // copy verbatim until a tag
|
|
|
+ while (point < codeLength && code.charAt(point) != '<')
|
|
|
+ {
|
|
|
+ point++;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (i != point)
|
|
|
+ {
|
|
|
+ cont = code.substr(i, point - i);
|
|
|
+ if (!cont.match(/^\s{2,}$/g))
|
|
|
+ {
|
|
|
+ if ('\n' == out.charAt(out.length - 1)) out += this.tabifier.getTabs();
|
|
|
+ else if ('\n' == cont.charAt(0))
|
|
|
+ {
|
|
|
+ out += '\n' + this.tabifier.getTabs();
|
|
|
+ cont = cont.replace(/^\s+/, '');
|
|
|
+ }
|
|
|
+
|
|
|
+ out += cont;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (cont.match(/\n/)) out += '\n' + this.tabifier.getTabs();
|
|
|
+ }
|
|
|
+
|
|
|
+ start = point;
|
|
|
+
|
|
|
+ // find the end of the tag
|
|
|
+ while (point < codeLength && '>' != code.charAt(point))
|
|
|
+ {
|
|
|
+ point++;
|
|
|
+ }
|
|
|
+
|
|
|
+ tag = code.substr(start, point - start);
|
|
|
+ i = point;
|
|
|
+
|
|
|
+ var t;
|
|
|
+
|
|
|
+ if ('!--' == tag.substr(1, 3))
|
|
|
+ {
|
|
|
+ if (!tag.match(/--$/))
|
|
|
+ {
|
|
|
+ while ('-->' != code.substr(point, 3))
|
|
|
+ {
|
|
|
+ point++;
|
|
|
+ }
|
|
|
+ point += 2;
|
|
|
+ tag = code.substr(start, point - start);
|
|
|
+ i = point;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ('\n' != out.charAt(out.length - 1)) out += '\n';
|
|
|
+
|
|
|
+ out += this.tabifier.getTabs();
|
|
|
+ out += tag + '>\n';
|
|
|
+ }
|
|
|
+ else if ('!' == tag[1])
|
|
|
+ {
|
|
|
+ out = this.tabifier.placeTag(tag + '>', out);
|
|
|
+ }
|
|
|
+ else if ('?' == tag[1])
|
|
|
+ {
|
|
|
+ out += tag + '>\n';
|
|
|
+ }
|
|
|
+ else if (t = tag.match(/^<(script|style|pre)/i))
|
|
|
+ {
|
|
|
+ t[1] = t[1].toLowerCase();
|
|
|
+ tag = this.tabifier.cleanTag(tag);
|
|
|
+ out = this.tabifier.placeTag(tag, out);
|
|
|
+ end = String(code.substr(i + 1)).toLowerCase().indexOf('</' + t[1]);
|
|
|
+
|
|
|
+ if (end)
|
|
|
+ {
|
|
|
+ cont = code.substr(i + 1, end);
|
|
|
+ i += end;
|
|
|
+ out += cont;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ tag = this.tabifier.cleanTag(tag);
|
|
|
+ out = this.tabifier.placeTag(tag, out);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return this.tabifier.finish(out);
|
|
|
+ },
|
|
|
+ getTabs: function()
|
|
|
+ {
|
|
|
+ var s = '';
|
|
|
+ for ( var j = 0; j < this.tabifier.cleanlevel; j++ )
|
|
|
+ {
|
|
|
+ s += '\t';
|
|
|
+ }
|
|
|
+
|
|
|
+ return s;
|
|
|
+ },
|
|
|
+ finish: function(code)
|
|
|
+ {
|
|
|
+ code = code.replace(/\n\s*\n/g, '\n');
|
|
|
+ code = code.replace(/^[\s\n]*/, '');
|
|
|
+ code = code.replace(/[\s\n]*$/, '');
|
|
|
+ code = code.replace(/<script(.*?)>\n<\/script>/gi, '<script$1></script>');
|
|
|
+
|
|
|
+ this.tabifier.cleanlevel = 0;
|
|
|
+
|
|
|
+ return code;
|
|
|
+ },
|
|
|
+ cleanTag: function (tag)
|
|
|
+ {
|
|
|
+ var tagout = '';
|
|
|
+ tag = tag.replace(/\n/g, ' ');
|
|
|
+ tag = tag.replace(/\s{2,}/g, ' ');
|
|
|
+ tag = tag.replace(/^\s+|\s+$/g, ' ');
|
|
|
+
|
|
|
+ var suffix = '';
|
|
|
+ if (tag.match(/\/$/))
|
|
|
+ {
|
|
|
+ suffix = '/';
|
|
|
+ tag = tag.replace(/\/+$/, '');
|
|
|
+ }
|
|
|
+
|
|
|
+ var m;
|
|
|
+ while (m = /\s*([^= ]+)(?:=((['"']).*?\3|[^ ]+))?/.exec(tag))
|
|
|
+ {
|
|
|
+ if (m[2]) tagout += m[1].toLowerCase() + '=' + m[2];
|
|
|
+ else if (m[1]) tagout += m[1].toLowerCase();
|
|
|
+
|
|
|
+ tagout += ' ';
|
|
|
+ tag = tag.substr(m[0].length);
|
|
|
+ }
|
|
|
+
|
|
|
+ return tagout.replace(/\s*$/, '') + suffix + '>';
|
|
|
+ },
|
|
|
+ placeTag: function (tag, out)
|
|
|
+ {
|
|
|
+ var nl = tag.match(this.tabifier.newLevel);
|
|
|
+
|
|
|
+ if (tag.match(this.tabifier.lineBefore) || nl)
|
|
|
+ {
|
|
|
+ out = out.replace(/\s*$/, '');
|
|
|
+ out += '\n';
|
|
|
+ }
|
|
|
+
|
|
|
+ if (nl && '/' == tag.charAt(1)) this.tabifier.cleanlevel--;
|
|
|
+ if ('\n' == out.charAt(out.length - 1)) out += this.tabifier.getTabs();
|
|
|
+ if (nl && '/' != tag.charAt(1)) this.tabifier.cleanlevel++;
|
|
|
+
|
|
|
+ out += tag;
|
|
|
+
|
|
|
+ if (tag.match(this.tabifier.lineAfter) || tag.match(this.tabifier.newLevel))
|
|
|
+ {
|
|
|
+ out = out.replace(/ *$/, '');
|
|
|
+ //out += '\n';
|
|
|
+ }
|
|
|
+
|
|
|
+ return out;
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ tidy: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ setupAllowed: function()
|
|
|
+ {
|
|
|
+ var index = $.inArray('span', this.opts.removeEmpty);
|
|
|
+ if (index !== -1)
|
|
|
+ {
|
|
|
+ this.opts.removeEmpty.splice(index, 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.opts.allowedTags) this.opts.deniedTags = false;
|
|
|
+ if (this.opts.allowedAttr) this.opts.removeAttr = false;
|
|
|
+
|
|
|
+ if (this.opts.linebreaks) return;
|
|
|
+
|
|
|
+ var tags = ['p', 'section'];
|
|
|
+ if (this.opts.allowedTags) this.tidy.addToAllowed(tags);
|
|
|
+ if (this.opts.deniedTags) this.tidy.removeFromDenied(tags);
|
|
|
+
|
|
|
+ },
|
|
|
+ addToAllowed: function(tags)
|
|
|
+ {
|
|
|
+ var len = tags.length;
|
|
|
+ for (var i = 0; i < len; i++)
|
|
|
+ {
|
|
|
+ if ($.inArray(tags[i], this.opts.allowedTags) == -1)
|
|
|
+ {
|
|
|
+ this.opts.allowedTags.push(tags[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ removeFromDenied: function(tags)
|
|
|
+ {
|
|
|
+ var len = tags.length;
|
|
|
+ for (var i = 0; i < len; i++)
|
|
|
+ {
|
|
|
+ var pos = $.inArray(tags[i], this.opts.deniedTags);
|
|
|
+ if (pos != -1)
|
|
|
+ {
|
|
|
+ this.opts.deniedTags.splice(pos, 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ load: function(html, options)
|
|
|
+ {
|
|
|
+ this.tidy.settings = {
|
|
|
+ deniedTags: this.opts.deniedTags,
|
|
|
+ allowedTags: this.opts.allowedTags,
|
|
|
+ removeComments: this.opts.removeComments,
|
|
|
+ replaceTags: this.opts.replaceTags,
|
|
|
+ replaceStyles: this.opts.replaceStyles,
|
|
|
+ removeDataAttr: this.opts.removeDataAttr,
|
|
|
+ removeAttr: this.opts.removeAttr,
|
|
|
+ allowedAttr: this.opts.allowedAttr,
|
|
|
+ removeWithoutAttr: this.opts.removeWithoutAttr,
|
|
|
+ removeEmpty: this.opts.removeEmpty
|
|
|
+ };
|
|
|
+
|
|
|
+ $.extend(this.tidy.settings, options);
|
|
|
+
|
|
|
+ html = this.tidy.removeComments(html);
|
|
|
+
|
|
|
+ // create container
|
|
|
+ this.tidy.$div = $('<div />').append(html);
|
|
|
+
|
|
|
+ // clean
|
|
|
+ this.tidy.replaceTags();
|
|
|
+ this.tidy.replaceStyles();
|
|
|
+ this.tidy.removeTags();
|
|
|
+
|
|
|
+ this.tidy.removeAttr();
|
|
|
+ this.tidy.removeEmpty();
|
|
|
+ this.tidy.removeParagraphsInLists();
|
|
|
+ this.tidy.removeDataAttr();
|
|
|
+ this.tidy.removeWithoutAttr();
|
|
|
+
|
|
|
+ html = this.tidy.$div.html();
|
|
|
+ this.tidy.$div.remove();
|
|
|
+
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+ removeComments: function(html)
|
|
|
+ {
|
|
|
+ if (!this.tidy.settings.removeComments) return html;
|
|
|
+
|
|
|
+ return html.replace(/<!--[\s\S]*?-->/gi, '');
|
|
|
+ },
|
|
|
+ replaceTags: function(html)
|
|
|
+ {
|
|
|
+ if (!this.tidy.settings.replaceTags) return html;
|
|
|
+
|
|
|
+ var len = this.tidy.settings.replaceTags.length;
|
|
|
+ var replacement = [], rTags = [];
|
|
|
+ for (var i = 0; i < len; i++)
|
|
|
+ {
|
|
|
+ rTags.push(this.tidy.settings.replaceTags[i][1]);
|
|
|
+ replacement.push(this.tidy.settings.replaceTags[i][0]);
|
|
|
+ }
|
|
|
+
|
|
|
+ $.each(replacement, $.proxy(function(key, value)
|
|
|
+ {
|
|
|
+ this.tidy.$div.find(value).replaceWith(function()
|
|
|
+ {
|
|
|
+ return $("<" + rTags[key] + " />", {html: $(this).html()});
|
|
|
+ });
|
|
|
+ }, this));
|
|
|
+ },
|
|
|
+ replaceStyles: function()
|
|
|
+ {
|
|
|
+ if (!this.tidy.settings.replaceStyles) return;
|
|
|
+
|
|
|
+ var len = this.tidy.settings.replaceStyles.length;
|
|
|
+ this.tidy.$div.find('span').each($.proxy(function(n,s)
|
|
|
+ {
|
|
|
+ var $el = $(s);
|
|
|
+ var style = $el.attr('style');
|
|
|
+ for (var i = 0; i < len; i++)
|
|
|
+ {
|
|
|
+ if (style && style.match(new RegExp('^' + this.tidy.settings.replaceStyles[i][0], 'i')))
|
|
|
+ {
|
|
|
+ var tagName = this.tidy.settings.replaceStyles[i][1];
|
|
|
+ $el.replaceWith(function()
|
|
|
+ {
|
|
|
+ var tag = document.createElement(tagName);
|
|
|
+ return $(tag).append($(this).contents());
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ },
|
|
|
+ removeTags: function()
|
|
|
+ {
|
|
|
+ if (!this.tidy.settings.deniedTags && this.tidy.settings.allowedTags)
|
|
|
+ {
|
|
|
+ this.tidy.$div.find('*').not(this.tidy.settings.allowedTags.join(',')).each(function(i, s)
|
|
|
+ {
|
|
|
+ if (s.innerHTML === '') $(s).remove();
|
|
|
+ else $(s).contents().unwrap();
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.tidy.settings.deniedTags)
|
|
|
+ {
|
|
|
+ this.tidy.$div.find(this.tidy.settings.deniedTags.join(',')).each(function(i, s)
|
|
|
+ {
|
|
|
+ if ($(s).hasClass('redactor-script-tag') || $(s).hasClass('redactor-selection-marker')) return;
|
|
|
+
|
|
|
+ if (s.innerHTML === '') $(s).remove();
|
|
|
+ else $(s).contents().unwrap();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ removeAttr: function()
|
|
|
+ {
|
|
|
+ var len;
|
|
|
+ if (!this.tidy.settings.removeAttr && this.tidy.settings.allowedAttr)
|
|
|
+ {
|
|
|
+
|
|
|
+ var allowedAttrTags = [], allowedAttrData = [];
|
|
|
+ len = this.tidy.settings.allowedAttr.length;
|
|
|
+ for (var i = 0; i < len; i++)
|
|
|
+ {
|
|
|
+ allowedAttrTags.push(this.tidy.settings.allowedAttr[i][0]);
|
|
|
+ allowedAttrData.push(this.tidy.settings.allowedAttr[i][1]);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ this.tidy.$div.find('*').each($.proxy(function(n,s)
|
|
|
+ {
|
|
|
+ var $el = $(s);
|
|
|
+ var pos = $.inArray($el[0].tagName.toLowerCase(), allowedAttrTags);
|
|
|
+ var attributesRemove = this.tidy.removeAttrGetRemoves(pos, allowedAttrData, $el);
|
|
|
+
|
|
|
+ if (attributesRemove)
|
|
|
+ {
|
|
|
+ $.each(attributesRemove, function(z,f) {
|
|
|
+ $el.removeAttr(f);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }, this));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.tidy.settings.removeAttr)
|
|
|
+ {
|
|
|
+ len = this.tidy.settings.removeAttr.length;
|
|
|
+ for (var i = 0; i < len; i++)
|
|
|
+ {
|
|
|
+ var attrs = this.tidy.settings.removeAttr[i][1];
|
|
|
+ if ($.isArray(attrs)) attrs = attrs.join(' ');
|
|
|
+
|
|
|
+ this.tidy.$div.find(this.tidy.settings.removeAttr[i][0]).removeAttr(attrs);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ },
|
|
|
+ removeAttrGetRemoves: function(pos, allowed, $el)
|
|
|
+ {
|
|
|
+ var attributesRemove = [];
|
|
|
+
|
|
|
+ // remove all attrs
|
|
|
+ if (pos == -1)
|
|
|
+ {
|
|
|
+ $.each($el[0].attributes, function(i, item)
|
|
|
+ {
|
|
|
+ attributesRemove.push(item.name);
|
|
|
+ });
|
|
|
+
|
|
|
+ }
|
|
|
+ // allow all attrs
|
|
|
+ else if (allowed[pos] == '*')
|
|
|
+ {
|
|
|
+ attributesRemove = [];
|
|
|
+ }
|
|
|
+ // allow specific attrs
|
|
|
+ else
|
|
|
+ {
|
|
|
+ $.each($el[0].attributes, function(i, item)
|
|
|
+ {
|
|
|
+ if ($.isArray(allowed[pos]))
|
|
|
+ {
|
|
|
+ if ($.inArray(item.name, allowed[pos]) == -1)
|
|
|
+ {
|
|
|
+ attributesRemove.push(item.name);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (allowed[pos] != item.name)
|
|
|
+ {
|
|
|
+ attributesRemove.push(item.name);
|
|
|
+ }
|
|
|
+
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ return attributesRemove;
|
|
|
+ },
|
|
|
+ removeAttrs: function (el, regex)
|
|
|
+ {
|
|
|
+ regex = new RegExp(regex, "g");
|
|
|
+ return el.each(function()
|
|
|
+ {
|
|
|
+ var self = $(this);
|
|
|
+ var len = this.attributes.length - 1;
|
|
|
+ for (var i = len; i >= 0; i--)
|
|
|
+ {
|
|
|
+ var item = this.attributes[i];
|
|
|
+ if (item && item.specified && item.name.search(regex)>=0)
|
|
|
+ {
|
|
|
+ self.removeAttr(item.name);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ removeEmpty: function()
|
|
|
+ {
|
|
|
+ if (!this.tidy.settings.removeEmpty) return;
|
|
|
+
|
|
|
+ this.tidy.$div.find(this.tidy.settings.removeEmpty.join(',')).each(function()
|
|
|
+ {
|
|
|
+ var $el = $(this);
|
|
|
+ var text = $el.text();
|
|
|
+ text = text.replace(/\u200B/g, '');
|
|
|
+ text = text.replace(/ /gi, '');
|
|
|
+ text = text.replace(/\s/g, '');
|
|
|
+
|
|
|
+ if (text === '' && $el.children().length === 0)
|
|
|
+ {
|
|
|
+ $el.remove();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ removeParagraphsInLists: function()
|
|
|
+ {
|
|
|
+ this.tidy.$div.find('li p').contents().unwrap();
|
|
|
+ },
|
|
|
+ removeDataAttr: function()
|
|
|
+ {
|
|
|
+ if (!this.tidy.settings.removeDataAttr) return;
|
|
|
+
|
|
|
+ var tags = this.tidy.settings.removeDataAttr;
|
|
|
+ if ($.isArray(this.tidy.settings.removeDataAttr)) tags = this.tidy.settings.removeDataAttr.join(',');
|
|
|
+
|
|
|
+ this.tidy.removeAttrs(this.tidy.$div.find(tags), '^(data-)');
|
|
|
+
|
|
|
+ },
|
|
|
+ removeWithoutAttr: function()
|
|
|
+ {
|
|
|
+ if (!this.tidy.settings.removeWithoutAttr) return;
|
|
|
+
|
|
|
+ this.tidy.$div.find(this.tidy.settings.removeWithoutAttr.join(',')).each(function()
|
|
|
+ {
|
|
|
+ if (this.attributes.length === 0)
|
|
|
+ {
|
|
|
+ $(this).contents().unwrap();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ toolbar: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ init: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ html:
|
|
|
+ {
|
|
|
+ title: this.lang.get('html'),
|
|
|
+ func: 'code.toggle'
|
|
|
+ },
|
|
|
+ formatting:
|
|
|
+ {
|
|
|
+ title: this.lang.get('formatting'),
|
|
|
+ dropdown:
|
|
|
+ {
|
|
|
+ p:
|
|
|
+ {
|
|
|
+ title: this.lang.get('paragraph'),
|
|
|
+ func: 'block.format'
|
|
|
+ },
|
|
|
+ blockquote:
|
|
|
+ {
|
|
|
+ title: this.lang.get('quote'),
|
|
|
+ func: 'block.format'
|
|
|
+ },
|
|
|
+ pre:
|
|
|
+ {
|
|
|
+ title: this.lang.get('code'),
|
|
|
+ func: 'block.format'
|
|
|
+ },
|
|
|
+ h1:
|
|
|
+ {
|
|
|
+ title: this.lang.get('header1'),
|
|
|
+ func: 'block.format'
|
|
|
+ },
|
|
|
+ h2:
|
|
|
+ {
|
|
|
+ title: this.lang.get('header2'),
|
|
|
+ func: 'block.format'
|
|
|
+ },
|
|
|
+ h3:
|
|
|
+ {
|
|
|
+ title: this.lang.get('header3'),
|
|
|
+ func: 'block.format'
|
|
|
+ },
|
|
|
+ h4:
|
|
|
+ {
|
|
|
+ title: this.lang.get('header4'),
|
|
|
+ func: 'block.format'
|
|
|
+ },
|
|
|
+ h5:
|
|
|
+ {
|
|
|
+ title: this.lang.get('header5'),
|
|
|
+ func: 'block.format'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ bold:
|
|
|
+ {
|
|
|
+ title: this.lang.get('bold'),
|
|
|
+ func: 'inline.format'
|
|
|
+ },
|
|
|
+ italic:
|
|
|
+ {
|
|
|
+ title: this.lang.get('italic'),
|
|
|
+ func: 'inline.format'
|
|
|
+ },
|
|
|
+ deleted:
|
|
|
+ {
|
|
|
+ title: this.lang.get('deleted'),
|
|
|
+ func: 'inline.format'
|
|
|
+ },
|
|
|
+ underline:
|
|
|
+ {
|
|
|
+ title: this.lang.get('underline'),
|
|
|
+ func: 'inline.format'
|
|
|
+ },
|
|
|
+ unorderedlist:
|
|
|
+ {
|
|
|
+ title: '• ' + this.lang.get('unorderedlist'),
|
|
|
+ func: 'list.toggle'
|
|
|
+ },
|
|
|
+ orderedlist:
|
|
|
+ {
|
|
|
+ title: '1. ' + this.lang.get('orderedlist'),
|
|
|
+ func: 'list.toggle'
|
|
|
+ },
|
|
|
+ outdent:
|
|
|
+ {
|
|
|
+ title: '< ' + this.lang.get('outdent'),
|
|
|
+ func: 'indent.decrease'
|
|
|
+ },
|
|
|
+ indent:
|
|
|
+ {
|
|
|
+ title: '> ' + this.lang.get('indent'),
|
|
|
+ func: 'indent.increase'
|
|
|
+ },
|
|
|
+ image:
|
|
|
+ {
|
|
|
+ title: this.lang.get('image'),
|
|
|
+ func: 'image.show'
|
|
|
+ },
|
|
|
+ file:
|
|
|
+ {
|
|
|
+ title: this.lang.get('file'),
|
|
|
+ func: 'file.show'
|
|
|
+ },
|
|
|
+ link:
|
|
|
+ {
|
|
|
+ title: this.lang.get('link'),
|
|
|
+ dropdown:
|
|
|
+ {
|
|
|
+ link:
|
|
|
+ {
|
|
|
+ title: this.lang.get('link_insert'),
|
|
|
+ func: 'link.show',
|
|
|
+ observe: {
|
|
|
+ element: 'a',
|
|
|
+ in: {
|
|
|
+ title: this.lang.get('link_edit'),
|
|
|
+ },
|
|
|
+ out: {
|
|
|
+ title: this.lang.get('link_insert')
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ unlink:
|
|
|
+ {
|
|
|
+ title: this.lang.get('unlink'),
|
|
|
+ func: 'link.unlink',
|
|
|
+ observe: {
|
|
|
+ element: 'a',
|
|
|
+ out: {
|
|
|
+ attr: {
|
|
|
+ 'class': 'redactor-dropdown-link-inactive',
|
|
|
+ 'aria-disabled': true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ alignment:
|
|
|
+ {
|
|
|
+ title: this.lang.get('alignment'),
|
|
|
+ dropdown:
|
|
|
+ {
|
|
|
+ left:
|
|
|
+ {
|
|
|
+ title: this.lang.get('align_left'),
|
|
|
+ func: 'alignment.left'
|
|
|
+ },
|
|
|
+ center:
|
|
|
+ {
|
|
|
+ title: this.lang.get('align_center'),
|
|
|
+ func: 'alignment.center'
|
|
|
+ },
|
|
|
+ right:
|
|
|
+ {
|
|
|
+ title: this.lang.get('align_right'),
|
|
|
+ func: 'alignment.right'
|
|
|
+ },
|
|
|
+ justify:
|
|
|
+ {
|
|
|
+ title: this.lang.get('align_justify'),
|
|
|
+ func: 'alignment.justify'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ horizontalrule:
|
|
|
+ {
|
|
|
+ title: this.lang.get('horizontalrule'),
|
|
|
+ func: 'line.insert'
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ build: function()
|
|
|
+ {
|
|
|
+ this.toolbar.hideButtons();
|
|
|
+ this.toolbar.hideButtonsOnMobile();
|
|
|
+ this.toolbar.isButtonSourceNeeded();
|
|
|
+
|
|
|
+ if (this.opts.buttons.length === 0) return;
|
|
|
+
|
|
|
+ this.$toolbar = this.toolbar.createContainer();
|
|
|
+
|
|
|
+ this.toolbar.setOverflow();
|
|
|
+ this.toolbar.append();
|
|
|
+ this.toolbar.setFormattingTags();
|
|
|
+ this.toolbar.loadButtons();
|
|
|
+ this.toolbar.setFixed();
|
|
|
+
|
|
|
+ // buttons response
|
|
|
+ if (this.opts.activeButtons)
|
|
|
+ {
|
|
|
+ this.$editor.on('mouseup.redactor keyup.redactor focus.redactor', $.proxy(this.observe.toolbar, this));
|
|
|
+ }
|
|
|
+
|
|
|
+ },
|
|
|
+ createContainer: function()
|
|
|
+ {
|
|
|
+ return $('<ul>').addClass('redactor-toolbar').attr({'id': 'redactor-toolbar-' + this.uuid, 'role': 'toolbar'});
|
|
|
+ },
|
|
|
+ setFormattingTags: function()
|
|
|
+ {
|
|
|
+ $.each(this.opts.toolbar.formatting.dropdown, $.proxy(function (i, s)
|
|
|
+ {
|
|
|
+ if ($.inArray(i, this.opts.formatting) == -1) delete this.opts.toolbar.formatting.dropdown[i];
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ },
|
|
|
+ loadButtons: function()
|
|
|
+ {
|
|
|
+ $.each(this.opts.buttons, $.proxy(function(i, btnName)
|
|
|
+ {
|
|
|
+ if (!this.opts.toolbar[btnName]) return;
|
|
|
+
|
|
|
+ if (btnName === 'file')
|
|
|
+ {
|
|
|
+ if (this.opts.fileUpload === false) return;
|
|
|
+ else if (!this.opts.fileUpload && this.opts.s3 === false) return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (btnName === 'image')
|
|
|
+ {
|
|
|
+ if (this.opts.imageUpload === false) return;
|
|
|
+ else if (!this.opts.imageUpload && this.opts.s3 === false) return;
|
|
|
+ }
|
|
|
+
|
|
|
+ var btnObject = this.opts.toolbar[btnName];
|
|
|
+ this.$toolbar.append($('<li>').append(this.button.build(btnName, btnObject)));
|
|
|
+
|
|
|
+ }, this));
|
|
|
+ },
|
|
|
+ append: function()
|
|
|
+ {
|
|
|
+ if (this.opts.toolbarExternal)
|
|
|
+ {
|
|
|
+ this.$toolbar.addClass('redactor-toolbar-external');
|
|
|
+ $(this.opts.toolbarExternal).html(this.$toolbar);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.$box.prepend(this.$toolbar);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ setFixed: function()
|
|
|
+ {
|
|
|
+ if (!this.utils.isDesktop()) return;
|
|
|
+ if (this.opts.toolbarExternal) return;
|
|
|
+ if (!this.opts.toolbarFixed) return;
|
|
|
+
|
|
|
+ this.toolbar.observeScroll();
|
|
|
+ $(this.opts.toolbarFixedTarget).on('scroll.redactor.' + this.uuid, $.proxy(this.toolbar.observeScroll, this));
|
|
|
+
|
|
|
+ },
|
|
|
+ setOverflow: function()
|
|
|
+ {
|
|
|
+ if (this.utils.isMobile() && this.opts.toolbarOverflow)
|
|
|
+ {
|
|
|
+ this.$toolbar.addClass('redactor-toolbar-overflow');
|
|
|
+ }
|
|
|
+ },
|
|
|
+ isButtonSourceNeeded: function()
|
|
|
+ {
|
|
|
+ if (this.opts.source) return;
|
|
|
+
|
|
|
+ var index = this.opts.buttons.indexOf('html');
|
|
|
+ if (index !== -1)
|
|
|
+ {
|
|
|
+ this.opts.buttons.splice(index, 1);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ hideButtons: function()
|
|
|
+ {
|
|
|
+ if (this.opts.buttonsHide.length === 0) return;
|
|
|
+
|
|
|
+ $.each(this.opts.buttonsHide, $.proxy(function(i, s)
|
|
|
+ {
|
|
|
+ var index = this.opts.buttons.indexOf(s);
|
|
|
+ this.opts.buttons.splice(index, 1);
|
|
|
+
|
|
|
+ }, this));
|
|
|
+ },
|
|
|
+ hideButtonsOnMobile: function()
|
|
|
+ {
|
|
|
+ if (!this.utils.isMobile() || this.opts.buttonsHideOnMobile.length === 0) return;
|
|
|
+
|
|
|
+ $.each(this.opts.buttonsHideOnMobile, $.proxy(function(i, s)
|
|
|
+ {
|
|
|
+ var index = this.opts.buttons.indexOf(s);
|
|
|
+ this.opts.buttons.splice(index, 1);
|
|
|
+
|
|
|
+ }, this));
|
|
|
+ },
|
|
|
+ observeScroll: function()
|
|
|
+ {
|
|
|
+ var scrollTop = $(this.opts.toolbarFixedTarget).scrollTop();
|
|
|
+ var boxTop = 1;
|
|
|
+
|
|
|
+ if (this.opts.toolbarFixedTarget === document)
|
|
|
+ {
|
|
|
+ boxTop = this.$box.offset().top;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((scrollTop + this.opts.toolbarFixedTopOffset) > boxTop)
|
|
|
+ {
|
|
|
+ this.toolbar.observeScrollEnable(scrollTop, boxTop);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.toolbar.observeScrollDisable();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ observeScrollEnable: function(scrollTop, boxTop)
|
|
|
+ {
|
|
|
+ var top = this.opts.toolbarFixedTopOffset + scrollTop - boxTop;
|
|
|
+ var left = 0;
|
|
|
+ var end = boxTop + this.$box.height() - 32;
|
|
|
+ var width = this.$box.innerWidth();
|
|
|
+
|
|
|
+ this.$toolbar.addClass('toolbar-fixed-box');
|
|
|
+ this.$toolbar.css({
|
|
|
+ position: 'absolute',
|
|
|
+ width: width,
|
|
|
+ top: top + 'px',
|
|
|
+ left: left
|
|
|
+ });
|
|
|
+
|
|
|
+ if (scrollTop > end)
|
|
|
+ $('.redactor-dropdown-' + this.uuid + ':visible').hide();
|
|
|
+
|
|
|
+ this.toolbar.setDropdownsFixed();
|
|
|
+ this.$toolbar.css('visibility', (scrollTop < end) ? 'visible' : 'hidden');
|
|
|
+ },
|
|
|
+ observeScrollDisable: function()
|
|
|
+ {
|
|
|
+ this.$toolbar.css({
|
|
|
+ position: 'relative',
|
|
|
+ width: 'auto',
|
|
|
+ top: 0,
|
|
|
+ left: 0,
|
|
|
+ visibility: 'visible'
|
|
|
+ });
|
|
|
+
|
|
|
+ this.toolbar.unsetDropdownsFixed();
|
|
|
+ this.$toolbar.removeClass('toolbar-fixed-box');
|
|
|
+ },
|
|
|
+ setDropdownsFixed: function()
|
|
|
+ {
|
|
|
+ var top = this.$toolbar.innerHeight() + this.opts.toolbarFixedTopOffset;
|
|
|
+ var position = 'fixed';
|
|
|
+ if (this.opts.toolbarFixedTarget !== document)
|
|
|
+ {
|
|
|
+ top = (this.$toolbar.innerHeight() + this.$toolbar.offset().top) + this.opts.toolbarFixedTopOffset;
|
|
|
+ position = 'absolute';
|
|
|
+ }
|
|
|
+
|
|
|
+ $('.redactor-dropdown-' + this.uuid).each(function()
|
|
|
+ {
|
|
|
+ $(this).css({ position: position, top: top + 'px' });
|
|
|
+ });
|
|
|
+ },
|
|
|
+ unsetDropdownsFixed: function()
|
|
|
+ {
|
|
|
+ var top = (this.$toolbar.innerHeight() + this.$toolbar.offset().top);
|
|
|
+ $('.redactor-dropdown-' + this.uuid).each(function()
|
|
|
+ {
|
|
|
+ $(this).css({ position: 'absolute', top: top + 'px' });
|
|
|
+ });
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ upload: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ init: function(id, url, callback)
|
|
|
+ {
|
|
|
+ this.upload.direct = false;
|
|
|
+ this.upload.callback = callback;
|
|
|
+ this.upload.url = url;
|
|
|
+ this.upload.$el = $(id);
|
|
|
+ this.upload.$droparea = $('<div id="redactor-droparea" />');
|
|
|
+
|
|
|
+ this.upload.$placeholdler = $('<div id="redactor-droparea-placeholder" />').text(this.lang.get('upload_label'));
|
|
|
+ this.upload.$input = $('<input type="file" name="file" />');
|
|
|
+
|
|
|
+ this.upload.$placeholdler.append(this.upload.$input);
|
|
|
+ this.upload.$droparea.append(this.upload.$placeholdler);
|
|
|
+ this.upload.$el.append(this.upload.$droparea);
|
|
|
+
|
|
|
+ this.upload.$droparea.off('redactor.upload');
|
|
|
+ this.upload.$input.off('redactor.upload');
|
|
|
+
|
|
|
+ this.upload.$droparea.on('dragover.redactor.upload', $.proxy(this.upload.onDrag, this));
|
|
|
+ this.upload.$droparea.on('dragleave.redactor.upload', $.proxy(this.upload.onDragLeave, this));
|
|
|
+
|
|
|
+ // change
|
|
|
+ this.upload.$input.on('change.redactor.upload', $.proxy(function(e)
|
|
|
+ {
|
|
|
+ e = e.originalEvent || e;
|
|
|
+ this.upload.traverseFile(this.upload.$input[0].files[0], e);
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ // drop
|
|
|
+ this.upload.$droparea.on('drop.redactor.upload', $.proxy(function(e)
|
|
|
+ {
|
|
|
+ e.preventDefault();
|
|
|
+
|
|
|
+ this.upload.$droparea.removeClass('drag-hover').addClass('drag-drop');
|
|
|
+ this.upload.onDrop(e);
|
|
|
+
|
|
|
+ }, this));
|
|
|
+ },
|
|
|
+ directUpload: function(file, e)
|
|
|
+ {
|
|
|
+ this.upload.direct = true;
|
|
|
+ this.upload.traverseFile(file, e);
|
|
|
+ },
|
|
|
+ onDrop: function(e)
|
|
|
+ {
|
|
|
+ e = e.originalEvent || e;
|
|
|
+ var files = e.dataTransfer.files;
|
|
|
+
|
|
|
+ this.upload.traverseFile(files[0], e);
|
|
|
+ },
|
|
|
+ traverseFile: function(file, e)
|
|
|
+ {
|
|
|
+ if (this.opts.s3)
|
|
|
+ {
|
|
|
+ this.upload.setConfig(file);
|
|
|
+ this.upload.s3uploadFile(file);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ var formData = !!window.FormData ? new FormData() : null;
|
|
|
+ if (window.FormData)
|
|
|
+ {
|
|
|
+ this.upload.setConfig(file);
|
|
|
+
|
|
|
+ var name = (this.upload.type == 'image') ? this.opts.imageUploadParam : this.opts.fileUploadParam;
|
|
|
+ formData.append(name, file);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.progress.show();
|
|
|
+ this.core.setCallback('uploadStart', e, formData);
|
|
|
+ this.upload.sendData(formData, e);
|
|
|
+ },
|
|
|
+ setConfig: function(file)
|
|
|
+ {
|
|
|
+ this.upload.getType(file);
|
|
|
+
|
|
|
+ if (this.upload.direct)
|
|
|
+ {
|
|
|
+ this.upload.url = (this.upload.type == 'image') ? this.opts.imageUpload : this.opts.fileUpload;
|
|
|
+ this.upload.callback = (this.upload.type == 'image') ? this.image.insert : this.file.insert;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ getType: function(file)
|
|
|
+ {
|
|
|
+ this.upload.type = 'image';
|
|
|
+ if (this.opts.imageTypes.indexOf(file.type) == -1)
|
|
|
+ {
|
|
|
+ this.upload.type = 'file';
|
|
|
+ }
|
|
|
+ },
|
|
|
+ getHiddenFields: function(obj, fd)
|
|
|
+ {
|
|
|
+ if (obj === false || typeof obj !== 'object') return fd;
|
|
|
+
|
|
|
+ $.each(obj, $.proxy(function(k, v)
|
|
|
+ {
|
|
|
+ if (v !== null && v.toString().indexOf('#') === 0) v = $(v).val();
|
|
|
+ fd.append(k, v);
|
|
|
+
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ return fd;
|
|
|
+
|
|
|
+ },
|
|
|
+ sendData: function(formData, e)
|
|
|
+ {
|
|
|
+ // append hidden fields
|
|
|
+ if (this.upload.type == 'image')
|
|
|
+ {
|
|
|
+ formData = this.upload.getHiddenFields(this.opts.uploadImageFields, formData);
|
|
|
+ formData = this.upload.getHiddenFields(this.upload.imageFields, formData);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ formData = this.upload.getHiddenFields(this.opts.uploadFileFields, formData);
|
|
|
+ formData = this.upload.getHiddenFields(this.upload.fileFields, formData);
|
|
|
+ }
|
|
|
+
|
|
|
+ var xhr = new XMLHttpRequest();
|
|
|
+ xhr.open('POST', this.upload.url);
|
|
|
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
|
|
+
|
|
|
+ // complete
|
|
|
+ xhr.onreadystatechange = $.proxy(function()
|
|
|
+ {
|
|
|
+ if (xhr.readyState == 4)
|
|
|
+ {
|
|
|
+ var data = xhr.responseText;
|
|
|
+
|
|
|
+ data = data.replace(/^\[/, '');
|
|
|
+ data = data.replace(/\]$/, '');
|
|
|
+
|
|
|
+ var json;
|
|
|
+ try
|
|
|
+ {
|
|
|
+ json = (typeof data === 'string' ? $.parseJSON(data) : data);
|
|
|
+ }
|
|
|
+ catch(err)
|
|
|
+ {
|
|
|
+ json = {
|
|
|
+ error: true
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ this.progress.hide();
|
|
|
+
|
|
|
+ if (!this.upload.direct)
|
|
|
+ {
|
|
|
+ this.upload.$droparea.removeClass('drag-drop');
|
|
|
+ }
|
|
|
+
|
|
|
+ this.upload.callback(json, this.upload.direct, e);
|
|
|
+ }
|
|
|
+ }, this);
|
|
|
+
|
|
|
+
|
|
|
+ /*
|
|
|
+ xhr.upload.onprogress = $.proxy(function(e)
|
|
|
+ {
|
|
|
+ if (e.lengthComputable)
|
|
|
+ {
|
|
|
+ var complete = (e.loaded / e.total * 100 | 0);
|
|
|
+ //progress.value = progress.innerHTML = complete;
|
|
|
+ }
|
|
|
+
|
|
|
+ }, this);
|
|
|
+ */
|
|
|
+
|
|
|
+
|
|
|
+ xhr.send(formData);
|
|
|
+ },
|
|
|
+ onDrag: function(e)
|
|
|
+ {
|
|
|
+ e.preventDefault();
|
|
|
+ this.upload.$droparea.addClass('drag-hover');
|
|
|
+ },
|
|
|
+ onDragLeave: function(e)
|
|
|
+ {
|
|
|
+ e.preventDefault();
|
|
|
+ this.upload.$droparea.removeClass('drag-hover');
|
|
|
+ },
|
|
|
+ clearImageFields: function()
|
|
|
+ {
|
|
|
+ this.upload.imageFields = {};
|
|
|
+ },
|
|
|
+ addImageFields: function(name, value)
|
|
|
+ {
|
|
|
+ this.upload.imageFields[name] = value;
|
|
|
+ },
|
|
|
+ removeImageFields: function(name)
|
|
|
+ {
|
|
|
+ delete this.upload.imageFields[name];
|
|
|
+ },
|
|
|
+ clearFileFields: function()
|
|
|
+ {
|
|
|
+ this.upload.fileFields = {};
|
|
|
+ },
|
|
|
+ addFileFields: function(name, value)
|
|
|
+ {
|
|
|
+ this.upload.fileFields[name] = value;
|
|
|
+ },
|
|
|
+ removeFileFields: function(name)
|
|
|
+ {
|
|
|
+ delete this.upload.fileFields[name];
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ // S3
|
|
|
+ s3uploadFile: function(file)
|
|
|
+ {
|
|
|
+ this.upload.s3executeOnSignedUrl(file, $.proxy(function(signedURL)
|
|
|
+ {
|
|
|
+ this.upload.s3uploadToS3(file, signedURL);
|
|
|
+ }, this));
|
|
|
+ },
|
|
|
+ s3executeOnSignedUrl: function(file, callback)
|
|
|
+ {
|
|
|
+ var xhr = new XMLHttpRequest();
|
|
|
+ var mark = (this.opts.s3.search(/\?/) !== '-1') ? '?' : '&';
|
|
|
+
|
|
|
+ xhr.open('GET', this.opts.s3 + mark + 'name=' + file.name + '&type=' + file.type, true);
|
|
|
+
|
|
|
+ // Hack to pass bytes through unprocessed.
|
|
|
+ if (xhr.overrideMimeType) xhr.overrideMimeType('text/plain; charset=x-user-defined');
|
|
|
+
|
|
|
+ var that = this;
|
|
|
+ xhr.onreadystatechange = function(e)
|
|
|
+ {
|
|
|
+ if (this.readyState == 4 && this.status == 200)
|
|
|
+ {
|
|
|
+ that.progress.show();
|
|
|
+ callback(decodeURIComponent(this.responseText));
|
|
|
+ }
|
|
|
+ else if (this.readyState == 4 && this.status != 200)
|
|
|
+ {
|
|
|
+ //setProgress(0, 'Could not contact signing script. Status = ' + this.status);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ xhr.send();
|
|
|
+ },
|
|
|
+ s3createCORSRequest: function(method, url)
|
|
|
+ {
|
|
|
+ var xhr = new XMLHttpRequest();
|
|
|
+ if ("withCredentials" in xhr)
|
|
|
+ {
|
|
|
+ xhr.open(method, url, true);
|
|
|
+ }
|
|
|
+ else if (typeof XDomainRequest != "undefined")
|
|
|
+ {
|
|
|
+ xhr = new XDomainRequest();
|
|
|
+ xhr.open(method, url);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ xhr = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return xhr;
|
|
|
+ },
|
|
|
+ s3uploadToS3: function(file, url)
|
|
|
+ {
|
|
|
+ var xhr = this.upload.s3createCORSRequest('PUT', url);
|
|
|
+ if (!xhr)
|
|
|
+ {
|
|
|
+ //setProgress(0, 'CORS not supported');
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ xhr.onload = $.proxy(function()
|
|
|
+ {
|
|
|
+ if (xhr.status == 200)
|
|
|
+ {
|
|
|
+ //setProgress(100, 'Upload completed.');
|
|
|
+
|
|
|
+ this.progress.hide();
|
|
|
+
|
|
|
+ var s3file = url.split('?');
|
|
|
+
|
|
|
+ if (!s3file[0])
|
|
|
+ {
|
|
|
+ // url parsing is fail
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if (!this.upload.direct)
|
|
|
+ {
|
|
|
+ this.upload.$droparea.removeClass('drag-drop');
|
|
|
+ }
|
|
|
+
|
|
|
+ var json = { filelink: s3file[0] };
|
|
|
+ if (this.upload.type == 'file')
|
|
|
+ {
|
|
|
+ var arr = s3file[0].split('/');
|
|
|
+ json.filename = arr[arr.length-1];
|
|
|
+ }
|
|
|
+
|
|
|
+ this.upload.callback(json, this.upload.direct, false);
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ //setProgress(0, 'Upload error: ' + xhr.status);
|
|
|
+ }
|
|
|
+ }, this);
|
|
|
+
|
|
|
+ xhr.onerror = function() {};
|
|
|
+
|
|
|
+ xhr.upload.onprogress = function(e) {};
|
|
|
+
|
|
|
+ xhr.setRequestHeader('Content-Type', file.type);
|
|
|
+ xhr.setRequestHeader('x-amz-acl', 'public-read');
|
|
|
+
|
|
|
+ xhr.send(file);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ utils: function()
|
|
|
+ {
|
|
|
+ return {
|
|
|
+ isMobile: function()
|
|
|
+ {
|
|
|
+ return /(iPhone|iPod|BlackBerry|Android)/.test(navigator.userAgent);
|
|
|
+ },
|
|
|
+ isDesktop: function()
|
|
|
+ {
|
|
|
+ return !/(iPhone|iPod|iPad|BlackBerry|Android)/.test(navigator.userAgent);
|
|
|
+ },
|
|
|
+ isString: function(obj)
|
|
|
+ {
|
|
|
+ return Object.prototype.toString.call(obj) == '[object String]';
|
|
|
+ },
|
|
|
+ isEmpty: function(html, removeEmptyTags)
|
|
|
+ {
|
|
|
+ html = html.replace(/[\u200B-\u200D\uFEFF]/g, '');
|
|
|
+ html = html.replace(/ /gi, '');
|
|
|
+ html = html.replace(/<\/?br\s?\/?>/g, '');
|
|
|
+ html = html.replace(/\s/g, '');
|
|
|
+ html = html.replace(/^<p>[^\W\w\D\d]*?<\/p>$/i, '');
|
|
|
+ html = html.replace(/<iframe(.*?[^>])>$/i, 'iframe');
|
|
|
+ html = html.replace(/<source(.*?[^>])>$/i, 'source');
|
|
|
+
|
|
|
+ // remove empty tags
|
|
|
+ if (removeEmptyTags !== false)
|
|
|
+ {
|
|
|
+ html = html.replace(/<[^\/>][^>]*><\/[^>]+>/gi, '');
|
|
|
+ html = html.replace(/<[^\/>][^>]*><\/[^>]+>/gi, '');
|
|
|
+ }
|
|
|
+
|
|
|
+ html = $.trim(html);
|
|
|
+
|
|
|
+ return html === '';
|
|
|
+ },
|
|
|
+ normalize: function(str)
|
|
|
+ {
|
|
|
+ if (typeof(str) === 'undefined') return 0;
|
|
|
+ return parseInt(str.replace('px',''), 10);
|
|
|
+ },
|
|
|
+ hexToRgb: function(hex)
|
|
|
+ {
|
|
|
+ if (typeof hex == 'undefined') return;
|
|
|
+ if (hex.search(/^#/) == -1) return hex;
|
|
|
+
|
|
|
+ var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
|
|
+ hex = hex.replace(shorthandRegex, function(m, r, g, b)
|
|
|
+ {
|
|
|
+ return r + r + g + g + b + b;
|
|
|
+ });
|
|
|
+
|
|
|
+ var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
|
+ return 'rgb(' + parseInt(result[1], 16) + ', ' + parseInt(result[2], 16) + ', ' + parseInt(result[3], 16) + ')';
|
|
|
+ },
|
|
|
+ getOuterHtml: function(el)
|
|
|
+ {
|
|
|
+ return $('<div>').append($(el).eq(0).clone()).html();
|
|
|
+ },
|
|
|
+ getAlignmentElement: function(el)
|
|
|
+ {
|
|
|
+ if ($.inArray(el.tagName, this.opts.alignmentTags) !== -1)
|
|
|
+ {
|
|
|
+ return $(el);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return $(el).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ removeEmptyAttr: function(el, attr)
|
|
|
+ {
|
|
|
+ var $el = $(el);
|
|
|
+ if (typeof $el.attr(attr) == 'undefined')
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($el.attr(attr) === '')
|
|
|
+ {
|
|
|
+ $el.removeAttr(attr);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ },
|
|
|
+ removeEmpty: function(i, s)
|
|
|
+ {
|
|
|
+ var $s = $($.parseHTML(s));
|
|
|
+
|
|
|
+ $s.find('.redactor-invisible-space').removeAttr('style').removeAttr('class');
|
|
|
+
|
|
|
+ if ($s.find('hr, br, img, iframe, source').length !== 0) return;
|
|
|
+ var text = $.trim($s.text());
|
|
|
+
|
|
|
+ if (this.utils.isEmpty(text, false))
|
|
|
+ {
|
|
|
+ $s.remove();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // save and restore scroll
|
|
|
+ saveScroll: function()
|
|
|
+ {
|
|
|
+ this.saveEditorScroll = this.$editor.scrollTop();
|
|
|
+ this.saveBodyScroll = $(window).scrollTop();
|
|
|
+
|
|
|
+ if (this.opts.scrollTarget) this.saveTargetScroll = $(this.opts.scrollTarget).scrollTop();
|
|
|
+ },
|
|
|
+ restoreScroll: function()
|
|
|
+ {
|
|
|
+ if (typeof this.saveScroll === 'undefined' && typeof this.saveBodyScroll === 'undefined') return;
|
|
|
+
|
|
|
+ $(window).scrollTop(this.saveBodyScroll);
|
|
|
+ this.$editor.scrollTop(this.saveEditorScroll);
|
|
|
+
|
|
|
+ if (this.opts.scrollTarget) $(this.opts.scrollTarget).scrollTop(this.saveTargetScroll);
|
|
|
+ },
|
|
|
+
|
|
|
+ // get invisible space element
|
|
|
+ createSpaceElement: function()
|
|
|
+ {
|
|
|
+ var space = document.createElement('span');
|
|
|
+ space.className = 'redactor-invisible-space';
|
|
|
+ space.innerHTML = this.opts.invisibleSpace;
|
|
|
+
|
|
|
+ return space;
|
|
|
+ },
|
|
|
+
|
|
|
+ // replace
|
|
|
+ removeInlineTags: function(node)
|
|
|
+ {
|
|
|
+ var tags = this.opts.inlineTags;
|
|
|
+ tags.push('span');
|
|
|
+
|
|
|
+ if (node.tagName == 'PRE') tags.push('a');
|
|
|
+
|
|
|
+ $(node).find(tags.join(',')).not('span.redactor-selection-marker').contents().unwrap();
|
|
|
+ },
|
|
|
+ replaceWithContents: function(node, removeInlineTags)
|
|
|
+ {
|
|
|
+ var self = this;
|
|
|
+ $(node).replaceWith(function()
|
|
|
+ {
|
|
|
+ if (removeInlineTags === true) self.utils.removeInlineTags(this);
|
|
|
+
|
|
|
+ return $(this).contents();
|
|
|
+ });
|
|
|
+
|
|
|
+ return $(node);
|
|
|
+ },
|
|
|
+ replaceToTag: function(node, tag, removeInlineTags)
|
|
|
+ {
|
|
|
+ var replacement;
|
|
|
+ var self = this;
|
|
|
+ $(node).replaceWith(function()
|
|
|
+ {
|
|
|
+ replacement = $('<' + tag + ' />').append($(this).contents());
|
|
|
+
|
|
|
+ for (var i = 0; i < this.attributes.length; i++)
|
|
|
+ {
|
|
|
+ replacement.attr(this.attributes[i].name, this.attributes[i].value);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (removeInlineTags === true) self.utils.removeInlineTags(replacement);
|
|
|
+
|
|
|
+ return replacement;
|
|
|
+ });
|
|
|
+
|
|
|
+ return replacement;
|
|
|
+ },
|
|
|
+
|
|
|
+ // start and end of element
|
|
|
+ isStartOfElement: function()
|
|
|
+ {
|
|
|
+ var block = this.selection.getBlock();
|
|
|
+ if (!block) return false;
|
|
|
+
|
|
|
+ var offset = this.caret.getOffsetOfElement(block);
|
|
|
+
|
|
|
+ return (offset === 0) ? true : false;
|
|
|
+ },
|
|
|
+ isEndOfElement: function(element)
|
|
|
+ {
|
|
|
+ if (typeof element == 'undefined')
|
|
|
+ {
|
|
|
+ var element = this.selection.getBlock();
|
|
|
+ if (!element) return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ var offset = this.caret.getOffsetOfElement(element);
|
|
|
+ var text = $.trim($(element).text()).replace(/\n\r\n/g, '');
|
|
|
+
|
|
|
+ return (offset == text.length) ? true : false;
|
|
|
+ },
|
|
|
+ isStartOfEditor: function()
|
|
|
+ {
|
|
|
+ var offset = this.caret.getOffsetOfElement(this.$editor[0]);
|
|
|
+
|
|
|
+ return (offset === 0) ? true : false;
|
|
|
+ },
|
|
|
+ isEndOfEditor: function()
|
|
|
+ {
|
|
|
+ var block = this.$editor[0];
|
|
|
+
|
|
|
+ var offset = this.caret.getOffsetOfElement(block);
|
|
|
+ var text = $.trim($(block).html().replace(/(<([^>]+)>)/gi,''));
|
|
|
+
|
|
|
+ return (offset == text.length) ? true : false;
|
|
|
+ },
|
|
|
+
|
|
|
+ // test blocks
|
|
|
+ isBlock: function(block)
|
|
|
+ {
|
|
|
+ block = block[0] || block;
|
|
|
+
|
|
|
+ return block && this.utils.isBlockTag(block.tagName);
|
|
|
+ },
|
|
|
+ isBlockTag: function(tag)
|
|
|
+ {
|
|
|
+ if (typeof tag == 'undefined') return false;
|
|
|
+
|
|
|
+ return this.reIsBlock.test(tag);
|
|
|
+ },
|
|
|
+
|
|
|
+ // tag detection
|
|
|
+ isTag: function(current, tag)
|
|
|
+ {
|
|
|
+ var element = $(current).closest(tag, this.$editor[0]);
|
|
|
+ if (element.length == 1)
|
|
|
+ {
|
|
|
+ return element[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ },
|
|
|
+
|
|
|
+ // select all
|
|
|
+ isSelectAll: function()
|
|
|
+ {
|
|
|
+ return this.selectAll;
|
|
|
+ },
|
|
|
+ enableSelectAll: function()
|
|
|
+ {
|
|
|
+ this.selectAll = true;
|
|
|
+ },
|
|
|
+ disableSelectAll: function()
|
|
|
+ {
|
|
|
+ this.selectAll = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ // parents detection
|
|
|
+ isRedactorParent: function(el)
|
|
|
+ {
|
|
|
+ if (!el)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($(el).parents('.redactor-editor').length === 0 || $(el).hasClass('redactor-editor'))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return el;
|
|
|
+ },
|
|
|
+ isCurrentOrParentHeader: function()
|
|
|
+ {
|
|
|
+ return this.utils.isCurrentOrParent(['H1', 'H2', 'H3', 'H4', 'H5', 'H6']);
|
|
|
+ },
|
|
|
+ isCurrentOrParent: function(tagName)
|
|
|
+ {
|
|
|
+ var parent = this.selection.getParent();
|
|
|
+ var current = this.selection.getCurrent();
|
|
|
+
|
|
|
+ if ($.isArray(tagName))
|
|
|
+ {
|
|
|
+ var matched = 0;
|
|
|
+ $.each(tagName, $.proxy(function(i, s)
|
|
|
+ {
|
|
|
+ if (this.utils.isCurrentOrParentOne(current, parent, s))
|
|
|
+ {
|
|
|
+ matched++;
|
|
|
+ }
|
|
|
+ }, this));
|
|
|
+
|
|
|
+ return (matched === 0) ? false : true;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return this.utils.isCurrentOrParentOne(current, parent, tagName);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ isCurrentOrParentOne: function(current, parent, tagName)
|
|
|
+ {
|
|
|
+ tagName = tagName.toUpperCase();
|
|
|
+
|
|
|
+ return parent && parent.tagName === tagName ? parent : current && current.tagName === tagName ? current : false;
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ // browsers detection
|
|
|
+ isOldIe: function()
|
|
|
+ {
|
|
|
+ return (this.utils.browser('msie') && parseInt(this.utils.browser('version'), 10) < 9) ? true : false;
|
|
|
+ },
|
|
|
+ isLessIe10: function()
|
|
|
+ {
|
|
|
+ return (this.utils.browser('msie') && parseInt(this.utils.browser('version'), 10) < 10) ? true : false;
|
|
|
+ },
|
|
|
+ isIe11: function()
|
|
|
+ {
|
|
|
+ return !!navigator.userAgent.match(/Trident\/7\./);
|
|
|
+ },
|
|
|
+ browser: function(browser)
|
|
|
+ {
|
|
|
+ var ua = navigator.userAgent.toLowerCase();
|
|
|
+ var match = /(opr)[\/]([\w.]+)/.exec( ua ) ||
|
|
|
+ /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
|
|
|
+ /(webkit)[ \/]([\w.]+).*(safari)[ \/]([\w.]+)/.exec(ua) ||
|
|
|
+ /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
|
|
|
+ /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
|
|
|
+ /(msie) ([\w.]+)/.exec( ua ) ||
|
|
|
+ ua.indexOf("trident") >= 0 && /(rv)(?::| )([\w.]+)/.exec( ua ) ||
|
|
|
+ ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
|
|
|
+ [];
|
|
|
+
|
|
|
+ if (browser == 'safari') return (typeof match[3] != 'undefined') ? match[3] == 'safari' : false;
|
|
|
+ if (browser == 'version') return match[2];
|
|
|
+ if (browser == 'webkit') return (match[1] == 'chrome' || match[1] == 'opr' || match[1] == 'webkit');
|
|
|
+ if (match[1] == 'rv') return browser == 'msie';
|
|
|
+ if (match[1] == 'opr') return browser == 'webkit';
|
|
|
+
|
|
|
+ return browser == match[1];
|
|
|
+ },
|
|
|
+ strpos: function(haystack, needle, offset)
|
|
|
+ {
|
|
|
+ var i = haystack.indexOf(needle, offset);
|
|
|
+ return i >= 0 ? i : false;
|
|
|
+ },
|
|
|
+ disableBodyScroll: function()
|
|
|
+ {
|
|
|
+
|
|
|
+ var $body = $('html');
|
|
|
+ var windowWidth = window.innerWidth;
|
|
|
+ if (!windowWidth)
|
|
|
+ {
|
|
|
+ var documentElementRect = document.documentElement.getBoundingClientRect();
|
|
|
+ windowWidth = documentElementRect.right - Math.abs(documentElementRect.left);
|
|
|
+ }
|
|
|
+
|
|
|
+ var isOverflowing = document.body.clientWidth < windowWidth;
|
|
|
+ var scrollbarWidth = this.utils.measureScrollbar();
|
|
|
+
|
|
|
+ $body.css('overflow', 'hidden');
|
|
|
+ if (isOverflowing) $body.css('padding-right', scrollbarWidth);
|
|
|
+
|
|
|
+
|
|
|
+ },
|
|
|
+ measureScrollbar: function()
|
|
|
+ {
|
|
|
+ var $body = $('body');
|
|
|
+ var scrollDiv = document.createElement('div');
|
|
|
+ scrollDiv.className = 'redactor-scrollbar-measure';
|
|
|
+
|
|
|
+ $body.append(scrollDiv);
|
|
|
+ var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
|
|
|
+ $body[0].removeChild(scrollDiv);
|
|
|
+ return scrollbarWidth;
|
|
|
+ },
|
|
|
+ enableBodyScroll: function()
|
|
|
+ {
|
|
|
+ $('html').css({ 'overflow': '', 'padding-right': '' });
|
|
|
+ $('body').remove('redactor-scrollbar-measure');
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ $(window).on('load.tools.redactor', function()
|
|
|
+ {
|
|
|
+ $('[data-tools="redactor"]').redactor();
|
|
|
+ });
|
|
|
+
|
|
|
+ // constructor
|
|
|
+ Redactor.prototype.init.prototype = Redactor.prototype;
|
|
|
+
|
|
|
+})(jQuery);
|