1jQuery.trumbowyg = { 2 langs: { 3 en: { 4 viewHTML: 'View HTML', 5 6 undo: 'Undo', 7 redo: 'Redo', 8 9 formatting: 'Formatting', 10 p: 'Paragraph', 11 blockquote: 'Quote', 12 code: 'Code', 13 header: 'Header', 14 15 bold: 'Bold', 16 italic: 'Italic', 17 strikethrough: 'Stroke', 18 underline: 'Underline', 19 20 strong: 'Strong', 21 em: 'Emphasis', 22 del: 'Deleted', 23 24 superscript: 'Superscript', 25 subscript: 'Subscript', 26 27 unorderedList: 'Unordered list', 28 orderedList: 'Ordered list', 29 30 insertImage: 'Insert Image', 31 link: 'Link', 32 createLink: 'Insert link', 33 unlink: 'Remove link', 34 35 justifyLeft: 'Align Left', 36 justifyCenter: 'Align Center', 37 justifyRight: 'Align Right', 38 justifyFull: 'Align Justify', 39 40 horizontalRule: 'Insert horizontal rule', 41 removeformat: 'Remove format', 42 43 fullscreen: 'Fullscreen', 44 45 close: 'Close', 46 47 submit: 'Confirm', 48 reset: 'Cancel', 49 50 required: 'Required', 51 description: 'Description', 52 title: 'Title', 53 text: 'Text', 54 target: 'Target' 55 } 56 }, 57 58 // Plugins 59 plugins: {}, 60 61 // SVG Path globally 62 svgPath: null, 63 64 hideButtonTexts: null 65}; 66 67// Makes default options read-only 68Object.defineProperty(jQuery.trumbowyg, 'defaultOptions', { 69 value: { 70 lang: 'en', 71 72 fixedBtnPane: false, 73 fixedFullWidth: false, 74 autogrow: false, 75 autogrowOnEnter: false, 76 77 prefix: 'trumbowyg-', 78 79 semantic: true, 80 resetCss: false, 81 removeformatPasted: false, 82 tagsToRemove: [], 83 btns: [ 84 ['viewHTML'], 85 ['undo', 'redo'], // Only supported in Blink browsers 86 ['formatting'], 87 ['strong', 'em', 'del'], 88 ['superscript', 'subscript'], 89 ['link'], 90 ['insertImage'], 91 ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'], 92 ['unorderedList', 'orderedList'], 93 ['horizontalRule'], 94 ['removeformat'], 95 ['fullscreen'] 96 ], 97 // For custom button definitions 98 btnsDef: {}, 99 100 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', 101 102 pasteHandlers: [], 103 104 // imgDblClickHandler: default is defined in constructor 105 106 plugins: {} 107 }, 108 writable: false, 109 enumerable: true, 110 configurable: false 111}); 112 113 114(function (navigator, window, document, $) { 115 'use strict'; 116 117 var CONFIRM_EVENT = 'tbwconfirm', 118 CANCEL_EVENT = 'tbwcancel'; 119 120 $.fn.trumbowyg = function (options, params) { 121 var trumbowygDataName = 'trumbowyg'; 122 if (options === Object(options) || !options) { 123 return this.each(function () { 124 if (!$(this).data(trumbowygDataName)) { 125 $(this).data(trumbowygDataName, new Trumbowyg(this, options)); 126 } 127 }); 128 } 129 if (this.length === 1) { 130 try { 131 var t = $(this).data(trumbowygDataName); 132 switch (options) { 133 // Exec command 134 case 'execCmd': 135 return t.execCmd(params.cmd, params.param, params.forceCss); 136 137 // Modal box 138 case 'openModal': 139 return t.openModal(params.title, params.content); 140 case 'closeModal': 141 return t.closeModal(); 142 case 'openModalInsert': 143 return t.openModalInsert(params.title, params.fields, params.callback); 144 145 // Range 146 case 'saveRange': 147 return t.saveRange(); 148 case 'getRange': 149 return t.range; 150 case 'getRangeText': 151 return t.getRangeText(); 152 case 'restoreRange': 153 return t.restoreRange(); 154 155 // Enable/disable 156 case 'enable': 157 return t.setDisabled(false); 158 case 'disable': 159 return t.setDisabled(true); 160 161 // Destroy 162 case 'destroy': 163 return t.destroy(); 164 165 // Empty 166 case 'empty': 167 return t.empty(); 168 169 // HTML 170 case 'html': 171 return t.html(params); 172 } 173 } catch (c) { 174 } 175 } 176 177 return false;
178 }; 179 180 // @param: editorElem is the DOM element 181 var Trumbowyg = function (editorElem, options) { 182 var t = this, 183 trumbowygIconsId = 'trumbowyg-icons', 184 $trumbowyg = $.trumbowyg; 185 186 // Get the document of the element. It use to makes the plugin 187 // compatible on iframes. 188 t.doc = editorElem.ownerDocument || document; 189 190 // jQuery object of the editor 191 t.$ta = $(editorElem); // $ta : Textarea 192 t.$c = $(editorElem); // $c : creator 193 194 options = options || {}; 195 196 // Localization management 197 if (options.lang != null || $trumbowyg.langs[options.lang] != null) { 198 t.lang = $.extend(true, {}, $trumbowyg.langs.en, $trumbowyg.langs[options.lang]); 199 } else { 200 t.lang = $trumbowyg.langs.en; 201 } 202 203 t.hideButtonTexts = $trumbowyg.hideButtonTexts != null ? $trumbowyg.hideButtonTexts : options.hideButtonTexts; 204 205 // SVG path 206 var svgPathOption = $trumbowyg.svgPath != null ? $trumbowyg.svgPath : options.svgPath; 207 t.hasSvg = svgPathOption !== false; 208 t.svgPath = !!t.doc.querySelector('base') ? window.location.href.split('#')[0] : ''; 209 if ($('#' + trumbowygIconsId, t.doc).length === 0 && svgPathOption !== false) { 210 if (svgPathOption == null) { 211 // Hack to get svgPathOption based on trumbowyg.js path 212 try { 213 throw new Error(); 214 } catch (e) { 215 if (!e.hasOwnProperty('stack')) { 216 console.warn('You must define svgPath: https://goo.gl/CfTY9U'); // jshint ignore:line 217 } else { 218 var stackLines = e.stack.split('\n'); 219 220 for (var i in stackLines) { 221 if (!stackLines[i].match(/https?:\/\//)) { 222 continue; 223 } 224 svgPathOption = stackLines[Number(i)].match(/((https?:\/\/.+\/)([^\/]+\.js))(\?.*)?:/)[1].split('/'); 225 svgPathOption.pop(); 226 svgPathOption = svgPathOption.join('/') + '/ui/icons.svg'; 227 break; 228 } 229 } 230 } 231 } 232 233 var div = t.doc.createElement('div'); 234 div.id = trumbowygIconsId; 235 t.doc.body.insertBefore(div, t.doc.body.childNodes[0]); 236 $.ajax({ 237 async: true, 238 type: 'GET', 239 contentType: 'application/x-www-form-urlencoded; charset=UTF-8', 240 dataType: 'xml', 241 crossDomain: true, 242 url: svgPathOption, 243 data: null, 244 beforeSend: null, 245 complete: null, 246 success: function (data) { 247 div.innerHTML = new XMLSerializer().serializeToString(data.documentElement); 248 } 249 }); 250 } 251 252 253 /** 254 * When the button is associated to a empty object 255 * fn and title attributs are defined from the button key value 256 * 257 * For example 258 * foo: {} 259 * is equivalent to : 260 * foo: { 261 * fn: 'foo', 262 * title: this.lang.foo 263 * } 264 */ 265 var h = t.lang.header, // Header translation 266 isBlinkFunction = function () { 267 return (window.chrome || (window.Intl && Intl.v8BreakIterator)) && 'CSS' in window; 268 }; 269 t.btnsDef = { 270 viewHTML: { 271 fn: 'toggle' 272 }, 273 274 undo: { 275 isSupported: isBlinkFunction, 276 key: 'Z' 277 }, 278 redo: { 279 isSupported: isBlinkFunction, 280 key: 'Y' 281 }, 282 283 p: { 284 fn: 'formatBlock' 285 }, 286 blockquote: { 287 fn: 'formatBlock' 288 }, 289 h1: { 290 fn: 'formatBlock', 291 title: h + ' 1' 292 }, 293 h2: { 294 fn: 'formatBlock', 295 title: h + ' 2' 296 }, 297 h3: { 298 fn: 'formatBlock', 299 title: h + ' 3' 300 }, 301 h4: { 302 fn: 'formatBlock', 303 title: h + ' 4' 304 }, 305 subscript: { 306 tag: 'sub' 307 }, 308 superscript: { 309 tag: 'sup' 310 }, 311 312 bold: { 313 key: 'B', 314 tag: 'b' 315 }, 316 italic: { 317 key: 'I', 318 tag: 'i' 319 }, 320 underline: { 321 tag: 'u' 322 },
323 strikethrough: { 324 tag: 'strike' 325 }, 326 327 strong: { 328 fn: 'bold', 329 key: 'B' 330 }, 331 em: { 332 fn: 'italic', 333 key: 'I' 334 }, 335 del: { 336 fn: 'strikethrough' 337 }, 338 339 createLink: { 340 key: 'K', 341 tag: 'a' 342 }, 343 unlink: {}, 344 345 insertImage: {}, 346 347 justifyLeft: { 348 tag: 'left', 349 forceCss: true 350 }, 351 justifyCenter: { 352 tag: 'center', 353 forceCss: true 354 }, 355 justifyRight: { 356 tag: 'right', 357 forceCss: true 358 }, 359 justifyFull: { 360 tag: 'justify', 361 forceCss: true 362 }, 363 364 unorderedList: { 365 fn: 'insertUnorderedList', 366 tag: 'ul' 367 }, 368 orderedList: { 369 fn: 'insertOrderedList', 370 tag: 'ol' 371 }, 372 373 horizontalRule: { 374 fn: 'insertHorizontalRule' 375 }, 376 377 removeformat: {}, 378 379 fullscreen: { 380 class: 'trumbowyg-not-disable' 381 }, 382 close: { 383 fn: 'destroy', 384 class: 'trumbowyg-not-disable' 385 }, 386 387 // Dropdowns 388 formatting: { 389 dropdown: ['p', 'blockquote', 'h1', 'h2', 'h3', 'h4'], 390 ico: 'p' 391 }, 392 link: { 393 dropdown: ['createLink', 'unlink'] 394 } 395 }; 396 397 // Defaults Options 398 t.o = $.extend(true, {}, $trumbowyg.defaultOptions, options); 399 if (!t.o.hasOwnProperty('imgDblClickHandler')) { 400 t.o.imgDblClickHandler = t.getDefaultImgDblClickHandler(); 401 } 402 403 t.disabled = t.o.disabled || (editorElem.nodeName === 'TEXTAREA' && editorElem.disabled); 404 405 if (options.btns) { 406 t.o.btns = options.btns; 407 } else if (!t.o.semantic) { 408 t.o.btns[3] = ['bold', 'italic', 'underline', 'strikethrough']; 409 } 410 411 $.each(t.o.btnsDef, function (btnName, btnDef) { 412 t.addBtnDef(btnName, btnDef); 413 }); 414 415 // put this here in the event it would be merged in with options 416 t.eventNamespace = 'trumbowyg-event'; 417 418 // Keyboard shortcuts are load in this array 419 t.keys = []; 420 421 // Tag to button dynamically hydrated 422 t.tagToButton = {}; 423 t.tagHandlers = []; 424 425 // Admit multiple paste handlers 426 t.pasteHandlers = [].concat(t.o.pasteHandlers); 427 428 // Check if browser is IE 429 t.isIE = (navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') !== -1); 430 431 t.init(); 432 }; 433 434 Trumbowyg.prototype = { 435 init: function () { 436 var t = this; 437 t.height = t.$ta.height(); 438 439 t.initPlugins(); 440 441 try { 442 // Disable image resize, try-catch for old IE 443 t.doc.execCommand('enableObjectResizing', false, false); 444 t.doc.execCommand('defaultParagraphSeparator', false, 'p'); 445 } catch (e) { 446 } 447 448 t.buildEditor(); 449 t.buildBtnPane(); 450 451 t.fixedBtnPaneEvents(); 452 453 t.buildOverlay(); 454 455 setTimeout(function () { 456 if (t.disabled) { 457 t.setDisabled(true); 458 } 459 t.$c.trigger('tbwinit'); 460 }); 461 }, 462 463 addBtnDef: function (btnName, btnDef) { 464 this.btnsDef[btnName] = btnDef; 465 }, 466 467 buildEditor: function () { 468 var t = this, 469 prefix = t.o.prefix, 470 html = ''; 471 472 t.$box = $('<div/>', { 473 class: prefix + 'box ' + prefix + 'editor-visible ' + prefix + t.o.lang + ' trumbowyg' 474 }); 475 476 // $ta = Textarea 477 // $ed = Editor 478 t.isTextarea = t.$ta.is('textarea'); 479 if (t.isTextarea) { 480 html = t.$ta.val(); 481 t.$ed = $('<div/>'); 482 t.$box 483 .insertAfter(t.$ta) 484 .append(t.$ed, t.$ta); 485 } else { 486 t.$ed = t.$ta; 487 html = t.$ed.html(); 488 489 t.$ta = $('<textarea/>', { 490 name: t.$ta.attr('id'), 491 height: t.height 492 }).val(html); 493 494 t.$box 495 .insertAfter(t.$ed) 496 .append(t.$ta, t.$ed); 497 t.syncCode(); 498 } 499 500 t.$ta 501 .addClass(prefix + 'textarea') 502 .attr('tabindex', -1) 503 ; 504 505 t.$ed 506 .addClass(prefix + 'editor') 507 .attr({ 508 contenteditable: true, 509 dir: t.lang._dir || 'ltr' 510 }) 511 .html(html) 512 ; 513 514 if (t.o.tabindex) { 515 t.$ed.attr('tabindex', t.o.tabindex); 516 } 517 518 if (t.$c.is('[placeholder]')) { 519 t.$ed.attr('placeholder', t.$c.attr('placeholder')); 520 } 521 522 if (t.$c.is('[spellcheck]')) { 523 t.$ed.attr('spellcheck', t.$c.attr('spellcheck')); 524 } 525 526 if (t.o.resetCss) { 527 t.$ed.addClass(prefix + 'reset-css'); 528 } 529 530 if (!t.o.autogrow) { 531 t.$ta.add(t.$ed).css({ 532 height: t.height 533 }); 534 } 535 536 t.semanticCode(); 537 538 if (t.o.autogrowOnEnter) { 539 t.$ed.addClass(prefix + 'autogrow-on-enter'); 540 } 541 542 var ctrl = false, 543 composition = false, 544 debounceButtonPaneStatus, 545 updateEventName = t.isIE ? 'keyup' : 'input'; 546 547 t.$ed 548 .on('dblclick', 'img', t.o.imgDblClickHandler) 549 .on('keydown', function (e) { 550 if ((e.ctrlKey || e.metaKey) && !e.altKey) { 551 ctrl = true; 552 var key = t.keys[String.fromCharCode(e.which).toUpperCase()]; 553 554 try { 555 t.execCmd(key.fn, key.param); 556 return false;
557 } catch (c) { 558 } 559 } 560 }) 561 .on('compositionstart compositionupdate', function () { 562 composition = true; 563 }) 564 .on(updateEventName + ' compositionend', function (e) { 565 if (e.type === 'compositionend') { 566 composition = false; 567 } else if (composition) { 568 return; 569 } 570 571 var keyCode = e.which; 572 573 if (keyCode >= 37 && keyCode <= 40) { 574 return; 575 } 576 577 if ((e.ctrlKey || e.metaKey) && (keyCode === 89 || keyCode === 90)) { 578 t.$c.trigger('tbwchange'); 579 } else if (!ctrl && keyCode !== 17) { 580 t.semanticCode(false, e.type === 'compositionend' && keyCode === 13); 581 t.$c.trigger('tbwchange'); 582 } else if (typeof e.which === 'undefined') { 583 t.semanticCode(false, false, true); 584 } 585 586 setTimeout(function () { 587 ctrl = false; 588 }, 200); 589 }) 590 .on('mouseup keydown keyup', function () { 591 clearTimeout(debounceButtonPaneStatus); 592 debounceButtonPaneStatus = setTimeout(function () { 593 t.updateButtonPaneStatus(); 594 }, 50); 595 }) 596 .on('focus blur', function (e) { 597 t.$c.trigger('tbw' + e.type); 598 if (e.type === 'blur') { 599 $('.' + prefix + 'active-button', t.$btnPane).removeClass(prefix + 'active-button ' + prefix + 'active'); 600 } 601 if (t.o.autogrowOnEnter) { 602 if (t.autogrowOnEnterDontClose) { 603 return; 604 } 605 if (e.type === 'focus') { 606 t.autogrowOnEnterWasFocused = true; 607 t.autogrowEditorOnEnter(); 608 } 609 else if (!t.o.autogrow) { 610 t.$ed.css({height: t.$ed.css('min-height')}); 611 t.$c.trigger('tbwresize'); 612 } 613 } 614 }) 615 .on('cut', function () { 616 setTimeout(function () { 617 t.semanticCode(false, true); 618 t.$c.trigger('tbwchange'); 619 }, 0); 620 }) 621 .on('paste', function (e) { 622 if (t.o.removeformatPasted) { 623 e.preventDefault(); 624 625 if (window.getSelection && window.getSelection().deleteFromDocument) { 626 window.getSelection().deleteFromDocument(); 627 } 628 629 try { 630 // IE 631 var text = window.clipboardData.getData('Text'); 632 633 try { 634 // <= IE10 635 t.doc.selection.createRange().pasteHTML(text); 636 } catch (c) { 637 // IE 11 638 t.doc.getSelection().getRangeAt(0).insertNode(t.doc.createTextNode(text)); 639 } 640 t.$c.trigger('tbwchange', e); 641 } catch (d) { 642 // Not IE 643 t.execCmd('insertText', (e.originalEvent || e).clipboardData.getData('text/plain')); 644 } 645 } 646 647 // Call pasteHandlers 648 $.each(t.pasteHandlers, function (i, pasteHandler) { 649 pasteHandler(e); 650 }); 651 652 setTimeout(function () { 653 t.semanticCode(false, true); 654 t.$c.trigger('tbwpaste', e); 655 }, 0); 656 }); 657 658 t.$ta 659 .on('keyup', function () { 660 t.$c.trigger('tbwchange'); 661 }) 662 .on('paste', function () { 663 setTimeout(function () { 664 t.$c.trigger('tbwchange'); 665 }, 0); 666 }); 667 668 t.$box.on('keydown', function (e) { 669 if (e.which === 27 && $('.' + prefix + 'modal-box', t.$box).length === 1) { 670 t.closeModal(); 671 return false;
672 } 673 }); 674 }, 675 676 //autogrow when entering logic 677 autogrowEditorOnEnter: function () { 678 var t = this; 679 t.$ed.removeClass('autogrow-on-enter'); 680 var oldHeight = t.$ed[0].clientHeight; 681 t.$ed.height('auto'); 682 var totalHeight = t.$ed[0].scrollHeight; 683 t.$ed.addClass('autogrow-on-enter'); 684 if (oldHeight !== totalHeight) { 685 t.$ed.height(oldHeight); 686 setTimeout(function () { 687 t.$ed.css({height: totalHeight}); 688 t.$c.trigger('tbwresize'); 689 }, 0); 690 } 691 }, 692 693 694 // Build button pane, use o.btns option 695 buildBtnPane: function () { 696 var t = this, 697 prefix = t.o.prefix; 698 699 var $btnPane = t.$btnPane = $('<div/>', { 700 class: prefix + 'button-pane' 701 }); 702 703 $.each(t.o.btns, function (i, btnGrp) { 704 if (!$.isArray(btnGrp)) { 705 btnGrp = [btnGrp]; 706 } 707 708 var $btnGroup = $('<div/>', { 709 class: prefix + 'button-group ' + ((btnGrp.indexOf('fullscreen') >= 0) ? prefix + 'right' : '') 710 }); 711 $.each(btnGrp, function (i, btn) { 712 try { // Prevent buildBtn error 713 if (t.isSupportedBtn(btn)) { // It's a supported button 714 $btnGroup.append(t.buildBtn(btn)); 715 } 716 } catch (c) { 717 } 718 }); 719 $btnPane.append($btnGroup); 720 }); 721 722 t.$box.prepend($btnPane); 723 }, 724 725 726 // Build a button and his action 727 buildBtn: function (btnName) { // btnName is name of the button 728 var t = this, 729 prefix = t.o.prefix, 730 btn = t.btnsDef[btnName], 731 isDropdown = btn.dropdown, 732 hasIcon = btn.hasIcon != null ? btn.hasIcon : true, 733 textDef = t.lang[btnName] || btnName, 734 735 $btn = $('<button/>', { 736 type: 'button', 737 class: prefix + btnName + '-button ' + (btn.class || '') + (!hasIcon ? ' ' + prefix + 'textual-button' : ''), 738 html: t.hasSvg && hasIcon ? 739 '<svg><use xlink:href="' + t.svgPath + '#' + prefix + (btn.ico || btnName).replace(/([A-Z]+)/g, '-$1').toLowerCase() + '"/></svg>' : 740 t.hideButtonTexts ? '' : (btn.text || btn.title || t.lang[btnName] || btnName), 741 title: (btn.title || btn.text || textDef) + ((btn.key) ? ' (Ctrl + ' + btn.key + ')' : ''), 742 tabindex: -1, 743 mousedown: function () { 744 if (!isDropdown || $('.' + btnName + '-' + prefix + 'dropdown', t.$box).is(':hidden')) { 745 $('body', t.doc).trigger('mousedown'); 746 } 747 748 if (t.$btnPane.hasClass(prefix + 'disable') && !$(this).hasClass(prefix + 'active') && !$(this).hasClass(prefix + 'not-disable')) { 749 return false; 750 } 751 752 t.execCmd((isDropdown ? 'dropdown' : false) || btn.fn || btnName, btn.param || btnName, btn.forceCss); 753 754 return false; 755 } 756 }); 757 758 if (isDropdown) { 759 $btn.addClass(prefix + 'open-dropdown'); 760 var dropdownPrefix = prefix + 'dropdown', 761 $dropdown = $('<div/>', { // the dropdown 762 class: dropdownPrefix + '-' + btnName + ' ' + dropdownPrefix + ' ' + prefix + 'fixed-top', 763 'data-dropdown': btnName 764 }); 765 $.each(isDropdown, function (i, def) { 766 if (t.btnsDef[def] && t.isSupportedBtn(def)) { 767 $dropdown.append(t.buildSubBtn(def)); 768 } 769 }); 770 t.$box.append($dropdown.hide()); 771 }
vendor: 4,468 bytes, lines 771-891
771 else if (btn.key) { 772 t.keys[btn.key] = { 773 fn: btn.fn || btnName, 774 param: btn.param || btnName 775 }; 776 } 777 778 if (!isDropdown) { 779 t.tagToButton[(btn.tag || btnName).toLowerCase()] = btnName; 780 } 781 782 return $btn; 783 }, 784 // Build a button for dropdown menu 785 // @param n : name of the subbutton 786 buildSubBtn: function (btnName) { 787 var t = this, 788 prefix = t.o.prefix, 789 btn = t.btnsDef[btnName], 790 hasIcon = btn.hasIcon != null ? btn.hasIcon : true; 791 792 if (btn.key) { 793 t.keys[btn.key] = { 794 fn: btn.fn || btnName, 795 param: btn.param || btnName 796 }; 797 } 798 799 t.tagToButton[(btn.tag || btnName).toLowerCase()] = btnName; 800 801 return $('<button/>', { 802 type: 'button', 803 class: prefix + btnName + '-dropdown-button' + (btn.ico ? ' ' + prefix + btn.ico + '-button' : ''), 804 html: t.hasSvg && hasIcon ? '<svg><use xlink:href="' + t.svgPath + '#' + prefix + (btn.ico || btnName).replace(/([A-Z]+)/g, '-$1').toLowerCase() + '"/></svg>' + (btn.text || btn.title || t.lang[btnName] || btnName) : (btn.text || btn.title || t.lang[btnName] || btnName), 805 title: ((btn.key) ? ' (Ctrl + ' + btn.key + ')' : null), 806 style: btn.style || null, 807 mousedown: function () { 808 $('body', t.doc).trigger('mousedown'); 809 810 t.execCmd(btn.fn || btnName, btn.param || btnName, btn.forceCss); 811 812 return false; 813 } 814 }); 815 }, 816 // Check if button is supported 817 isSupportedBtn: function (b) { 818 try { 819 return this.btnsDef[b].isSupported(); 820 } catch (c) { 821 } 822 return true; 823 }, 824 825 // Build overlay for modal box 826 buildOverlay: function () { 827 var t = this; 828 t.$overlay = $('<div/>', { 829 class: t.o.prefix + 'overlay' 830 }).appendTo(t.$box); 831 return t.$overlay; 832 }, 833 showOverlay: function () { 834 var t = this; 835 $(window).trigger('scroll'); 836 t.$overlay.fadeIn(200); 837 t.$box.addClass(t.o.prefix + 'box-blur'); 838 }, 839 hideOverlay: function () { 840 var t = this; 841 t.$overlay.fadeOut(50); 842 t.$box.removeClass(t.o.prefix + 'box-blur'); 843 }, 844 845 // Management of fixed button pane 846 fixedBtnPaneEvents: function () { 847 var t = this, 848 fixedFullWidth = t.o.fixedFullWidth, 849 $box = t.$box; 850 851 if (!t.o.fixedBtnPane) { 852 return; 853 } 854 855 t.isFixed = false; 856 857 $(window) 858 .on('scroll.' + t.eventNamespace + ' resize.' + t.eventNamespace, function () { 859 if (!$box) { 860 return; 861 } 862 863 t.syncCode(); 864 865 var scrollTop = $(window).scrollTop(), 866 offset = $box.offset().top + 1, 867 bp = t.$btnPane, 868 oh = bp.outerHeight() - 2; 869 870 if ((scrollTop - offset > 0) && ((scrollTop - offset - t.height) < 0)) { 871 if (!t.isFixed) { 872 t.isFixed = true; 873 bp.css({ 874 position: 'fixed', 875 top: 0, 876 left: fixedFullWidth ? '0' : 'auto', 877 zIndex: 7 878 }); 879 $([t.$ta, t.$ed]).css({marginTop: bp.height()}); 880 } 881 bp.css({ 882 width: fixedFullWidth ? '100%' : (($box.width() - 1) + 'px') 883 }); 884 885 $('.' + t.o.prefix + 'fixed-top', $box).css({ 886 position: fixedFullWidth ? 'fixed' : 'absolute', 887 top: fixedFullWidth ? oh : oh + (scrollTop - offset) + 'px', 888 zIndex: 15 889 }); 890 } else if (t.isFixed) { 891 t.isFixed = false;
892 bp.removeAttr('style'); 893 $([t.$ta, t.$ed]).css({marginTop: 0}); 894 $('.' + t.o.prefix + 'fixed-top', $box).css({ 895 position: 'absolute', 896 top: oh 897 }); 898 } 899 }); 900 }, 901 902 // Disable editor 903 setDisabled: function (disable) { 904 var t = this, 905 prefix = t.o.prefix; 906 907 t.disabled = disable; 908 909 if (disable) { 910 t.$ta.attr('disabled', true); 911 } else { 912 t.$ta.removeAttr('disabled'); 913 } 914 t.$box.toggleClass(prefix + 'disabled', disable); 915 t.$ed.attr('contenteditable', !disable); 916 }, 917 918 // Destroy the editor 919 destroy: function () { 920 var t = this, 921 prefix = t.o.prefix; 922 923 if (t.isTextarea) { 924 t.$box.after( 925 t.$ta 926 .css({height: ''}) 927 .val(t.html()) 928 .removeClass(prefix + 'textarea') 929 .show() 930 ); 931 } else { 932 t.$box.after( 933 t.$ed 934 .css({height: ''}) 935 .removeClass(prefix + 'editor') 936 .removeAttr('contenteditable') 937 .removeAttr('dir') 938 .html(t.html()) 939 .show() 940 ); 941 } 942 943 t.$ed.off('dblclick', 'img'); 944 945 t.destroyPlugins(); 946 947 t.$box.remove(); 948 t.$c.removeData('trumbowyg'); 949 $('body').removeClass(prefix + 'body-fullscreen'); 950 t.$c.trigger('tbwclose'); 951 $(window).off('scroll.' + t.eventNamespace + ' resize.' + t.eventNamespace); 952 }, 953 954 955 // Empty the editor 956 empty: function () { 957 this.$ta.val(''); 958 this.syncCode(true); 959 }, 960 961 962 // Function call when click on viewHTML button 963 toggle: function () { 964 var t = this, 965 prefix = t.o.prefix; 966 967 if (t.o.autogrowOnEnter) { 968 t.autogrowOnEnterDontClose = !t.$box.hasClass(prefix + 'editor-hidden'); 969 } 970 971 t.semanticCode(false, true); 972 973 setTimeout(function () { 974 t.doc.activeElement.blur(); 975 t.$box.toggleClass(prefix + 'editor-hidden ' + prefix + 'editor-visible'); 976 t.$btnPane.toggleClass(prefix + 'disable'); 977 $('.' + prefix + 'viewHTML-button', t.$btnPane).toggleClass(prefix + 'active'); 978 if (t.$box.hasClass(prefix + 'editor-visible')) { 979 t.$ta.attr('tabindex', -1); 980 } else { 981 t.$ta.removeAttr('tabindex'); 982 } 983 984 if (t.o.autogrowOnEnter && !t.autogrowOnEnterDontClose) { 985 t.autogrowEditorOnEnter(); 986 } 987 }, 0); 988 }, 989 990 // Open dropdown when click on a button which open that 991 dropdown: function (name) { 992 var t = this, 993 d = t.doc, 994 prefix = t.o.prefix, 995 $dropdown = $('[data-dropdown=' + name + ']', t.$box), 996 $btn = $('.' + prefix + name + '-button', t.$btnPane), 997 show = $dropdown.is(':hidden'); 998 999 $('body', d).trigger('mousedown'); 1000 1001 if (show) { 1002 var o = $btn.offset().left; 1003 $btn.addClass(prefix + 'active'); 1004 1005 $dropdown.css({ 1006 position: 'absolute', 1007 top: $btn.offset().top - t.$btnPane.offset().top + $btn.outerHeight(), 1008 left: (t.o.fixedFullWidth && t.isFixed) ? o + 'px' : (o - t.$btnPane.offset().left) + 'px' 1009 }).show(); 1010 1011 $(window).trigger('scroll'); 1012 1013 $('body', d).on('mousedown.' + t.eventNamespace, function (e) { 1014 if (!$dropdown.is(e.target)) { 1015 $('.' + prefix + 'dropdown', d).hide(); 1016 $('.' + prefix + 'active', d).removeClass(prefix + 'active'); 1017 $('body', d).off('mousedown.' + t.eventNamespace); 1018 } 1019 }); 1020 } 1021 }, 1022 1023 1024 // HTML Code management 1025 html: function (html) { 1026 var t = this; 1027 if (html != null) { 1028 t.$ta.val(html); 1029 t.syncCode(true); 1030 return t; 1031 } 1032 return t.$ta.val(); 1033 }, 1034 syncTextarea: function () { 1035 var t = this; 1036 t.$ta.val(t.$ed.text().trim().length > 0 || t.$ed.find('hr,img,embed,iframe,input').length > 0 ? t.$ed.html() : ''); 1037 }, 1038 syncCode: function (force) { 1039 var t = this; 1040 if (!force && t.$ed.is(':visible')) { 1041 t.syncTextarea(); 1042 } else { 1043 // wrap the content in a div it's easier to get the innerhtml 1044 var html = $('<div>').html(t.$ta.val()); 1045 //scrub the html before loading into the doc 1046 var safe = $('<div>').append(html); 1047 $(t.o.tagsToRemove.join(','), safe).remove(); 1048 t.$ed.html(safe.contents().html()); 1049 } 1050 1051 if (t.o.autogrow) { 1052 t.height = t.$ed.height(); 1053 if (t.height !== t.$ta.css('height')) { 1054 t.$ta.css({height: t.height}); 1055 t.$c.trigger('tbwresize'); 1056 } 1057 } 1058 if (t.o.autogrowOnEnter) { 1059 // t.autogrowEditorOnEnter(); 1060 t.$ed.height('auto'); 1061 var totalheight = t.autogrowOnEnterWasFocused ? t.$ed[0].scrollHeight : t.$ed.css('min-height'); 1062 if (totalheight !== t.$ta.css('height')) { 1063 t.$ed.css({height: totalheight}); 1064 t.$c.trigger('tbwresize'); 1065 } 1066 } 1067 }, 1068 1069 // Analyse and update to semantic code 1070 // @param force : force to sync code from textarea 1071 // @param full : wrap text nodes in <p> 1072 // @param keepRange : leave selection range as it is 1073 semanticCode: function (force, full, keepRange) { 1074 var t = this; 1075 t.saveRange(); 1076 t.syncCode(force); 1077 1078 if (t.o.semantic) { 1079 t.semanticTag('b', 'strong'); 1080 t.semanticTag('i', 'em'); 1081 t.semanticTag('strike', 'del'); 1082 1083 if (full) { 1084 var inlineElementsSelector = t.o.inlineElementsSelector, 1085 blockElementsSelector = ':not(' + inlineElementsSelector + ')'; 1086 1087 // Wrap text nodes in span for easier processing 1088 t.$ed.contents().filter(function () { 1089 return this.nodeType === 3 && this.nodeValue.trim().length > 0; 1090 }).wrap('<span data-tbw/>'); 1091 1092 // Wrap groups of inline elements in paragraphs (recursive) 1093 var wrapInlinesInParagraphsFrom = function ($from) { 1094 if ($from.length !== 0) { 1095 var $finalParagraph = $from.nextUntil(blockElementsSelector).addBack().wrapAll('<p/>').parent(), 1096 $nextElement = $finalParagraph.nextAll(inlineElementsSelector).first(); 1097 $finalParagraph.next('br').remove(); 1098 wrapInlinesInParagraphsFrom($nextElement); 1099 } 1100 }; 1101 wrapInlinesInParagraphsFrom(t.$ed.children(inlineElementsSelector).first()); 1102 1103 t.semanticTag('div', 'p', true); 1104 1105 // Unwrap paragraphs content, containing nothing usefull 1106 t.$ed.find('p').filter(function () { 1107 // Don't remove currently being edited element 1108 if (t.range && this === t.range.startContainer) { 1109 return false;
1110 } 1111 return $(this).text().trim().length === 0 && $(this).children().not('br,span').length === 0; 1112 }).contents().unwrap(); 1113 1114 // Get rid of temporial span's 1115 $('[data-tbw]', t.$ed).contents().unwrap(); 1116 1117 // Remove empty <p> 1118 t.$ed.find('p:empty').remove(); 1119 } 1120 1121 if (!keepRange) { 1122 t.restoreRange(); 1123 } 1124 1125 t.syncTextarea(); 1126 } 1127 }, 1128 1129 semanticTag: function (oldTag, newTag, copyAttributes) { 1130 $(oldTag, this.$ed).each(function () { 1131 var $oldTag = $(this); 1132 $oldTag.wrap('<' + newTag + '/>'); 1133 if (copyAttributes) { 1134 $.each($oldTag.prop('attributes'), function () { 1135 $oldTag.parent().attr(this.name, this.value); 1136 }); 1137 } 1138 $oldTag.contents().unwrap(); 1139 }); 1140 }, 1141 1142 // Function call when user click on "Insert Link" 1143 createLink: function () { 1144 var t = this, 1145 documentSelection = t.doc.getSelection(), 1146 node = documentSelection.focusNode, 1147 url, 1148 title, 1149 target; 1150 1151 while (['A', 'DIV'].indexOf(node.nodeName) < 0) { 1152 node = node.parentNode; 1153 } 1154 1155 if (node && node.nodeName === 'A') { 1156 var $a = $(node); 1157 url = $a.attr('href'); 1158 title = $a.attr('title'); 1159 target = $a.attr('target'); 1160 var range = t.doc.createRange(); 1161 range.selectNode(node); 1162 documentSelection.removeAllRanges(); 1163 documentSelection.addRange(range); 1164 } 1165 1166 t.saveRange(); 1167 1168 t.openModalInsert(t.lang.createLink, { 1169 url: { 1170 label: 'URL', 1171 required: true, 1172 value: url 1173 }, 1174 title: { 1175 label: t.lang.title, 1176 value: title 1177 }, 1178 text: { 1179 label: t.lang.text, 1180 value: t.getRangeText() 1181 }, 1182 target: { 1183 label: t.lang.target, 1184 value: target 1185 } 1186 }, function (v) { // v is value 1187 var link = $(['<a href="', v.url, '">', v.text, '</a>'].join('')); 1188 if (v.title.length > 0) { 1189 link.attr('title', v.title); 1190 } 1191 if (v.target.length > 0) { 1192 link.attr('target', v.target); 1193 } 1194 t.range.deleteContents(); 1195 t.range.insertNode(link[0]); 1196 return true; 1197 }); 1198 }, 1199 unlink: function () { 1200 var t = this, 1201 documentSelection = t.doc.getSelection(), 1202 node = documentSelection.focusNode; 1203 1204 if (documentSelection.isCollapsed) { 1205 while (['A', 'DIV'].indexOf(node.nodeName) < 0) { 1206 node = node.parentNode; 1207 } 1208 1209 if (node && node.nodeName === 'A') { 1210 var range = t.doc.createRange(); 1211 range.selectNode(node); 1212 documentSelection.removeAllRanges(); 1213 documentSelection.addRange(range); 1214 } 1215 } 1216 t.execCmd('unlink', undefined, undefined, true); 1217 }, 1218 insertImage: function () { 1219 var t = this; 1220 t.saveRange(); 1221 t.openModalInsert(t.lang.insertImage, { 1222 url: { 1223 label: 'URL', 1224 required: true 1225 }, 1226 alt: { 1227 label: t.lang.description, 1228 value: t.getRangeText() 1229 } 1230 }, function (v) { // v are values 1231 t.execCmd('insertImage', v.url); 1232 $('img[src="' + v.url + '"]:not([alt])', t.$box).attr('alt', v.alt); 1233 return true; 1234 }); 1235 }, 1236 fullscreen: function () { 1237 var t = this, 1238 prefix = t.o.prefix, 1239 fullscreenCssClass = prefix + 'fullscreen', 1240 isFullscreen; 1241 1242 t.$box.toggleClass(fullscreenCssClass); 1243 isFullscreen = t.$box.hasClass(fullscreenCssClass); 1244 $('body').toggleClass(prefix + 'body-fullscreen', isFullscreen); 1245 $(window).trigger('scroll'); 1246 t.$c.trigger('tbw' + (isFullscreen ? 'open' : 'close') + 'fullscreen'); 1247 }, 1248 1249 1250 /* 1251 * Call method of trumbowyg if exist 1252 * else try to call anonymous function 1253 * and finaly native execCommand 1254 */ 1255 execCmd: function (cmd, param, forceCss, skipTrumbowyg) { 1256 var t = this; 1257 skipTrumbowyg = !!skipTrumbowyg || ''; 1258 1259 if (cmd !== 'dropdown') { 1260 t.$ed.focus(); 1261 } 1262 1263 try { 1264 t.doc.execCommand('styleWithCSS', false, forceCss || false); 1265 } catch (c) { 1266 } 1267 1268 try {
1269 t[cmd + skipTrumbowyg](param); 1270 } catch (c) { 1271 try { 1272 cmd(param); 1273 } catch (e2) { 1274 if (cmd === 'insertHorizontalRule') { 1275 param = undefined; 1276 } else if (cmd === 'formatBlock' && t.isIE) { 1277 param = '<' + param + '>'; 1278 } 1279 1280 t.doc.execCommand(cmd, false, param); 1281 1282 t.syncCode(); 1283 t.semanticCode(false, true); 1284 } 1285 1286 if (cmd !== 'dropdown') { 1287 t.updateButtonPaneStatus(); 1288 t.$c.trigger('tbwchange'); 1289 } 1290 } 1291 }, 1292 1293 1294 // Open a modal box 1295 openModal: function (title, content) { 1296 var t = this, 1297 prefix = t.o.prefix; 1298 1299 // No open a modal box when exist other modal box 1300 if ($('.' + prefix + 'modal-box', t.$box).length > 0) { 1301 return false; 1302 } 1303 if (t.o.autogrowOnEnter) { 1304 t.autogrowOnEnterDontClose = true; 1305 } 1306 1307 t.saveRange(); 1308 t.showOverlay(); 1309 1310 // Disable all btnPane btns 1311 t.$btnPane.addClass(prefix + 'disable'); 1312 1313 // Build out of ModalBox, it's the mask for animations 1314 var $modal = $('<div/>', { 1315 class: prefix + 'modal ' + prefix + 'fixed-top' 1316 }).css({ 1317 top: t.$btnPane.height() 1318 }).appendTo(t.$box); 1319 1320 // Click on overlay close modal by cancelling them 1321 t.$overlay.one('click', function () { 1322 $modal.trigger(CANCEL_EVENT); 1323 return false; 1324 }); 1325 1326 // Build the form 1327 var $form = $('<form/>', { 1328 action: '', 1329 html: content 1330 }) 1331 .on('submit', function () { 1332 $modal.trigger(CONFIRM_EVENT); 1333 return false; 1334 }) 1335 .on('reset', function () { 1336 $modal.trigger(CANCEL_EVENT); 1337 return false; 1338 }) 1339 .on('submit reset', function () { 1340 if (t.o.autogrowOnEnter) { 1341 t.autogrowOnEnterDontClose = false; 1342 } 1343 }); 1344 1345 1346 // Build ModalBox and animate to show them 1347 var $box = $('<div/>', { 1348 class: prefix + 'modal-box', 1349 html: $form 1350 }) 1351 .css({ 1352 top: '-' + t.$btnPane.outerHeight() + 'px', 1353 opacity: 0 1354 }) 1355 .appendTo($modal) 1356 .animate({ 1357 top: 0, 1358 opacity: 1 1359 }, 100); 1360 1361 1362 // Append title 1363 $('<span/>', { 1364 text: title, 1365 class: prefix + 'modal-title' 1366 }).prependTo($box); 1367 1368 $modal.height($box.outerHeight() + 10); 1369 1370 1371 // Focus in modal box 1372 $('input:first', $box).focus(); 1373 1374 1375 // Append Confirm and Cancel buttons 1376 t.buildModalBtn('submit', $box); 1377 t.buildModalBtn('reset', $box); 1378 1379 1380 $(window).trigger('scroll'); 1381 1382 return $modal; 1383 }, 1384 // @param n is name of modal 1385 buildModalBtn: function (n, $modal) { 1386 var t = this, 1387 prefix = t.o.prefix; 1388 1389 return $('<button/>', { 1390 class: prefix + 'modal-button ' + prefix + 'modal-' + n, 1391 type: n, 1392 text: t.lang[n] || n 1393 }).appendTo($('form', $modal)); 1394 }, 1395 // close current modal box 1396 closeModal: function () { 1397 var t = this, 1398 prefix = t.o.prefix; 1399 1400 t.$btnPane.removeClass(prefix + 'disable'); 1401 t.$overlay.off(); 1402 1403 // Find the modal box 1404 var $modalBox = $('.' + prefix + 'modal-box', t.$box); 1405 1406 $modalBox.animate({ 1407 top: '-' + $modalBox.height() 1408 }, 100, function () { 1409 $modalBox.parent().remove(); 1410 t.hideOverlay(); 1411 }); 1412 1413 t.restoreRange(); 1414 }, 1415 // Preformated build and management modal 1416 openModalInsert: function (title, fields, cmd) { 1417 var t = this, 1418 prefix = t.o.prefix, 1419 lg = t.lang, 1420 html = ''; 1421 1422 $.each(fields, function (fieldName, field) { 1423 var l = field.label, 1424 n = field.name || fieldName, 1425 a = field.attributes || {}; 1426 1427 var attr = Object.keys(a).map(function (prop) { 1428 return prop + '="' + a[prop] + '"'; 1429 }).join(' '); 1430 1431 html += '<label><input type="' + (field.type || 'text') + '" name="' + n + '" value="' + (field.value || '').replace(/"/g, '"') + '"' + attr + '><span class="' + prefix + 'input-infos"><span>' + 1432 ((!l) ? (lg[fieldName] ? lg[fieldName] : fieldName) : (lg[l] ? lg[l] : l)) + 1433 '</span></span></label>'; 1434 }); 1435 1436 return t.openModal(title, html) 1437 .on(CONFIRM_EVENT, function () { 1438 var $form = $('form', $(this)), 1439 valid = true, 1440 values = {}; 1441 1442 $.each(fields, function (fieldName, field) { 1443 var $field = $('input[name="' + fieldName + '"]', $form), 1444 inputType = $field.attr('type'); 1445 1446 if (inputType.toLowerCase() === 'checkbox') { 1447 values[fieldName] = $field.is(':checked'); 1448 } else { 1449 values[fieldName] = $.trim($field.val()); 1450 } 1451 // Validate value 1452 if (field.required && values[fieldName] === '') { 1453 valid = false;
1454 t.addErrorOnModalField($field, t.lang.required); 1455 } else if (field.pattern && !field.pattern.test(values[fieldName])) { 1456 valid = false; 1457 t.addErrorOnModalField($field, field.patternError); 1458 } 1459 }); 1460 1461 if (valid) { 1462 t.restoreRange(); 1463 1464 if (cmd(values, fields)) { 1465 t.syncCode(); 1466 t.$c.trigger('tbwchange'); 1467 t.closeModal(); 1468 $(this).off(CONFIRM_EVENT); 1469 } 1470 } 1471 }) 1472 .one(CANCEL_EVENT, function () { 1473 $(this).off(CONFIRM_EVENT); 1474 t.closeModal(); 1475 }); 1476 }, 1477 addErrorOnModalField: function ($field, err) { 1478 var prefix = this.o.prefix, 1479 $label = $field.parent(); 1480 1481 $field 1482 .on('change keyup', function () { 1483 $label.removeClass(prefix + 'input-error'); 1484 }); 1485 1486 $label 1487 .addClass(prefix + 'input-error') 1488 .find('input+span') 1489 .append( 1490 $('<span/>', { 1491 class: prefix + 'msg-error', 1492 text: err 1493 }) 1494 ); 1495 }, 1496 1497 getDefaultImgDblClickHandler: function () { 1498 var t = this; 1499 1500 return function () { 1501 var $img = $(this), 1502 src = $img.attr('src'), 1503 base64 = '(Base64)'; 1504 1505 if (src.indexOf('data:image') === 0) { 1506 src = base64; 1507 } 1508 1509 t.openModalInsert(t.lang.insertImage, { 1510 url: { 1511 label: 'URL', 1512 value: src, 1513 required: true 1514 }, 1515 alt: { 1516 label: t.lang.description, 1517 value: $img.attr('alt') 1518 } 1519 }, function (v) { 1520 if (v.src !== base64) { 1521 $img.attr({ 1522 src: v.src 1523 }); 1524 } 1525 $img.attr({ 1526 alt: v.alt 1527 }); 1528 return true; 1529 }); 1530 return false; 1531 }; 1532 }, 1533 1534 // Range management 1535 saveRange: function () { 1536 var t = this, 1537 documentSelection = t.doc.getSelection(); 1538 1539 t.range = null; 1540 1541 if (documentSelection.rangeCount) { 1542 var savedRange = t.range = documentSelection.getRangeAt(0), 1543 range = t.doc.createRange(), 1544 rangeStart; 1545 range.selectNodeContents(t.$ed[0]); 1546 range.setEnd(savedRange.startContainer, savedRange.startOffset); 1547 rangeStart = (range + '').length; 1548 t.metaRange = { 1549 start: rangeStart, 1550 end: rangeStart + (savedRange + '').length 1551 }; 1552 } 1553 }, 1554 restoreRange: function () { 1555 var t = this, 1556 metaRange = t.metaRange, 1557 savedRange = t.range, 1558 documentSelection = t.doc.getSelection(), 1559 range; 1560 1561 if (!savedRange) { 1562 return; 1563 } 1564 1565 if (metaRange && metaRange.start !== metaRange.end) { // Algorithm from http://jsfiddle.net/WeWy7/3/ 1566 var charIndex = 0, 1567 nodeStack = [t.$ed[0]], 1568 node, 1569 foundStart = false, 1570 stop = false; 1571 1572 range = t.doc.createRange(); 1573 1574 while (!stop && (node = nodeStack.pop())) { 1575 if (node.nodeType === 3) { 1576 var nextCharIndex = charIndex + node.length; 1577 if (!foundStart && metaRange.start >= charIndex && metaRange.start <= nextCharIndex) { 1578 range.setStart(node, metaRange.start - charIndex); 1579 foundStart = true; 1580 } 1581 if (foundStart && metaRange.end >= charIndex && metaRange.end <= nextCharIndex) { 1582 range.setEnd(node, metaRange.end - charIndex); 1583 stop = true; 1584 } 1585 charIndex = nextCharIndex; 1586 } else { 1587 var cn = node.childNodes, 1588 i = cn.length; 1589 1590 while (i > 0) { 1591 i -= 1; 1592 nodeStack.push(cn[i]); 1593 } 1594 } 1595 } 1596 } 1597 1598 documentSelection.removeAllRanges(); 1599 documentSelection.addRange(range || savedRange); 1600 }, 1601 getRangeText: function () { 1602 return this.range + ''; 1603 }, 1604 1605 updateButtonPaneStatus: function () { 1606 var t = this, 1607 prefix = t.o.prefix, 1608 tags = t.getTagsRecursive(t.doc.getSelection().focusNode), 1609 activeClasses = prefix + 'active-button ' + prefix + 'active'; 1610 1611 $('.' + prefix + 'active-button', t.$btnPane).removeClass(activeClasses); 1612 $.each(tags, function (i, tag) { 1613 var btnName = t.tagToButton[tag.toLowerCase()], 1614 $btn = $('.' + prefix + btnName + '-button', t.$btnPane); 1615 1616 if ($btn.length > 0) { 1617 $btn.addClass(activeClasses); 1618 } else { 1619 try { 1620 $btn = $('.' + prefix + 'dropdown .' + prefix + btnName + '-dropdown-button', t.$box); 1621 var dropdownBtnName = $btn.parent().data('dropdown'); 1622 $('.' + prefix + dropdownBtnName + '-button', t.$box).addClass(activeClasses); 1623 } catch (e) { 1624 } 1625 } 1626 }); 1627 }, 1628 getTagsRecursive: function (element, tags) { 1629 var t = this; 1630 tags = tags || (element && element.tagName ? [element.tagName] : []); 1631 1632 if (element && element.parentNode) { 1633 element = element.parentNode; 1634 } else { 1635 return tags; 1636 } 1637 1638 var tag = element.tagName; 1639 if (tag === 'DIV') { 1640 return tags; 1641 } 1642 if (tag === 'P' && element.style.textAlign !== '') { 1643 tags.push(element.style.textAlign); 1644 } 1645 1646 $.each(t.tagHandlers, function (i, tagHandler) { 1647 tags = tags.concat(tagHandler(element, t)); 1648 }); 1649 1650 tags.push(tag); 1651 1652 return t.getTagsRecursive(element, tags).filter(function(tag) { return tag != null; }); 1653 }, 1654 1655 // Plugins 1656 initPlugins: function () { 1657 var t = this; 1658 t.loadedPlugins = []; 1659 $.each($.trumbowyg.plugins, function (name, plugin) { 1660 if (!plugin.shouldInit || plugin.shouldInit(t)) { 1661 plugin.init(t); 1662 if (plugin.tagHandler) { 1663 t.tagHandlers.push(plugin.tagHandler); 1664 } 1665 t.loadedPlugins.push(plugin); 1666 } 1667 }); 1668 }, 1669 destroyPlugins: function () { 1670 $.each(this.loadedPlugins, function (i, plugin) { 1671 if (plugin.destroy) { 1672 plugin.destroy(); 1673 } 1674 }); 1675 } 1676 }; 1677})(navigator, window, document, jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.