/** * Trumbowyg v2.23.0 - A lightweight WYSIWYG editor * Trumbowyg core file * ------------------------ * @link http://alex-d.github.io/Trumbowyg * @license MIT * @author Alexandre Demode (Alex-D) * Twitter : @AlexandreDemode * Website : alex-d.fr */ jQuery.trumbowyg = { langs: { en: { viewHTML: 'View HTML', undo: 'Undo', redo: 'Redo', formatting: 'Formatting', p: 'Paragraph', blockquote: 'Quote', code: 'Code', header: 'Header', bold: 'Bold', italic: 'Italic', strikethrough: 'Strikethrough', underline: 'Underline', strong: 'Strong', em: 'Emphasis', del: 'Deleted', superscript: 'Superscript', subscript: 'Subscript', unorderedList: 'Unordered list', orderedList: 'Ordered list', insertImage: 'Insert Image', link: 'Link', createLink: 'Insert link', unlink: 'Remove link', justifyLeft: 'Align Left', justifyCenter: 'Align Center', justifyRight: 'Align Right', justifyFull: 'Align Justify', horizontalRule: 'Insert horizontal rule', removeformat: 'Remove format', fullscreen: 'Fullscreen', close: 'Close', submit: 'Confirm', reset: 'Cancel', required: 'Required', description: 'Description', title: 'Title', text: 'Text', target: 'Target', width: 'Width' } }, // Plugins plugins: {}, // SVG Path globally svgPath: null, svgAbsoluteUseHref: false, hideButtonTexts: null }; // Makes default options read-only Object.defineProperty(jQuery.trumbowyg, 'defaultOptions', { value: { lang: 'en', fixedBtnPane: false, fixedFullWidth: false, autogrow: false, autogrowOnEnter: false, imageWidthModalEdit: false, hideButtonTexts: null, prefix: 'trumbowyg-', tagClasses: {}, semantic: true, semanticKeepAttributes: false, resetCss: false, removeformatPasted: false, tabToIndent: false, tagsToRemove: [], tagsToKeep: ['hr', 'img', 'embed', 'iframe', 'input'], btns: [ ['viewHTML'], ['undo', 'redo'], // Only supported in Blink browsers ['formatting'], ['strong', 'em', 'del'], ['superscript', 'subscript'], ['link'], ['insertImage'], ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'], ['unorderedList', 'orderedList'], ['horizontalRule'], ['removeformat'], ['fullscreen'] ], // For custom button definitions btnsDef: {}, changeActiveDropdownIcon: false, inlineElementsSelector: 'a,abbr,acronym,b,caption,cite,code,col,dfn,dir,dt,dd,em,font,hr,i,kbd,li,q,span,strikeout,strong,sub,sup,u', pasteHandlers: [], // imgDblClickHandler: default is defined in constructor plugins: {}, urlProtocol: false, minimalLinks: false, defaultLinkTarget: undefined, svgPath: null }, writable: false, enumerable: true, configurable: false }); (function (navigator, window, document, $) { 'use strict'; var CONFIRM_EVENT = 'tbwconfirm', CANCEL_EVENT = 'tbwcancel'; $.fn.trumbowyg = function (options, params) { var trumbowygDataName = 'trumbowyg'; if (options === Object(options) || !options) { return this.each(function () { if (!$(this).data(trumbowygDataName)) { $(this).data(trumbowygDataName, new Trumbowyg(this, options)); } }); } if (this.length === 1) { try { var t = $(this).data(trumbowygDataName); switch (options) { // Exec command case 'execCmd': return t.execCmd(params.cmd, params.param, params.forceCss, params.skipTrumbowyg); // Modal box case 'openModal': return t.openModal(params.title, params.content); case 'closeModal': return t.closeModal(); case 'openModalInsert': return t.openModalInsert(params.title, params.fields, params.callback); // Range case 'saveRange': return t.saveRange(); case 'getRange': return t.range; case 'getRangeText': return t.getRangeText(); case 'restoreRange': return t.restoreRange(); // Enable/disable case 'enable': return t.setDisabled(false); case 'disable': return t.setDisabled(true); // Toggle case 'toggle': return t.toggle(); // Destroy case 'destroy': return t.destroy(); // Empty case 'empty': return t.empty(); // HTML case 'html': return t.html(params); } } catch (c) { } } return false; }; // @param: editorElem is the DOM element var Trumbowyg = function (editorElem, options) { var t = this, trumbowygIconsId = 'trumbowyg-icons', $trumbowyg = $.trumbowyg; // Get the document of the element. It use to makes the plugin // compatible on iframes. t.doc = editorElem.ownerDocument || document; // jQuery object of the editor t.$ta = $(editorElem); // $ta : Textarea t.$c = $(editorElem); // $c : creator options = options || {}; // Localization management if (options.lang != null || $trumbowyg.langs[options.lang] != null) { t.lang = $.extend(true, {}, $trumbowyg.langs.en, $trumbowyg.langs[options.lang]); } else { t.lang = $trumbowyg.langs.en; } t.hideButtonTexts = $trumbowyg.hideButtonTexts != null ? $trumbowyg.hideButtonTexts : options.hideButtonTexts; // SVG path var svgPathOption = $trumbowyg.svgPath != null ? $trumbowyg.svgPath : options.svgPath; t.hasSvg = svgPathOption !== false; if (svgPathOption !== false && ($trumbowyg.svgAbsoluteUseHref || $('#' + trumbowygIconsId, t.doc).length === 0)) { if (svgPathOption == null) { // Hack to get svgPathOption based on trumbowyg.js path var $scriptElements = $('script[src]'); $scriptElements.each(function (i, scriptElement) { var source = scriptElement.src; var matches = source.match('trumbowyg(\.min)?\.js'); if (matches != null) { svgPathOption = source.substring(0, source.indexOf(matches[0])) + 'ui/icons.svg'; } }); } // Do not merge with previous if block: svgPathOption can be redefined in it. // Here we are checking that we find a match if (svgPathOption == null) { console.warn('You must define svgPath: https://goo.gl/CfTY9U'); // jshint ignore:line } else if (!$trumbowyg.svgAbsoluteUseHref) { var div = t.doc.createElement('div'); div.id = trumbowygIconsId; t.doc.body.insertBefore(div, t.doc.body.childNodes[0]); $.ajax({ async: true, type: 'GET', contentType: 'application/x-www-form-urlencoded; charset=UTF-8', dataType: 'xml', crossDomain: true, url: svgPathOption, data: null, beforeSend: null, complete: null, success: function (data) { div.innerHTML = new XMLSerializer().serializeToString(data.documentElement); } }); } } var baseHref = !!t.doc.querySelector('base') ? window.location.href.split(/[?#]/)[0] : ''; t.svgPath = $trumbowyg.svgAbsoluteUseHref ? svgPathOption : baseHref; /** * When the button is associated to a empty object * fn and title attributes are defined from the button key value * * For example * foo: {} * is equivalent to : * foo: { * fn: 'foo', * title: this.lang.foo * } */ var h = t.lang.header, // Header translation isBlinkFunction = function () { return (window.chrome || (window.Intl && Intl.v8BreakIterator)) && 'CSS' in window; }; t.btnsDef = { viewHTML: { fn: 'toggle', class: 'trumbowyg-not-disable', }, undo: { isSupported: isBlinkFunction, key: 'Z' }, redo: { isSupported: isBlinkFunction, key: 'Y' }, p: { fn: 'formatBlock' }, blockquote: { fn: 'formatBlock' }, h1: { fn: 'formatBlock', title: h + ' 1' }, h2: { fn: 'formatBlock', title: h + ' 2' }, h3: { fn: 'formatBlock', title: h + ' 3' }, h4: { fn: 'formatBlock', title: h + ' 4' }, h5: { fn: 'formatBlock', title: h + ' 5' }, h6: { fn: 'formatBlock', title: h + ' 6' }, subscript: { tag: 'sub' }, superscript: { tag: 'sup' }, bold: { key: 'B', tag: 'b' }, italic: { key: 'I', tag: 'i' }, underline: { tag: 'u' }, strikethrough: { tag: 'strike' }, strong: { fn: 'bold', key: 'B' }, em: { fn: 'italic', key: 'I' }, del: { fn: 'strikethrough' }, createLink: { key: 'K', tag: 'a' }, unlink: {}, insertImage: {}, justifyLeft: { tag: 'left', forceCss: true }, justifyCenter: { tag: 'center', forceCss: true }, justifyRight: { tag: 'right', forceCss: true }, justifyFull: { tag: 'justify', forceCss: true }, unorderedList: { fn: 'insertUnorderedList', tag: 'ul' }, orderedList: { fn: 'insertOrderedList', tag: 'ol' }, horizontalRule: { fn: 'insertHorizontalRule' }, removeformat: {}, fullscreen: { class: 'trumbowyg-not-disable' }, close: { fn: 'destroy', class: 'trumbowyg-not-disable' }, // Dropdowns formatting: { dropdown: ['p', 'blockquote', 'h1', 'h2', 'h3', 'h4'], ico: 'p' }, link: { dropdown: ['createLink', 'unlink'] } }; // Default Options t.o = $.extend(true, {}, $trumbowyg.defaultOptions, options); if (!t.o.hasOwnProperty('imgDblClickHandler')) { t.o.imgDblClickHandler = t.getDefaultImgDblClickHandler(); } t.urlPrefix = t.setupUrlPrefix(); t.disabled = t.o.disabled || (editorElem.nodeName === 'TEXTAREA' && editorElem.disabled); if (options.btns) { t.o.btns = options.btns; } else if (!t.o.semantic) { t.o.btns[3] = ['bold', 'italic', 'underline', 'strikethrough']; } $.each(t.o.btnsDef, function (btnName, btnDef) { t.addBtnDef(btnName, btnDef); }); // put this here in the event it would be merged in with options t.eventNamespace = 'trumbowyg-event'; // Keyboard shortcuts are load in this array t.keys = []; // Tag to button dynamically hydrated t.tagToButton = {}; t.tagHandlers = []; // Admit multiple paste handlers t.pasteHandlers = [].concat(t.o.pasteHandlers); // Check if browser is IE t.isIE = navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') !== -1; // Check if we are on macOs t.isMac = navigator.platform.toUpperCase().indexOf('MAC') !== -1; t.init(); }; Trumbowyg.prototype = { DEFAULT_SEMANTIC_MAP: { 'b': 'strong', 'i': 'em', 's': 'del', 'strike': 'del', 'div': 'p' }, init: function () { var t = this; t.height = t.$ta.height(); t.initPlugins(); try { // Disable image resize, try-catch for old IE t.doc.execCommand('enableObjectResizing', false, false); t.doc.execCommand('defaultParagraphSeparator', false, 'p'); } catch (e) { } t.buildEditor(); t.buildBtnPane(); t.fixedBtnPaneEvents(); t.buildOverlay(); setTimeout(function () { if (t.disabled) { t.setDisabled(true); } t.$c.trigger('tbwinit'); }); }, addBtnDef: function (btnName, btnDef) { this.btnsDef[btnName] = $.extend(btnDef, this.btnsDef[btnName] || {}); }, setupUrlPrefix: function () { var protocol = this.o.urlProtocol; if (!protocol) { return; } if (typeof (protocol) !== 'string') { return 'https://'; } return protocol.replace('://', '') + '://'; }, buildEditor: function () { var t = this, prefix = t.o.prefix, html = ''; t.$box = $('
', { class: prefix + 'box ' + prefix + 'editor-visible ' + prefix + t.o.lang + ' trumbowyg' }); // $ta = Textarea // $ed = Editor t.isTextarea = t.$ta.is('textarea'); if (t.isTextarea) { html = t.$ta.val(); t.$ed = $(''); t.$box .insertAfter(t.$ta) .append(t.$ed, t.$ta); } else { t.$ed = t.$ta; html = t.$ed.html(); t.$ta = $('', { name: t.$ta.attr('id'), height: t.height }).val(html); t.$box .insertAfter(t.$ed) .append(t.$ta, t.$ed); t.syncCode(); } t.$ta .addClass(prefix + 'textarea') .attr('tabindex', -1) ; t.$ed .addClass(prefix + 'editor') .attr({ contenteditable: true, dir: t.lang._dir || 'ltr' }) .html(html) ; if (t.o.tabindex) { t.$ed.attr('tabindex', t.o.tabindex); } if (t.$c.is('[placeholder]')) { t.$ed.attr('placeholder', t.$c.attr('placeholder')); } if (t.$c.is('[spellcheck]')) { t.$ed.attr('spellcheck', t.$c.attr('spellcheck')); } if (t.o.resetCss) { t.$ed.addClass(prefix + 'reset-css'); } if (!t.o.autogrow) { t.$ta.add(t.$ed).css({ height: t.height }); } t.semanticCode(); if (t.o.autogrowOnEnter) { t.$ed.addClass(prefix + 'autogrow-on-enter'); } var ctrl = false, composition = false, debounceButtonPaneStatus; t.$ed .on('dblclick', 'img', t.o.imgDblClickHandler) .on('keydown', function (e) { // append flags to differentiate Chrome spans var keyCode = e.which; if (keyCode === 8 || keyCode === 13 || keyCode === 46) { t.toggleSpan(true); } if ((e.ctrlKey || e.metaKey) && !e.altKey) { ctrl = true; var key = t.keys[String.fromCharCode(e.which).toUpperCase()]; try { t.execCmd(key.fn, key.param); return false; } catch (c) { } } else { if (t.o.tabToIndent && e.key === 'Tab') { try { if (e.shiftKey) { t.execCmd('outdent', true, null); } else { t.execCmd('indent', true, null); } return false; } catch (c) { } } } }) .on('compositionstart compositionupdate', function () { composition = true; }) .on('keyup compositionend', function (e) { if (e.type === 'compositionend') { composition = false; } else if (composition) { return; } var keyCode = e.which; if (keyCode >= 37 && keyCode <= 40) { return; } // remove Chrome generated span tags if (keyCode === 8 || keyCode === 13 || keyCode === 46) { t.toggleSpan(); } if ((e.ctrlKey || e.metaKey) && (keyCode === 89 || keyCode === 90)) { t.semanticCode(false, true); t.$c.trigger('tbwchange'); } else if (!ctrl && keyCode !== 17) { var compositionEndIE = t.isIE ? e.type === 'compositionend' : true; t.semanticCode(false, compositionEndIE && keyCode === 13); t.$c.trigger('tbwchange'); } else if (typeof e.which === 'undefined') { t.semanticCode(false, false, true); } setTimeout(function () { ctrl = false; }, 50); }) .on('mouseup keydown keyup', function (e) { if ((!e.ctrlKey && !e.metaKey) || e.altKey) { setTimeout(function () { // "hold on" to the ctrl key for 50ms ctrl = false; }, 50); } clearTimeout(debounceButtonPaneStatus); debounceButtonPaneStatus = setTimeout(function () { t.updateButtonPaneStatus(); }, 50); }) .on('focus blur', function (e) { if (e.type === 'blur') { t.clearButtonPaneStatus(); } t.$c.trigger('tbw' + e.type); if (t.o.autogrowOnEnter) { if (t.autogrowOnEnterDontClose) { return; } if (e.type === 'focus') { t.autogrowOnEnterWasFocused = true; t.autogrowEditorOnEnter(); } else if (!t.o.autogrow) { t.$ed.css({height: t.$ed.css('min-height')}); t.$c.trigger('tbwresize'); } } }) .on('keyup focus', function () { if (!t.$ta.val().match(/<.*>/) && !t.$ed.html().match(/<.*>/)) { setTimeout(function () { var block = t.isIE ? '' : 'p'; t.doc.execCommand('formatBlock', false, block); t.syncCode(); }, 0); } }) .on('cut drop', function () { setTimeout(function () { t.semanticCode(false, true); t.$c.trigger('tbwchange'); }, 0); }) .on('paste', function (e) { if (t.o.removeformatPasted) { e.preventDefault(); if (window.getSelection && window.getSelection().deleteFromDocument) { window.getSelection().deleteFromDocument(); } try { // IE var text = window.clipboardData.getData('Text'); try { // <= IE10 t.doc.selection.createRange().pasteHTML(text); } catch (c) { // IE 11 t.doc.getSelection().getRangeAt(0).insertNode(t.doc.createTextNode(text)); } t.$c.trigger('tbwchange', e); } catch (d) { // Not IE t.execCmd('insertText', (e.originalEvent || e).clipboardData.getData('text/plain')); } } // Call pasteHandlers $.each(t.pasteHandlers, function (i, pasteHandler) { pasteHandler(e); }); setTimeout(function () { t.semanticCode(false, true); t.$c.trigger('tbwpaste', e); t.$c.trigger('tbwchange'); }, 0); }); t.$ta .on('keyup', function () { t.$c.trigger('tbwchange'); }) .on('paste', function () { setTimeout(function () { t.$c.trigger('tbwchange'); }, 0); }); $(t.doc.body).on('keydown.' + t.eventNamespace, function (e) { if (e.which === 27 && $('.' + prefix + 'modal-box').length >= 1) { t.closeModal(); return false; } }); }, //autogrow when entering logic autogrowEditorOnEnter: function () { var t = this; t.$ed.removeClass('autogrow-on-enter'); var oldHeight = t.$ed[0].clientHeight; t.$ed.height('auto'); var totalHeight = t.$ed[0].scrollHeight; t.$ed.addClass('autogrow-on-enter'); if (oldHeight !== totalHeight) { t.$ed.height(oldHeight); setTimeout(function () { t.$ed.css({height: totalHeight}); t.$c.trigger('tbwresize'); }, 0); } }, // Build button pane, use o.btns option buildBtnPane: function () { var t = this, prefix = t.o.prefix; var $btnPane = t.$btnPane = $('
', { class: prefix + 'button-pane' }); $.each(t.o.btns, function (i, btnGrp) { if (!$.isArray(btnGrp)) { btnGrp = [btnGrp]; } var $btnGroup = $('', { class: prefix + 'button-group ' + ((btnGrp.indexOf('fullscreen') >= 0) ? prefix + 'right' : '') }); $.each(btnGrp, function (i, btn) { try { // Prevent buildBtn error if (t.isSupportedBtn(btn)) { // It's a supported button $btnGroup.append(t.buildBtn(btn)); } } catch (c) { } }); if ($btnGroup.html().trim().length > 0) { $btnPane.append($btnGroup); } }); t.$box.prepend($btnPane); }, // Build a button and his action buildBtn: function (btnName) { // btnName is name of the button var t = this, prefix = t.o.prefix, btn = t.btnsDef[btnName], isDropdown = btn.dropdown, hasIcon = btn.hasIcon != null ? btn.hasIcon : true, textDef = t.lang[btnName] || btnName, $btn = $('', { type: 'button', class: prefix + btnName + '-button ' + (btn.class || '') + (!hasIcon ? ' ' + prefix + 'textual-button' : ''), html: t.hasSvg && hasIcon ? '' : t.hideButtonTexts ? '' : (btn.text || btn.title || t.lang[btnName] || btnName), title: (btn.title || btn.text || textDef) + (btn.key ? ' (' + (t.isMac ? 'Cmd' : 'Ctrl') + ' + ' + btn.key + ')' : ''), tabindex: -1, mousedown: function () { if (!isDropdown || $('.' + btnName + '-' + prefix + 'dropdown', t.$box).is(':hidden')) { $('body', t.doc).trigger('mousedown'); } if ((t.$btnPane.hasClass(prefix + 'disable') || t.$box.hasClass(prefix + 'disabled')) && !$(this).hasClass(prefix + 'active') && !$(this).hasClass(prefix + 'not-disable')) { return false; } t.execCmd((isDropdown ? 'dropdown' : false) || btn.fn || btnName, btn.param || btnName, btn.forceCss); return false; } }); if (isDropdown) { $btn.addClass(prefix + 'open-dropdown'); var dropdownPrefix = prefix + 'dropdown', dropdownOptions = { // the dropdown class: dropdownPrefix + '-' + btnName + ' ' + dropdownPrefix + ' ' + prefix + 'fixed-top ' + (btn.dropdownClass || '') }; dropdownOptions['data-' + dropdownPrefix] = btnName; var $dropdown = $('', dropdownOptions); $.each(isDropdown, function (i, def) { if (t.btnsDef[def] && t.isSupportedBtn(def)) { $dropdown.append(t.buildSubBtn(def)); } }); t.$box.append($dropdown.hide()); } else if (btn.key) { t.keys[btn.key] = { fn: btn.fn || btnName, param: btn.param || btnName }; } if (!isDropdown) { t.tagToButton[(btn.tag || btnName).toLowerCase()] = btnName; } return $btn; }, // Build a button for dropdown menu // @param n : name of the subbutton buildSubBtn: function (btnName) { var t = this, prefix = t.o.prefix, btn = t.btnsDef[btnName], hasIcon = btn.hasIcon != null ? btn.hasIcon : true; if (btn.key) { t.keys[btn.key] = { fn: btn.fn || btnName, param: btn.param || btnName }; } t.tagToButton[(btn.tag || btnName).toLowerCase()] = btnName; return $('', { type: 'button', class: prefix + btnName + '-dropdown-button ' + (btn.class || '') + (btn.ico ? ' ' + prefix + btn.ico + '-button' : ''), html: t.hasSvg && hasIcon ? '' + (btn.text || btn.title || t.lang[btnName] || btnName) : (btn.text || btn.title || t.lang[btnName] || btnName), title: (btn.key ? '(' + (t.isMac ? 'Cmd' : 'Ctrl') + ' + ' + btn.key + ')' : null), style: btn.style || null, mousedown: function () { $('body', t.doc).trigger('mousedown'); t.execCmd(btn.fn || btnName, btn.param || btnName, btn.forceCss); return false; } }); }, // Check if button is supported isSupportedBtn: function (btnName) { try { return this.btnsDef[btnName].isSupported(); } catch (e) { } return true; }, // Build overlay for modal box buildOverlay: function () { var t = this; t.$overlay = $('', { class: t.o.prefix + 'overlay' }).appendTo(t.$box); return t.$overlay; }, showOverlay: function () { var t = this; $(window).trigger('scroll'); t.$overlay.fadeIn(200); t.$box.addClass(t.o.prefix + 'box-blur'); }, hideOverlay: function () { var t = this; t.$overlay.fadeOut(50); t.$box.removeClass(t.o.prefix + 'box-blur'); }, // Management of fixed button pane fixedBtnPaneEvents: function () { var t = this, fixedFullWidth = t.o.fixedFullWidth, $box = t.$box; if (!t.o.fixedBtnPane) { return; } t.isFixed = false; $(window) .on('scroll.' + t.eventNamespace + ' resize.' + t.eventNamespace, function () { if (!$box) { return; } t.syncCode(); var scrollTop = $(window).scrollTop(), offset = $box.offset().top + 1, $buttonPane = t.$btnPane, buttonPaneOuterHeight = $buttonPane.outerHeight() - 2; if ((scrollTop - offset > 0) && ((scrollTop - offset - t.height) < 0)) { if (!t.isFixed) { t.isFixed = true; $buttonPane.css({ position: 'fixed', top: 0, left: fixedFullWidth ? 0 : 'auto', zIndex: 7 }); t.$box.css({paddingTop: $buttonPane.height()}); } $buttonPane.css({ width: fixedFullWidth ? '100%' : (($box.width() - 1)) }); $('.' + t.o.prefix + 'fixed-top', $box).css({ position: fixedFullWidth ? 'fixed' : 'absolute', top: fixedFullWidth ? buttonPaneOuterHeight : buttonPaneOuterHeight + (scrollTop - offset), zIndex: 15 }); } else if (t.isFixed) { t.isFixed = false; $buttonPane.removeAttr('style'); t.$box.css({paddingTop: 0}); $('.' + t.o.prefix + 'fixed-top', $box).css({ position: 'absolute', top: buttonPaneOuterHeight }); } }); }, // Disable editor setDisabled: function (disable) { var t = this, prefix = t.o.prefix; t.disabled = disable; if (disable) { t.$ta.attr('disabled', true); } else { t.$ta.removeAttr('disabled'); } t.$box.toggleClass(prefix + 'disabled', disable); t.$ed.attr('contenteditable', !disable); }, // Destroy the editor destroy: function () { var t = this, prefix = t.o.prefix; if (t.isTextarea) { t.$box.after( t.$ta .css({height: ''}) .val(t.html()) .removeClass(prefix + 'textarea') .show() ); } else { t.$box.after( t.$ed .css({height: ''}) .removeClass(prefix + 'editor') .removeAttr('contenteditable') .removeAttr('dir') .html(t.html()) .show() ); } t.$ed.off('dblclick', 'img'); t.destroyPlugins(); t.$box.remove(); t.$c.removeData('trumbowyg'); $('body').removeClass(prefix + 'body-fullscreen'); t.$c.trigger('tbwclose'); $(window).off('scroll.' + t.eventNamespace + ' resize.' + t.eventNamespace); $(t.doc.body).off('keydown.' + t.eventNamespace); }, // Empty the editor empty: function () { this.$ta.val(''); this.syncCode(true); }, // Function call when click on viewHTML button toggle: function () { var t = this, prefix = t.o.prefix; if (t.o.autogrowOnEnter) { t.autogrowOnEnterDontClose = !t.$box.hasClass(prefix + 'editor-hidden'); } t.semanticCode(false, true); t.$c.trigger('tbwchange'); setTimeout(function () { t.doc.activeElement.blur(); t.$box.toggleClass(prefix + 'editor-hidden ' + prefix + 'editor-visible'); t.$btnPane.toggleClass(prefix + 'disable'); $('.' + prefix + 'viewHTML-button', t.$btnPane).toggleClass(prefix + 'active'); if (t.$box.hasClass(prefix + 'editor-visible')) { t.$ta.attr('tabindex', -1); } else { t.$ta.removeAttr('tabindex'); } if (t.o.autogrowOnEnter && !t.autogrowOnEnterDontClose) { t.autogrowEditorOnEnter(); } }, 0); }, // Remove or add flags to span tags to remove Chrome generated spans toggleSpan: function (addFlag) { var t = this; t.$ed.find('span').each(function () { if (addFlag === true) { $(this).attr('data-tbw-flag', true); } else { if ($(this).attr('data-tbw-flag')) { $(this).removeAttr('data-tbw-flag'); } else { $(this).contents().unwrap(); } } }); }, // Open dropdown when click on a button which open that dropdown: function (name) { var t = this, $body = $('body', t.doc), prefix = t.o.prefix, $dropdown = $('[data-' + prefix + 'dropdown=' + name + ']', t.$box), $btn = $('.' + prefix + name + '-button', t.$btnPane), show = $dropdown.is(':hidden'); $body.trigger('mousedown'); if (show) { var btnOffsetLeft = $btn.offset().left; $btn.addClass(prefix + 'active'); $dropdown.css({ position: 'absolute', top: $btn.offset().top - t.$btnPane.offset().top + $btn.outerHeight(), left: (t.o.fixedFullWidth && t.isFixed) ? btnOffsetLeft : (btnOffsetLeft - t.$btnPane.offset().left) }).show(); $(window).trigger('scroll'); $body.on('mousedown.' + t.eventNamespace, function (e) { if (!$dropdown.is(e.target)) { $('.' + prefix + 'dropdown', t.$box).hide(); $('.' + prefix + 'active', t.$btnPane).removeClass(prefix + 'active'); $body.off('mousedown.' + t.eventNamespace); } }); } }, // HTML Code management html: function (html) { var t = this; if (html != null) { t.$ta.val(html); t.syncCode(true); t.$c.trigger('tbwchange'); return t; } return t.$ta.val(); }, syncTextarea: function () { var t = this; t.$ta.val(t.$ed.text().trim().length > 0 || t.$ed.find(t.o.tagsToKeep.join(',')).length > 0 ? t.$ed.html() : ''); }, syncCode: function (force) { var t = this; if (!force && t.$ed.is(':visible')) { t.syncTextarea(); } else { // wrap the content in a div it's easier to get the inner html var html = $('// @param keepRange : leave selection range as it is semanticCode: function (force, full, keepRange) { var t = this; t.saveRange(); t.syncCode(force); var restoreRange = true; if (t.range && t.range.collapsed) { restoreRange = false; } if (t.o.semantic) { t.semanticTag('b', t.o.semanticKeepAttributes); t.semanticTag('i', t.o.semanticKeepAttributes); t.semanticTag('s', t.o.semanticKeepAttributes); t.semanticTag('strike', t.o.semanticKeepAttributes); if (full) { var inlineElementsSelector = t.o.inlineElementsSelector, blockElementsSelector = ':not(' + inlineElementsSelector + ')'; // Wrap text nodes in span for easier processing t.$ed.contents().filter(function () { return this.nodeType === 3 && this.nodeValue.trim().length > 0; }).wrap(''); // Wrap groups of inline elements in paragraphs (recursive) var wrapInlinesInParagraphsFrom = function ($from) { if ($from.length !== 0) { var $finalParagraph = $from.nextUntil(blockElementsSelector).addBack().wrapAll('
').parent(), $nextElement = $finalParagraph.nextAll(inlineElementsSelector).first(); $finalParagraph.next('br').remove(); wrapInlinesInParagraphsFrom($nextElement); } }; wrapInlinesInParagraphsFrom(t.$ed.children(inlineElementsSelector).first()); t.semanticTag('div', true); // Get rid of temporary span's $('[data-tbw]', t.$ed).contents().unwrap(); // Remove emptyt.$ed.find('p:empty').remove(); } if (!keepRange && restoreRange) { t.restoreRange(); } t.syncTextarea(); } }, semanticTag: function (oldTag, copyAttributes, revert) { var newTag, t = this; var tmpTag = oldTag; if (this.o.semantic != null && typeof this.o.semantic === 'object' && this.o.semantic.hasOwnProperty(oldTag)) { newTag = this.o.semantic[oldTag]; } else if (this.o.semantic === true && this.DEFAULT_SEMANTIC_MAP.hasOwnProperty(oldTag)) { newTag = this.DEFAULT_SEMANTIC_MAP[oldTag]; } else { return; } if(revert) { oldTag = newTag; newTag = tmpTag; } $(oldTag, this.$ed).each(function () { var resetRange = false; var $oldTag = $(this); if ($oldTag.contents().length === 0) { return false; } if(t.range.startContainer.parentNode && t.range.startContainer.parentNode === this) { resetRange = true; } var $newTag = $('<' + newTag + '/>'); $newTag.insertBefore($oldTag); if (copyAttributes) { $.each($oldTag.prop('attributes'), function () { $newTag.attr(this.name, this.value); }); } $newTag.html($oldTag.html()); $oldTag.remove(); if(resetRange === true) { t.range.selectNodeContents($newTag.get(0)); t.range.collapse(false); } }); }, // Function call when user click on "Insert Link" createLink: function () { var t = this, documentSelection = t.doc.getSelection(), selectedRange = documentSelection.getRangeAt(0), node = documentSelection.focusNode, text = new XMLSerializer().serializeToString(selectedRange.cloneContents()) || selectedRange + '', url, title, target; while (['A', 'DIV'].indexOf(node.nodeName) < 0) { node = node.parentNode; } if (node && node.nodeName === 'A') { var $a = $(node); text = $a.text(); url = $a.attr('href'); if (!t.o.minimalLinks) { title = $a.attr('title'); target = $a.attr('target') || t.o.defaultLinkTarget; } var range = t.doc.createRange(); range.selectNode(node); documentSelection.removeAllRanges(); documentSelection.addRange(range); } t.saveRange(); var options = { url: { label: t.lang.linkUrl || 'URL', required: true, value: url }, text: { label: t.lang.text, value: text } }; if (!t.o.minimalLinks) { $.extend(options, { title: { label: t.lang.title, value: title }, target: { label: t.lang.target, value: target } }); } t.openModalInsert(t.lang.createLink, options, function (v) { // v is value var url = t.prependUrlPrefix(v.url); if (!url.length) { return false; } var link = $(['', v.text || v.url, ''].join('')); if (v.title) { link.attr('title', v.title); } if (v.target || t.o.defaultLinkTarget) { link.attr('target', v.target || t.o.defaultLinkTarget); } t.range.deleteContents(); t.range.insertNode(link[0]); t.syncCode(); t.$c.trigger('tbwchange'); return true; }); }, prependUrlPrefix: function (url) { var t = this; if (!t.urlPrefix) { return url; } var VALID_LINK_PREFIX = /^([a-z][-+.a-z0-9]*:|\/|#)/i; if (VALID_LINK_PREFIX.test(url)) { return url; } var SIMPLE_EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (SIMPLE_EMAIL_REGEX.test(url)) { return 'mailto:' + url; } return t.urlPrefix + url; }, unlink: function () { var t = this, documentSelection = t.doc.getSelection(), node = documentSelection.focusNode; if (documentSelection.isCollapsed) { while (['A', 'DIV'].indexOf(node.nodeName) < 0) { node = node.parentNode; } if (node && node.nodeName === 'A') { var range = t.doc.createRange(); range.selectNode(node); documentSelection.removeAllRanges(); documentSelection.addRange(range); } } t.execCmd('unlink', undefined, undefined, true); }, insertImage: function () { var t = this; t.saveRange(); var options = { url: { label: 'URL', required: true }, alt: { label: t.lang.description, value: t.getRangeText() } }; if (t.o.imageWidthModalEdit) { options.width = {}; } t.openModalInsert(t.lang.insertImage, options, function (v) { // v are values t.execCmd('insertImage', v.url, false, true); var $img = $('img[src="' + v.url + '"]:not([alt])', t.$box); $img.attr('alt', v.alt); if (t.o.imageWidthModalEdit) { $img.attr({ width: v.width }); } t.syncCode(); t.$c.trigger('tbwchange'); return true; }); }, fullscreen: function () { var t = this, prefix = t.o.prefix, fullscreenCssClass = prefix + 'fullscreen', fullscreenPlaceholderClass = fullscreenCssClass + '-placeholder', isFullscreen, editorHeight = t.$box.outerHeight(); t.$box.toggleClass(fullscreenCssClass); isFullscreen = t.$box.hasClass(fullscreenCssClass); if (isFullscreen) { t.$box.before( $('
', { class: fullscreenPlaceholderClass }).css({ height: editorHeight }) ); } else { $('.' + fullscreenPlaceholderClass).remove(); } $('body').toggleClass(prefix + 'body-fullscreen', isFullscreen); $(window).trigger('scroll'); t.$c.trigger('tbw' + (isFullscreen ? 'open' : 'close') + 'fullscreen'); }, /* * Call method of trumbowyg if exist * else try to call anonymous function * and finally native execCommand */ execCmd: function (cmd, param, forceCss, skipTrumbowyg) { var t = this; skipTrumbowyg = !!skipTrumbowyg || ''; if (cmd !== 'dropdown') { t.$ed.focus(); } if(cmd === 'strikethrough' && t.o.semantic) { t.semanticTag('strike', t.o.semanticKeepAttributes, true); // browsers cannot undo e.g.