/** * Trumbowyg v2.10.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: 'Stroke', 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, 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, prefix: 'trumbowyg-', semantic: true, resetCss: false, removeformatPasted: false, tagsToRemove: [], 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: {}, 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 }, 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); // 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; t.svgPath = !!t.doc.querySelector('base') ? window.location.href.split('#')[0] : ''; if ($('#' + trumbowygIconsId, t.doc).length === 0 && svgPathOption !== false) { if (svgPathOption == null) { // Hack to get svgPathOption based on trumbowyg.js path var scriptElements = document.getElementsByTagName('script'); for (var i = 0; i < scriptElements.length; i += 1) { var source = scriptElements[i].src; var matches = source.match('trumbowyg(\.min)?\.js'); if (matches != null) { svgPathOption = source.substring(0, source.indexOf(matches[0])) + 'ui/icons.svg'; } } if (svgPathOption == null) { console.warn('You must define svgPath: https://goo.gl/CfTY9U'); // jshint ignore:line } } 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); } }); } /** * When the button is associated to a empty object * fn and title attributs 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' }, 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' }, 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'] } }; // Defaults 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); 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] = btnDef; }, setupUrlPrefix: function () { var protocol = this.o.urlProtocol; if (!protocol) { return; } if (typeof(protocol) !== 'string') { return 'https://'; } return /:\/\/$/.test(protocol) ? protocol : protocol + '://'; }, 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 = $('