1// 4.1.7 (2014-11-27) 2 3/** 4 * Compiled inline version. (Library mode) 5 */ 6 7/*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */ 8/*globals $code */ 9 10(function(exports, undefined) { 11 "use strict"; 12 13 var modules = {}; 14 15 function require(ids, callback) { 16 var module, defs = []; 17 18 for (var i = 0; i < ids.length; ++i) { 19 module = modules[ids[i]] || resolve(ids[i]); 20 if (!module) { 21 throw 'module definition dependecy not found: ' + ids[i]; 22 } 23 24 defs.push(module); 25 } 26 27 callback.apply(null, defs); 28 } 29 30 function define(id, dependencies, definition) { 31 if (typeof id !== 'string') { 32 throw 'invalid module definition, module id must be defined and be a string'; 33 } 34 35 if (dependencies === undefined) { 36 throw 'invalid module definition, dependencies must be specified'; 37 } 38 39 if (definition === undefined) { 40 throw 'invalid module definition, definition function must be specified'; 41 } 42 43 require(dependencies, function() { 44 modules[id] = definition.apply(null, arguments); 45 }); 46 } 47 48 function defined(id) { 49 return !!modules[id]; 50 } 51 52 function resolve(id) { 53 var target = exports; 54 var fragments = id.split(/[.\/]/); 55 56 for (var fi = 0; fi < fragments.length; ++fi) { 57 if (!target[fragments[fi]]) { 58 return; 59 } 60 61 target = target[fragments[fi]]; 62 } 63 64 return target; 65 } 66 67 function expose(ids) { 68 for (var i = 0; i < ids.length; i++) { 69 var target = exports; 70 var id = ids[i]; 71 var fragments = id.split(/[.\/]/); 72 73 for (var fi = 0; fi < fragments.length - 1; ++fi) { 74 if (target[fragments[fi]] === undefined) { 75 target[fragments[fi]] = {}; 76 } 77 78 target = target[fragments[fi]]; 79 } 80 81 target[fragments[fragments.length - 1]] = modules[id]; 82 } 83 } 84 85// Included from: js/tinymce/classes/dom/EventUtils.js 86 87/** 88 * EventUtils.js 89 * 90 * Copyright, Moxiecode Systems AB 91 * Released under LGPL License. 92 * 93 * License: http://www.tinymce.com/license 94 * Contributing: http://www.tinymce.com/contributing 95 */ 96 97/*jshint loopfunc:true*/ 98/*eslint no-loop-func:0 */ 99 100/** 101 * This class wraps the browsers native event logic with more convenient methods. 102 * 103 * @class tinymce.dom.EventUtils 104 */ 105define("tinymce/dom/EventUtils", [], function() { 106 "use strict"; 107 108 var eventExpandoPrefix = "mce-data-"; 109 var mouseEventRe = /^(?:mouse|contextmenu)|click/; 110 var deprecated = {keyLocation: 1, layerX: 1, layerY: 1, returnValue: 1}; 111 112 /** 113 * Binds a native event to a callback on the speified target. 114 */ 115 function addEvent(target, name, callback, capture) { 116 if (target.addEventListener) { 117 target.addEventListener(name, callback, capture || false); 118 } else if (target.attachEvent) { 119 target.attachEvent('on' + name, callback); 120 } 121 } 122 123 /** 124 * Unbinds a native event callback on the specified target. 125 */ 126 function removeEvent(target, name, callback, capture) { 127 if (target.removeEventListener) { 128 target.removeEventListener(name, callback, capture || false); 129 } else if (target.detachEvent) { 130 target.detachEvent('on' + name, callback); 131 } 132 } 133 134 /** 135 * Normalizes a native event object or just adds the event specific methods on a custom event. 136 */ 137 function fix(originalEvent, data) { 138 var name, event = data || {}, undef; 139 140 // Dummy function that gets replaced on the delegation state functions 141 function returnFalse() { 142 return false; 143 } 144 145 // Dummy function that gets replaced on the delegation state functions 146 function returnTrue() { 147 return true; 148 } 149 150 // Copy all properties from the original event 151 for (name in originalEvent) { 152 // layerX/layerY is deprecated in Chrome and produces a warning 153 if (!deprecated[name]) { 154 event[name] = originalEvent[name]; 155 } 156 } 157 158 // Normalize target IE uses srcElement 159 if (!event.target) { 160 event.target = event.srcElement || document; 161 } 162 163 // Calculate pageX/Y if missing and clientX/Y available 164 if (originalEvent && mouseEventRe.test(originalEvent.type) && originalEvent.pageX === undef && originalEvent.clientX !== undef) { 165 var eventDoc = event.target.ownerDocument || document; 166 var doc = eventDoc.documentElement; 167 var body = eventDoc.body; 168 169 event.pageX = originalEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - 170 (doc && doc.clientLeft || body && body.clientLeft || 0); 171 172 event.pageY = originalEvent.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - 173 (doc && doc.clientTop || body && body.clientTop || 0); 174 } 175 176 // Add preventDefault method 177 event.preventDefault = function() { 178 event.isDefaultPrevented = returnTrue; 179 180 // Execute preventDefault on the original event object 181 if (originalEvent) { 182 if (originalEvent.preventDefault) { 183 originalEvent.preventDefault(); 184 } else { 185 originalEvent.returnValue = false; // IE 186 } 187 } 188 }; 189 190 // Add stopPropagation 191 event.stopPropagation = function() { 192 event.isPropagationStopped = returnTrue; 193 194 // Execute stopPropagation on the original event object 195 if (originalEvent) { 196 if (originalEvent.stopPropagation) { 197 originalEvent.stopPropagation(); 198 } else { 199 originalEvent.cancelBubble = true; // IE 200 } 201 } 202 }; 203 204 // Add stopImmediatePropagation 205 event.stopImmediatePropagation = function() { 206 event.isImmediatePropagationStopped = returnTrue; 207 event.stopPropagation(); 208 }; 209 210 // Add event delegation states 211 if (!event.isDefaultPrevented) { 212 event.isDefaultPrevented = returnFalse; 213 event.isPropagationStopped = returnFalse; 214 event.isImmediatePropagationStopped = returnFalse; 215 } 216 217 return event; 218 } 219 220 /** 221 * Bind a DOMContentLoaded event across browsers and executes the callback once the page DOM is initialized. 222 * It will also set/check the domLoaded state of the event_utils instance so ready isn't called multiple times. 223 */ 224 function bindOnReady(win, callback, eventUtils) { 225 var doc = win.document, event = {type: 'ready'}; 226 227 if (eventUtils.domLoaded) { 228 callback(event); 229 return; 230 } 231 232 // Gets called when the DOM is ready 233 function readyHandler() { 234 if (!eventUtils.domLoaded) { 235 eventUtils.domLoaded = true; 236 callback(event); 237 } 238 } 239 240 function waitForDomLoaded() { 241 // Check complete or interactive state if there is a body 242 // element on some iframes IE 8 will produce a null body 243 if (doc.readyState === "complete" || (doc.readyState === "interactive" && doc.body)) { 244 removeEvent(doc, "readystatechange", waitForDomLoaded); 245 readyHandler(); 246 } 247 } 248 249 function tryScroll() { 250 try { 251 // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author. 252 // http://javascript.nwbox.com/IEContentLoaded/ 253 doc.documentElement.doScroll("left"); 254 } catch (ex) { 255 setTimeout(tryScroll, 0); 256 return; 257 } 258 259 readyHandler(); 260 } 261 262 // Use W3C method 263 if (doc.addEventListener) { 264 if (doc.readyState === "complete") { 265 readyHandler(); 266 } else { 267 addEvent(win, 'DOMContentLoaded', readyHandler); 268 } 269 } else { 270 // Use IE method 271 addEvent(doc, "readystatechange", waitForDomLoaded); 272 273 // Wait until we can scroll, when we can the DOM is initialized 274 if (doc.documentElement.doScroll && win.self === win.top) { 275 tryScroll(); 276 } 277 } 278 279 // Fallback if any of the above methods should fail for some odd reason 280 addEvent(win, 'load', readyHandler); 281 } 282 283 /** 284 * This class enables you to bind/unbind native events to elements and normalize it's behavior across browsers. 285 */ 286 function EventUtils() { 287 var self = this, events = {}
287, count, expando, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave; 288 289 expando = eventExpandoPrefix + (+new Date()).toString(32); 290 hasMouseEnterLeave = "onmouseenter" in document.documentElement; 291 hasFocusIn = "onfocusin" in document.documentElement; 292 mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'}; 293 count = 1; 294 295 // State if the DOMContentLoaded was executed or not 296 self.domLoaded = false; 297 self.events = events; 298 299 /** 300 * Executes all event handler callbacks for a specific event. 301 * 302 * @private 303 * @param {Event} evt Event object. 304 * @param {String} id Expando id value to look for. 305 */ 306 function executeHandlers(evt, id) { 307 var callbackList, i, l, callback, container = events[id]; 308 309 callbackList = container && container[evt.type]; 310 if (callbackList) { 311 for (i = 0, l = callbackList.length; i < l; i++) { 312 callback = callbackList[i]; 313 314 // Check if callback exists might be removed if a unbind is called inside the callback 315 if (callback && callback.func.call(callback.scope, evt) === false) { 316 evt.preventDefault(); 317 } 318 319 // Should we stop propagation to immediate listeners 320 if (evt.isImmediatePropagationStopped()) { 321 return; 322 } 323 } 324 } 325 } 326 327 /** 328 * Binds a callback to an event on the specified target. 329 * 330 * @method bind 331 * @param {Object} target Target node/window or custom object. 332 * @param {String} names Name of the event to bind. 333 * @param {function} callback Callback function to execute when the event occurs. 334 * @param {Object} scope Scope to call the callback function on, defaults to target. 335 * @return {function} Callback function that got bound. 336 */ 337 self.bind = function(target, names, callback, scope) { 338 var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window; 339 340 // Native event handler function patches the event and executes the callbacks for the expando 341 function defaultNativeHandler(evt) { 342 executeHandlers(fix(evt || win.event), id); 343 } 344 345 // Don't bind to text nodes or comments 346 if (!target || target.nodeType === 3 || target.nodeType === 8) { 347 return; 348 } 349 350 // Create or get events id for the target 351 if (!target[expando]) { 352 id = count++; 353 target[expando] = id; 354 events[id] = {}; 355 } else { 356 id = target[expando]; 357 } 358 359 // Setup the specified scope or use the target as a default 360 scope = scope || target; 361 362 // Split names and bind each event, enables you to bind multiple events with one call 363 names = names.split(' '); 364 i = names.length; 365 while (i--) { 366 name = names[i]; 367 nativeHandler = defaultNativeHandler; 368 fakeName = capture = false; 369 370 // Use ready instead of DOMContentLoaded 371 if (name === "DOMContentLoaded") { 372 name = "ready"; 373 } 374 375 // DOM is already ready 376 if (self.domLoaded && name === "ready" && target.readyState == 'complete') { 377 callback.call(scope, fix({type: name})); 378 continue; 379 } 380 381 // Handle mouseenter/mouseleaver 382 if (!hasMouseEnterLeave) { 383 fakeName = mouseEnterLeave[name]; 384 385 if (fakeName) { 386 nativeHandler = function(evt) { 387 var current, related; 388 389 current = evt.currentTarget; 390 related = evt.relatedTarget; 391 392 // Check if related is inside the current target if it's not then the event should 393 // be ignored since it's a mouseover/mouseout inside the element 394 if (related && current.contains) { 395 // Use contains for performance 396 related = current.contains(related); 397 } else { 398 while (related && related !== current) { 399 related = related.parentNode; 400 } 401 } 402 403 // Fire fake event 404 if (!related) { 405 evt = fix(evt || win.event); 406 evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter'; 407 evt.target = current; 408 executeHandlers(evt, id); 409 } 410 }; 411 } 412 } 413 414 // Fake bubbeling of focusin/focusout 415 if (!hasFocusIn && (name === "focusin" || name === "focusout")) { 416 capture = true; 417 fakeName = name === "focusin" ? "focus" : "blur"; 418 nativeHandler = function(evt) { 419 evt = fix(evt || win.event);
420 evt.type = evt.type === 'focus' ? 'focusin' : 'focusout'; 421 executeHandlers(evt, id); 422 }; 423 } 424 425 // Setup callback list and bind native event 426 callbackList = events[id][name]; 427 if (!callbackList) { 428 events[id][name] = callbackList = [{func: callback, scope: scope}]; 429 callbackList.fakeName = fakeName; 430 callbackList.capture = capture; 431 //callbackList.callback = callback; 432 433 // Add the nativeHandler to the callback list so that we can later unbind it 434 callbackList.nativeHandler = nativeHandler; 435 436 // Check if the target has native events support 437 438 if (name === "ready") { 439 bindOnReady(target, nativeHandler, self); 440 } else { 441 addEvent(target, fakeName || name, nativeHandler, capture); 442 } 443 } else { 444 if (name === "ready" && self.domLoaded) { 445 callback({type: name}); 446 } else { 447 // If it already has an native handler then just push the callback 448 callbackList.push({func: callback, scope: scope}); 449 } 450 } 451 } 452 453 target = callbackList = 0; // Clean memory for IE 454 455 return callback; 456 }; 457 458 /** 459 * Unbinds the specified event by name, name and callback or all events on the target. 460 * 461 * @method unbind 462 * @param {Object} target Target node/window or custom object. 463 * @param {String} names Optional event name to unbind. 464 * @param {function} callback Optional callback function to unbind. 465 * @return {EventUtils} Event utils instance. 466 */ 467 self.unbind = function(target, names, callback) { 468 var id, callbackList, i, ci, name, eventMap; 469 470 // Don't bind to text nodes or comments 471 if (!target || target.nodeType === 3 || target.nodeType === 8) { 472 return self; 473 } 474 475 // Unbind event or events if the target has the expando 476 id = target[expando]; 477 if (id) { 478 eventMap = events[id]; 479 480 // Specific callback 481 if (names) { 482 names = names.split(' '); 483 i = names.length; 484 while (i--) { 485 name = names[i]; 486 callbackList = eventMap[name]; 487 488 // Unbind the event if it exists in the map 489 if (callbackList) { 490 // Remove specified callback 491 if (callback) { 492 ci = callbackList.length; 493 while (ci--) { 494 if (callbackList[ci].func === callback) { 495 var nativeHandler = callbackList.nativeHandler; 496 var fakeName = callbackList.fakeName, capture = callbackList.capture; 497 498 // Clone callbackList since unbind inside a callback would otherwise break the handlers loop 499 callbackList = callbackList.slice(0, ci).concat(callbackList.slice(ci + 1)); 500 callbackList.nativeHandler = nativeHandler; 501 callbackList.fakeName = fakeName; 502 callbackList.capture = capture; 503 504 eventMap[name] = callbackList; 505 } 506 } 507 } 508 509 // Remove all callbacks if there isn't a specified callback or there is no callbacks left 510 if (!callback || callbackList.length === 0) { 511 delete eventMap[name]; 512 removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture); 513 } 514 } 515 } 516 } else { 517 // All events for a specific element 518 for (name in eventMap) { 519 callbackList = eventMap[name]; 520 removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture); 521 } 522 523 eventMap = {}; 524 } 525 526 // Check if object is empty, if it isn't then we won't remove the expando map 527 for (name in eventMap) { 528 return self; 529 } 530 531 // Delete event object 532 delete events[id]; 533 534 // Remove expando from target 535 try { 536 // IE will fail here since it can't delete properties from window 537 delete target[expando]; 538 } catch (ex) { 539 // IE will set it to null 540 target[expando] = null; 541 } 542 } 543 544 return self; 545 }; 546 547 /** 548 * Fires the specified event on the specified target. 549 * 550 * @method fire 551 * @param {Object} target Target node/window or custom object. 552 * @param {String} name Event name to fire. 553 * @param {Object} args Optional arguments to send to the observers. 554 * @return {EventUtils} Event utils instance. 555 */ 556 self.fire = function(target, name, args) { 557 var id; 558 559 // Don't bind to text nodes or comments 560 if (!target || target.nodeType === 3 || target.nodeType === 8) { 561 return self; 562 } 563 564 // Build event object by patching the args
565 args = fix(null, args); 566 args.type = name; 567 args.target = target; 568 569 do { 570 // Found an expando that means there is listeners to execute 571 id = target[expando]; 572 if (id) { 573 executeHandlers(args, id); 574 } 575 576 // Walk up the DOM 577 target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow; 578 } while (target && !args.isPropagationStopped()); 579 580 return self; 581 }; 582 583 /** 584 * Removes all bound event listeners for the specified target. This will also remove any bound 585 * listeners to child nodes within that target. 586 * 587 * @method clean 588 * @param {Object} target Target node/window object. 589 * @return {EventUtils} Event utils instance. 590 */ 591 self.clean = function(target) { 592 var i, children, unbind = self.unbind; 593 594 // Don't bind to text nodes or comments 595 if (!target || target.nodeType === 3 || target.nodeType === 8) { 596 return self; 597 } 598 599 // Unbind any element on the specificed target 600 if (target[expando]) { 601 unbind(target); 602 } 603 604 // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children 605 if (!target.getElementsByTagName) { 606 target = target.document; 607 } 608 609 // Remove events from each child element 610 if (target && target.getElementsByTagName) { 611 unbind(target); 612 613 children = target.getElementsByTagName('*'); 614 i = children.length; 615 while (i--) { 616 target = children[i]; 617 618 if (target[expando]) { 619 unbind(target); 620 } 621 } 622 } 623 624 return self; 625 }; 626 627 /** 628 * Destroys the event object. Call this on IE to remove memory leaks. 629 */ 630 self.destroy = function() { 631 events = {}; 632 }; 633 634 // Legacy function for canceling events 635 self.cancel = function(e) { 636 if (e) { 637 e.preventDefault(); 638 e.stopImmediatePropagation(); 639 } 640 641 return false; 642 }; 643 } 644 645 EventUtils.Event = new EventUtils(); 646 EventUtils.Event.bind(window, 'ready', function() {}); 647 648 return EventUtils; 649}); 650 651// Included from: js/tinymce/classes/dom/Sizzle.jQuery.js 652 653/** 654 * Sizzle.jQuery.js 655 * 656 * Copyright, Moxiecode Systems AB 657 * Released under LGPL License. 658 * 659 * License: http://www.tinymce.com/license 660 * Contributing: http://www.tinymce.com/contributing 661 */ 662 663/*global jQuery:true */ 664 665/* 666 * Fake Sizzle using jQuery. 667 */ 668define("tinymce/dom/Sizzle", [], function() { 669 // Detect if jQuery is loaded 670 if (!window.jQuery) { 671 throw new Error("Load jQuery first"); 672 } 673 674 return jQuery.find; 675}); 676 677// Included from: js/tinymce/classes/Env.js 678 679/** 680 * Env.js 681 * 682 * Copyright, Moxiecode Systems AB 683 * Released under LGPL License. 684 * 685 * License: http://www.tinymce.com/license 686 * Contributing: http://www.tinymce.com/contributing 687 */ 688 689/** 690 * This class contains various environment constants like browser versions etc. 691 * Normally you don't want to sniff specific browser versions but sometimes you have 692 * to when it's impossible to feature detect. So use this with care. 693 * 694 * @class tinymce.Env 695 * @static 696 */ 697define("tinymce/Env", [], function() { 698 var nav = navigator, userAgent = nav.userAgent; 699 var opera, webkit, ie, ie11, gecko, mac, iDevice; 700 701 opera = window.opera && window.opera.buildNumber; 702 webkit = /WebKit/.test(userAgent); 703 ie = !webkit && !opera && (/MSIE/gi).test(userAgent) && (/Explorer/gi).test(nav.appName); 704 ie = ie && /MSIE (\w+)\./.exec(userAgent)[1]; 705 ie11 = userAgent.indexOf('Trident/') != -1 && (userAgent.indexOf('rv:') != -1 || nav.appName.indexOf('Netscape') != -1) ? 11 : false; 706 ie = ie || ie11; 707 gecko = !webkit && !ie11 && /Gecko/.test(userAgent); 708 mac = userAgent.indexOf('Mac') != -1; 709 iDevice = /(iPad|iPhone)/.test(userAgent); 710 711 // Is a iPad/iPhone and not on iOS5 sniff the WebKit version since older iOS WebKit versions 712 // says it has contentEditable support but there is no visible caret. 713 var contentEditable = !iDevice || userAgent.match(/AppleWebKit\/(\d*)/)[1] >= 534; 714 715 return { 716 /** 717 * Constant that is true if the browser is Opera. 718 * 719 * @property opera 720 * @type Boolean 721 * @final 722 */ 723 opera: opera, 724 725 /** 726 * Constant that is true if the browser is WebKit (Safari/Chrome). 727 * 728 * @property webKit 729 * @type Boolean 730 * @final 731 */ 732 webkit: webkit, 733 734 /** 735 * Constant that is more than zero if the browser is IE. 736 * 737 * @property ie 738 * @type Boolean 739 * @final 740 */ 741 ie: ie, 742 743 /** 744 * Constant that is true if the browser is Gecko. 745 * 746 * @property gecko 747 * @type Boolean 748 * @final 749 */ 750 gecko: gecko, 751 752 /** 753 * Constant that is true if the os is Mac OS. 754 * 755 * @property mac 756 * @type Boolean 757 * @final 758 */ 759 mac: mac, 760 761 /** 762 * Constant that is true if the os is iOS. 763 * 764 * @property iOS 765 * @type Boolean 766 * @final 767 */ 768 iOS: iDevice, 769 770 /** 771 * Constant that is true if the browser supports editing. 772 * 773 * @property contentEditable 774 * @type Boolean 775 * @final 776 */ 777 contentEditable: contentEditable, 778 779 /** 780 * Transparent image data url. 781 * 782 * @property transparentSrc 783 * @type Boolean 784 * @final 785 */ 786 transparentSrc: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", 787 788 /** 789 * Returns true/false if the browser can or can't place the caret after a inline block like an image. 790 * 791 * @property noCaretAfter 792 * @type Boolean 793 * @final 794 */ 795 caretAfter: ie != 8, 796 797 /** 798 * Constant that is true if the browser supports native DOM Ranges. IE 9+. 799 * 800 * @property range 801 * @type Boolean 802 */ 803 range: window.getSelection && "Range" in window, 804 805 /** 806 * Returns the IE document mode for non IE browsers this will fake IE 10. 807 * 808 * @property documentMode 809 * @type Number 810 */ 811 documentMode: ie ? (document.documentMode || 7) : 10 812 }; 813}); 814 815// Included from: js/tinymce/classes/util/Tools.js 816 817/** 818 * Tools.js 819 * 820 * Copyright, Moxiecode Systems AB 821 * Released under LGPL License. 822 * 823 * License: http://www.tinymce.com/license 824 * Contributing: http://www.tinymce.com/contributing 825 */ 826 827/** 828 * This class contains various utlity functions. These are also exposed 829 * directly on the tinymce namespace. 830 * 831 * @class tinymce.util.Tools 832 */ 833define("tinymce/util/Tools", [ 834 "tinymce/Env" 835], function(Env) { 836 /** 837 * Removes whitespace from the beginning and end of a string. 838 * 839 * @method trim 840 * @param {String} s String to remove whitespace from. 841 * @return {String} New string with removed whitespace. 842 */ 843 var whiteSpaceRegExp = /^\s*|\s*$/g; 844 845 function trim(str) { 846 return (str === null || str === undefined) ? '' : ("" + str).replace(whiteSpaceRegExp, ''); 847 } 848 849 /** 850 * Returns true/false if the object is an array or not. 851 * 852 * @method isArray 853 * @param {Object} obj Object to check. 854 * @return {boolean} true/false state if the object is an array or not. 855 */ 856 var isArray = Array.isArray || function(obj) { 857 return Object.prototype.toString.call(obj) === "[object Array]"; 858 }; 859 860 /** 861 * Checks if a object is of a specific type for example an array.
862 * 863 * @method is 864 * @param {Object} o Object to check type of. 865 * @param {string} t Optional type to check for. 866 * @return {Boolean} true/false if the object is of the specified type. 867 */ 868 function is(o, t) { 869 if (!t) { 870 return o !== undefined; 871 } 872 873 if (t == 'array' && isArray(o)) { 874 return true; 875 } 876 877 return typeof(o) == t; 878 } 879 880 /** 881 * Converts the specified object into a real JavaScript array. 882 * 883 * @method toArray 884 * @param {Object} obj Object to convert into array. 885 * @return {Array} Array object based in input. 886 */ 887 function toArray(obj) { 888 var array = obj, i, l; 889 890 if (!isArray(obj)) { 891 array = []; 892 for (i = 0, l = obj.length; i < l; i++) { 893 array[i] = obj[i]; 894 } 895 } 896 897 return array; 898 } 899 900 /** 901 * Makes a name/object map out of an array with names. 902 * 903 * @method makeMap 904 * @param {Array/String} items Items to make map out of. 905 * @param {String} delim Optional delimiter to split string by. 906 * @param {Object} map Optional map to add items to. 907 * @return {Object} Name/value map of items. 908 */ 909 function makeMap(items, delim, map) { 910 var i; 911 912 items = items || []; 913 delim = delim || ','; 914 915 if (typeof(items) == "string") { 916 items = items.split(delim); 917 } 918 919 map = map || {}; 920 921 i = items.length; 922 while (i--) { 923 map[items[i]] = {}; 924 } 925 926 return map; 927 } 928 929 /** 930 * Performs an iteration of all items in a collection such as an object or array. This method will execure the 931 * callback function for each item in the collection, if the callback returns false the iteration will terminate. 932 * The callback has the following format: cb(value, key_or_index). 933 * 934 * @method each 935 * @param {Object} o Collection to iterate. 936 * @param {function} cb Callback function to execute for each item. 937 * @param {Object} s Optional scope to execute the callback in. 938 * @example 939 * // Iterate an array 940 * tinymce.each([1,2,3], function(v, i) { 941 * console.debug("Value: " + v + ", Index: " + i); 942 * }); 943 * 944 * // Iterate an object 945 * tinymce.each({a: 1, b: 2, c: 3], function(v, k) { 946 * console.debug("Value: " + v + ", Key: " + k); 947 * }); 948 */ 949 function each(o, cb, s) { 950 var n, l; 951 952 if (!o) { 953 return 0; 954 } 955 956 s = s || o; 957 958 if (o.length !== undefined) { 959 // Indexed arrays, needed for Safari 960 for (n = 0, l = o.length; n < l; n++) { 961 if (cb.call(s, o[n], n, o) === false) { 962 return 0; 963 } 964 } 965 } else { 966 // Hashtables 967 for (n in o) { 968 if (o.hasOwnProperty(n)) { 969 if (cb.call(s, o[n], n, o) === false) { 970 return 0; 971 } 972 } 973 } 974 } 975 976 return 1; 977 } 978 979 /** 980 * Creates a new array by the return value of each iteration function call. This enables you to convert 981 * one array list into another. 982 * 983 * @method map 984 * @param {Array} array Array of items to iterate. 985 * @param {function} callback Function to call for each item. It's return value will be the new value. 986 * @return {Array} Array with new values based on function return values. 987 */ 988 function map(array, callback) { 989 var out = []; 990 991 each(array, function(item) { 992 out.push(callback(item)); 993 }); 994 995 return out; 996 } 997 998 /** 999 * Filters out items from the input array by calling the specified function for each item. 1000 * If the function returns false the item will be excluded if it returns true it will be included. 1001 * 1002 * @method grep 1003 * @param {Array} a Array of items to loop though. 1004 * @param {function} f Function to call for each item. Include/exclude depends on it's return value. 1005 * @return {Array} New array with values imported and filtered based in input. 1006 * @example 1007 * // Filter out some items, this will return an array with 4 and 5 1008 * var items = tinymce.grep([1,2,3,4,5], function(v) {return v > 3;}); 1009 */ 1010 function grep(a, f) { 1011 var o = []; 1012 1013 each(a, function(v) { 1014 if (!f || f(v)) { 1015 o.push(v); 1016 } 1017 }); 1018 1019 return o; 1020 } 1021 1022 /** 1023 * Creates a class, subclass or static singleton. 1024 * More details on this method can be found in the Wiki. 1025 * 1026 * @method create 1027 * @param {String} s Class name, inheritage and prefix. 1028 * @param {Object} p Collection of methods to add to the class. 1029 * @param {Object} root Optional root object defaults to the global window object. 1030 * @example 1031 * // Creates a basic class 1032 * tinymce.create('tinymce.somepackage.SomeClass', { 1033 * SomeClass: function() { 1034 * // Class constructor 1035 * }, 1036 * 1037 * method: function() { 1038 * // Some method 1039 * } 1040 * }); 1041 * 1042 * // Creates a basic subclass class 1043 * tinymce.create('tinymce.somepackage.SomeSubClass:tinymce.somepackage.SomeClass', { 1044 * SomeSubClass: function() { 1045 * // Class constructor 1046 * this.parent(); // Call parent constructor 1047 * }, 1048 * 1049 * method: function() { 1050 * // Some method 1051 * this.parent(); // Call parent method 1052 * }, 1053 * 1054 * 'static': { 1055 * staticMethod: function() { 1056 * // Static method 1057 * } 1058 * } 1059 * }); 1060 * 1061 * // Creates a singleton/static class 1062 * tinymce.create('static tinymce.somepackage.SomeSingletonClass', { 1063 * method: function() { 1064 * // Some method 1065 * } 1066 * }); 1067 */ 1068 function create(s, p, root) { 1069 var self = this, sp, ns, cn, scn, c, de = 0; 1070 1071 // Parse : <prefix> <class>:<super class> 1072 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s); 1073 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name 1074 1075 // Create namespace for new class 1076 ns = self.createNS(s[3].replace(/\.\w+$/, ''), root); 1077 1078 // Class already exists 1079 if (ns[cn]) { 1080 return; 1081 } 1082 1083 // Make pure static class 1084 if (s[2] == 'static') { 1085 ns[cn] = p; 1086 1087 if (this.onCreate) { 1088 this.onCreate(s[2], s[3], ns[cn]); 1089 } 1090 1091 return; 1092 } 1093 1094 // Create default constructor 1095 if (!p[cn]) { 1096 p[cn] = function() {}; 1097 de = 1; 1098 } 1099 1100 // Add constructor and methods 1101 ns[cn] = p[cn]; 1102 self.extend(ns[cn].prototype, p); 1103 1104 // Extend 1105 if (s[5]) { 1106 sp = self.resolve(s[5]).prototype; 1107 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name 1108 1109 // Extend constructor 1110 c = ns[cn]; 1111 if (de) { 1112 // Add passthrough constructor 1113 ns[cn] = function() { 1114 return sp[scn].apply(this, arguments); 1115 }; 1116 } else { 1117 // Add inherit constructor 1118 ns[cn] = function() { 1119 this.parent = sp[scn]; 1120 return c.apply(this, arguments); 1121 }; 1122 } 1123 ns[cn].prototype[cn] = ns[cn]; 1124 1125 // Add super methods 1126 self.each(sp, function(f, n) { 1127 ns[cn].prototype[n] = sp[n]; 1128 }); 1129 1130 // Add overridden methods 1131 self.each(p, function(f, n) { 1132 // Extend methods if needed 1133 if (sp[n]) { 1134 ns[cn].prototype[n] = function() { 1135 this.parent = sp[n]; 1136 return f.apply(this, arguments); 1137 }; 1138 } else { 1139 if (n != cn) { 1140 ns[cn].prototype[n] = f; 1141 } 1142 } 1143 }); 1144 } 1145 1146 // Add static methods 1147 /*jshint sub:true*/ 1148 self.each(p['static'], function(f, n) { 1149 ns[cn][n] = f; 1150 }); 1151 } 1152 1153 /** 1154 * Returns the index of a value in an array, this method will return -1 if the item wasn't found. 1155 * 1156 * @method inArray 1157 * @param {Array} a Array/Object to search for value in. 1158 * @param {Object} v Value to check for inside the array. 1159 * @return {Number/String} Index of item inside the array inside an object. Or -1 if it wasn't found. 1160 * @example 1161 * // Get index of value in array this will alert 1 since 2 is at that index 1162 * alert(tinymce.inArray([1,2,3], 2)); 1163 */ 1164 function inArray(a, v) { 1165 var i, l; 1166 1167 if (a) { 1168 for (i = 0, l = a.length; i < l; i++) { 1169 if (a[i] === v) { 1170 return i; 1171 } 1172 } 1173 } 1174 1175 return -1; 1176 } 1177 1178 function extend(obj, ext) { 1179 var i, l, name, args = arguments, value; 1180 1181 for (i = 1, l = args.length; i < l; i++) { 1182 ext = args[i]; 1183 for (name in ext) { 1184 if (ext.hasOwnProperty(name)) { 1185 value = ext[name]; 1186 1187 if (value !== undefined) { 1188 obj[name] = value; 1189 } 1190 } 1191 } 1192 } 1193 1194 return obj; 1195 } 1196 1197 /** 1198 * Executed the specified function for each item in a object tree. 1199 * 1200 * @method walk 1201 * @param {Object} o Object tree to walk though. 1202 * @param {function} f Function to call for each item. 1203 * @param {String} n Optional name of collection inside the objects to walk for example childNodes. 1204 * @param {String} s Optional scope to execute the function in. 1205 */ 1206 function walk(o, f, n, s) { 1207 s = s || this; 1208 1209 if (o) { 1210 if (n) { 1211 o = o[n]; 1212 } 1213 1214 each(o, function(o, i) { 1215 if (f.call(s, o, i, n) === false) { 1216 return false;
1217 } 1218 1219 walk(o, f, n, s); 1220 }); 1221 } 1222 } 1223 1224 /** 1225 * Creates a namespace on a specific object. 1226 * 1227 * @method createNS 1228 * @param {String} n Namespace to create for example a.b.c.d. 1229 * @param {Object} o Optional object to add namespace to, defaults to window. 1230 * @return {Object} New namespace object the last item in path. 1231 * @example 1232 * // Create some namespace 1233 * tinymce.createNS('tinymce.somepackage.subpackage'); 1234 * 1235 * // Add a singleton 1236 * var tinymce.somepackage.subpackage.SomeSingleton = { 1237 * method: function() { 1238 * // Some method 1239 * } 1240 * }; 1241 */ 1242 function createNS(n, o) { 1243 var i, v; 1244 1245 o = o || window; 1246 1247 n = n.split('.'); 1248 for (i = 0; i < n.length; i++) { 1249 v = n[i]; 1250 1251 if (!o[v]) { 1252 o[v] = {}; 1253 } 1254 1255 o = o[v]; 1256 } 1257 1258 return o; 1259 } 1260 1261 /** 1262 * Resolves a string and returns the object from a specific structure. 1263 * 1264 * @method resolve 1265 * @param {String} n Path to resolve for example a.b.c.d. 1266 * @param {Object} o Optional object to search though, defaults to window. 1267 * @return {Object} Last object in path or null if it couldn't be resolved. 1268 * @example 1269 * // Resolve a path into an object reference 1270 * var obj = tinymce.resolve('a.b.c.d'); 1271 */ 1272 function resolve(n, o) { 1273 var i, l; 1274 1275 o = o || window; 1276 1277 n = n.split('.'); 1278 for (i = 0, l = n.length; i < l; i++) { 1279 o = o[n[i]]; 1280 1281 if (!o) { 1282 break; 1283 } 1284 } 1285 1286 return o; 1287 } 1288 1289 /** 1290 * Splits a string but removes the whitespace before and after each value. 1291 * 1292 * @method explode 1293 * @param {string} s String to split. 1294 * @param {string} d Delimiter to split by. 1295 * @example 1296 * // Split a string into an array with a,b,c 1297 * var arr = tinymce.explode('a, b, c'); 1298 */ 1299 function explode(s, d) { 1300 if (!s || is(s, 'array')) { 1301 return s; 1302 } 1303 1304 return map(s.split(d || ','), trim); 1305 } 1306 1307 function _addCacheSuffix(url) { 1308 var cacheSuffix = Env.cacheSuffix; 1309 1310 if (cacheSuffix) { 1311 url += (url.indexOf('?') === -1 ? '?' : '&') + cacheSuffix; 1312 } 1313 1314 return url; 1315 } 1316 1317 return { 1318 trim: trim, 1319 isArray: isArray, 1320 is: is, 1321 toArray: toArray, 1322 makeMap: makeMap, 1323 each: each, 1324 map: map, 1325 grep: grep, 1326 inArray: inArray, 1327 extend: extend, 1328 create: create, 1329 walk: walk, 1330 createNS: createNS, 1331 resolve: resolve, 1332 explode: explode, 1333 _addCacheSuffix: _addCacheSuffix 1334 }; 1335}); 1336 1337// Included from: js/tinymce/classes/dom/DomQuery.js 1338 1339/** 1340 * DomQuery.js 1341 * 1342 * Copyright, Moxiecode Systems AB 1343 * Released under LGPL License. 1344 * 1345 * License: http://www.tinymce.com/license 1346 * Contributing: http://www.tinymce.com/contributing 1347 */ 1348 1349/** 1350 * This class mimics most of the jQuery API: 1351 * 1352 * This is whats currently implemented: 1353 * - Utility functions 1354 * - DOM traversial 1355 * - DOM manipulation 1356 * - Event binding 1357 * 1358 * This is not currently implemented: 1359 * - Dimension 1360 * - Ajax 1361 * - Animation 1362 * - Advanced chaining 1363 * 1364 * @example 1365 * var $ = tinymce.dom.DomQuery; 1366 * $('p').attr('attr', 'value').addClass('class'); 1367 * 1368 * @class tinymce.dom.DomQuery 1369 */ 1370define("tinymce/dom/DomQuery", [ 1371 "tinymce/dom/EventUtils", 1372 "tinymce/dom/Sizzle", 1373 "tinymce/util/Tools", 1374 "tinymce/Env" 1375], function(EventUtils, Sizzle, Tools, Env) { 1376 var doc = document, push = Array.prototype.push, slice = Array.prototype.slice; 1377 var rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/; 1378 var Event = EventUtils.Event, undef; 1379 1380 function isDefined(obj) { 1381 return typeof obj !== 'undefined'; 1382 } 1383 1384 function isString(obj) { 1385 return typeof obj === 'string'; 1386 } 1387 1388 function isWindow(obj) { 1389 return obj && obj == obj.window; 1390 } 1391 1392 function createFragment(html, fragDoc) { 1393 var frag, node, container; 1394 1395 fragDoc = fragDoc || doc; 1396 container = fragDoc.createElement('div'); 1397 frag = fragDoc.createDocumentFragment(); 1398 container.innerHTML = html; 1399 1400 while ((node = container.firstChild)) { 1401 frag.appendChild(node); 1402 } 1403 1404 return frag; 1405 } 1406 1407 function domManipulate(targetNodes, sourceItem, callback, reverse) { 1408 var i; 1409 1410 if (isString(sourceItem)) { 1411 sourceItem = createFragment(sourceItem, getElementDocument(targetNodes[0])); 1412 } else if (sourceItem.length && !sourceItem.nodeType) { 1413 sourceItem = DomQuery.makeArray(sourceItem); 1414 1415 if (reverse) { 1416 for (i = sourceItem.length - 1; i >= 0; i--) { 1417 domManipulate(targetNodes, sourceItem[i], callback, reverse); 1418 } 1419 } else { 1420 for (i = 0; i < sourceItem.length; i++) { 1421 domManipulate(targetNodes, sourceItem[i], callback, reverse); 1422 } 1423 } 1424 1425 return targetNodes; 1426 } 1427 1428 if (sourceItem.nodeType) { 1429 i = targetNodes.length; 1430 while (i--) { 1431 callback.call(targetNodes[i], sourceItem); 1432 } 1433 } 1434 1435 return targetNodes; 1436 } 1437 1438 function hasClass(node, className) { 1439 return node && className && (' ' + node.className + ' ').indexOf(' ' + className + ' ') !== -1; 1440 } 1441 1442 function wrap(elements, wrapper, all) { 1443 var lastParent, newWrapper; 1444 1445 wrapper = DomQuery(wrapper)[0]; 1446 1447 elements.each(function() { 1448 var self = this; 1449 1450 if (!all || lastParent != self.parentNode) { 1451 lastParent = self.parentNode; 1452 newWrapper = wrapper.cloneNode(false); 1453 self.parentNode.insertBefore(newWrapper, self); 1454 newWrapper.appendChild(self); 1455 } else { 1456 newWrapper.appendChild(self); 1457 } 1458 }); 1459 1460 return elements; 1461 } 1462 1463 var numericCssMap = Tools.makeMap('fillOpacity fontWeight lineHeight opacity orphans widows zIndex zoom', ' '); 1464 var booleanMap = Tools.makeMap('checked compact declare defer disabled ismap multiple nohref nosh
1464ade nowrap readonly selected', ' '); 1465 var propFix = { 1466 'for': 'htmlFor', 1467 'class': 'className', 1468 'readonly': 'readOnly' 1469 }; 1470 var cssFix = { 1471 'float': 'cssFloat' 1472 }; 1473 1474 var attrHooks = {}, cssHooks = {}; 1475 1476 function DomQuery(selector, context) { 1477 /*eslint new-cap:0 */ 1478 return new DomQuery.fn.init(selector, context); 1479 } 1480 1481 function inArray(item, array) { 1482 var i; 1483 1484 if (array.indexOf) { 1485 return array.indexOf(item); 1486 } 1487 1488 i = array.length; 1489 while (i--) { 1490 if (array[i] === item) { 1491 return i; 1492 } 1493 } 1494 1495 return -1; 1496 } 1497 1498 var whiteSpaceRegExp = /^\s*|\s*$/g; 1499 1500 function trim(str) { 1501 return (str === null || str === undef) ? '' : ("" + str).replace(whiteSpaceRegExp, ''); 1502 } 1503 1504 function each(obj, callback) { 1505 var length, key, i, undef, value; 1506 1507 if (obj) { 1508 length = obj.length; 1509 1510 if (length === undef) { 1511 // Loop object items 1512 for (key in obj) { 1513 if (obj.hasOwnProperty(key)) { 1514 value = obj[key]; 1515 if (callback.call(value, key, value) === false) { 1516 break; 1517 } 1518 } 1519 } 1520 } else { 1521 // Loop array items 1522 for (i = 0; i < length; i++) { 1523 value = obj[i]; 1524 if (callback.call(value, i, value) === false) { 1525 break; 1526 } 1527 } 1528 } 1529 } 1530 1531 return obj; 1532 } 1533 1534 function grep(array, callback) { 1535 var out = []; 1536 1537 each(array, function(i, item) { 1538 if (callback(item, i)) { 1539 out.push(item); 1540 } 1541 }); 1542 1543 return out; 1544 } 1545 1546 function getElementDocument(element) { 1547 if (!element) { 1548 return doc; 1549 } 1550 1551 if (element.nodeType == 9) { 1552 return element; 1553 } 1554 1555 return element.ownerDocument; 1556 } 1557 1558 DomQuery.fn = DomQuery.prototype = { 1559 constructor: DomQuery, 1560 1561 /** 1562 * Selector for the current set. 1563 * 1564 * @property selector
1565 * @type String 1566 */ 1567 selector: "", 1568 1569 /** 1570 * Context used to create the set. 1571 * 1572 * @property context 1573 * @type Element 1574 */ 1575 context: null, 1576 1577 /** 1578 * Number of items in the current set. 1579 * 1580 * @property length 1581 * @type Number 1582 */ 1583 length: 0, 1584 1585 /** 1586 * Constructs a new DomQuery instance with the specified selector or context. 1587 * 1588 * @constructor 1589 * @method init 1590 * @param {String/Array/DomQuery} selector Optional CSS selector/Array or array like object or HTML string. 1591 * @param {Document/Element} context Optional context to search in. 1592 */ 1593 init: function(selector, context) { 1594 var self = this, match, node; 1595 1596 if (!selector) { 1597 return self; 1598 } 1599 1600 if (selector.nodeType) { 1601 self.context = self[0] = selector; 1602 self.length = 1; 1603 1604 return self; 1605 } 1606 1607 if (context && context.nodeType) { 1608 self.context = context; 1609 } else { 1610 if (context) { 1611 return DomQuery(selector).attr(context); 1612 } else { 1613 self.context = context = document; 1614 } 1615 } 1616 1617 if (isString(selector)) { 1618 self.selector = selector; 1619 1620 if (selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3) { 1621 match = [null, selector, null]; 1622 } else { 1623 match = rquickExpr.exec(selector); 1624 } 1625 1626 if (match) { 1627 if (match[1]) { 1628 node = createFragment(selector, getElementDocument(context)).firstChild; 1629 1630 while (node) { 1631 push.call(self, node); 1632 node = node.nextSibling; 1633 } 1634 } else { 1635 node = getElementDocument(context).getElementById(match[2]); 1636 1637 if (!node) { 1638 return self; 1639 } 1640 1641 if (node.id !== match[2]) { 1642 return self.find(selector); 1643 } 1644 1645 self.length = 1; 1646 self[0] = node; 1647 } 1648 } else { 1649 return DomQuery(context).find(selector); 1650 } 1651 } else { 1652 this.add(selector, false); 1653 } 1654 1655 return self; 1656 }, 1657 1658 /** 1659 * Converts the current set to an array. 1660 * 1661 * @method toArray 1662 * @param {Array} Array of all nodes in set. 1663 */ 1664 toArray: function() { 1665 return Tools.toArray(this); 1666 }, 1667 1668 /** 1669 * Adds new nodes to the set. 1670 * 1671 * @method add 1672 * @param {Array/tinymce.dom.DomQuery} items Array of all nodes to add to set. 1673 * @return {tinymce.dom.DomQuery} New instance with nodes added. 1674 */ 1675 add: function(items, sort) { 1676 var self = this, nodes, i; 1677 1678 if (isString(items)) { 1679 return self.add(DomQuery(items)); 1680 } 1681 1682 if (sort !== false) { 1683 nodes = DomQuery.unique(self.toArray().concat(DomQuery.makeArray(items))); 1684 self.length = nodes.length; 1685 for (i = 0; i < nodes.length; i++) { 1686 self[i] = nodes[i]; 1687 } 1688 } else { 1689 push.apply(self, DomQuery.makeArray(items)); 1690 } 1691 1692 return self; 1693 }, 1694 1695 /** 1696 * Sets/gets attributes on the elements in the current set. 1697 * 1698 * @method attr 1699 * @param {String/Object} name Name of attribute to get or an object with attributes to set. 1700 * @param {String} value Optional value to set. 1701 * @return {tinymce.dom.DomQuery/String} Current set or the specified attribute when only the name is specified. 1702 */ 1703 attr: function(name, value) { 1704 var self = this, hook; 1705 1706 if (typeof name === "object") { 1707 each(name, function(name, value) { 1708 self.attr(name, value); 1709 }); 1710 } else if (isDefined(value)) { 1711 this.each(function() { 1712 var hook; 1713 1714 if (this.nodeType === 1) { 1715 hook = attrHooks[name]; 1716 if (hook && hook.set) { 1717 hook.set(this, value); 1718 return; 1719 } 1720 1721 if (value === null) { 1722 this.removeAttribute(name, 2); 1723 } else { 1724 this.setAttribute(name, value, 2); 1725 } 1726 } 1727 }); 1728 } else { 1729 if (self[0] && self[0].nodeType === 1) { 1730 hook = attrHooks[name]; 1731 if (hook && hook.get) { 1732 return hook.get(self[0], name); 1733 } 1734 1735 if (booleanMap[name]) { 1736 return self.prop(name) ? name : undef; 1737 } 1738 1739 value = self[0].getAttribute(name, 2); 1740 1741 if (value === null) { 1742 value = undef; 1743 } 1744 } 1745 1746 return value; 1747 } 1748 1749 return self; 1750 }, 1751 1752 /** 1753 * Removes attributse on the elements in the current set. 1754 * 1755 * @method removeAttr 1756 * @param {String/Object}
1756 name Name of attribute to remove. 1757 * @return {tinymce.dom.DomQuery/String} Current set. 1758 */ 1759 removeAttr: function(name) { 1760 return this.attr(name, null); 1761 }, 1762 1763 /** 1764 * Sets/gets properties on the elements in the current set. 1765 * 1766 * @method attr 1767 * @param {String/Object} name Name of property to get or an object with properties to set. 1768 * @param {String} value Optional value to set. 1769 * @return {tinymce.dom.DomQuery/String} Current set or the specified property when only the name is specified. 1770 */ 1771 prop: function(name, value) { 1772 var self = this; 1773 1774 name = propFix[name] || name; 1775 1776 if (typeof name === "object") { 1777 each(name, function(name, value) { 1778 self.prop(name, value); 1779 }); 1780 } else if (isDefined(value)) { 1781 this.each(function() { 1782 if (this.nodeType == 1) { 1783 this[name] = value; 1784 } 1785 }); 1786 } else { 1787 if (self[0] && self[0].nodeType && name in self[0]) { 1788 return self[0][name]; 1789 } 1790 1791 return value; 1792 } 1793 1794 return self; 1795 }, 1796 1797 /** 1798 * Sets/gets styles on the elements in the current set. 1799 * 1800 * @method css 1801 * @param {String/Object} name Name of style to get or an object with styles to set. 1802 * @param {String} value Optional value to set. 1803 * @return {tinymce.dom.DomQuery/String} Current set or the specified style when only the name is specified. 1804 */ 1805 css: function(name, value) { 1806 var self = this, elm, hook; 1807 1808 function camel(name) { 1809 return name.replace(/-(\D)/g, function(a, b) { 1810 return b.toUpperCase(); 1811 }); 1812 } 1813 1814 function dashed(name) { 1815 return name.replace(/[A-Z]/g, function(a) { 1816 return '-' + a; 1817 }); 1818 } 1819 1820 if (typeof name === "object") { 1821 each(name, function(name, value) { 1822 self.css(name, value); 1823 }); 1824 } else { 1825 if (isDefined(value)) { 1826 name = camel(name); 1827 1828 // Default px suffix on these 1829 if (typeof(value) === 'number' && !numericCssMap[name]) { 1830 value += 'px'; 1831 } 1832 1833 self.each(function() { 1834 var style = this.style; 1835 1836 hook = cssHooks[name]; 1837 if (hook && hook.set) { 1838 hook.set(this, value); 1839 return; 1840 } 1841 1842 try { 1843 this.style[cssFix[name] || name] = value; 1844 } catch (ex) { 1845 // Ignore 1846 } 1847 1848 if (value === null || value === '') { 1849 if (style.removeProperty) { 1850 style.removeProperty(dashed(name)); 1851 } else { 1852 style.removeAttribute(name); 1853 } 1854 } 1855 }); 1856 } else { 1857 elm = self[0]; 1858 1859 hook = cssHooks[name]; 1860 if (hook && hook.get) { 1861 return hook.get(elm); 1862 } 1863 1864 if (elm.ownerDocument.defaultView) { 1865 try { 1866 return elm.ownerDocument.defaultView.getComputedStyle(elm, null).getPropertyValue(dashed(name)); 1867 } catch (ex) { 1868 return undef; 1869 } 1870 } else if (elm.currentStyle) { 1871 return elm.currentStyle[camel(name)]; 1872 } 1873 } 1874 } 1875 1876 return self; 1877 }, 1878 1879 /** 1880 * Removes all nodes in set from the document. 1881 * 1882 * @method remove 1883 * @return {tinymce.dom.DomQuery} Current set with the removed nodes. 1884 */ 1885 remove: function() { 1886 var self = this, node, i = this.length; 1887 1888 while (i--) { 1889 node = self[i]; 1890 Event.clean(node); 1891 1892 if (node.parentNode) { 1893 node.parentNode.removeChild(node); 1894 } 1895 } 1896 1897 return this; 1898 }, 1899 1900 /** 1901 * Empties all elements in set. 1902 * 1903 * @method empty 1904 * @return {tinymce.dom.DomQuery} Current set with the empty nodes. 1905 */ 1906 empty: function() { 1907 var self = this, node, i = this.length; 1908 1909 while (i--) { 1910 node = self[i]; 1911 while (node.firstChild) { 1912 node.removeChild(node.firstChild); 1913 } 1914 } 1915 1916 return this; 1917 }, 1918 1919 /** 1920 * Sets or gets the HTML of the current set or first set node. 1921 * 1922 * @method html 1923 * @param {String} value Optional innerHTML value to set on each element. 1924 * @return {tinymce.dom.DomQuery/String} Current set or the innerHTML of the first element. 1925 */ 1926 html: function(value) { 1927 var self = this, i; 1928 1929 if (isDefined(value)) { 1930 i = self.length; 1931 1932 try { 1933 while (i--) { 1934 self[i].innerHTML = value; 1935 } 1936 } catch (ex) { 1937 // Workaround for "Unknown runtime error" when DIV is added to P on IE 1938 DomQuery(self[i]).empty().append(value); 1939 } 1940 1941 return self; 1942 } 1943 1944 return self[0] ? self[0].innerHTML : ''; 1945 }, 1946 1947 /** 1948 * Sets or gets the text of the current set or first set node. 1949 * 1950 * @method text 1951 * @param {String} value Optional innerText value to set on each element. 1952 * @return {tinymce.dom.DomQuery/String} Current set or the innerText of the first element. 1953 */ 1954 text: function(value) { 1955 var self = this, i; 1956 1957 if (isDefined(value)) { 1958 i = self.length; 1959 while (i--) { 1960 if ("innerText" in self[i]) { 1961 self[i].innerText = value; 1962 } else { 1963 self[0].textContent = value; 1964 } 1965 } 1966 1967 return self; 1968 } 1969 1970 return self[0] ? (self[0].innerText || self[0].textContent) : ''; 1971 }, 1972 1973 /** 1974 * Appends the specified node/html or node set to the current set nodes. 1975 * 1976 * @method append 1977 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to append to each element in set. 1978 * @return {tinymce.dom.DomQuery} Current set. 1979 */ 1980 append: function() { 1981 return domManipulate(this, arguments, function(node) { 1982 if (this.nodeType === 1) { 1983 this.appendChild(node); 1984 } 1985 }); 1986 }, 1987 1988 /** 1989 * Prepends the specified node/html or node set to the current set nodes. 1990 * 1991 * @method prepend 1992 * @param {String/Element/Array/tinymce.dom.DomQuery}
1992 content Content to prepend to each element in set. 1993 * @return {tinymce.dom.DomQuery} Current set. 1994 */ 1995 prepend: function() { 1996 return domManipulate(this, arguments, function(node) { 1997 if (this.nodeType === 1) { 1998 this.insertBefore(node, this.firstChild); 1999 } 2000 }, true); 2001 }, 2002 2003 /** 2004 * Adds the specified elements before current set nodes. 2005 * 2006 * @method before 2007 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to add before to each element in set. 2008 * @return {tinymce.dom.DomQuery} Current set. 2009 */ 2010 before: function() { 2011 var self = this; 2012 2013 if (self[0] && self[0].parentNode) { 2014 return domManipulate(self, arguments, function(node) { 2015 this.parentNode.insertBefore(node, this); 2016 }); 2017 } 2018 2019 return self; 2020 }, 2021 2022 /** 2023 * Adds the specified elements after current set nodes. 2024 * 2025 * @method after 2026 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to add after to each element in set. 2027 * @return {tinymce.dom.DomQuery} Current set. 2028 */ 2029 after: function() { 2030 var self = this; 2031 2032 if (self[0] && self[0].parentNode) { 2033 return domManipulate(self, arguments, function(node) { 2034 this.parentNode.insertBefore(node, this.nextSibling); 2035 }, true); 2036 } 2037 2038 return self; 2039 }, 2040 2041 /** 2042 * Appends the specified set nodes to the specified selector/instance. 2043 * 2044 * @method appendTo 2045 * @param {String/Element/Array/tinymce.dom.DomQuery} val Item to append the current set to. 2046 * @return {tinymce.dom.DomQuery} Current set with the appended nodes. 2047 */ 2048 appendTo: function(val) { 2049 DomQuery(val).append(this); 2050 2051 return this; 2052 }, 2053 2054 /** 2055 * Prepends the specified set nodes to the specified selector/instance. 2056 * 2057 * @method prependTo 2058 * @param {String/Element/Array/tinymce.dom.DomQuery} val Item to prepend the current set to. 2059 * @return {tinymce.dom.DomQuery} Current set with the prepended nodes. 2060 */ 2061 prependTo: function(val) { 2062 DomQuery(val).prepend(this); 2063 2064 return this; 2065 }, 2066 2067 /** 2068 * Replaces the nodes in set with the specified content. 2069 * 2070 * @method replaceWith 2071 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to replace nodes with. 2072 * @return {tinymce.dom.DomQuery} Set with replaced nodes. 2073 */ 2074 replaceWith: function(content) { 2075 return this.before(content).remove(); 2076 }, 2077 2078 /** 2079 * Wraps all elements in set with the specified wrapper. 2080 * 2081 * @method wrap 2082 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to wrap nodes with. 2083 * @return {tinymce.dom.DomQuery} Set with wrapped nodes. 2084 */ 2085 wrap: function(wrapper) { 2086 return wrap(this, wrapper); 2087 }, 2088 2089 /** 2090 * Wraps all nodes in set with the specified wrapper. If the nodes are siblings all of them 2091 * will be wrapped in the same wrapper. 2092 * 2093 * @method wrapAll 2094 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to wrap nodes with. 2095 * @return {tinymce.dom.DomQuery} Set with wrapped nodes. 2096 */ 2097 wrapAll: function(wrapper) { 2098 return wrap(this, wrapper, true); 2099 }, 2100 2101 /** 2102 * Wraps all elements inner contents in set with the specified wrapper. 2103 * 2104 * @method wrapInner 2105 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to wrap nodes with. 2106 * @return {tinymce.dom.DomQuery} Set with wrapped nodes. 2107 */ 2108 wrapInner: function(wrapper) { 2109 this.each(function() { 2110 DomQuery(this).contents().wrapAll(wrapper); 2111 }); 2112 2113 return this; 2114 }, 2115 2116 /** 2117 * Unwraps all elements by removing the parent element of each item in set. 2118 * 2119 * @method unwrap 2120 * @return {tinymce.dom.DomQuery} Set with unwrapped nodes. 2121 */ 2122 unwrap: function() { 2123 return this.parent().each(function() { 2124 DomQuery(this).replaceWith(this.childNodes); 2125 }); 2126 }, 2127 2128 /** 2129 * Clones all nodes in set. 2130 * 2131 * @method clone 2132 * @return {tinymce.dom.DomQuery} Set with cloned nodes. 2133 */ 2134 clone: function() { 2135 var result = []; 2136 2137 this.each(function() { 2138 result.push(this.cloneNode(true)); 2139 }); 2140 2141 return DomQuery(result); 2142 }, 2143 2144 /** 2145 * Adds the specified class name to the current set elements. 2146 * 2147 * @method addClass 2148 * @param {String} className Class name to add. 2149 * @return {tinymce.dom.DomQuery} Current set. 2150 */ 2151 addClass: function(className) { 2152 return this.toggleClass(className, true); 2153 }, 2154 2155 /** 2156 * Removes the specified class name to the current set elements. 2157 * 2158 * @method removeClass 2159 * @param {String} className Class name to remove. 2160 * @return {tinymce.dom.DomQuery} Current set. 2161 */ 2162 removeClass: function(className) { 2163 return this.toggleClass(className, false); 2164 }, 2165 2166 /** 2167 * Toggles the specified class name on the current set elements. 2168 * 2169 * @method toggleClass 2170 * @param {String} className Class name to add/remove. 2171 * @param {Boolean} state Optional state to toggle on/off. 2172 * @return {tinymce.dom.DomQuery} Current set. 2173 */ 2174 toggleClass: function(className, state) { 2175 var self = this; 2176 2177 // Functions are not supported 2178 if (typeof className != 'string') { 2179 return self; 2180 } 2181 2182 if (className.indexOf(' ') !== -1) { 2183 each(className.split(' '), function() { 2184 self.toggleClass(this, state); 2185 }); 2186 } else { 2187 self.each(function(index, node) { 2188 var existingClassName, classState; 2189 2190 classState = hasClass(node, className); 2191 if (classState !== state) {
2192 existingClassName = node.className; 2193 2194 if (classState) { 2195 node.className = trim((" " + existingClassName + " ").replace(' ' + className + ' ', ' ')); 2196 } else { 2197 node.className += existingClassName ? ' ' + className : className; 2198 } 2199 } 2200 }); 2201 } 2202 2203 return self; 2204 }, 2205 2206 /** 2207 * Returns true/false if the first item in set has the specified class. 2208 * 2209 * @method hasClass 2210 * @param {String} className Class name to check for. 2211 * @return {Boolean} True/false if the set has the specified class. 2212 */ 2213 hasClass: function(className) { 2214 return hasClass(this[0], className); 2215 }, 2216 2217 /** 2218 * Executes the callback function for each item DomQuery collection. If you return false in the 2219 * callback it will break the loop. 2220 * 2221 * @method each 2222 * @param {function} callback Callback function to execute for each item. 2223 * @return {tinymce.dom.DomQuery} Current set. 2224 */ 2225 each: function(callback) { 2226 return each(this, callback); 2227 }, 2228 2229 /** 2230 * Binds an event with callback function to the elements in set. 2231 * 2232 * @method on 2233 * @param {String} name Name of the event to bind. 2234 * @param {function} callback Callback function to execute when the event occurs. 2235 * @return {tinymce.dom.DomQuery} Current set. 2236 */ 2237 on: function(name, callback) { 2238 return this.each(function() { 2239 Event.bind(this, name, callback); 2240 }); 2241 }, 2242 2243 /** 2244 * Unbinds an event with callback function to the elements in set. 2245 * 2246 * @method off 2247 * @param {String} name Optional name of the event to bind. 2248 * @param {function} callback Optional callback function to execute when the event occurs. 2249 * @return {tinymce.dom.DomQuery} Current set. 2250 */ 2251 off: function(name, callback) { 2252 return this.each(function() { 2253 Event.unbind(this, name, callback); 2254 }); 2255 }, 2256 2257 /** 2258 * Triggers the specified event by name or event object. 2259 * 2260 * @method trigger 2261 * @param {String/Object} name Name of the event to trigger or event object. 2262 * @return {tinymce.dom.DomQuery} Current set. 2263 */ 2264 trigger: function(name) { 2265 return this.each(function() { 2266 if (typeof name == 'object') { 2267 Event.fire(this, name.type, name); 2268 } else { 2269 Event.fire(this, name); 2270 } 2271 }); 2272 }, 2273 2274 /** 2275 * Shows all elements in set. 2276 * 2277 * @method show 2278 * @return {tinymce.dom.DomQuery} Current set. 2279 */ 2280 show: function() { 2281 return this.css('display', ''); 2282 }, 2283 2284 /** 2285 * Hides all elements in set. 2286 * 2287 * @method hide 2288 * @return {tinymce.dom.DomQuery} Current set. 2289 */ 2290 hide: function() { 2291 return this.css('display', 'none'); 2292 }, 2293 2294 /** 2295 * Slices the current set. 2296 * 2297 * @method slice 2298 * @param {Number} start Start index to slice at. 2299 * @param {Number} end Optional ened index to end slice at. 2300 * @return {tinymce.dom.DomQuery} Sliced set. 2301 */ 2302 slice: function() { 2303 return new DomQuery(slice.apply(this, arguments)); 2304 }, 2305 2306 /** 2307 * Makes the set equal to the specified index. 2308 * 2309 * @method eq 2310 * @param {Number} index Index to set it equal to. 2311 * @return {tinymce.dom.DomQuery} Single item set. 2312 */ 2313 eq: function(index) { 2314 return index === -1 ? this.slice(index) : this.slice(index, +index + 1); 2315 }, 2316 2317 /** 2318 * Makes the set equal to first element in set. 2319 * 2320 * @method first 2321 * @return {tinymce.dom.DomQuery} Single item set. 2322 */ 2323 first: function() { 2324 return this.eq(0); 2325 }, 2326 2327 /** 2328 * Makes the set equal to last element in set. 2329 * 2330 * @method last 2331 * @return {tinymce.dom.DomQuery} Single item set. 2332 */ 2333 last: function() { 2334 return this.eq(-1); 2335 }, 2336 2337 /** 2338 * Finds elements by the specified selector for each element in set. 2339 * 2340 * @method find 2341 * @param {String} selector Selector to find elements by. 2342 * @return {tinymce.dom.DomQuery} Set with matches elements. 2343 */ 2344 find: function(selector) { 2345 var i, l, ret = []; 2346 2347 for (i = 0, l = this.length; i < l; i++) { 2348 DomQuery.find(selector, this[i], ret); 2349 } 2350 2351 return DomQuery(ret); 2352 }, 2353 2354 /** 2355 * Filters the current set with the specified selector. 2356 * 2357 * @method filter 2358 * @param {String/function} selector Selector to filter elements by. 2359 * @return {tinymce.dom.DomQuery} Set with filtered elements. 2360 */ 2361 filter: function(selector) { 2362 if (typeof selector == 'function') { 2363 return DomQuery(grep(this.toArray(), function(item, i) { 2364 return selector(i, item); 2365 })); 2366 } 2367 2368 return DomQuery(DomQuery.filter(selector, this.toArray())); 2369 }, 2370 2371 /** 2372 * Gets the current node or any partent matching the specified selector. 2373 * 2374 * @method closest 2375 * @param {String/Element/tinymce.dom.DomQuery} selector Selector or element to find. 2376 * @return {tinymce.dom.DomQuery} Set with closest elements. 2377 */ 2378 closest: function(selector) { 2379 var result = []; 2380 2381 if (selector instanceof DomQuery) { 2382 selector = selector[0]; 2383 } 2384 2385 this.each(function(i, node) { 2386 while (node) { 2387 if (typeof selector == 'string' && DomQuery(node).is(selector)) { 2388 result.push(node); 2389 break; 2390 } else if (node == selector) { 2391 result.push(node); 2392 break; 2393 } 2394 2395 node = node.parentNode; 2396 } 2397 }); 2398 2399 return DomQuery(result); 2400 }, 2401 2402 /** 2403 * Returns the offset of the first element in set or sets the top/left css properties of all elements in set. 2404 * 2405 * @method offset 2406 * @param {Object} offset Optional offset object to set on each item. 2407 * @return {Object/tinymce.dom.DomQuery} Returns the first element offset or the current set if you specified an offset. 2408 */ 2409 offset: function(offset) { 2410 var elm, doc, docElm; 2411 var x = 0, y = 0, pos; 2412 2413 if (!offset) { 2414 elm = this[0]; 2415 2416 if (elm) { 2417 doc = elm.ownerDocument; 2418 docElm = doc.documentElement; 2419 2420 if (elm.getBoundingClientRect) { 2421 pos = elm.getBoundingClientRect(); 2422 x = pos.left + (docElm.scrollLeft || doc.body.scrollLeft) - docElm.clientLeft; 2423 y = pos.top + (docElm.scrollTop || doc.body.scrollTop) - docElm.clientTop; 2424 } 2425 } 2426 2427 return { 2428 left: x, 2429 top: y 2430 }; 2431 } 2432 2433 return this.css(offset); 2434 }, 2435 2436 push: push, 2437 sort: [].sort, 2438 splice: [].splice 2439 }; 2440 2441 // Static members 2442 Tools.extend(DomQuery, { 2443 /** 2444 * Extends the specified object with one or more objects. 2445 * 2446 * @static 2447 * @method extend 2448 * @param {Object} target Target object to extend with new items. 2449 * @param {Object..} object Object to extend the target with. 2450 * @return {Object} Extended input object. 2451 */ 2452 extend: Tools.extend, 2453 2454 /** 2455 * Creates an array out of an array like object. 2456 * 2457 * @static 2458 * @method makeArray 2459 * @param {Object} object Object to convert to array.
2460 * @return {Arrau} Array produced from object. 2461 */ 2462 makeArray: function(array) { 2463 if (isWindow(array) || array.nodeType) { 2464 return [array]; 2465 } 2466 2467 return Tools.toArray(array); 2468 }, 2469 2470 /** 2471 * Returns the index of the specified item inside the array. 2472 * 2473 * @static 2474 * @method inArray 2475 * @param {Object} item Item to look for. 2476 * @param {Array} array Array to look for item in. 2477 * @return {Number} Index of the item or -1. 2478 */ 2479 inArray: inArray, 2480 2481 /** 2482 * Returns true/false if the specified object is an array or not. 2483 * 2484 * @static 2485 * @method isArray 2486 * @param {Object} array Object to check if it's an array or not. 2487 * @return {Boolean} True/false if the object is an array. 2488 */ 2489 isArray: Tools.isArray, 2490 2491 /** 2492 * Executes the callback function for each item in array/object. If you return false in the 2493 * callback it will break the loop. 2494 * 2495 * @static 2496 * @method each 2497 * @param {Object} obj Object to iterate. 2498 * @param {function} callback Callback function to execute for each item. 2499 */ 2500 each: each, 2501 2502 /** 2503 * Removes whitespace from the beginning and end of a string. 2504 * 2505 * @static 2506 * @method trim 2507 * @param {String} str String to remove whitespace from. 2508 * @return {String} New string with removed whitespace. 2509 */ 2510 trim: trim, 2511 2512 /** 2513 * Filters out items from the input array by calling the specified function for each item. 2514 * If the function returns false the item will be excluded if it returns true it will be included. 2515 * 2516 * @static 2517 * @method grep 2518 * @param {Array} array Array of items to loop though. 2519 * @param {function} callback Function to call for each item. Include/exclude depends on it's return value. 2520 * @return {Array} New array with values imported and filtered based in input. 2521 * @example 2522 * // Filter out some items, this will return an array with 4 and 5 2523 * var items = DomQuery.grep([1, 2, 3, 4, 5], function(v) {return v > 3;}); 2524 */ 2525 grep: grep, 2526 2527 // Sizzle 2528 find: Sizzle, 2529 expr: Sizzle.selectors, 2530 unique: Sizzle.uniqueSort, 2531 text: Sizzle.getText, 2532 contains: Sizzle.contains, 2533 filter: function(expr, elems, not) { 2534 var i = elems.length; 2535 2536 if (not) { 2537 expr = ":not(" + expr + ")"; 2538 } 2539 2540 while (i--) { 2541 if (elems[i].nodeType != 1) { 2542 elems.splice(i, 1); 2543 } 2544 } 2545 2546 if (elems.length === 1) { 2547 elems = DomQuery.find.matchesSelector(elems[0], expr) ? [elems[0]] : []; 2548 } else { 2549 elems = DomQuery.find.matches(expr, elems); 2550 } 2551 2552 return elems; 2553 } 2554 }); 2555 2556 function dir(el, prop, until) { 2557 var matched = [], cur = el[prop]; 2558 2559 if (typeof until != 'string' && until instanceof DomQuery) { 2560 until = until[0]; 2561 } 2562 2563 while (cur && cur.nodeType !== 9) { 2564 if (until !== undefined) { 2565 if (cur === until) { 2566 break; 2567 } 2568 2569 if (typeof until == 'string' && DomQuery(cur).is(until)) { 2570 break; 2571 } 2572 } 2573 2574 if (cur.nodeType === 1) { 2575 matched.push(cur); 2576 } 2577 2578 cur = cur[prop]; 2579 } 2580 2581 return matched; 2582 } 2583 2584 function sibling(node, siblingName, nodeType, until) { 2585 var result = []; 2586 2587 if (until instanceof DomQuery) { 2588 until = until[0]; 2589 } 2590 2591 for (; node; node = node[siblingName]) { 2592 if (nodeType && node.nodeType !== nodeType) { 2593 continue; 2594 } 2595 2596 if (until !== undefined) { 2597 if (node === until) { 2598 break; 2599 } 2600 2601 if (typeof until == 'string' && DomQuery(node).is(until)) { 2602 break; 2603 } 2604 } 2605 2606 result.push(node); 2607 } 2608 2609 return result; 2610 } 2611 2612 function firstSibling(node, siblingName, nodeType) { 2613 for (node = node[siblingName]; node; node = node[siblingName]) { 2614 if (node.nodeType == nodeType) { 2615 return node; 2616 } 2617 } 2618 2619 return null; 2620 } 2621 2622 each({ 2623 /** 2624 * Returns a new collection with the parent of each item in current collection matching the optional selector. 2625 * 2626 * @method parent 2627 * @param {String} selector Selector to match parents agains. 2628 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching parents. 2629 */ 2630 parent: function(node) { 2631 var parent = node.parentNode; 2632 2633 return parent && parent.nodeType !== 11 ? parent : null; 2634 }, 2635 2636 /** 2637 * Returns a new collection with the all the parents of each item in current collection matching the optional selector. 2638 * 2639 * @method parents 2640 * @param {String} selector Selector to match parents agains. 2641 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching parents. 2642 */ 2643 parents: function(node) { 2644 return dir(node, "parentNode"); 2645 }, 2646 2647 /** 2648 * Returns a new collection with next sibling of each item in current collection matching the optional selector. 2649 * 2650 * @method next 2651 * @param {String} selector Selector to match the next element against. 2652 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements. 2653 */ 2654 next: function(node) { 2655 return firstSibling(node, 'nextSibling', 1); 2656 }, 2657 2658 /** 2659 * Returns a new collection with previous sibling of each item in current collection matching the optional selector. 2660 * 2661 * @method prev 2662 * @param {String} selector Selector to match the previous element against. 2663 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements. 2664 */ 2665 prev: function(node) { 2666 return firstSibling(node, 'previousSibling', 1); 2667 }, 2668 2669 /** 2670 * Returns all child elements matching the optional selector. 2671 * 2672 * @method children 2673 * @param {String} selector Selector to match the elements against. 2674 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements. 2675 */ 2676 children: function(node) { 2677 return sibling(node.firstChild, 'nextSibling', 1); 2678 }, 2679 2680 /** 2681 * Returns all child nodes matching the optional selector. 2682 * 2683 * @method contents 2684 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements. 2685 */ 2686 contents: function(node) { 2687 return Tools.toArray((node.nodeName === "iframe" ? node.contentDocument || node.contentWindow.document : node).childNodes); 2688 } 2689 }, function(name, fn) { 2690 DomQuery.fn[name] = function(selector) { 2691 var self = this, result = []; 2692 2693 self.each(function() { 2694 var nodes = fn.call(result, this, selector, result); 2695 2696 if (nodes) { 2697 if (DomQuery.isArray(nodes)) { 2698 result.push.apply(result, nodes); 2699 } else { 2700 result.push(nodes); 2701 } 2702 } 2703 }); 2704 2705 // If traversing on multiple elements we might get the same elements twice 2706 if (this.length > 1) { 2707 result = DomQuery.unique(result); 2708 2709 if (name.indexOf('parents') === 0) { 2710 result = result.reverse(); 2711 } 2712 } 2713 2714 result = DomQuery(result); 2715 2716 if (selector) { 2717 return result.filter(selector); 2718 } 2719 2720 return result; 2721 }; 2722 }); 2723 2724 each({ 2725 /** 2726 * Returns a new collection with the all the parents until the matching selector/element 2727 * of each item in current collection matching the optional selector. 2728 * 2729 * @method parentsUntil 2730 * @param {String/Element/tinymce.dom.DomQuery} until Until the matching selector or element. 2731 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching parents. 2732 */ 2733 parentsUntil: function(node, until) { 2734 return dir(node, "parentNode", until); 2735 }, 2736 2737 /** 2738 * Returns a new collection with all next siblings of each item in current collection matching the optional selector. 2739 * 2740 * @method nextUntil 2741 * @param {String/Element/tinymce.dom.DomQuery} until Until the matching selector or element. 2742 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements. 2743 */ 2744 nextUntil: function(node, until) { 2745 return sibling(node, 'nextSibling', 1, until).slice(1); 2746 }, 2747 2748 /** 2749 * Returns a new collection with all previous siblings of each item in current collection matching the optional selector. 2750 * 2751 * @method prevUntil 2752 * @param {String/Element/tinymce.dom.DomQuery} until Until the matching selector or element. 2753 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements. 2754 */ 2755 prevUntil: function(node, until) { 2756 return sibling(node, 'previousSibling', 1, until).slice(1); 2757 } 2758 }, function(name, fn) { 2759 DomQuery.fn[name] = function(selector, filter) { 2760 var self = this, result = []; 2761 2762 self.each(function() { 2763 var nodes = fn.call(result, this, selector, result); 2764 2765 if (nodes) { 2766 if (DomQuery.isArray(nodes)) { 2767 result.push.apply(result, nodes); 2768 } else { 2769 result.push(nodes); 2770 } 2771 } 2772 }); 2773 2774 // If traversing on multiple elements we might get the same elements twice 2775 if (this.length > 1) { 2776 result = DomQuery.unique(result); 2777 2778 if (name.indexOf('parents') === 0 || name === 'prevUntil') { 2779 result = result.reverse(); 2780 } 2781 } 2782 2783 result = DomQuery(result); 2784 2785 if (filter) { 2786 return result.filter(filter); 2787 } 2788 2789 return result; 2790 }; 2791 }); 2792 2793 /** 2794 * Returns true/false if the current set items matches the selector. 2795 * 2796 * @method is 2797 * @param {String} selector Selector to match the elements against. 2798 * @return {Boolean} True/false if the current set matches the selector. 2799 */ 2800 DomQuery.fn.is = function(selector) { 2801 return !!selector && this.filter(selector).length > 0; 2802 }; 2803 2804 DomQuery.fn.init.prototype = DomQuery.fn; 2805 2806 DomQuery.overrideDefaults = function(callback) { 2807 var defaults; 2808 2809 function sub(selector, context) { 2810 defaults = defaults || callback(); 2811
2812 if (arguments.length === 0) { 2813 selector = defaults.element; 2814 } 2815 2816 if (!context) { 2817 context = defaults.context; 2818 } 2819 2820 return new sub.fn.init(selector, context); 2821 } 2822 2823 DomQuery.extend(sub, this); 2824 2825 return sub; 2826 }; 2827 2828 function appendHooks(targetHooks, prop, hooks) { 2829 each(hooks, function(name, func) { 2830 targetHooks[name] = targetHooks[name] || {}; 2831 targetHooks[name][prop] = func; 2832 }); 2833 } 2834 2835 if (Env.ie && Env.ie < 8) { 2836 appendHooks(attrHooks, 'get', { 2837 maxlength: function(elm) { 2838 var value = elm.maxLength; 2839 2840 if (value === 0x7fffffff) { 2841 return undef; 2842 } 2843 2844 return value; 2845 }, 2846 2847 size: function(elm) { 2848 var value = elm.size; 2849 2850 if (value === 20) { 2851 return undef; 2852 } 2853 2854 return value; 2855 }, 2856 2857 'class': function(elm) { 2858 return elm.className; 2859 }, 2860 2861 style: function(elm) { 2862 var value = elm.style.cssText; 2863 2864 if (value.length === 0) { 2865 return undef; 2866 } 2867 2868 return value; 2869 } 2870 }); 2871 2872 appendHooks(attrHooks, 'set', { 2873 'class': function(elm, value) { 2874 elm.className = value; 2875 }, 2876 2877 style: function(elm, value) { 2878 elm.style.cssText = value; 2879 } 2880 }); 2881 } 2882 2883 if (Env.ie && Env.ie < 9) { 2884 /*jshint sub:true */ 2885 /*eslint dot-notation: 0*/ 2886 cssFix['float'] = 'styleFloat'; 2887 2888 appendHooks(cssHooks, 'set', { 2889 opacity: function(elm, value) { 2890 var style = elm.style; 2891 2892 if (value === null || value === '') { 2893 style.removeAttribute('filter'); 2894 } else { 2895 style.zoom = 1; 2896 style.filter = 'alpha(opacity=' + (value * 100) + ')'; 2897 } 2898 } 2899 }); 2900 } 2901 2902 DomQuery.attrHooks = attrHooks; 2903 DomQuery.cssHooks = cssHooks; 2904 2905 return DomQuery; 2906}); 2907 2908// Included from: js/tinymce/classes/html/Styles.js 2909 2910/** 2911 * Styles.js 2912 * 2913 * Copyright, Moxiecode Systems AB 2914 * Released under LGPL License. 2915 * 2916 * License: http://www.tinymce.com/license 2917 * Contributing: http://www.tinymce.com/contributing 2918 */ 2919 2920/** 2921 * This class is used to parse CSS styles it also compresses styles to reduce the output size. 2922 * 2923 * @example 2924 * var Styles = new tinymce.html.Styles({ 2925 * url_converter: function(url) { 2926 * return url; 2927 * } 2928 * }); 2929 * 2930 * styles = Styles.parse('border: 1px solid red'); 2931 * styles.color = 'red'; 2932 * 2933 * console.log(new tinymce.html.StyleSerializer().serialize(styles)); 2934 * 2935 * @class tinymce.html.Styles 2936 * @version 3.4 2937 */ 2938define("tinymce/html/Styles", [], function() { 2939 return function(settings, schema) { 2940 /*jshint maxlen:255 */ 2941 /*eslint max-len:0 */ 2942 var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi, 2943 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi, 2944 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g, 2945 trimRightRegExp = /\s+$/, 2946 undef, i, encodingLookup = {}, encodingItems, validStyles, invalidStyles, invisibleChar = '\uFEFF'; 2947 2948 settings = settings || {}; 2949 2950 if (schema) { 2951 validStyles = schema.getValidStyles(); 2952 invalidStyles = schema.getInvalidStyles(); 2953 } 2954 2955 encodingItems = ('\\" \\\' \\; \\: ; : ' + invisibleChar).split(' '); 2956 for (i = 0; i < encodingItems.length; i++) { 2957 encodingLookup[encodingItems[i]] = invisibleChar + i; 2958 encodingLookup[invisibleChar + i] = encodingItems[i]; 2959 } 2960 2961 function toHex(match, r, g, b) { 2962 function hex(val) { 2963 val = parseInt(val, 10).toString(16); 2964 2965 return val.length > 1 ? val : '0' + val; // 0 -> 00 2966 } 2967 2968 return '#' + hex(r) + hex(g) + hex(b); 2969 } 2970 2971 return { 2972 /** 2973 * Parses the specified RGB color value and returns a hex version of that color. 2974 * 2975 * @method toHex 2976 * @param {String} color RGB string value like rgb(1,2,3) 2977 * @return {String} Hex version of that RGB value like #FF00FF. 2978 */ 2979 toHex: function(color) { 2980 return color.replace(rgbRegExp, toHex); 2981 }, 2982 2983 /**
2984 * Parses the specified style value into an object collection. This parser will also 2985 * merge and remove any redundant items that browsers might have added. It will also convert non hex 2986 * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings. 2987 * 2988 * @method parse 2989 * @param {String} css Style value to parse for example: border:1px solid red;. 2990 * @return {Object} Object representation of that style like {border: '1px solid red'} 2991 */ 2992 parse: function(css) { 2993 var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter; 2994 var urlConverterScope = settings.url_converter_scope || this; 2995 2996 function compress(prefix, suffix, noJoin) { 2997 var top, right, bottom, left; 2998 2999 top = styles[prefix + '-top' + suffix]; 3000 if (!top) { 3001 return; 3002 } 3003 3004 right = styles[prefix + '-right' + suffix]; 3005 if (!right) { 3006 return; 3007 } 3008 3009 bottom = styles[prefix + '-bottom' + suffix]; 3010 if (!bottom) { 3011 return; 3012 } 3013 3014 left = styles[prefix + '-left' + suffix]; 3015 if (!left) { 3016 return; 3017 } 3018 3019 var box = [top, right, bottom, left]; 3020 i = box.length - 1; 3021 while (i--) { 3022 if (box[i] !== box[i + 1]) { 3023 break; 3024 } 3025 } 3026 3027 if (i > -1 && noJoin) { 3028 return; 3029 } 3030 3031 styles[prefix + suffix] = i == -1 ? box[0] : box.join(' '); 3032 delete styles[prefix + '-top' + suffix]; 3033 delete styles[prefix + '-right' + suffix]; 3034 delete styles[prefix + '-bottom' + suffix]; 3035 delete styles[prefix + '-left' + suffix]; 3036 } 3037 3038 /** 3039 * Checks if the specific style can be compressed in other words if all border-width are equal. 3040 */ 3041 function canCompress(key) { 3042 var value = styles[key], i; 3043 3044 if (!value) { 3045 return; 3046 } 3047 3048 value = value.split(' '); 3049 i = value.length; 3050 while (i--) { 3051 if (value[i] !== value[0]) { 3052 return false; 3053 } 3054 } 3055 3056 styles[key] = value[0]; 3057 3058 return true; 3059 } 3060 3061 /** 3062 * Compresses multiple styles into one style. 3063 */ 3064 function compress2(target, a, b, c) { 3065 if (!canCompress(a)) { 3066 return; 3067 } 3068 3069 if (!canCompress(b)) { 3070 return; 3071 } 3072 3073 if (!canCompress(c)) { 3074 return; 3075 } 3076 3077 // Compress 3078 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c]; 3079 delete styles[a]; 3080 delete styles[b]; 3081 delete styles[c]; 3082 } 3083 3084 // Encodes the specified string by replacing all \" \' ; : with _<num> 3085 function encode(str) { 3086 isEncoded = true; 3087 3088 return encodingLookup[str]; 3089 } 3090 3091 // Decodes the specified string by replacing all _<num> with it's original value \" \' etc 3092 // It will also decode the \" \' if keep_slashes is set to fale or omitted 3093 function decode(str, keep_slashes) { 3094 if (isEncoded) { 3095 str = str.replace(/\uFEFF[0-9]/g, function(str) { 3096 return encodingLookup[str]; 3097 }); 3098 } 3099 3100 if (!keep_slashes) { 3101 str = str.replace(/\\([\'\";:])/g, "$1"); 3102 } 3103 3104 return str; 3105 } 3106 3107 function processUrl(match, url, url2, url3, str, str2) { 3108 str = str || str2; 3109 3110 if (str) { 3111 str = decode(str); 3112 3113 // Force strings into single quote format 3114 return "'" + str.replace(/\'/g, "\\'") + "'"; 3115 } 3116 3117 url = decode(url || url2 || url3); 3118 3119 if (!settings.allow_script_urls) { 3120 var scriptUrl = url.replace(/[\s\r\n]+/, ''); 3121 3122 if (/(java|vb)script:/i.test(scriptUrl)) { 3123 return ""; 3124 } 3125 3126 if (!settings.allow_svg_data_urls && /^data:image\/svg/i.test(scriptUrl)) { 3127 return ""; 3128 } 3129 } 3130 3131 // Convert the URL to relative/absolute depending on config 3132 if (urlConverter) { 3133 url = urlConverter.call(urlConverterScope, url, 'style'); 3134 } 3135 3136 // Output new URL format 3137 return "url('" + url.replace(/\'/g, "\\'") + "')"; 3138 } 3139 3140 if (css) { 3141 css = css.replace(/[\u0000-\u001F]/g, ''); 3142 3143 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing 3144 css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) { 3145 return str.replace(/[;:]/g, encode); 3146 }); 3147 3148 // Parse styles 3149 while ((matches = styleRegExp.exec(css))) { 3150 name = matches[1].replace(trimRightRegExp, '').toLowerCase(); 3151 value = matches[2].replace(trimRightRegExp, ''); 3152 3153 // Decode escaped sequences like \65 -> e 3154 /*jshint loopfunc:true*/ 3155 /*eslint no-loop-func:0 */ 3156 value = value.replace(/\\[0-9a-f]+/g, function(e) { 3157 return String.fromCharCode(parseInt(e.substr(1), 16)); 3158 }); 3159 3160 if (name && value.length > 0) { 3161 // Don't allow behavior name or expression/comments within the values 3162 if (!settings.allow_script_urls && (name == "behavior" || /expression\s*\(|\/\*|\*\//.test(value))) { 3163 continue; 3164 } 3165 3166 // Opera will produce 700 instead of bold in their style values 3167 if (name === 'font-weight' && value === '700') { 3168 value = 'bold'; 3169 } else if (name === 'color' || name === 'background-color') { // Lowercase colors like RED 3170 value = value.toLowerCase(); 3171 } 3172 3173 // Convert RGB colors to HEX 3174 value = value.replace(rgbRegExp, toHex); 3175 3176 // Convert URLs and force them into url('value') format 3177 value = value.replace(urlOrStrRegExp, processUrl); 3178 styles[name] = isEncoded ? decode(value, true) : value; 3179 } 3180 3181 styleRegExp.lastIndex = matches.index + matches[0].length; 3182 } 3183 // Compress the styles to reduce it's size for example IE will expand styles 3184 compress("border", "", true); 3185 compress("border", "-width"); 3186 compress("border", "-color"); 3187 compress("border", "-style"); 3188 compress("padding", ""); 3189 compress("margin", ""); 3190 compress2('border', 'border-width', 'border-style', 'border-color'); 3191 3192 // Remove pointless border, IE produces these 3193 if (styles.border === 'medium none') { 3194 delete styles.border; 3195 } 3196 3197 // IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p> 3198 // So lets asume it shouldn't be there 3199 if (styles['border-image'] === 'none') { 3200 delete styles['border-image']; 3201 } 3202 } 3203 3204 return styles; 3205 }, 3206 3207 /** 3208 * Serializes the specified style object into a string. 3209 * 3210 * @method serialize 3211 * @param {Object} styles Object to serialize as string for example: {border: '1px solid red'} 3212 * @param {String} elementName Optional element name, if specified only the styles that matches the schema will be serialized. 3213 * @return {String} String representation of the style object for example: border: 1px solid red. 3214 */ 3215 serialize: function(styles, elementName) { 3216 var css = '', name, value; 3217 3218 function serializeStyles(name) { 3219 var styleList, i, l, value; 3220 3221 styleList = validStyles[name]; 3222 if (styleList) { 3223 for (i = 0, l = styleList.length; i < l; i++) { 3224 name = styleList[i]; 3225 value = styles[name]; 3226 3227 if (value !== undef && value.length > 0) { 3228 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; 3229 } 3230 } 3231 } 3232 } 3233 3234 function isValid(name, elementName) { 3235 var styleMap; 3236 3237 styleMap = invalidStyles['*']; 3238 if (styleMap && styleMap[name]) { 3239 return false;
3240 } 3241 3242 styleMap = invalidStyles[elementName]; 3243 if (styleMap && styleMap[name]) { 3244 return false; 3245 } 3246 3247 return true; 3248 } 3249 3250 // Serialize styles according to schema 3251 if (elementName && validStyles) { 3252 // Serialize global styles and element specific styles 3253 serializeStyles('*'); 3254 serializeStyles(elementName); 3255 } else { 3256 // Output the styles in the order they are inside the object 3257 for (name in styles) { 3258 value = styles[name]; 3259 3260 if (value !== undef && value.length > 0) { 3261 if (!invalidStyles || isValid(name, elementName)) { 3262 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; 3263 } 3264 } 3265 } 3266 } 3267 3268 return css; 3269 } 3270 }; 3271 }; 3272}); 3273 3274// Included from: js/tinymce/classes/dom/TreeWalker.js 3275 3276/** 3277 * TreeWalker.js 3278 * 3279 * Copyright, Moxiecode Systems AB 3280 * Released under LGPL License. 3281 * 3282 * License: http://www.tinymce.com/license 3283 * Contributing: http://www.tinymce.com/contributing 3284 */ 3285 3286/** 3287 * TreeWalker class enables you to walk the DOM in a linear manner. 3288 * 3289 * @class tinymce.dom.TreeWalker 3290 * @example 3291 * var walker = new tinymce.dom.TreeWalker(startNode); 3292 * 3293 * do { 3294 * console.log(walker.current()); 3295 * } while (walker.next()); 3296 */ 3297define("tinymce/dom/TreeWalker", [], function() { 3298 /** 3299 * Constructs a new TreeWalker instance. 3300 * 3301 * @constructor 3302 * @method TreeWalker 3303 * @param {Node} startNode Node to start walking from. 3304 * @param {node} rootNode Optional root node to never walk out of. 3305 */ 3306 return function(startNode, rootNode) { 3307 var node = startNode; 3308 3309 function findSibling(node, startName, siblingName, shallow) { 3310 var sibling, parent; 3311 3312 if (node) { 3313 // Walk into nodes if it has a start 3314 if (!shallow && node[startName]) { 3315 return node[startName]; 3316 } 3317 3318 // Return the sibling if it has one 3319 if (node != rootNode) { 3320 sibling = node[siblingName]; 3321 if (sibling) { 3322 return sibling; 3323 } 3324 3325 // Walk up the parents to look for siblings 3326 for (parent = node.parentNode; parent && parent != rootNode; parent = parent.parentNode) { 3327 sibling = parent[siblingName]; 3328 if (sibling) { 3329 return sibling; 3330 } 3331 } 3332 } 3333 } 3334 } 3335 3336 /** 3337 * Returns the current node. 3338 * 3339 * @method current 3340 * @return {Node} Current node where the walker is. 3341 */ 3342 this.current = function() { 3343 return node; 3344 }; 3345 3346 /** 3347 * Walks to the next node in tree. 3348 * 3349 * @method next 3350 * @return {Node} Current node where the walker is after moving to the next node. 3351 */ 3352 this.next = function(shallow) { 3353 node = findSibling(node, 'firstChild', 'nextSibling', shallow); 3354 return node; 3355 }; 3356 3357 /** 3358 * Walks to the previous node in tree. 3359 * 3360 * @method prev 3361 * @return {Node} Current node where the walker is after moving to the previous node. 3362 */ 3363 this.prev = function(shallow) { 3364 node = findSibling(node, 'lastChild', 'previousSibling', shallow); 3365 return node; 3366 }; 3367 }; 3368}); 3369 3370// Included from: js/tinymce/classes/dom/Range.js 3371 3372/** 3373 * Range.js 3374 * 3375 * Copyright, Moxiecode Systems AB 3376 * Released under LGPL License. 3377 * 3378 * License: http://www.tinymce.com/license 3379 * Contributing: http://www.tinymce.com/contributing 3380 */ 3381 3382define("tinymce/dom/Range", [ 3383 "tinymce/util/Tools" 3384], function(Tools) { 3385 // Range constructor 3386 function Range(dom) { 3387 var self = this, 3388 doc = dom.doc, 3389 EXTRACT = 0, 3390 CLONE = 1, 3391 DELETE = 2, 3392 TRUE = true, 3393 FALSE = false, 3394 START_OFFSET = 'startOffset', 3395 START_CONTAINER = 'startContainer', 3396 END_CONTAINER = 'endContainer', 3397 END_OFFSET = 'endOffset', 3398 extend = Tools.extend, 3399 nodeIndex = dom.nodeIndex; 3400 3401 function createDocumentFragment() { 3402 return doc.createDocumentFragment(); 3403 } 3404 3405 function setStart(n, o) { 3406 _setEndPoint(TRUE, n, o); 3407 } 3408 3409 function setEnd(n, o) { 3410 _setEndPoint(FALSE, n, o); 3411 } 3412 3413 function setStartBefore(n) { 3414 setStart(n.parentNode, nodeIndex(n)); 3415 } 3416 3417 function setStartAfter(n) { 3418 setStart(n.parentNode, nodeIndex(n) + 1); 3419 } 3420 3421 function setEndBefore(n) { 3422 setEnd(n.parentNode, nodeIndex(n)); 3423 } 3424 3425 function setEndAfter(n) { 3426 setEnd(n.parentNode, nodeIndex(n) + 1); 3427 } 3428 3429 function collapse(ts) { 3430 if (ts) { 3431 self[END_CONTAINER] = self[START_CONTAINER]; 3432 self[END_OFFSET] = self[START_OFFSET]; 3433 } else { 3434 self[START_CONTAINER] = self[END_CONTAINER]; 3435 self[START_OFFSET] = self[END_OFFSET]; 3436 } 3437 3438 self.collapsed = TRUE; 3439 } 3440 3441 function selectNode(n) { 3442 setStartBefore(n); 3443 setEndAfter(n); 3444 } 3445 3446 function selectNodeContents(n) { 3447 setStart(n, 0); 3448 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); 3449 } 3450 3451 function compareBoundaryPoints(h, r) { 3452 var sc = self[START_CONTAINER], so = self[START_OFFSET], ec = self[END_CONTAINER], eo = self[END_OFFSET], 3453 rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset; 3454 3455 // Check START_TO_START 3456 if (h === 0) { 3457 return _compareBoundaryPoints(sc, so, rsc, rso); 3458 } 3459 3460 // Check START_TO_END 3461 if (h === 1) { 3462 return _compareBoundaryPoints(ec, eo, rsc, rso); 3463 } 3464 3465 // Check END_TO_END 3466 if (h === 2) { 3467 return _compareBoundaryPoints(ec, eo, rec, reo); 3468 } 3469 3470 // Check END_TO_START 3471 if (h === 3) { 3472 return _compareBoundaryPoints(sc, so, rec, reo); 3473 } 3474 } 3475 3476 function deleteContents() { 3477 _traverse(DELETE); 3478 } 3479 3480 function extractContents() { 3481 return _traverse(EXTRACT); 3482 } 3483 3484 function cloneContents() { 3485 return _traverse(CLONE); 3486 } 3487
3488 function insertNode(n) { 3489 var startContainer = this[START_CONTAINER], 3490 startOffset = this[START_OFFSET], nn, o; 3491 3492 // Node is TEXT_NODE or CDATA 3493 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) { 3494 if (!startOffset) { 3495 // At the start of text 3496 startContainer.parentNode.insertBefore(n, startContainer); 3497 } else if (startOffset >= startContainer.nodeValue.length) { 3498 // At the end of text 3499 dom.insertAfter(n, startContainer); 3500 } else { 3501 // Middle, need to split 3502 nn = startContainer.splitText(startOffset); 3503 startContainer.parentNode.insertBefore(n, nn); 3504 } 3505 } else { 3506 // Insert element node 3507 if (startContainer.childNodes.length > 0) { 3508 o = startContainer.childNodes[startOffset]; 3509 } 3510 3511 if (o) { 3512 startContainer.insertBefore(n, o); 3513 } else { 3514 if (startContainer.nodeType == 3) { 3515 dom.insertAfter(n, startContainer); 3516 } else { 3517 startContainer.appendChild(n); 3518 } 3519 } 3520 } 3521 } 3522 3523 function surroundContents(n) { 3524 var f = self.extractContents(); 3525 3526 self.insertNode(n); 3527 n.appendChild(f); 3528 self.selectNode(n); 3529 } 3530 3531 function cloneRange() { 3532 return extend(new Range(dom), { 3533 startContainer: self[START_CONTAINER], 3534 startOffset: self[START_OFFSET], 3535 endContainer: self[END_CONTAINER], 3536 endOffset: self[END_OFFSET], 3537 collapsed: self.collapsed, 3538 commonAncestorContainer: self.commonAncestorContainer 3539 }); 3540 } 3541 3542 // Private methods 3543 3544 function _getSelectedNode(container, offset) { 3545 var child; 3546 3547 if (container.nodeType == 3 /* TEXT_NODE */) { 3548 return container; 3549 } 3550 3551 if (offset < 0) { 3552 return container; 3553 } 3554 3555 child = container.firstChild; 3556 while (child && offset > 0) { 3557 --offset; 3558 child = child.nextSibling; 3559 } 3560 3561 if (child) { 3562 return child; 3563 } 3564 3565 return container; 3566 } 3567 3568 function _isCollapsed() { 3569 return (self[START_CONTAINER] == self[END_CONTAINER] && self[START_OFFSET] == self[END_OFFSET]); 3570 } 3571 3572 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
3573 var c, offsetC, n, cmnRoot, childA, childB; 3574 3575 // In the first case the boundary-points have the same container. A is before B 3576 // if its offset is less than the offset of B, A is equal to B if its offset is 3577 // equal to the offset of B, and A is after B if its offset is greater than the 3578 // offset of B. 3579 if (containerA == containerB) { 3580 if (offsetA == offsetB) { 3581 return 0; // equal 3582 } 3583 3584 if (offsetA < offsetB) { 3585 return -1; // before 3586 } 3587 3588 return 1; // after 3589 } 3590 3591 // In the second case a child node C of the container of A is an ancestor 3592 // container of B. In this case, A is before B if the offset of A is less than or 3593 // equal to the index of the child node C and A is after B otherwise. 3594 c = containerB; 3595 while (c && c.parentNode != containerA) { 3596 c = c.parentNode; 3597 } 3598 3599 if (c) { 3600 offsetC = 0; 3601 n = containerA.firstChild; 3602 3603 while (n != c && offsetC < offsetA) { 3604 offsetC++; 3605 n = n.nextSibling; 3606 } 3607 3608 if (offsetA <= offsetC) { 3609 return -1; // before 3610 } 3611 3612 return 1; // after 3613 } 3614 3615 // In the third case a child node C of the container of B is an ancestor container 3616 // of A. In this case, A is before B if the index of the child node C is less than 3617 // the offset of B and A is after B otherwise. 3618 c = containerA; 3619 while (c && c.parentNode != containerB) { 3620 c = c.parentNode; 3621 } 3622 3623 if (c) { 3624 offsetC = 0; 3625 n = containerB.firstChild; 3626 3627 while (n != c && offsetC < offsetB) { 3628 offsetC++; 3629 n = n.nextSibling; 3630 } 3631 3632 if (offsetC < offsetB) { 3633 return -1; // before 3634 } 3635 3636 return 1; // after 3637 } 3638 3639 // In the fourth case, none of three other cases hold: the containers of A and B 3640 // are siblings or descendants of sibling nodes. In this case, A is before B if 3641 // the container of A is before the container of B in a pre-order traversal of the 3642 // Ranges' context tree and A is after B otherwise. 3643 cmnRoot = dom.findCommonAncestor(containerA, containerB); 3644 childA = containerA; 3645 3646 while (childA && childA.parentNode != cmnRoot) { 3647 childA = childA.parentNode; 3648 } 3649 3650 if (!childA) { 3651 childA = cmnRoot; 3652 } 3653 3654 childB = containerB; 3655 while (childB && childB.parentNode != cmnRoot) { 3656 childB = childB.parentNode; 3657 } 3658 3659 if (!childB) { 3660 childB = cmnRoot; 3661 } 3662 3663 if (childA == childB) { 3664 return 0; // equal 3665 } 3666 3667 n = cmnRoot.firstChild; 3668 while (n) { 3669 if (n == childA) { 3670 return -1; // before 3671 } 3672 3673 if (n == childB) { 3674 return 1; // after 3675 } 3676 3677 n = n.nextSibling; 3678 } 3679 } 3680 3681 function _setEndPoint(st, n, o) { 3682 var ec, sc; 3683 3684 if (st) { 3685 self[START_CONTAINER] = n; 3686 self[START_OFFSET] = o; 3687 } else { 3688 self[END_CONTAINER] = n; 3689 self[END_OFFSET] = o; 3690 } 3691 3692 // If one boundary-point of a Range is set to have a root container 3693 // other than the current one for the Range, the Range is collapsed to 3694 // the new position. This enforces the restriction that both boundary- 3695 // points of a Range must have the same root container. 3696 ec = self[END_CONTAINER]; 3697 while (ec.parentNode) { 3698 ec = ec.parentNode; 3699 } 3700 3701 sc = self[START_CONTAINER]; 3702 while (sc.parentNode) { 3703 sc = sc.parentNode; 3704 } 3705 3706 if (sc == ec) { 3707 // The start position of a Range is guaranteed to never be after the 3708 // end position. To enforce this restriction, if the start is set to 3709 // be at a position after the end, the Range is collapsed to that 3710 // position. 3711 if (_compareBoundaryPoints(self[START_CONTAINER], self[START_OFFSET], self[END_CONTAINER], self[END_OFFSET]) > 0) { 3712 self.collapse(st); 3713 } 3714 } else { 3715 self.collapse(st); 3716 } 3717 3718 self.collapsed = _isCollapsed(); 3719 self.commonAncestorContainer = dom.findCommonAncestor(self[START_CONTAINER], self[END_CONTAINER]); 3720 } 3721 3722 function _traverse(how) { 3723 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; 3724 3725 if (self[START_CONTAINER] == self[END_CONTAINER]) { 3726 return _traverseSameContainer(how); 3727 } 3728 3729 for (c = self[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { 3730 if (p == self[START_CONTAINER]) { 3731 return _traverseCommonStartContainer(c, how); 3732 } 3733 3734 ++endContainerDepth; 3735 } 3736 3737 for (c = self[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { 3738 if (p == self[END_CONTAINER]) { 3739 return _traverseCommonEndContainer(c, how); 3740 } 3741 3742 ++startContainerDepth; 3743 } 3744 3745 depthDiff = startContainerDepth - endContainerDepth; 3746 3747 startNode = self[START_CONTAINER]; 3748 while (depthDiff > 0) { 3749 startNode = startNode.parentNode; 3750 depthDiff--; 3751 } 3752 3753 endNode = self[END_CONTAINER]; 3754 while (depthDiff < 0) { 3755 endNode = endNode.parentNode; 3756 depthDiff++; 3757 } 3758 3759 // ascend the ancestor hierarchy until we have a common parent. 3760 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) { 3761 startNode = sp; 3762 endNode = ep; 3763 } 3764 3765 return _traverseCommonAncestors(startNode, endNode, how); 3766 } 3767 3768 function _traverseSameContainer(how) { 3769 var frag, s, sub, n, cnt, sibling, xferNode, start, len; 3770 3771 if (how != DELETE) { 3772 frag = createDocumentFragment(); 3773 } 3774 3775 // If selection is empty, just return the fragment 3776 if (self[START_OFFSET] == self[END_OFFSET]) { 3777 return frag; 3778 } 3779 3780 // Text node needs special case handling 3781 if (self[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) { 3782 // get the substring 3783 s = self[START_CONTAINER].nodeValue; 3784 sub = s.substring(self[START_OFFSET], self[END_OFFSET]); 3785 3786 // set the original text node to its new value 3787 if (how != CLONE) { 3788 n = self[START_CONTAINER]; 3789 start = self[START_OFFSET]; 3790 len = self[END_OFFSET] - self[START_OFFSET]; 3791 3792 if (start === 0 && len >= n.nodeValue.length - 1) { 3793 n.parentNode.removeChild(n); 3794 } else { 3795 n.deleteData(start, len); 3796 } 3797 3798 // Nothing is partially selected, so collapse to start point 3799 self.collapse(TRUE); 3800 } 3801 3802 if (how == DELETE) { 3803 return; 3804 } 3805 3806 if (sub.length > 0) { 3807 frag.appendChild(doc.createTextNode(sub)); 3808 } 3809 3810 return frag; 3811 } 3812 3813 // Copy nodes between the start/end offsets. 3814 n = _getSelectedNode(self[START_CONTAINER], self[START_OFFSET]); 3815 cnt = self[END_OFFSET] - self[START_OFFSET]; 3816 3817 while (n && cnt > 0) { 3818 sibling = n.nextSibling; 3819 xferNode = _traverseFullySelected(n, how); 3820 3821 if (frag) { 3822 frag.appendChild(xferNode); 3823 } 3824 3825 --cnt; 3826 n = sibling; 3827 } 3828 3829 // Nothing is partially selected, so collapse to start point 3830 if (how != CLONE) { 3831 self.collapse(TRUE); 3832 } 3833 3834 return frag; 3835 } 3836 3837 function _traverseCommonStartContainer(endAncestor, how) { 3838 var frag, n, endIdx, cnt, sibling, xferNode; 3839 3840 if (how != DELETE) { 3841 frag = createDocumentFragment(); 3842 } 3843 3844 n = _traverseRightBoundary(endAncestor, how); 3845 3846 if (frag) { 3847 frag.appendChild(n); 3848 } 3849 3850 endIdx = nodeIndex(endAncestor); 3851 cnt = endIdx - self[START_OFFSET]; 3852 3853 if (cnt <= 0) { 3854 // Collapse to just before the endAncestor, which 3855 // is partially selected. 3856 if (how != CLONE) { 3857 self.setEndBefore(endAncestor); 3858 self.collapse(FALSE); 3859 } 3860 3861 return frag; 3862 } 3863 3864 n = endAncestor.previousSibling; 3865 while (cnt > 0) { 3866 sibling = n.previousSibling; 3867 xferNode = _traverseFullySelected(n, how); 3868 3869 if (frag) { 3870 frag.insertBefore(xferNode, frag.firstChild); 3871 } 3872 3873 --cnt; 3874 n = sibling; 3875 } 3876 3877 // Collapse to just before the endAncestor, which 3878 // is partially selected. 3879 if (how != CLONE) { 3880 self.setEndBefore(endAncestor); 3881 self.collapse(FALSE); 3882 } 3883 3884 return frag; 3885 } 3886 3887 function _traverseCommonEndContainer(startAncestor, how) { 3888 var frag, startIdx, n, cnt, sibling, xferNode; 3889 3890 if (how != DELETE) { 3891 frag = createDocumentFragment(); 3892 } 3893 3894 n = _traverseLeftBoundary(startAncestor, how); 3895 if (frag) { 3896 frag.appendChild(n); 3897 } 3898 3899 startIdx = nodeIndex(startAncestor); 3900 ++startIdx; // Because we already traversed it 3901 3902 cnt = self[END_OFFSET] - startIdx; 3903 n = startAncestor.nextSibling; 3904 while (n && cnt > 0) { 3905 sibling = n.nextSibling; 3906 xferNode = _traverseFullySelected(n, how); 3907 3908 if (frag) { 3909 frag.appendChild(xferNode); 3910 } 3911 3912 --cnt; 3913 n = sibling; 3914 } 3915 3916 if (how != CLONE) { 3917 self.setStartAfter(startAncestor); 3918 self.collapse(TRUE); 3919 } 3920 3921 return frag; 3922 } 3923 3924 function _traverseCommonAncestors(startAncestor, endAncestor, how) { 3925 var n, frag, startOffset, endOffset, cnt, sibling, nextSibling; 3926 3927 if (how != DELETE) { 3928 frag = createDocumentFragment(); 3929 } 3930 3931 n = _traverseLeftBoundary(startAncestor, how); 3932 if (frag) { 3933 frag.appendChild(n); 3934 } 3935 3936 startOffset = nodeIndex(startAncestor); 3937 endOffset = nodeIndex(endAncestor); 3938 ++startOffset; 3939 3940 cnt = endOffset - startOffset; 3941 sibling = startAncestor.nextSibling; 3942 3943 while (cnt > 0) { 3944 nextSibling = sibling.nextSibling; 3945 n = _traverseFullySelected(sibling, how); 3946 3947 if (frag) { 3948 frag.appendChild(n); 3949 } 3950 3951 sibling = nextSibling; 3952 --cnt; 3953 } 3954 3955 n = _traverseRightBoundary(endAncestor, how); 3956 3957 if (frag) { 3958 frag.appendChild(n); 3959 } 3960 3961 if (how != CLONE) { 3962 self.setStartAfter(startAncestor); 3963 self.collapse(TRUE); 3964 } 3965 3966 return frag; 3967 } 3968 3969 function _traverseRightBoundary(root, how) {
3970 var next = _getSelectedNode(self[END_CONTAINER], self[END_OFFSET] - 1), parent, clonedParent; 3971 var prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != self[END_CONTAINER]; 3972 3973 if (next == root) { 3974 return _traverseNode(next, isFullySelected, FALSE, how); 3975 } 3976 3977 parent = next.parentNode; 3978 clonedParent = _traverseNode(parent, FALSE, FALSE, how); 3979 3980 while (parent) { 3981 while (next) { 3982 prevSibling = next.previousSibling; 3983 clonedChild = _traverseNode(next, isFullySelected, FALSE, how); 3984 3985 if (how != DELETE) { 3986 clonedParent.insertBefore(clonedChild, clonedParent.firstChild); 3987 } 3988 3989 isFullySelected = TRUE; 3990 next = prevSibling; 3991 } 3992 3993 if (parent == root) { 3994 return clonedParent; 3995 } 3996 3997 next = parent.previousSibling; 3998 parent = parent.parentNode; 3999 4000 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how); 4001 4002 if (how != DELETE) { 4003 clonedGrandParent.appendChild(clonedParent); 4004 } 4005 4006 clonedParent = clonedGrandParent; 4007 } 4008 } 4009 4010 function _traverseLeftBoundary(root, how) { 4011 var next = _getSelectedNode(self[START_CONTAINER], self[START_OFFSET]), isFullySelected = next != self[START_CONTAINER]; 4012 var parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; 4013 4014 if (next == root) { 4015 return _traverseNode(next, isFullySelected, TRUE, how); 4016 } 4017 4018 parent = next.parentNode; 4019 clonedParent = _traverseNode(parent, FALSE, TRUE, how); 4020 4021 while (parent) { 4022 while (next) { 4023 nextSibling = next.nextSibling; 4024 clonedChild = _traverseNode(next, isFullySelected, TRUE, how); 4025 4026 if (how != DELETE) { 4027 clonedParent.appendChild(clonedChild); 4028 } 4029 4030 isFullySelected = TRUE; 4031 next = nextSibling; 4032 } 4033 4034 if (parent == root) { 4035 return clonedParent; 4036 } 4037 4038 next = parent.nextSibling; 4039 parent = parent.parentNode; 4040 4041 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how); 4042 4043 if (how != DELETE) { 4044 clonedGrandParent.appendChild(clonedParent); 4045 } 4046 4047 clonedParent = clonedGrandParent; 4048 } 4049 } 4050 4051 function _traverseNode(n, isFullySelected, isLeft, how) { 4052 var txtValue, newNodeValue, oldNodeValue, offset, newNode; 4053 4054 if (isFullySelected) { 4055 return _traverseFullySelected(n, how); 4056 } 4057 4058 if (n.nodeType == 3 /* TEXT_NODE */) { 4059 txtValue = n.nodeValue; 4060 4061 if (isLeft) { 4062 offset = self[START_OFFSET]; 4063 newNodeValue = txtValue.substring(offset); 4064 oldNodeValue = txtValue.substring(0, offset); 4065 } else { 4066 offset = self[END_OFFSET]; 4067 newNodeValue = txtValue.substring(0, offset); 4068 oldNodeValue = txtValue.substring(offset); 4069 } 4070 4071 if (how != CLONE) { 4072 n.nodeValue = oldNodeValue; 4073 } 4074 4075 if (how == DELETE) { 4076 return; 4077 } 4078 4079 newNode = dom.clone(n, FALSE); 4080 newNode.nodeValue = newNodeValue; 4081 4082 return newNode; 4083 } 4084 4085 if (how == DELETE) { 4086 return; 4087 } 4088 4089 return dom.clone(n, FALSE); 4090 } 4091 4092 function _traverseFullySelected(n, how) { 4093 if (how != DELETE) { 4094 return how == CLONE ? dom.clone(n, TRUE) : n; 4095 } 4096 4097 n.parentNode.removeChild(n); 4098 } 4099 4100 function toStringIE() { 4101 return dom.create('body', null, cloneContents()).outerText; 4102 } 4103 4104 extend(self, { 4105 // Inital states 4106 startContainer: doc, 4107 startOffset: 0, 4108 endContainer: doc, 4109 endOffset: 0, 4110 collapsed: TRUE, 4111 commonAncestorContainer: doc, 4112 4113 // Range constants 4114 START_TO_START: 0, 4115 START_TO_END: 1, 4116 END_TO_END: 2, 4117 END_TO_START: 3, 4118 4119 // Public methods 4120 setStart: setStart, 4121 setEnd: setEnd, 4122 setStartBefore: setStartBefore, 4123 setStartAfter: setStartAfter, 4124 setEndBefore: setEndBefore, 4125 setEndAfter: setEndAfter, 4126 collapse: collapse, 4127 selectNode: selectNode, 4128 selectNodeContents: selectNodeContents, 4129 compareBoundaryPoints: compareBoundaryPoints, 4130 deleteContents: deleteContents, 4131 extractContents: extractContents, 4132 cloneContents: cloneContents, 4133 insertNode: insertNode, 4134 surroundContents: surroundContents, 4135 cloneRange: cloneRange, 4136 toStringIE: toStringIE 4137 }); 4138 4139 return self; 4140 } 4141 4142 // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype 4143 Range.prototype.toString = function() { 4144 return this.toStringIE(); 4145 }; 4146 4147 return Range; 4148}); 4149 4150// Included from: js/tinymce/classes/html/Entities.js 4151 4152/** 4153 * Entities.js 4154 * 4155 * Copyright, Moxiecode Systems AB 4156 * Released under LGPL License. 4157 * 4158 * License: http://www.tinymce.com/license 4159 * Contributing: http://www.tinymce.com/contributing 4160 */ 4161 4162/*jshint bitwise:false */ 4163/*eslint no-bitwise:0 */ 4164
4165/** 4166 * Entity encoder class. 4167 * 4168 * @class tinymce.html.Entities 4169 * @static 4170 * @version 3.4 4171 */ 4172define("tinymce/html/Entities", [ 4173 "tinymce/util/Tools" 4174], function(Tools) { 4175 var makeMap = Tools.makeMap; 4176 4177 var namedEntities, baseEntities, reverseEntities, 4178 attrsCharsRegExp = /[&<>\"\u0060\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 4179 textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 4180 rawCharsRegExp = /[<>&\"\']/g, 4181 entityRegExp = /&(#x|#)?([\w]+);/g, 4182 asciiMap = { 4183 128: "\u20AC", 130: "\u201A", 131: "\u0192", 132: "\u201E", 133: "\u2026", 134: "\u2020", 4184 135: "\u2021", 136: "\u02C6", 137: "\u2030", 138: "\u0160", 139: "\u2039", 140: "\u0152", 4185 142: "\u017D", 145: "\u2018", 146: "\u2019", 147: "\u201C", 148: "\u201D", 149: "\u2022", 4186 150: "\u2013", 151: "\u2014", 152: "\u02DC", 153: "\u2122", 154: "\u0161", 155: "\u203A", 4187 156: "\u0153", 158: "\u017E", 159: "\u0178" 4188 }; 4189 4190 // Raw entities 4191 baseEntities = { 4192 '\"': '"', // Needs to be escaped since the YUI compressor would otherwise break the code 4193 "'": ''', 4194 '<': '<', 4195 '>': '>', 4196 '&': '&', 4197 '\u0060': '`' 4198 }; 4199 4200 // Reverse lookup table for raw entities 4201 reverseEntities = { 4202 '<': '<', 4203 '>': '>', 4204 '&': '&', 4205 '"': '"', 4206 ''': "'" 4207 }; 4208 4209 // Decodes text by using the browser 4210 function nativeDecode(text) { 4211 var elm; 4212 4213 elm = document.createElement("div"); 4214 elm.innerHTML = text; 4215 4216 return elm.textContent || elm.innerText || text; 4217 } 4218 4219 // Build a two way lookup table for the entities 4220 function buildEntitiesLookup(items, radix) { 4221 var i, chr, entity, lookup = {}; 4222 4223 if (items) { 4224 items = items.split(','); 4225 radix = radix || 10; 4226 4227 // Build entities lookup table 4228 for (i = 0; i < items.length; i += 2) { 4229 chr = String.fromCharCode(parseInt(items[i], radix)); 4230 4231 // Only add non base entities 4232 if (!baseEntities[chr]) { 4233 entity = '&' + items[i + 1] + ';'; 4234 lookup[chr] = entity; 4235 lookup[entity] = chr; 4236 } 4237 } 4238 4239 return lookup; 4240 } 4241 } 4242 4243 // Unpack entities lookup where the numbers are in radix 32 to reduce the size 4244 namedEntities = buildEntitiesLookup( 4245 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' + 4246 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' + 4247 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' + 4248 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' + 4249 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' + 4250 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' + 4251 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' + 4252 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' + 4253 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' + 4254 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' + 4255 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' + 4256 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' + 4257 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' + 4258 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' + 4259 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' + 4260 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' + 4261 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' + 4262 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' + 4263 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' + 4264 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' + 4265 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' + 4266 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' + 4267 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' + 4268 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' + 4269 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32); 4270 4271 var Entities = { 4272 /** 4273 * Encodes the specified string using raw entities. This means only the required XML base entities will be endoded. 4274 * 4275 * @method encodeRaw 4276 * @param {String} text Text to encode. 4277 * @param {Boolean} attr Optional flag to specify if the text is attribute contents. 4278 * @return {String} Entity encoded text. 4279 */ 4280 encodeRaw: function(text, attr) { 4281 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 4282 return baseEntities[chr] || chr; 4283 }); 4284 }, 4285 4286 /** 4287 * Encoded the specified text with both the attributes and text entities. This function will produce larger text contents 4288 * since it doesn't know if the context is within a attribute or text node. This was added for c
4288ompatibility 4289 * and is exposed as the DOMUtils.encode function. 4290 * 4291 * @method encodeAllRaw 4292 * @param {String} text Text to encode. 4293 * @return {String} Entity encoded text. 4294 */ 4295 encodeAllRaw: function(text) { 4296 return ('' + text).replace(rawCharsRegExp, function(chr) { 4297 return baseEntities[chr] || chr; 4298 }); 4299 }, 4300 4301 /** 4302 * Encodes the specified string using numeric entities. The core entities will be 4303 * encoded as named ones but all non lower ascii characters will be encoded into numeric entities. 4304 * 4305 * @method encodeNumeric 4306 * @param {String} text Text to encode. 4307 * @param {Boolean} attr Optional flag to specify if the text is attribute contents. 4308 * @return {String} Entity encoded text. 4309 */ 4310 encodeNumeric: function(text, attr) { 4311 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 4312 // Multi byte sequence convert it to a single entity 4313 if (chr.length > 1) { 4314 return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';'; 4315 } 4316 4317 return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';'; 4318 }); 4319 }, 4320 4321 /** 4322 * Encodes the specified string using named entities. The core entities will be encoded 4323 * as named ones but all non lower ascii characters will be encoded into named entities. 4324 * 4325 * @method encodeNamed 4326 * @param {String} text Text to encode. 4327 * @param {Boolean} attr Optional flag to specify if the text is attribute contents. 4328 * @param {Object} entities Optional parameter with entities to use. 4329 * @return {String} Entity encoded text. 4330 */ 4331 encodeNamed: function(text, attr, entities) { 4332 entities = entities || namedEntities; 4333 4334 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 4335 return baseEntities[chr] || entities[chr] || chr; 4336 }); 4337 }, 4338 4339 /** 4340 * Returns an encode function based on the name(s) and it's optional entities. 4341 * 4342 * @method getEncodeFunc 4343 * @param {String} name Comma separated list of encoders for example named,numeric. 4344 * @param {String} entities Optional parameter with entities to use instead of the built in set. 4345 * @return {function} Encode function to be used. 4346 */ 4347 getEncodeFunc: function(name, entities) { 4348 entities = buildEntitiesLookup(entities) || namedEntities; 4349 4350 function encodeNamedAndNumeric(text, attr) { 4351 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 4352 return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr; 4353 }); 4354 } 4355 4356 function encodeCustomNamed(text, attr) { 4357 return Entities.encodeNamed(text, attr, entities); 4358 } 4359 4360 // Replace + with , to be compatible with previous TinyMCE versions 4361 name = makeMap(name.replace(/\+/g, ',')); 4362 4363 // Named and numeric encoder 4364 if (name.named && name.numeric) { 4365 return encodeNamedAndNumeric; 4366 } 4367 4368 // Named encoder 4369 if (name.named) { 4370 // Custom names 4371 if (entities) { 4372 return encodeCustomNamed; 4373 } 4374 4375 return Entities.encodeNamed; 4376 } 4377 4378 // Numeric 4379 if (name.numeric) { 4380 return Entities.encodeNumeric; 4381 } 4382 4383 // Raw encoder 4384 return Entities.encodeRaw; 4385 }, 4386 4387 /** 4388 * Decodes the specified string, this will replace entities with raw UTF characters. 4389 * 4390 * @method decode 4391 * @param {String} text Text to entity decode. 4392 * @return {String} Entity decoded string. 4393 */ 4394 decode: function(text) { 4395 return text.replace(entityRegExp, function(all, numeric, value) { 4396 if (numeric) { 4397 value = parseInt(value, numeric.length === 2 ? 16 : 10); 4398 4399 // Support upper UTF 4400 if (value > 0xFFFF) { 4401 value -= 0x10000; 4402 4403 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF)); 4404 } else { 4405 return asciiMap[value] || String.fromCharCode(value); 4406 } 4407 } 4408 4409 return reverseEntities[all] || namedEntities[all] || nativeDecode(all); 4410 }); 4411 } 4412 }; 4413 4414 return Entities; 4415}); 4416 4417// Included from: js/tinymce/classes/dom/StyleSheetLoader.js 4418 4419/** 4420 * StyleSheetLoader.js 4421 * 4422 * Copyright, Moxiecode Systems AB 4423 * Released under LGPL License. 4424 * 4425 * License: http://www.tinymce.com/license 4426 * Contributing: http://www.tinymce.com/contributing 4427 */ 4428 4429/** 4430 * This class handles loading of external stylesheets and fires events when these are loaded. 4431 * 4432 * @class tinymce.dom.StyleSheetLoader 4433 * @private 4434 */ 4435define("tinymce/dom/StyleSheetLoader", [ 4436 "tinymce/util/Tools" 4437], function(Tools) { 4438 "use strict"; 4439 4440 return function(document, settings) { 4441 var idCount = 0, loadedStates = {}, maxLoadTime; 4442 4443 settings = settings || {}; 4444 maxLoadTime = settings.maxLoadTime || 5000; 4445 4446 function appendToHead(node) { 4447 document.getElementsByTagName('head')[0].appendChild(node); 4448 } 4449 4450 /** 4451 * Loads the specified css style sheet file and call the loadedCallback once it's finished loading. 4452 * 4453 * @method load 4454 * @param {String} url Url to be loaded. 4455 * @param {Function} loadedCallback Callback to be executed when loaded. 4456 * @param {Function} errorCallback Callback to be executed when failed loading. 4457 */ 4458 function load(url, loadedCallback, errorCallback) { 4459 var link, style, startTime, state; 4460 4461 function passed() { 4462 var callbacks = state.passed, i = callbacks.length; 4463 4464 while (i--) { 4465 callbacks[i](); 4466 } 4467 4468 state.status = 2; 4469 state.passed = []; 4470 state.failed = []; 4471 } 4472 4473 function failed() { 4474 var callbacks = state.failed, i = callbacks.length; 4475 4476 while (i--) { 4477 callbacks[i](); 4478 } 4479 4480 state.status = 3; 4481 state.passed = []; 4482 state.failed = []; 4483 } 4484 4485 // Sniffs for older WebKit versions that have the link.onload but a broken one 4486 function isOldWebKit() { 4487 var webKitChunks = navigator.userAgent.match(/WebKit\/(\d*)/); 4488 return !!(webKitChunks && webKitChunks[1] < 536); 4489 } 4490 4491 // Calls the waitCallback until the test returns true or the timeout occurs 4492 function wait(testCallback, waitCallback) { 4493 if (!testCallback()) { 4494 // Wait for timeout 4495 if ((new Date().getTime()) - startTime < maxLoadTime) { 4496 window.setTimeout(waitCallback, 0); 4497 } else { 4498 failed(); 4499 } 4500 } 4501 } 4502 4503 // Workaround for WebKit that doesn't properly support the onload event for link elements 4504 // Or WebKit that fires the onload event before the StyleSheet is added to the document 4505 function waitForWebKitLinkLoaded() { 4506 wait(function() { 4507 var styleSheets = document.styleSheets, styleSheet, i = styleSheets.length, owner; 4508 4509 while (i--) { 4510 styleSheet = styleSheets[i]; 4511 owner = styleSheet.ownerNode ? styleSheet.ownerNode : styleSheet.owningElement; 4512 if (owner && owner.id === link.id) { 4513 passed(); 4514 return true; 4515 } 4516 } 4517 }, waitForWebKitLinkLoaded); 4518 } 4519 4520 // Workaround for older Geckos that doesn't have any onload event for StyleSheets 4521 function waitForGeckoLinkLoaded() { 4522 wait(function() { 4523 try { 4524 // Accessing the cssRules will throw an exception until the CSS file is loaded 4525 var cssRules = style.sheet.cssRules; 4526 passed(); 4527 return !!cssRules; 4528 } catch (ex) { 4529 // Ignore 4530 } 4531 }, waitForGeckoLinkLoaded); 4532 } 4533 4534 url = Tools._addCacheSuffix(url); 4535 4536 if (!loadedStates[url]) { 4537 state = { 4538 passed: [], 4539 failed: [] 4540 }; 4541 4542 loadedStates[url] = state; 4543 } else { 4544 state = loadedStates[url]; 4545 } 4546 4547 if (loadedCallback) { 4548 state.passed.push(loadedCallback); 4549 } 4550 4551 if (errorCallback) { 4552 state.failed.push(errorCallback); 4553 } 4554 4555 // Is loading wait for it to pass 4556 if (state.status == 1) { 4557 return; 4558 } 4559 4560 // Has finished loading and was success 4561 if (state.status == 2) { 4562 passed(); 4563 return; 4564 } 4565 4566 // Has finished loading and was a failure 4567 if (state.status == 3) { 4568 failed(); 4569 return; 4570 } 4571 4572 // Start loading 4573 state.status = 1; 4574 link = document.createElement('link'); 4575 link.rel = 'stylesheet'; 4576 link.type = 'text/css'; 4577 link.id = 'u' + (idCount++); 4578 link.async = false;
4579 link.defer = false; 4580 startTime = new Date().getTime(); 4581 4582 // Feature detect onload on link element and sniff older webkits since it has an broken onload event 4583 if ("onload" in link && !isOldWebKit()) { 4584 link.onload = waitForWebKitLinkLoaded; 4585 link.onerror = failed; 4586 } else { 4587 // Sniff for old Firefox that doesn't support the onload event on link elements 4588 // TODO: Remove this in the future when everyone uses modern browsers 4589 if (navigator.userAgent.indexOf("Firefox") > 0) { 4590 style = document.createElement('style'); 4591 style.textContent = '@import "' + url + '"'; 4592 waitForGeckoLinkLoaded(); 4593 appendToHead(style); 4594 return; 4595 } else { 4596 // Use the id owner on older webkits 4597 waitForWebKitLinkLoaded(); 4598 } 4599 } 4600 4601 appendToHead(link); 4602 link.href = url; 4603 } 4604 4605 this.load = load; 4606 }; 4607}); 4608 4609// Included from: js/tinymce/classes/dom/DOMUtils.js 4610 4611/** 4612 * DOMUtils.js 4613 * 4614 * Copyright, Moxiecode Systems AB 4615 * Released under LGPL License. 4616 * 4617 * License: http://www.tinymce.com/license 4618 * Contributing: http://www.tinymce.com/contributing 4619 */ 4620 4621/** 4622 * Utility class for various DOM manipulation and retrieval functions. 4623 * 4624 * @class tinymce.dom.DOMUtils 4625 * @example 4626 * // Add a class to an element by id in the page 4627 * tinymce.DOM.addClass('someid', 'someclass'); 4628 * 4629 * // Add a class to an element by id inside the editor 4630 * tinymce.activeEditor.dom.addClass('someid', 'someclass'); 4631 */ 4632define("tinymce/dom/DOMUtils", [ 4633 "tinymce/dom/Sizzle", 4634 "tinymce/dom/DomQuery", 4635 "tinymce/html/Styles", 4636 "tinymce/dom/EventUtils", 4637 "tinymce/dom/TreeWalker", 4638 "tinymce/dom/Range", 4639 "tinymce/html/Entities", 4640 "tinymce/Env", 4641 "tinymce/util/Tools", 4642 "tinymce/dom/StyleSheetLoader" 4643], function(Sizzle, $, Styles, EventUtils, TreeWalker, Range, Entities, Env, Tools, StyleSheetLoader) { 4644 // Shorten names 4645 var each = Tools.each, is = Tools.is, grep = Tools.grep, trim = Tools.trim; 4646 var isIE = Env.ie; 4647 var simpleSelectorRe = /^([a-z0-9],?)+$/i; 4648 var whiteSpaceRegExp = /^[ \t\r\n]*$/; 4649 4650 function setupAttrHooks(domUtils, settings) { 4651 var attrHooks = {}, keepValues = settings.keep_values, keepUrlHook; 4652 4653 keepUrlHook = { 4654 set: function($elm, value, name) { 4655 if (settings.url_converter) { 4656 value = settings.url_converter.call(settings.url_converter_scope || domUtils, value, name, $elm[0]); 4657 } 4658 4659 $elm.attr('data-mce-' + name, value).attr(name, value); 4660 }, 4661 4662 get: function($elm, name) { 4663 return $elm.attr('data-mce-' + name) || $elm.attr(name); 4664 } 4665 }; 4666 4667 attrHooks = { 4668 style: { 4669 set: function($elm, value) { 4670 if (value !== null && typeof value === 'object') { 4671 $elm.css(value); 4672 return; 4673 } 4674 4675 if (keepValues) { 4676 $elm.attr('data-mce-style', value); 4677 } 4678 4679 $elm.attr('style', value); 4680 }, 4681 4682 get: function($elm) { 4683 var value = $elm.attr('data-mce-style') || $elm.attr('style'); 4684 4685 value = domUtils.serializeStyle(domUtils.parseStyle(value), $elm[0].nodeName); 4686 4687 return value; 4688 } 4689 } 4690 }; 4691 4692 if (keepValues) { 4693 attrHooks.href = attrHooks.src = keepUrlHook; 4694 } 4695 4696 return attrHooks; 4697 } 4698 4699 /** 4700 * Constructs a new DOMUtils instance. Consult the Wiki for more details on settings etc for this class. 4701 * 4702 * @constructor 4703 * @method DOMUtils 4704 * @param {Document} d Document reference to bind the utility class to. 4705 * @param {settings} s Optional settings collection. 4706 */ 4707 function DOMUtils(doc, settings) { 4708 var self = this, blockElementsMap; 4709 4710 self.doc = doc; 4711 self.win = window; 4712 self.files = {}; 4713 self.counter = 0; 4714 self.stdMode = !isIE || doc.documentMode >= 8; 4715 self.boxModel = !isIE || doc.compatMode == "CSS1Compat" || self.stdMode; 4716 self.styleSheetLoader = new StyleSheetLoader(doc); 4717 self.boundEvents = []; 4718 self.settings = settings = settings || {}; 4719 self.schema = settings.schema; 4720 self.styles = new Styles({ 4721 url_converter: settings.url_converter, 4722 url_converter_scope: settings.url_converter_scope 4723 }, settings.schema); 4724 4725 self.fixDoc(doc); 4726 self.events = settings.ownEvents ? new EventUtils(settings.proxy) : EventUtils.Event; 4727 self.attrHooks = setupAttrHooks(self, settings); 4728 blockElementsMap = settings.schema ? settings.schema.getBlockElements() : {}; 4729 self.$ = $.overrideDefaults(function() { 4730 return { 4731 context: doc, 4732 element: self.getRoot() 4733 }; 4734 }); 4735 4736 /** 4737 * Returns true/false if the specified element is a block element or not. 4738 * 4739 * @method isBlock 4740 * @param {Node/String}
4740 node Element/Node to check. 4741 * @return {Boolean} True/False state if the node is a block element or not. 4742 */ 4743 self.isBlock = function(node) { 4744 // Fix for #5446 4745 if (!node) { 4746 return false; 4747 } 4748 4749 // This function is called in module pattern style since it might be executed with the wrong this scope 4750 var type = node.nodeType; 4751 4752 // If it's a node then check the type and use the nodeName 4753 if (type) { 4754 return !!(type === 1 && blockElementsMap[node.nodeName]); 4755 } 4756 4757 return !!blockElementsMap[node]; 4758 }; 4759 } 4760 4761 DOMUtils.prototype = { 4762 $$: function(elm) { 4763 if (typeof elm == 'string') { 4764 elm = this.get(elm); 4765 } 4766 4767 return this.$(elm); 4768 }, 4769 4770 root: null, 4771 4772 fixDoc: function(doc) { 4773 var settings = this.settings, name; 4774 4775 if (isIE && settings.schema) { 4776 // Add missing HTML 4/5 elements to IE 4777 ('abbr article aside audio canvas ' + 4778 'details figcaption figure footer ' + 4779 'header hgroup mark menu meter nav ' + 4780 'output progress section summary ' + 4781 'time video').replace(/\w+/g, function(name) { 4782 doc.createElement(name); 4783 }); 4784 4785 // Create all custom elements 4786 for (name in settings.schema.getCustomElements()) { 4787 doc.createElement(name); 4788 } 4789 } 4790 }, 4791 4792 clone: function(node, deep) { 4793 var self = this, clone, doc; 4794 4795 // TODO: Add feature detection here in the future 4796 if (!isIE || node.nodeType !== 1 || deep) { 4797 return node.cloneNode(deep); 4798 } 4799 4800 doc = self.doc; 4801 4802 // Make a HTML5 safe shallow copy 4803 if (!deep) { 4804 clone = doc.createElement(node.nodeName); 4805 4806 // Copy attribs 4807 each(self.getAttribs(node), function(attr) { 4808 self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName)); 4809 }); 4810 4811 return clone; 4812 } 4813 4814 return clone.firstChild; 4815 }, 4816 4817 /** 4818 * Returns the root node of the document. This is normally the body but might be a DIV. Parents like getParent will not 4819 * go above the point of this root node. 4820 * 4821 * @method getRoot 4822 * @return {Element} Root element for the utility class. 4823 */ 4824 getRoot: function() { 4825 var self = this; 4826 4827 return self.settings.root_element || self.doc.body; 4828 }, 4829 4830 /** 4831 * Returns the viewport of the window. 4832 * 4833 * @method getViewPort 4834 * @param {Window} win Optional window to get viewport of. 4835 * @return {Object} Viewport object with fields x, y, w and h. 4836 */ 4837 getViewPort: function(win) { 4838 var doc, rootElm; 4839 4840 win = !win ? this.win : win; 4841 doc = win.document; 4842 rootElm = this.boxModel ? doc.documentElement : doc.body; 4843 4844 // Returns viewport size excluding scrollbars 4845 return { 4846 x: win.pageXOffset || rootElm.scrollLeft, 4847 y: win.pageYOffset || rootElm.scrollTop, 4848 w: win.innerWidth || rootElm.clientWidth, 4849 h: win.innerHeight || rootElm.clientHeight 4850 }; 4851 }, 4852 4853 /** 4854 * Returns the rectangle for a specific element. 4855 * 4856 * @method getRect 4857 * @param {Element/String} elm Element object or element ID to get rectangle from. 4858 * @return {object} Rectangle for specified element object with x, y, w, h fields. 4859 */ 4860 getRect: function(elm) { 4861 var self = this, pos, size; 4862 4863 elm = self.get(elm); 4864 pos = self.getPos(elm); 4865 size = self.getSize(elm); 4866 4867 return { 4868 x: pos.x, y: pos.y, 4869 w: size.w, h: size.h 4870 }; 4871 }, 4872 4873 /** 4874 * Returns the size dimensions of the specified element. 4875 * 4876 * @method getSize 4877 * @param {Element/String} elm Element object or element ID to get rectangle from. 4878 * @return {object} Rectangle for specified element object with w, h fields. 4879 */ 4880 getSize: function(elm) { 4881 var self = this, w, h; 4882 4883 elm = self.get(elm); 4884 w = self.getStyle(elm, 'width'); 4885 h = self.getStyle(elm, 'height'); 4886 4887 // Non pixel value, then force offset/clientWidth 4888 if (w.indexOf('px') === -1) { 4889 w = 0; 4890 } 4891 4892 // Non pixel value, then force offset/clientWidth 4893 if (h.indexOf('px') === -1) { 4894 h = 0; 4895 } 4896 4897 return { 4898 w: parseInt(w, 10) || elm.offsetWidth || elm.clientWidth, 4899 h: parseInt(h, 10) || elm.offsetHeight || elm.clientHeight 4900 }; 4901 }, 4902 4903 /** 4904 * Returns a node by the specified selector function. This function will 4905 * loop through all parent nodes and call the specified function for each node. 4906 * If the function then returns true indicating that it has found what it was looking for, the loop execution will then end 4907 * and the node it found will be returned. 4908 * 4909 * @method getParent 4910 * @param {Node/String}
4910 node DOM node to search parents on or ID string. 4911 * @param {function} selector Selection function or CSS selector to execute on each node. 4912 * @param {Node} root Optional root element, never go below this point. 4913 * @return {Node} DOM Node or null if it wasn't found. 4914 */ 4915 getParent: function(node, selector, root) { 4916 return this.getParents(node, selector, root, false); 4917 }, 4918 4919 /** 4920 * Returns a node list of all parents matching the specified selector function or pattern. 4921 * If the function then returns true indicating that it has found what it was looking for and that node will be collected. 4922 * 4923 * @method getParents 4924 * @param {Node/String} node DOM node to search parents on or ID string. 4925 * @param {function} selector Selection function to execute on each node or CSS pattern. 4926 * @param {Node} root Optional root element, never go below this point. 4927 * @return {Array} Array of nodes or null if it wasn't found. 4928 */ 4929 getParents: function(node, selector, root, collect) { 4930 var self = this, selectorVal, result = []; 4931 4932 node = self.get(node); 4933 collect = collect === undefined; 4934 4935 // Default root on inline mode 4936 root = root || (self.getRoot().nodeName != 'BODY' ? self.getRoot().parentNode : null); 4937 4938 // Wrap node name as func 4939 if (is(selector, 'string')) { 4940 selectorVal = selector; 4941 4942 if (selector === '*') { 4943 selector = function(node) { 4944 return node.nodeType == 1; 4945 }; 4946 } else { 4947 selector = function(node) { 4948 return self.is(node, selectorVal); 4949 }; 4950 } 4951 } 4952 4953 while (node) { 4954 if (node == root || !node.nodeType || node.nodeType === 9) { 4955 break; 4956 } 4957 4958 if (!selector || selector(node)) { 4959 if (collect) { 4960 result.push(node); 4961 } else { 4962 return node; 4963 } 4964 } 4965 4966 node = node.parentNode; 4967 } 4968 4969 return collect ? result : null; 4970 }, 4971 4972 /** 4973 * Returns the specified element by ID or the input element if it isn't a string. 4974 * 4975 * @method get 4976 * @param {String/Element} n Element id to look for or element to just pass though. 4977 * @return {Element} Element matching the specified id or null if it wasn't found. 4978 */ 4979 get: function(elm) { 4980 var name; 4981 4982 if (elm && this.doc && typeof(elm) == 'string') { 4983 name = elm; 4984 elm = this.doc.getElementById(elm); 4985 4986 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick 4987 if (elm && elm.id !== name) { 4988 return this.doc.getElementsByName(name)[1]; 4989 } 4990 } 4991 4992 return elm; 4993 }, 4994 4995 /** 4996 * Returns the next node that matches selector or function 4997 * 4998 * @method getNext 4999 * @param {Node} node Node to find siblings from. 5000 * @param {String/function} selector Selector CSS expression or function. 5001 * @return {Node} Next node item matching the selector or null if it wasn't found. 5002 */ 5003 getNext: function(node, selector) { 5004 return this._findSib(node, selector, 'nextSibling'); 5005 }, 5006 5007 /** 5008 * Returns the previous node that matches selector or function 5009 * 5010 * @method getPrev 5011 * @param {Node} node Node to find siblings from. 5012 * @param {String/function} selector Selector CSS expression or function. 5013 * @return {Node} Previous node item matching the selector or null if it wasn't found. 5014 */ 5015 getPrev: function(node, selector) { 5016 return this._findSib(node, selector, 'previousSibling'); 5017 }, 5018 5019 // #ifndef jquery 5020 5021 /** 5022 * Selects specific elements by a CSS level 3 pattern. For example "div#a1 p.test". 5023 * This function is optimized for the most common patterns needed in TinyMCE but it also performs well enough 5024 * on more complex patterns. 5025 * 5026 * @method select 5027 * @param {String} selector CSS level 3 pattern to select/find elements by. 5028 * @param {Object} scope Optional root element/scope element to search in. 5029 * @return {Array} Array with all matched elements. 5030 * @example 5031 * // Adds a class to all paragraphs in the currently active editor 5032 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass'); 5033 * 5034 * // Adds a class to all spans that have the test class in the currently active editor 5035 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('span.test'), 'someclass') 5036 */ 5037 select: function(selector, scope) { 5038 var self = this; 5039 5040 /*eslint new-cap:0 */ 5041 return Sizzle(selector, self.get(scope) || self.settings.root_element || self.doc, []); 5042 }, 5043 5044 /** 5045 * Returns true/false if the specified element matches the specified css pattern. 5046 * 5047 * @method is 5048 * @param {Node/NodeList} elm DOM node to match or an array of nodes to match. 5049 * @param {String} selector CSS pattern to match the element against. 5050 */ 5051 is: function(elm, selector) { 5052 var i; 5053 5054 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance 5055 if (elm.length === undefined) { 5056 // Simple all selector 5057 if (selector === '*') { 5058 return elm.nodeType == 1; 5059 } 5060 5061 // Simple selector just elements 5062 if (simpleSelectorRe.test(selector)) { 5063 selector = selector.toLowerCase().split(/,/); 5064 elm = elm.nodeName.toLowerCase(); 5065 5066 for (i = selector.length - 1; i >= 0; i--) { 5067 if (selector[i] == elm) { 5068 return true; 5069 } 5070 } 5071 5072 return false;
5073 } 5074 } 5075 5076 // Is non element 5077 if (elm.nodeType && elm.nodeType != 1) { 5078 return false; 5079 } 5080 5081 var elms = elm.nodeType ? [elm] : elm; 5082 5083 /*eslint new-cap:0 */ 5084 return Sizzle(selector, elms[0].ownerDocument || elms[0], null, elms).length > 0; 5085 }, 5086 5087 // #endif 5088 5089 /** 5090 * Adds the specified element to another element or elements. 5091 * 5092 * @method add 5093 * @param {String/Element/Array} parentElm Element id string, DOM node element or array of ids or elements to add to. 5094 * @param {String/Element} name Name of new element to add or existing element to add. 5095 * @param {Object} attrs Optional object collection with arguments to add to the new element(s). 5096 * @param {String} html Optional inner HTML contents to add for each element. 5097 * @return {Element/Array} Element that got created, or an array of created elements if multiple input elements 5098 * were passed in. 5099 * @example 5100 * // Adds a new paragraph to the end of the active editor 5101 * tinymce.activeEditor.dom.add(tinymce.activeEditor.getBody(), 'p', {title: 'my title'}, 'Some content'); 5102 */ 5103 add: function(parentElm, name, attrs, html, create) { 5104 var self = this; 5105 5106 return this.run(parentElm, function(parentElm) { 5107 var newElm; 5108 5109 newElm = is(name, 'string') ? self.doc.createElement(name) : name; 5110 self.setAttribs(newElm, attrs); 5111 5112 if (html) { 5113 if (html.nodeType) { 5114 newElm.appendChild(html); 5115 } else { 5116 self.setHTML(newElm, html); 5117 } 5118 } 5119 5120 return !create ? parentElm.appendChild(newElm) : newElm; 5121 }); 5122 }, 5123 5124 /** 5125 * Creates a new element. 5126 * 5127 * @method create 5128 * @param {String} name Name of new element. 5129 * @param {Object} attrs Optional object name/value collection with element attributes. 5130 * @param {String} html Optional HTML string to set as inner HTML of the element. 5131 * @return {Element} HTML DOM node element that got created. 5132 * @example 5133 * // Adds an element where the caret/selection is in the active editor 5134 * var el = tinymce.activeEditor.dom.create('div', {id: 'test', 'class': 'myclass'}, 'some content'); 5135 * tinymce.activeEditor.selection.setNode(el); 5136 */ 5137 create: function(name, attrs, html) { 5138 return this.add(this.doc.createElement(name), name, attrs, html, 1); 5139 }, 5140 5141 /** 5142 * Creates HTML string for element. The element will be closed unless an empty inner HTML string is passed in. 5143 * 5144 * @method createHTML 5145 * @param {String} name Name of new element. 5146 * @param {Object} attrs Optional object name/value collection with element attributes. 5147 * @param {String} html Optional HTML string to set as inner HTML of the element. 5148 * @return {String} String with new HTML element, for example: <a href="#">test</a>. 5149 * @example 5150 * // Creates a html chunk and inserts it at the current selection/caret location 5151 * tinymce.activeEditor.selection.setContent(tinymce.activeEditor.dom.createHTML('a', {href: 'test.html'}, 'some line')); 5152 */ 5153 createHTML: function(name, attrs, html) { 5154 var outHtml = '', key; 5155 5156 outHtml += '<' + name; 5157 5158 for (key in attrs) { 5159 if (attrs.hasOwnProperty(key) && attrs[key] !== null && typeof attrs[key] != 'undefined') { 5160 outHtml += ' ' + key + '="' + this.encode(attrs[key]) + '"'; 5161 } 5162 } 5163 5164 // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime 5165 if (typeof(html) != "undefined") { 5166 return outHtml + '>' + html + '</' + name + '>'; 5167 } 5168 5169 return outHtml + ' />'; 5170 }, 5171 5172 /** 5173 * Creates a document fragment out of the specified HTML string. 5174 * 5175 * @method createFragment 5176 * @param {String} html Html string to create fragment from. 5177 * @return {DocumentFragment} Document fragment node. 5178 */ 5179 createFragment: function(html) { 5180 var frag, node, doc = this.doc, container; 5181 5182 container = doc.createElement("div"); 5183 frag = doc.createDocumentFragment(); 5184 5185 if (html) { 5186 container.innerHTML = html; 5187 } 5188 5189 while ((node = container.firstChild)) { 5190 frag.appendChild(node); 5191 } 5192 5193 return frag; 5194 }, 5195 5196 /** 5197 * Removes/deletes the specified element(s) from the DOM. 5198 * 5199 * @method remove 5200 * @param {String/Element/Array} node ID of element or DOM element object or array containing multiple elements/ids. 5201 * @param {Boolean} keepChildren Optional state to keep children or not. If set to true all children will be 5202 * placed at the location of the removed element. 5203 * @return {Element/Array} HTML DOM element that got removed, or an array of removed elements if multiple input elements 5204 * were passed in. 5205 * @example 5206 * // Removes all paragraphs in the active editor 5207 * tinymce.activeEditor.dom.remove(tinymce.activeEditor.dom.select('p')); 5208 * 5209 * // Removes an element by id in the document 5210 * tinymce.DOM.remove('mydiv'); 5211 */ 5212 remove: function(node, keepChildren) { 5213 node = this.$$(node); 5214 5215 if (keepChildren) { 5216 node.each(function() { 5217 var child; 5218 5219 while ((child = this.firstChild)) { 5220 if (child.nodeType == 3 && child.data.length === 0) { 5221 this.removeChild(child); 5222 } else { 5223 this.parentNode.insertBefore(child, this); 5224 } 5225 } 5226 }).remove(); 5227 } else { 5228 node.remove(); 5229 } 5230 5231 return node.length > 1 ? node.toArray() : node[0]; 5232 }, 5233 5234 /** 5235 * Sets the CSS style value on a HTML element. The name can be a camelcase string 5236 * or the CSS style name like background-color. 5237 * 5238 * @method setStyle 5239 * @param {String/Element/Array} n HTML element/Element ID or Array of elements/ids to set CSS style value on. 5240 * @param {String} na Name of the style value to set. 5241 * @param {String} v Value to set on the style. 5242 * @example
5243 * // Sets a style value on all paragraphs in the currently active editor 5244 * tinymce.activeEditor.dom.setStyle(tinymce.activeEditor.dom.select('p'), 'background-color', 'red'); 5245 * 5246 * // Sets a style value to an element by id in the current document 5247 * tinymce.DOM.setStyle('mydiv', 'background-color', 'red'); 5248 */ 5249 setStyle: function(elm, name, value) { 5250 elm = this.$$(elm).css(name, value); 5251 5252 if (this.settings.update_styles) { 5253 elm.attr('data-mce-style', null); 5254 } 5255 }, 5256 5257 /** 5258 * Returns the current style or runtime/computed value of an element. 5259 * 5260 * @method getStyle 5261 * @param {String/Element} elm HTML element or element id string to get style from. 5262 * @param {String} name Style name to return. 5263 * @param {Boolean} computed Computed style. 5264 * @return {String} Current style or computed style value of an element. 5265 */ 5266 getStyle: function(elm, name, computed) { 5267 elm = this.$$(elm); 5268 5269 if (computed) { 5270 return elm.css(name); 5271 } 5272 5273 // Camelcase it, if needed 5274 name = name.replace(/-(\D)/g, function(a, b) { 5275 return b.toUpperCase(); 5276 }); 5277 5278 if (name == 'float') { 5279 name = isIE ? 'styleFloat' : 'cssFloat'; 5280 } 5281 5282 return elm[0] && elm[0].style ? elm[0].style[name] : undefined; 5283 }, 5284 5285 /** 5286 * Sets multiple styles on the specified element(s). 5287 * 5288 * @method setStyles 5289 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set styles on. 5290 * @param {Object} o Name/Value collection of style items to add to the element(s). 5291 * @example 5292 * // Sets styles on all paragraphs in the currently active editor 5293 * tinymce.activeEditor.dom.setStyles(tinymce.activeEditor.dom.select('p'), {'background-color': 'red', 'color': 'green'}); 5294 *
5295 * // Sets styles to an element by id in the current document 5296 * tinymce.DOM.setStyles('mydiv', {'background-color': 'red', 'color': 'green'}); 5297 */ 5298 setStyles: function(elm, styles) { 5299 elm = this.$$(elm).css(styles); 5300 5301 if (this.settings.update_styles) { 5302 elm.attr('data-mce-style', null); 5303 } 5304 }, 5305 5306 /** 5307 * Removes all attributes from an element or elements. 5308 * 5309 * @method removeAllAttribs 5310 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to remove attributes from. 5311 */ 5312 removeAllAttribs: function(e) { 5313 return this.run(e, function(e) { 5314 var i, attrs = e.attributes; 5315 for (i = attrs.length - 1; i >= 0; i--) { 5316 e.removeAttributeNode(attrs.item(i)); 5317 } 5318 }); 5319 }, 5320 5321 /** 5322 * Sets the specified attribute of an element or elements. 5323 * 5324 * @method setAttrib 5325 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set attribute on. 5326 * @param {String} n Name of attribute to set. 5327 * @param {String} v Value to set on the attribute - if this value is falsy like null, 0 or '' it will remove the attribute instead. 5328 * @example 5329 * // Sets class attribute on all paragraphs in the active editor 5330 * tinymce.activeEditor.dom.setAttrib(tinymce.activeEditor.dom.select('p'), 'class', 'myclass'); 5331 *
5332 * // Sets class attribute on a specific element in the current page 5333 * tinymce.dom.setAttrib('mydiv', 'class', 'myclass'); 5334 */ 5335 setAttrib: function(elm, name, value) { 5336 var self = this, originalValue, hook, settings = self.settings; 5337 5338 if (value === '') { 5339 value = null; 5340 } 5341 5342 elm = self.$$(elm); 5343 originalValue = elm.attr(name); 5344 5345 if (!elm.length) { 5346 return; 5347 } 5348 5349 hook = self.attrHooks[name]; 5350 if (hook && hook.set) { 5351 hook.set(elm, value, name); 5352 } else { 5353 elm.attr(name, value); 5354 } 5355 5356 if (originalValue != value && settings.onSetAttrib) { 5357 settings.onSetAttrib({ 5358 attrElm: elm, 5359 attrName: name, 5360 attrValue: value 5361 }); 5362 } 5363 }, 5364 5365 /** 5366 * Sets two or more specified attributes of an element or elements. 5367 * 5368 * @method setAttribs 5369 * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set attributes on. 5370 * @param {Object} attrs Name/Value collection of attribute items to add to the element(s). 5371 * @example 5372 * // Sets class and title attributes on all paragraphs in the active editor 5373 * tinymce.activeEditor.dom.setAttribs(tinymce.activeEditor.dom.select('p'), {'class': 'myclass', title: 'some title'}); 5374 *
5375 * // Sets class and title attributes on a specific element in the current page 5376 * tinymce.DOM.setAttribs('mydiv', {'class': 'myclass', title: 'some title'}); 5377 */ 5378 setAttribs: function(elm, attrs) { 5379 var self = this; 5380 5381 self.$$(elm).each(function(i, node) { 5382 each(attrs, function(value, name) { 5383 self.setAttrib(node, name, value); 5384 }); 5385 }); 5386 }, 5387 5388 /** 5389 * Returns the specified attribute by name. 5390 * 5391 * @method getAttrib 5392 * @param {String/Element} elm Element string id or DOM element to get attribute from. 5393 * @param {String} name Name of attribute to get. 5394 * @param {String} defaultVal Optional default value to return if the attribute didn't exist. 5395 * @return {String} Attribute value string, default value or null if the attribute wasn't found. 5396 */ 5397 getAttrib: function(elm, name, defaultVal) { 5398 var self = this, hook, value; 5399 5400 elm = self.$$(elm); 5401 5402 if (elm.length) { 5403 hook = self.attrHooks[name]; 5404 5405 if (hook && hook.get) { 5406 value = hook.get(elm, name); 5407 } else { 5408 value = elm.attr(name); 5409 } 5410 } 5411 5412 if (typeof value == 'undefined') { 5413 value = defaultVal || ''; 5414 } 5415 5416 return value; 5417 }, 5418 5419 /** 5420 * Returns the absolute x, y position of a node. The position will be returned in an object with x, y fields. 5421 * 5422 * @method getPos 5423 * @param {Element/String} elm HTML element or element id to get x, y position from. 5424 * @param {Element} rootElm Optional root element to stop calculations at. 5425 * @return {object} Absolute position of the specified element object with x, y fields. 5426 */ 5427 getPos: function(elm, rootElm) { 5428 var self = this, x = 0, y = 0, offsetParent, doc = self.doc, body = doc.body, pos; 5429 5430 elm = self.get(elm); 5431 rootElm = rootElm || body; 5432 5433 if (elm) { 5434 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes 5435 // Fallback to offsetParent calculations if the body isn't static better since it stops at the body root 5436 if (rootElm === body && elm.getBoundingClientRect && $(body).css('position') === 'static') { 5437 pos = elm.getBoundingClientRect(); 5438 rootElm = self.boxModel ? doc.documentElement : body; 5439 5440 // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit 5441 // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position 5442 x = pos.left + (doc.documentElement.scrollLeft || body.scrollLeft) - rootElm.clientLeft; 5443 y = pos.top + (doc.documentElement.scrollTop || body.scrollTop) - rootElm.clientTop; 5444 5445 return {x: x, y: y}; 5446 } 5447 5448 offsetParent = elm; 5449 while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) { 5450 x += offsetParent.offsetLeft || 0; 5451 y += offsetParent.offsetTop || 0; 5452 offsetParent = offsetParent.offsetParent; 5453 } 5454 5455 offsetParent = elm.parentNode; 5456 while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) { 5457 x -= offsetParent.scrollLeft || 0; 5458 y -= offsetParent.scrollTop || 0; 5459 offsetParent = offsetParent.parentNode; 5460 } 5461 } 5462 5463 return {x: x, y: y}; 5464 }, 5465 5466 /**
5467 * Parses the specified style value into an object collection. This parser will also 5468 * merge and remove any redundant items that browsers might have added. It will also convert non-hex 5469 * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings. 5470 * 5471 * @method parseStyle 5472 * @param {String} cssText Style value to parse, for example: border:1px solid red;. 5473 * @return {Object} Object representation of that style, for example: {border: '1px solid red'} 5474 */ 5475 parseStyle: function(cssText) { 5476 return this.styles.parse(cssText); 5477 }, 5478 5479 /** 5480 * Serializes the specified style object into a string. 5481 * 5482 * @method serializeStyle 5483 * @param {Object} styles Object to serialize as string, for example: {border: '1px solid red'} 5484 * @param {String} name Optional element name. 5485 * @return {String} String representation of the style object, for example: border: 1px solid red. 5486 */ 5487 serializeStyle: function(styles, name) { 5488 return this.styles.serialize(styles, name); 5489 }, 5490 5491 /** 5492 * Adds a style element at the top of the document with the specified cssText content. 5493 * 5494 * @method addStyle 5495 * @param {String} cssText CSS Text style to add to top of head of document. 5496 */ 5497 addStyle: function(cssText) { 5498 var self = this, doc = self.doc, head, styleElm; 5499 5500 // Prevent inline from loading the same styles twice 5501 if (self !== DOMUtils.DOM && doc === document) { 5502 var addedStyles = DOMUtils.DOM.addedStyles; 5503 5504 addedStyles = addedStyles || []; 5505 if (addedStyles[cssText]) { 5506 return; 5507 } 5508 5509 addedStyles[cssText] = true; 5510 DOMUtils.DOM.addedStyles = addedStyles; 5511 } 5512 5513 // Create style element if needed 5514 styleElm = doc.getElementById('mceDefaultStyles'); 5515 if (!styleElm) { 5516 styleElm = doc.createElement('style'); 5517 styleElm.id = 'mceDefaultStyles'; 5518 styleElm.type = 'text/css'; 5519 5520 head = doc.getElementsByTagName('head')[0]; 5521 if (head.firstChild) { 5522 head.insertBefore(styleElm, head.firstChild); 5523 } else { 5524 head.appendChild(styleElm); 5525 } 5526 } 5527 5528 // Append style data to old or new style element 5529 if (styleElm.styleSheet) { 5530 styleElm.styleSheet.cssText += cssText; 5531 } else { 5532 styleElm.appendChild(doc.createTextNode(cssText)); 5533 } 5534 }, 5535 5536 /** 5537 * Imports/loads the specified CSS file into the document bound to the class. 5538 * 5539 * @method loadCSS 5540 * @param {String} u URL to CSS file to load. 5541 * @example 5542 * // Loads a CSS file dynamically into the current document 5543 * tinymce.DOM.loadCSS('somepath/some.css'); 5544 * 5545 * // Loads a CSS file into the currently active editor instance 5546 * tinymce.activeEditor.dom.loadCSS('somepath/some.css'); 5547 * 5548 * // Loads a CSS file into an editor instance by id 5549 * tinymce.get('someid').dom.loadCSS('somepath/some.css'); 5550 * 5551 * // Loads multiple CSS files into the current document 5552 * tinymce.DOM.loadCSS('somepath/some.css,somepath/someother.css'); 5553 */ 5554 loadCSS: function(url) { 5555 var self = this, doc = self.doc, head; 5556 5557 // Prevent inline from loading the same CSS file twice 5558 if (self !== DOMUtils.DOM && doc === document) { 5559 DOMUtils.DOM.loadCSS(url); 5560 return; 5561 } 5562 5563 if (!url) { 5564 url = ''; 5565 } 5566 5567 head = doc.getElementsByTagName('head')[0]; 5568 5569 each(url.split(','), function(url) { 5570 var link; 5571 5572 url = Tools._addCacheSuffix(url); 5573 5574 if (self.files[url]) { 5575 return; 5576 } 5577 5578 self.files[url] = true; 5579 link = self.create('link', {rel: 'stylesheet', href: url}); 5580 5581 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug 5582 // This fix seems to resolve that issue by recalcing the document once a stylesheet finishes loading 5583 // It's ugly but it seems to work fine. 5584 if (isIE && doc.documentMode && doc.recalc) { 5585 link.onload = function() { 5586 if (doc.recalc) { 5587 doc.recalc(); 5588 } 5589 5590 link.onload = null; 5591 }; 5592 } 5593 5594 head.appendChild(link); 5595 }); 5596 }, 5597 5598 /** 5599 * Adds a class to the specified element or elements. 5600 * 5601 * @method addClass 5602 * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs. 5603 * @param {String} cls Class name to add to each element. 5604 * @return {String/Array}
5604 String with new class value or array with new class values for all elements. 5605 * @example 5606 * // Adds a class to all paragraphs in the active editor 5607 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'myclass'); 5608 * 5609 * // Adds a class to a specific element in the current page 5610 * tinymce.DOM.addClass('mydiv', 'myclass'); 5611 */ 5612 addClass: function(elm, cls) { 5613 this.$$(elm).addClass(cls); 5614 }, 5615 5616 /** 5617 * Removes a class from the specified element or elements. 5618 * 5619 * @method removeClass 5620 * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs. 5621 * @param {String} cls Class name to remove from each element. 5622 * @return {String/Array} String of remaining class name(s), or an array of strings if multiple input elements 5623 * were passed in. 5624 * @example 5625 * // Removes a class from all paragraphs in the active editor 5626 * tinymce.activeEditor.dom.removeClass(tinymce.activeEditor.dom.select('p'), 'myclass'); 5627 * 5628 * // Removes a class from a specific element in the current page 5629 * tinymce.DOM.removeClass('mydiv', 'myclass'); 5630 */ 5631 removeClass: function(elm, cls) { 5632 this.toggleClass(elm, cls, false); 5633 }, 5634 5635 /** 5636 * Returns true if the specified element has the specified class. 5637 * 5638 * @method hasClass 5639 * @param {String/Element} n HTML element or element id string to check CSS class on. 5640 * @param {String} c CSS class to check for. 5641 * @return {Boolean} true/false if the specified element has the specified class. 5642 */ 5643 hasClass: function(elm, cls) { 5644 return this.$$(elm).hasClass(cls); 5645 }, 5646 5647 /** 5648 * Toggles the specified class on/off. 5649 * 5650 * @method toggleClass 5651 * @param {Element} elm Element to toggle class on. 5652 * @param {[type]} cls Class to toggle on/off. 5653 * @param {[type]} state Optional state to set. 5654 */ 5655 toggleClass: function(elm, cls, state) { 5656 this.$$(elm).toggleClass(cls, state).each(function() { 5657 if (this.className === '') { 5658 $(this).attr('class', null); 5659 } 5660 }); 5661 }, 5662 5663 /** 5664 * Shows the specified element(s) by ID by setting the "display" style. 5665 * 5666 * @method show 5667 * @param {String/Element/Array} elm ID of DOM element or DOM element or array with elements or IDs to show. 5668 */ 5669 show: function(elm) { 5670 this.$$(elm).show(); 5671 }, 5672 5673 /** 5674 * Hides the specified element(s) by ID by setting the "display" style. 5675 * 5676 * @method hide 5677 * @param {String/Element/Array} e ID of DOM element or DOM element or array with elements or IDs to hide. 5678 * @example 5679 * // Hides an element by id in the document 5680 * tinymce.DOM.hide('myid'); 5681 */ 5682 hide: function(elm) { 5683 this.$$(elm).hide(); 5684 }, 5685 5686 /** 5687 * Returns true/false if the element is hidden or not by checking the "display" style. 5688 * 5689 * @method isHidden 5690 * @param {String/Element} e Id or element to check display state on. 5691 * @return {Boolean} true/false if the element is hidden or not. 5692 */ 5693 isHidden: function(elm) { 5694 return this.$$(elm).css('display') == 'none'; 5695 }, 5696 5697 /** 5698 * Returns a unique id. This can be useful when generating elements on the fly. 5699 * This method will not check if the element already exists. 5700 * 5701 * @method uniqueId 5702 * @param {String} prefix Optional prefix to add in front of all ids - defaults to "mce_". 5703 * @return {String} Unique id. 5704 */ 5705 uniqueId: function(prefix) { 5706 return (!prefix ? 'mce_' : prefix) + (this.counter++); 5707 }, 5708 5709 /** 5710 * Sets the specified HTML content inside the element or elements. The HTML will first be processed. This means 5711 * URLs will get converted, hex color values fixed etc. Check processHTML for details. 5712 * 5713 * @method setHTML 5714 * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set HTML inside of. 5715 * @param {String} h HTML content to set as inner HTML of the element. 5716 * @example 5717 * // Sets the inner HTML of all paragraphs in the active editor 5718 * tinymce.activeEditor.dom.setHTML(tinymce.activeEditor.dom.select('p'), 'some inner html'); 5719 *
5720 * // Sets the inner HTML of an element by id in the document 5721 * tinymce.DOM.setHTML('mydiv', 'some inner html'); 5722 */ 5723 setHTML: function(elm, html) { 5724 elm = this.$$(elm); 5725 5726 if (isIE) { 5727 elm.each(function(i, target) { 5728 if (target.canHaveHTML === false) { 5729 return; 5730 } 5731 5732 // Remove all child nodes, IE keeps empty text nodes in DOM 5733 while (target.firstChild) { 5734 target.removeChild(target.firstChild); 5735 } 5736 5737 try { 5738 // IE will remove comments from the beginning 5739 // unless you padd the contents with something 5740 target.innerHTML = '<br>' + html; 5741 target.removeChild(target.firstChild); 5742 } catch (ex) { 5743 // IE sometimes produces an unknown runtime error on innerHTML if it's a div inside a p 5744 $('<div>').html('<br>' + html).contents().slice(1).appendTo(target); 5745 } 5746 5747 return html; 5748 }); 5749 } else { 5750 elm.html(html); 5751 } 5752 }, 5753 5754 /** 5755 * Returns the outer HTML of an element. 5756 * 5757 * @method getOuterHTML 5758 * @param {String/Element} elm Element ID or element object to get outer HTML from. 5759 * @return {String} Outer HTML string. 5760 * @example 5761 * tinymce.DOM.getOuterHTML(editorElement); 5762 * tinymce.activeEditor.getOuterHTML(tinymce.activeEditor.getBody()); 5763 */ 5764 getOuterHTML: function(elm) { 5765 elm = this.get(elm); 5766 return elm.nodeType == 1 ? elm.outerHTML : $('<div>').append($(elm).clone()).html(); 5767 }, 5768 5769 /** 5770 * Sets the specified outer HTML on an element or elements. 5771 * 5772 * @method setOuterHTML 5773 * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set outer HTML on. 5774 * @param {Object} html HTML code to set as outer value for the element. 5775 * @param {Document} doc Optional document scope to use in this process - defaults to the document of the DOM class. 5776 * @example 5777 * // Sets the outer HTML of all paragraphs in the active editor 5778 * tinymce.activeEditor.dom.setOuterHTML(tinymce.activeEditor.dom.select('p'), '<div>some html</div>'); 5779 *
5780 * // Sets the outer HTML of an element by id in the document 5781 * tinymce.DOM.setOuterHTML('mydiv', '<div>some html</div>'); 5782 */ 5783 setOuterHTML: function(elm, html) { 5784 var self = this; 5785 5786 self.$$(elm).each(function() { 5787 try { 5788 this.outerHTML = html; 5789 } catch (ex) { 5790 // OuterHTML for IE it sometimes produces an "unknown runtime error" 5791 self.remove($(this).html(html), true); 5792 } 5793 }); 5794 }, 5795 5796 /** 5797 * Entity decodes a string. This method decodes any HTML entities, such as å. 5798 * 5799 * @method decode 5800 * @param {String} s String to decode entities on. 5801 * @return {String} Entity decoded string. 5802 */ 5803 decode: Entities.decode, 5804 5805 /** 5806 * Entity encodes a string. This method encodes the most common entities, such as <>"&. 5807 * 5808 * @method encode 5809 * @param {String} text String to encode with entities. 5810 * @return {String} Entity encoded string. 5811 */ 5812 encode: Entities.encodeAllRaw, 5813 5814 /** 5815 * Inserts an element after the reference element. 5816 * 5817 * @method insertAfter 5818 * @param {Element} node Element to insert after the reference. 5819 * @param {Element/String/Array} reference_node Reference element, element id or array of elements to insert after. 5820 * @return {Element/Array} Element that got added or an array with elements. 5821 */ 5822 insertAfter: function(node, referenceNode) { 5823 referenceNode = this.get(referenceNode); 5824 5825 return this.run(node, function(node) { 5826 var parent, nextSibling; 5827 5828 parent = referenceNode.parentNode; 5829 nextSibling = referenceNode.nextSibling; 5830 5831 if (nextSibling) { 5832 parent.insertBefore(node, nextSibling); 5833 } else { 5834 parent.appendChild(node); 5835 } 5836 5837 return node; 5838 }); 5839 }, 5840 5841 /** 5842 * Replaces the specified element or elements with the new element specified. The new element will 5843 * be cloned if multiple input elements are passed in. 5844 * 5845 * @method replace 5846 * @param {Element} newElm New element to replace old ones with. 5847 * @param {Element/String/Array} oldELm Element DOM node, element id or array of elements or ids to replace. 5848 * @param {Boolean} k Optional keep children state, if set to true child nodes from the old object will be added to new ones. 5849 */ 5850 replace: function(newElm, oldElm, keepChildren) { 5851 var self = this; 5852 5853 return self.run(oldElm, function(oldElm) { 5854 if (is(oldElm, 'array')) { 5855 newElm = newElm.cloneNode(true); 5856 } 5857 5858 if (keepChildren) { 5859 each(grep(oldElm.childNodes), function(node) { 5860 newElm.appendChild(node); 5861 }); 5862 } 5863 5864 return oldElm.parentNode.replaceChild(newElm, oldElm); 5865 }); 5866 }, 5867 5868 /** 5869 * Renames the specified element and keeps its attributes and children. 5870 * 5871 * @method rename 5872 * @param {Element} elm Element to rename. 5873 * @param {String} name Name of the new element. 5874 * @return {Element} New element or the old element if it needed renaming. 5875 */ 5876 rename: function(elm, name) { 5877 var self = this, newElm; 5878 5879 if (elm.nodeName != name.toUpperCase()) { 5880 // Rename block element 5881 newElm = self.create(name); 5882 5883 // Copy attribs to new block 5884 each(self.getAttribs(elm), function(attrNode) { 5885 self.setAttrib(newElm, attrNode.nodeName, self.getAttrib(elm, attrNode.nodeName)); 5886 }); 5887 5888 // Replace block 5889 self.replace(newElm, elm, 1); 5890 } 5891 5892 return newElm || elm; 5893 }, 5894 5895 /** 5896 * Find the common ancestor of two elements. This is a shorter method than using the DOM Range logic. 5897 * 5898 * @method findCommonAncestor 5899 * @param {Element} a Element to find common ancestor of. 5900 * @param {Element} b Element to find common ancestor of. 5901 * @return {Element} Common ancestor element of the two input elements. 5902 */ 5903 findCommonAncestor: function(a, b) { 5904 var ps = a, pe; 5905 5906 while (ps) { 5907 pe = b; 5908 5909 while (pe && ps != pe) { 5910 pe = pe.parentNode; 5911 } 5912 5913 if (ps == pe) { 5914 break; 5915 } 5916 5917 ps = ps.parentNode; 5918 } 5919 5920 if (!ps && a.ownerDocument) { 5921 return a.ownerDocument.documentElement; 5922 } 5923 5924 return ps; 5925 }, 5926 5927 /** 5928 * Parses the specified RGB color value and returns a hex version of that color. 5929 * 5930 * @method toHex 5931 * @param {String} rgbVal RGB string value like rgb(1,2,3) 5932 * @return {String} Hex version of that RGB value like #FF00FF. 5933 */ 5934 toHex: function(rgbVal) { 5935 return this.styles.toHex(Tools.trim(rgbVal)); 5936 }, 5937 5938 /** 5939 * Executes the specified function on the element by id or dom element node or array of elements/id. 5940 * 5941 * @method run 5942 * @param {String/Element/Array} Element ID or DOM element object or array with ids or elements. 5943 * @param {function} f Function to execute for each item. 5944 * @param {Object} s Optional scope to execute the function in. 5945 * @return {Object/Array} Single object, or an array of objects if multiple input elements were passed in. 5946 */ 5947 run: function(elm, func, scope) { 5948 var self = this, result; 5949 5950 if (typeof(elm) === 'string') { 5951 elm = self.get(elm); 5952 } 5953 5954 if (!elm) { 5955 return false;
5956 } 5957 5958 scope = scope || this; 5959 if (!elm.nodeType && (elm.length || elm.length === 0)) { 5960 result = []; 5961 5962 each(elm, function(elm, i) { 5963 if (elm) { 5964 if (typeof(elm) == 'string') { 5965 elm = self.get(elm); 5966 } 5967 5968 result.push(func.call(scope, elm, i)); 5969 } 5970 }); 5971 5972 return result; 5973 } 5974 5975 return func.call(scope, elm); 5976 }, 5977 5978 /** 5979 * Returns a NodeList with attributes for the element. 5980 * 5981 * @method getAttribs 5982 * @param {HTMLElement/string} elm Element node or string id to get attributes from. 5983 * @return {NodeList} NodeList with attributes. 5984 */ 5985 getAttribs: function(elm) { 5986 var attrs; 5987 5988 elm = this.get(elm); 5989 5990 if (!elm) { 5991 return []; 5992 } 5993 5994 if (isIE) { 5995 attrs = []; 5996 5997 // Object will throw exception in IE 5998 if (elm.nodeName == 'OBJECT') { 5999 return elm.attributes; 6000 } 6001 6002 // IE doesn't keep the selected attribute if you clone option elements 6003 if (elm.nodeName === 'OPTION' && this.getAttrib(elm, 'selected')) { 6004 attrs.push({specified: 1, nodeName: 'selected'}); 6005 } 6006 6007 // It's crazy that this is faster in IE but it's because it returns all attributes all the time 6008 var attrRegExp = /<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi; 6009 elm.cloneNode(false).outerHTML.replace(attrRegExp, '').replace(/[\w:\-]+/gi, function(a) { 6010 attrs.push({specified: 1, nodeName: a}); 6011 }); 6012 6013 return attrs; 6014 } 6015 6016 return elm.attributes; 6017 }, 6018 6019 /** 6020 * Returns true/false if the specified node is to be considered empty or not. 6021 * 6022 * @example 6023 * tinymce.DOM.isEmpty(node, {img: true}); 6024 * @method isEmpty 6025 * @param {Object} elements Optional name/value object with elements that are automatically treated as non-empty elements. 6026 * @return {Boolean} true/false if the node is empty or not. 6027 */ 6028 isEmpty: function(node, elements) { 6029 var self = this, i, attributes, type, walker, name, brCount = 0; 6030 6031 node = node.firstChild; 6032 if (node) { 6033 walker = new TreeWalker(node, node.parentNode); 6034 elements = elements || (self.schema ? self.schema.getNonEmptyElements() : null); 6035 6036 do { 6037 type = node.nodeType; 6038 6039 if (type === 1) { 6040 // Ignore bogus elements 6041 if (node.getAttribute('data-mce-bogus')) { 6042 continue; 6043 } 6044 6045 // Keep empty elements like <img /> 6046 name = node.nodeName.toLowerCase(); 6047 if (elements && elements[name]) { 6048 // Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p> 6049 if (name === 'br') { 6050 brCount++; 6051 continue; 6052 } 6053 6054 return false; 6055 } 6056 6057 // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a> 6058 attributes = self.getAttribs(node); 6059 i = attributes.length; 6060 while (i--) { 6061 name = attributes[i].nodeName; 6062 if (name === "name" || name === 'data-mce-bookmark') { 6063 return false; 6064 } 6065 } 6066 } 6067 6068 // Keep comment nodes 6069 if (type == 8) { 6070 return false; 6071 } 6072 6073 // Keep non whitespace text nodes 6074 if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) { 6075 return false; 6076 } 6077 } while ((node = walker.next())); 6078 } 6079 6080 return brCount <= 1; 6081 }, 6082 6083 /** 6084 * Creates a new DOM Range object. This will use the native DOM Range API if it's 6085 * available. If it's not, it will fall back to the custom TinyMCE implementation. 6086 * 6087 * @method createRng 6088 * @return {DOMRange} DOM Range object. 6089 * @example 6090 * var rng = tinymce.DOM.createRng(); 6091 * alert(rng.startContainer + "," + rng.startOffset); 6092 */ 6093 createRng: function() { 6094 var doc = this.doc; 6095 6096 return doc.createRange ? doc.createRange() : new Range(this); 6097 }, 6098 6099 /** 6100 * Returns the index of the specified node within its parent. 6101 * 6102 * @method nodeIndex 6103 * @param {Node} node Node to look for. 6104 * @param {boolean} normalized Optional true/false state if the index is what it would be after a normalization. 6105 * @return {Number} Index of the specified node. 6106 */ 6107 nodeIndex: function(node, normalized) { 6108 var idx = 0, lastNodeType, nodeType; 6109 6110 if (node) { 6111 for (lastNodeType = node.nodeType, node = node.previousSibling; node; node = node.previousSibling) { 6112 nodeType = node.nodeType; 6113 6114 // Normalize text nodes 6115 if (normalized && nodeType == 3) { 6116 if (nodeType == lastNodeType || !node.nodeValue.length) { 6117 continue; 6118 } 6119 } 6120 idx++; 6121 lastNodeType = nodeType; 6122 } 6123 } 6124 6125 return idx; 6126 }, 6127 6128 /** 6129 * Splits an element into two new elements and places the specified split 6130 * element or elements between the new ones. For example splitting the paragraph at the bold element in 6131 * this example <p>abc<b>abc</b>123</p> would produce <p>abc</p><b>abc</b><p>123</p>. 6132 * 6133 * @method split 6134 * @param {Element} parentElm Parent element to split. 6135 * @param {Element} splitElm Element to split at. 6136 * @param {Element} replacementElm Optional replacement element to replace the split element with. 6137 * @return {Element} Returns the split element or the replacement element if that is specified. 6138 */ 6139 split: function(parentElm, splitElm, replacementElm) { 6140 var self = this, r = self.createRng(), bef, aft, pa; 6141 6142 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents - this makes sense
6143 // but we don't want that in our code since it serves no purpose for the end user 6144 // For example splitting this html at the bold element: 6145 // <p>text 1<span><b>CHOP</b></span>text 2</p> 6146 // would produce: 6147 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p> 6148 // this function will then trim off empty edges and produce: 6149 // <p>text 1</p><b>CHOP</b><p>text 2</p> 6150 function trimNode(node) { 6151 var i, children = node.childNodes, type = node.nodeType; 6152 6153 function surroundedBySpans(node) { 6154 var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN'; 6155 var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN'; 6156 return previousIsSpan && nextIsSpan; 6157 } 6158 6159 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') { 6160 return; 6161 } 6162 6163 for (i = children.length - 1; i >= 0; i--) { 6164 trimNode(children[i]); 6165 } 6166 6167 if (type != 9) { 6168 // Keep non whitespace text nodes 6169 if (type == 3 && node.nodeValue.length > 0) { 6170 // If parent element isn't a block or there isn't any useful contents for example "<p> </p>" 6171 // Also keep text nodes with only spaces if surrounded by spans. 6172 // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b 6173 var trimmedLength = trim(node.nodeValue).length; 6174 if (!self.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) { 6175 return; 6176 } 6177 } else if (type == 1) { 6178 // If the only child is a bookmark then move it up 6179 children = node.childNodes; 6180 6181 // TODO fix this complex if 6182 if (children.length == 1 && children[0] && children[0].nodeType == 1 && 6183 children[0].getAttribute('data-mce-type') == 'bookmark') { 6184 node.parentNode.insertBefore(children[0], node); 6185 } 6186 6187 // Keep non empty elements or img, hr etc 6188 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) { 6189 return; 6190 } 6191 } 6192 6193 self.remove(node); 6194 } 6195 6196 return node; 6197 } 6198 6199 if (parentElm && splitElm) { 6200 // Get before chunk 6201 r.setStart(parentElm.parentNode, self.nodeIndex(parentElm)); 6202 r.setEnd(splitElm.parentNode, self.nodeIndex(splitElm)); 6203 bef = r.extractContents(); 6204 6205 // Get after chunk 6206 r = self.createRng(); 6207 r.setStart(splitElm.parentNode, self.nodeIndex(splitElm) + 1); 6208 r.setEnd(parentElm.parentNode, self.nodeIndex(parentElm) + 1); 6209 aft = r.extractContents(); 6210 6211 // Insert before chunk 6212 pa = parentElm.parentNode; 6213 pa.insertBefore(trimNode(bef), parentElm); 6214 6215 // Insert middle chunk 6216 if (replacementElm) { 6217 pa.replaceChild(replacementElm, splitElm); 6218 } else { 6219 pa.insertBefore(splitElm, parentElm); 6220 } 6221 6222 // Insert after chunk 6223 pa.insertBefore(trimNode(aft), parentElm); 6224 self.remove(parentElm); 6225 6226 return replacementElm || splitElm; 6227 } 6228 }, 6229 6230 /** 6231 * Adds an event handler to the specified object. 6232 * 6233 * @method bind 6234 * @param {Element/Document/Window/Array} target Target element to bind events to. 6235 * handler to or an array of elements/ids/documents. 6236 * @param {String} name Name of event handler to add, for example: click. 6237 * @param {function} func Function to execute when the event occurs. 6238 * @param {Object} scope Optional scope to execute the function in. 6239 * @return {function} Function callback handler the same as the one passed in. 6240 */ 6241 bind: function(target, name, func, scope) { 6242 var self = this; 6243 6244 if (Tools.isArray(target)) { 6245 var i = target.length; 6246 6247 while (i--) { 6248 target[i] = self.bind(target[i], name, func, scope); 6249 } 6250 6251 return target; 6252 } 6253 6254 // Collect all window/document events bound by editor instance 6255 if (self.settings.collect && (target === self.doc || target === self.win)) { 6256 self.boundEvents.push([target, name, func, scope]); 6257 } 6258 6259 return self.events.bind(target, name, func, scope || self); 6260 }, 6261 6262 /** 6263 * Removes the specified event handler by name and function from an element or collection of elements. 6264 * 6265 * @method unbind 6266 * @param {Element/Document/Window/Array} target Target element to unbind events on. 6267 * @param {String} name Event handler name, for example: "click" 6268 * @param {function}
6268 func Function to remove. 6269 * @return {bool/Array} Bool state of true if the handler was removed, or an array of states if multiple input elements 6270 * were passed in. 6271 */ 6272 unbind: function(target, name, func) { 6273 var self = this, i; 6274 6275 if (Tools.isArray(target)) { 6276 i = target.length; 6277 6278 while (i--) { 6279 target[i] = self.unbind(target[i], name, func); 6280 } 6281 6282 return target; 6283 } 6284 6285 // Remove any bound events matching the input 6286 if (self.boundEvents && (target === self.doc || target === self.win)) { 6287 i = self.boundEvents.length; 6288 6289 while (i--) { 6290 var item = self.boundEvents[i]; 6291 6292 if (target == item[0] && (!name || name == item[1]) && (!func || func == item[2])) { 6293 this.events.unbind(item[0], item[1], item[2]); 6294 } 6295 } 6296 } 6297 6298 return this.events.unbind(target, name, func); 6299 }, 6300 6301 /** 6302 * Fires the specified event name with object on target. 6303 * 6304 * @method fire 6305 * @param {Node/Document/Window} target Target element or object to fire event on. 6306 * @param {String} name Name of the event to fire. 6307 * @param {Object} evt Event object to send. 6308 * @return {Event} Event object. 6309 */ 6310 fire: function(target, name, evt) { 6311 return this.events.fire(target, name, evt); 6312 }, 6313 6314 // Returns the content editable state of a node 6315 getContentEditable: function(node) { 6316 var contentEditable; 6317 6318 // Check type 6319 if (!node || node.nodeType != 1) { 6320 return null; 6321 } 6322 6323 // Check for fake content editable 6324 contentEditable = node.getAttribute("data-mce-contenteditable"); 6325 if (contentEditable && contentEditable !== "inherit") { 6326 return contentEditable; 6327 } 6328 6329 // Check for real content editable 6330 return node.contentEditable !== "inherit" ? node.contentEditable : null; 6331 }, 6332 6333 getContentEditableParent: function(node) { 6334 var root = this.getRoot(), state = null; 6335 6336 for (; node && node !== root; node = node.parentNode) { 6337 state = this.getContentEditable(node); 6338 6339 if (state !== null) { 6340 break; 6341 } 6342 } 6343 6344 return state; 6345 }, 6346 6347 /** 6348 * Destroys all internal references to the DOM to solve IE leak issues. 6349 * 6350 * @method destroy 6351 */ 6352 destroy: function() { 6353 var self = this; 6354 6355 // Unbind all events bound to window/document by editor instance 6356 if (self.boundEvents) { 6357 var i = self.boundEvents.length; 6358 6359 while (i--) { 6360 var item = self.boundEvents[i]; 6361 this.events.unbind(item[0], item[1], item[2]); 6362 } 6363 6364 self.boundEvents = null; 6365 } 6366 6367 // Restore sizzle document to window.document 6368 // Since the current document might be removed producing "Permission denied" on IE see #6325 6369 if (Sizzle.setDocument) { 6370 Sizzle.setDocument(); 6371 } 6372 6373 self.win = self.doc = self.root = self.events = self.frag = null; 6374 }, 6375 6376 isChildOf: function(node, parent) { 6377 while (node) { 6378 if (parent === node) { 6379 return true; 6380 } 6381 6382 node = node.parentNode; 6383 } 6384 6385 return false; 6386 }, 6387 6388 // #ifdef debug 6389 6390 dumpRng: function(r) { 6391 return ( 6392 'startContainer: ' + r.startContainer.nodeName + 6393 ', startOffset: ' + r.startOffset + 6394 ', endContainer: ' + r.endContainer.nodeName + 6395 ', endOffset: ' + r.endOffset 6396 ); 6397 }, 6398 6399 // #endif 6400 6401 _findSib: function(node, selector, name) { 6402 var self = this, func = selector; 6403 6404 if (node) { 6405 // If expression make a function of it using is 6406 if (typeof(func) == 'string') { 6407 func = function(node) { 6408 return self.is(node, selector); 6409 }; 6410 } 6411 6412 // Loop all siblings 6413 for (node = node[name]; node; node = node[name]) { 6414 if (func(node)) { 6415 return node; 6416 } 6417 } 6418 } 6419 6420 return null; 6421 } 6422 }; 6423 6424 /** 6425 * Instance of DOMUtils for the current document. 6426 * 6427 * @static 6428 * @property DOM 6429 * @type tinymce.dom.DOMUtils 6430 * @example 6431 * // Example of how to add a class to some element by id 6432 * tinymce.DOM.addClass('someid', 'someclass'); 6433 */ 6434 DOMUtils.DOM = new DOMUtils(document); 6435 6436 return DOMUtils; 6437}); 6438 6439// Included from: js/tinymce/classes/dom/ScriptLoader.js 6440 6441/** 6442 * ScriptLoader.js 6443 * 6444 * Copyright, Moxiecode Systems AB 6445 * Released under LGPL License. 6446 * 6447 * License: http://www.tinymce.com/license 6448 * Contributing: http://www.tinymce.com/contributing 6449 */ 6450 6451/*globals console*/ 6452 6453/** 6454 * This class handles asynchronous/synchronous loading of JavaScript files it will execute callbacks 6455 * when various items gets loaded. This class is useful to load external JavaScript files. 6456 * 6457 * @class tinymce.dom.ScriptLoader 6458 * @example 6459 * // Load a script from a specific URL using the global script loader 6460 * tinymce.ScriptLoader.load('somescript.js'); 6461 *
6462 * // Load a script using a unique instance of the script loader 6463 * var scriptLoader = new tinymce.dom.ScriptLoader(); 6464 * 6465 * scriptLoader.load('somescript.js'); 6466 * 6467 * // Load multiple scripts 6468 * var scriptLoader = new tinymce.dom.ScriptLoader(); 6469 * 6470 * scriptLoader.add('somescript1.js'); 6471 * scriptLoader.add('somescript2.js'); 6472 * scriptLoader.add('somescript3.js'); 6473 * 6474 * scriptLoader.loadQueue(function() { 6475 * alert('All scripts are now loaded.'); 6476 * }); 6477 */ 6478define("tinymce/dom/ScriptLoader", [ 6479 "tinymce/dom/DOMUtils", 6480 "tinymce/util/Tools" 6481], function(DOMUtils, Tools) { 6482 var DOM = DOMUtils.DOM; 6483 var each = Tools.each, grep = Tools.grep; 6484 6485 function ScriptLoader() { 6486 var QUEUED = 0, 6487 LOADING = 1, 6488 LOADED = 2, 6489 states = {}, 6490 queue = [], 6491 scriptLoadedCallbacks = {}, 6492 queueLoadedCallbacks = [], 6493 loading = 0, 6494 undef; 6495 6496 /** 6497 * Loads a specific script directly without adding it to the load queue. 6498 * 6499 * @method load 6500 * @param {String} url Absolute URL to script to add. 6501 * @param {function} callback Optional callback function to execute ones this script gets loaded. 6502 * @param {Object} scope Optional scope to execute callback in. 6503 */ 6504 function loadScript(url, callback) { 6505 var dom = DOM, elm, id; 6506 6507 // Execute callback when script is loaded 6508 function done() { 6509 dom.remove(id); 6510 6511 if (elm) { 6512 elm.onreadystatechange = elm.onload = elm = null; 6513 } 6514 6515 callback(); 6516 } 6517 6518 function error() { 6519 /*eslint no-console:0 */ 6520 6521 // Report the error so it's easier for people to spot loading errors 6522 if (typeof(console) !== "undefined" && console.log) { 6523 console.log("Failed to load: " + url); 6524 } 6525 6526 // We can't mark it as done if there is a load error since 6527 // A) We don't want to produce 404 errors on the server and 6528 // B) the onerror event won't fire on all browsers. 6529 // done(); 6530 } 6531 6532 id = dom.uniqueId(); 6533 6534 // Create new script element 6535 elm = document.createElement('script'); 6536 elm.id = id; 6537 elm.type = 'text/javascript'; 6538 elm.src = Tools._addCacheSuffix(url); 6539 6540 // Seems that onreadystatechange works better on IE 10 onload seems to fire incorrectly 6541 if ("onreadystatechange" in elm) { 6542 elm.onreadystatechange = function() { 6543 if (/loaded|complete/.test(elm.readyState)) { 6544 done(); 6545 } 6546 }; 6547 } else { 6548 elm.onload = done; 6549 } 6550 6551 // Add onerror event will get fired on some browsers but not all of them 6552 elm.onerror = error; 6553 6554 // Add script to document 6555 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm); 6556 } 6557 6558 /** 6559 * Returns true/false if a script has been loaded or not. 6560 * 6561 * @method isDone 6562 * @param {String} url URL to check for. 6563 * @return {Boolean} true/false if the URL is loaded. 6564 */ 6565 this.isDone = function(url) { 6566 return states[url] == LOADED; 6567 }; 6568 6569 /** 6570 * Marks a specific script to be loaded. This can be useful if a script got loaded outside 6571 * the script loader or to skip it from loading some script. 6572 * 6573 * @method markDone 6574 * @param {string} u Absolute URL to the script to mark as loaded. 6575 */ 6576 this.markDone = function(url) { 6577 states[url] = LOADED; 6578 }; 6579 6580 /** 6581 * Adds a specific script to the load queue of the script loader. 6582 * 6583 * @method add 6584 * @param {String} url Absolute URL to script to add. 6585 * @param {function} callback Optional callback function to execute ones this script gets loaded. 6586 * @param {Object} scope Optional scope to execute callback in. 6587 */ 6588 this.add = this.load = function(url, callback, scope) { 6589 var state = states[url]; 6590 6591 // Add url to load queue 6592 if (state == undef) { 6593 queue.push(url); 6594 states[url] = QUEUED; 6595 } 6596 6597 if (callback) { 6598 // Store away callback for later execution 6599 if (!scriptLoadedCallbacks[url]) { 6600 scriptLoadedCallbacks[url] = []; 6601 } 6602 6603 scriptLoadedCallbacks[url].push({ 6604 func: callback, 6605 scope: scope || this 6606 }); 6607 } 6608 }; 6609 6610 /** 6611 * Starts the loading of the queue. 6612 * 6613 * @method loadQueue 6614 * @param {function} callback Optional callback to execute when all queued items are loaded. 6615 * @param {Object} scope Optional scope to execute the callback in. 6616 */ 6617 this.loadQueue = function(callback, scope) { 6618 this.loadScripts(queue, callback, scope); 6619 }; 6620 6621 /** 6622 * Loads the specified queue of files and executes the callback ones they are loaded. 6623 * This method is generally not used outside this class but it might be useful in some scenarios. 6624 * 6625 * @method loadScripts 6626 * @param {Array} scripts Array of queue items to load. 6627 * @param {function} callback Optional callback to execute ones all items are loaded. 6628 * @param {Object} scope Optional scope to execute callback in. 6629 */ 6630 this.loadScripts = function(scripts, callback, scope) { 6631 var loadScripts; 6632 6633 function execScriptLoadedCallbacks(url) { 6634 // Execute URL callback functions 6635 each(scriptLoadedCallbacks[url], function(callback) { 6636 callback.func.call(callback.scope); 6637 }); 6638 6639 scriptLoadedCallbacks[url] = undef; 6640 } 6641 6642 queueLoadedCallbacks.push({ 6643 func: callback, 6644 scope: scope || this 6645 }); 6646 6647 loadScripts = function() { 6648 var loadingScripts = grep(scripts); 6649 6650 // Current scripts has been handled 6651 scripts.length = 0; 6652 6653 // Load scripts that needs to be loaded 6654 each(loadingScripts, function(url) { 6655 // Script is already loaded then execute script callbacks directly 6656 if (states[url] == LOADED) { 6657 execScriptLoadedCallbacks(url); 6658 return; 6659 } 6660 6661 // Is script not loading then start loading it 6662 if (states[url] != LOADING) { 6663 states[url] = LOADING; 6664 loading++; 6665 6666 loadScript(url, function() { 6667 states[url] = LOADED; 6668 loading--; 6669 6670 execScriptLoadedCallbacks(url); 6671 6672 // Load more scripts if they where added by the recently loaded script 6673 loadScripts(); 6674 }); 6675 } 6676 }); 6677 6678 // No scripts are currently loading then execute all pending queue loaded callbacks 6679 if (!loading) { 6680 each(queueLoadedCallbacks, function(callback) { 6681 callback.func.call(callback.scope); 6682 }); 6683 6684 queueLoadedCallbacks.length = 0; 6685 } 6686 }; 6687 6688 loadScripts(); 6689 }; 6690 } 6691 6692 ScriptLoader.ScriptLoader = new ScriptLoader(); 6693 6694 return ScriptLoader; 6695}); 6696 6697// Included from: js/tinymce/classes/AddOnManager.js 6698 6699/** 6700 * AddOnManager.js 6701 * 6702 * Copyright, Moxiecode Systems AB 6703 * Released under LGPL License. 6704 * 6705 * License: http://www.tinymce.com/license 6706 * Contributing: http://www.tinymce.com/contributing 6707 */ 6708 6709/** 6710 * This class handles the loading of themes/plugins or other add-ons and their language packs. 6711 * 6712 * @class tinymce.AddOnManager 6713 */ 6714define("tinymce/AddOnManager", [ 6715 "tinymce/dom/ScriptLoader", 6716 "tinymce/util/Tools" 6717], function(ScriptLoader, Tools) { 6718 var each = Tools.each; 6719 6720 function AddOnManager() { 6721 var self = this; 6722 6723 self.items = []; 6724 self.urls = {}; 6725 self.lookup = {}; 6726 } 6727 6728 AddOnManager.prototype = { 6729 /** 6730 * Returns the specified add on by the short name. 6731 * 6732 * @method get 6733 * @param {String} name Add-on to look for. 6734 * @return {tinymce.Theme/tinymce.Plugin}
6734 Theme or plugin add-on instance or undefined. 6735 */ 6736 get: function(name) { 6737 if (this.lookup[name]) { 6738 return this.lookup[name].instance; 6739 } else { 6740 return undefined; 6741 } 6742 }, 6743 6744 dependencies: function(name) { 6745 var result; 6746 6747 if (this.lookup[name]) { 6748 result = this.lookup[name].dependencies; 6749 } 6750 6751 return result || []; 6752 }, 6753 6754 /** 6755 * Loads a language pack for the specified add-on. 6756 * 6757 * @method requireLangPack 6758 * @param {String} name Short name of the add-on. 6759 * @param {String} languages Optional comma or space separated list of languages to check if it matches the name. 6760 */ 6761 requireLangPack: function(name, languages) { 6762 var language = AddOnManager.language; 6763 6764 if (language && AddOnManager.languageLoad !== false) { 6765 if (languages) { 6766 languages = ',' + languages + ','; 6767 6768 // Load short form sv.js or long form sv_SE.js 6769 if (languages.indexOf(',' + language.substr(0, 2) + ',') != -1) { 6770 language = language.substr(0, 2); 6771 } else if (languages.indexOf(',' + language + ',') == -1) { 6772 return; 6773 } 6774 } 6775 6776 ScriptLoader.ScriptLoader.add(this.urls[name] + '/langs/' + language + '.js'); 6777 } 6778 }, 6779 6780 /** 6781 * Adds a instance of the add-on by it's short name. 6782 * 6783 * @method add 6784 * @param {String} id Short name/id for the add-on. 6785 * @param {tinymce.Theme/tinymce.Plugin} addOn Theme or plugin to add. 6786 * @return {tinymce.Theme/tinymce.Plugin} The same theme or plugin instance that got passed in. 6787 * @example 6788 * // Create a simple plugin 6789 * tinymce.create('tinymce.plugins.TestPlugin', { 6790 * TestPlugin: function(ed, url) { 6791 * ed.on('click', function(e) { 6792 * ed.windowManager.alert('Hello World!'); 6793 * }); 6794 * } 6795 * }); 6796 * 6797 * // Register plugin using the add method 6798 * tinymce.PluginManager.add('test', tinymce.plugins.TestPlugin); 6799 * 6800 * // Initialize TinyMCE 6801 * tinymce.init({ 6802 * ... 6803 * plugins: '-test' // Init the plugin but don't try to load it 6804 * }); 6805 */ 6806 add: function(id, addOn, dependencies) { 6807 this.items.push(addOn); 6808 this.lookup[id] = {instance: addOn, dependencies: dependencies}; 6809 6810 return addOn; 6811 }, 6812 6813 createUrl: function(baseUrl, dep) { 6814 if (typeof dep === "object") { 6815 return dep; 6816 } else { 6817 return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix}; 6818 } 6819 }, 6820 6821 /** 6822 * Add a set of components that will make up the add-on. Using the url of the add-on name as the base url. 6823 * This should be used in development mode. A new compressor/javascript munger process will ensure that the 6824 * components are put together into the plugin.js file and compressed correctly. 6825 * 6826 * @method addComponents 6827 * @param {String} pluginName name of the plugin to load scripts from (will be used to get the base url for the plugins). 6828 * @param {Array} scripts Array containing the names of the scripts to load. 6829 */ 6830 addComponents: function(pluginName, scripts) { 6831 var pluginUrl = this.urls[pluginName]; 6832 6833 each(scripts, function(script) { 6834 ScriptLoader.ScriptLoader.add(pluginUrl + "/" + script); 6835 }); 6836 }, 6837 6838 /** 6839 * Loads an add-on from a specific url. 6840 * 6841 * @method load 6842 * @param {String} name Short name of the add-on that gets loaded. 6843 * @param {String} addOnUrl URL to the add-on that will get loaded. 6844 * @param {function} callback Optional callback to execute ones the add-on is loaded. 6845 * @param {Object} scope Optional scope to execute the callback in. 6846 * @example 6847 * // Loads a plugin from an external URL 6848 * tinymce.PluginManager.load('myplugin', '/some/dir/someplugin/plugin.js'); 6849 * 6850 * // Initialize TinyMCE 6851 * tinymce.init({ 6852 * ... 6853 * plugins: '-myplugin' // Don't try to load it again 6854 * }); 6855 */ 6856 load: function(name, addOnUrl, callback, scope) { 6857 var self = this, url = addOnUrl; 6858 6859 function loadDependencies() { 6860 var dependencies = self.dependencies(name); 6861 6862 each(dependencies, function(dep) { 6863 var newUrl = self.createUrl(addOnUrl, dep); 6864 6865 self.load(newUrl.resource, newUrl, undefined, undefined); 6866 }); 6867 6868 if (callback) { 6869 if (scope) { 6870 callback.call(scope); 6871 } else { 6872 callback.call(ScriptLoader); 6873 } 6874 } 6875 } 6876 6877 if (self.urls[name]) { 6878 return; 6879 } 6880 6881 if (typeof addOnUrl === "object") { 6882 url = addOnUrl.prefix + addOnUrl.resource + addOnUrl.suffix; 6883 } 6884 6885 if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) { 6886 url = AddOnManager.baseURL + '/' + url; 6887 } 6888 6889 self.urls[name] = url.substring(0, url.lastIndexOf('/')); 6890 6891 if (self.lookup[name]) { 6892 loadDependencies(); 6893 } else { 6894 ScriptLoader.ScriptLoader.add(url, loadDependencies, scope); 6895 } 6896 } 6897 }; 6898 6899 AddOnManager.PluginManager = new AddOnManager(); 6900 AddOnManager.ThemeManager = new AddOnManager(); 6901 6902 return AddOnManager; 6903}); 6904 6905/** 6906 * TinyMCE theme class. 6907 * 6908 * @class tinymce.Theme 6909 */ 6910 6911/** 6912 * This method is responsible for rendering/generating the overall user interface with toolbars, buttons, iframe containers etc. 6913 * 6914 * @method renderUI 6915 * @param {Object} obj Object parameter containing the targetNode DOM node that will be replaced visually with an editor instance. 6916 * @return {Object}
6916 an object with items like iframeContainer, editorContainer, sizeContainer, deltaWidth, deltaHeight. 6917 */ 6918 6919/** 6920 * Plugin base class, this is a pseudo class that describes how a plugin is to be created for TinyMCE. The methods below are all optional. 6921 * 6922 * @class tinymce.Plugin 6923 * @example 6924 * tinymce.PluginManager.add('example', function(editor, url) { 6925 * // Add a button that opens a window 6926 * editor.addButton('example', { 6927 * text: 'My button', 6928 * icon: false, 6929 * onclick: function() { 6930 * // Open window 6931 * editor.windowManager.open({ 6932 * title: 'Example plugin', 6933 * body: [ 6934 * {type: 'textbox', name: 'title', label: 'Title'} 6935 * ], 6936 * onsubmit: function(e) { 6937 * // Insert content when the window form is submitted 6938 * editor.insertContent('Title: ' + e.data.title); 6939 * } 6940 * }); 6941 * } 6942 * }); 6943 * 6944 * // Adds a menu item to the tools menu 6945 * editor.addMenuItem('example', { 6946 * text: 'Example plugin', 6947 * context: 'tools', 6948 * onclick: function() { 6949 * // Open window with a specific url 6950 * editor.windowManager.open({ 6951 * title: 'TinyMCE site', 6952 * url: 'http://www.tinymce.com', 6953 * width: 800, 6954 * height: 600, 6955 * buttons: [{ 6956 * text: 'Close', 6957 * onclick: 'close' 6958 * }] 6959 * }); 6960 * } 6961 * }); 6962 * }); 6963 */ 6964 6965// Included from: js/tinymce/classes/dom/RangeUtils.js 6966 6967/** 6968 * RangeUtils.js 6969 * 6970 * Copyright, Moxiecode Systems AB 6971 * Released under LGPL License. 6972 * 6973 * License: http://www.tinymce.com/license 6974 * Contributing: http://www.tinymce.com/contributing 6975 */ 6976 6977/** 6978 * This class contains a few utility methods for ranges. 6979 * 6980 * @class tinymce.dom.RangeUtils 6981 * @private 6982 */ 6983define("tinymce/dom/RangeUtils", [ 6984 "tinymce/util/Tools", 6985 "tinymce/dom/TreeWalker" 6986], function(Tools, TreeWalker) { 6987 var each = Tools.each; 6988 6989 function getEndChild(container, index) { 6990 var childNodes = container.childNodes; 6991 6992 index--; 6993 6994 if (index > childNodes.length - 1) { 6995 index = childNodes.length - 1; 6996 } else if (index < 0) { 6997 index = 0; 6998 } 6999 7000 return childNodes[index] || container; 7001 } 7002 7003 function RangeUtils(dom) { 7004 /** 7005 * Walks the specified range like object and executes the callback for each sibling collection it finds. 7006 * 7007 * @method walk 7008 * @param {Object} rng Range like object. 7009 * @param {function} callback Callback function to execute for each sibling collection. 7010 */ 7011 this.walk = function(rng, callback) { 7012 var startContainer = rng.startContainer, 7013 startOffset = rng.startOffset, 7014 endContainer = rng.endContainer, 7015 endOffset = rng.endOffset, 7016 ancestor, startPoint, 7017 endPoint, node, parent, siblings, nodes; 7018 7019 // Handle table cell selection the table plugin enables 7020 // you to fake select table cells and perform formatting actions on them
7021 nodes = dom.select('td.mce-item-selected,th.mce-item-selected'); 7022 if (nodes.length > 0) { 7023 each(nodes, function(node) { 7024 callback([node]); 7025 }); 7026 7027 return; 7028 } 7029 7030 /** 7031 * Excludes start/end text node if they are out side the range 7032 * 7033 * @private 7034 * @param {Array} nodes Nodes to exclude items from. 7035 * @return {Array} Array with nodes excluding the start/end container if needed. 7036 */ 7037 function exclude(nodes) { 7038 var node; 7039 7040 // First node is excluded 7041 node = nodes[0]; 7042 if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) { 7043 nodes.splice(0, 1); 7044 } 7045 7046 // Last node is excluded 7047 node = nodes[nodes.length - 1]; 7048 if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) { 7049 nodes.splice(nodes.length - 1, 1); 7050 } 7051 7052 return nodes; 7053 } 7054 7055 /** 7056 * Collects siblings 7057 * 7058 * @private 7059 * @param {Node} node Node to collect siblings from. 7060 * @param {String} name Name of the sibling to check for. 7061 * @return {Array} Array of collected siblings. 7062 */ 7063 function collectSiblings(node, name, end_node) { 7064 var siblings = []; 7065 7066 for (; node && node != end_node; node = node[name]) { 7067 siblings.push(node); 7068 } 7069 7070 return siblings; 7071 } 7072 7073 /** 7074 * Find an end point this is the node just before the common ancestor root. 7075 * 7076 * @private 7077 * @param {Node} node Node to start at. 7078 * @param {Node} root Root/ancestor element to stop just before. 7079 * @return {Node} Node just before the root element. 7080 */ 7081 function findEndPoint(node, root) { 7082 do { 7083 if (node.parentNode == root) { 7084 return node; 7085 } 7086 7087 node = node.parentNode; 7088 } while (node); 7089 } 7090
7091 function walkBoundary(start_node, end_node, next) { 7092 var siblingName = next ? 'nextSibling' : 'previousSibling'; 7093 7094 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) { 7095 parent = node.parentNode; 7096 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName); 7097 7098 if (siblings.length) { 7099 if (!next) { 7100 siblings.reverse(); 7101 } 7102 7103 callback(exclude(siblings)); 7104 } 7105 } 7106 } 7107 7108 // If index based start position then resolve it 7109 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { 7110 startContainer = startContainer.childNodes[startOffset]; 7111 } 7112 7113 // If index based end position then resolve it 7114 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { 7115 endContainer = getEndChild(endContainer, endOffset); 7116 } 7117 7118 // Same container 7119 if (startContainer == endContainer) { 7120 return callback(exclude([startContainer])); 7121 } 7122 7123 // Find common ancestor and end points 7124 ancestor = dom.findCommonAncestor(startContainer, endContainer); 7125 7126 // Process left side 7127 for (node = startContainer; node; node = node.parentNode) { 7128 if (node === endContainer) { 7129 return walkBoundary(startContainer, ancestor, true); 7130 } 7131 7132 if (node === ancestor) { 7133 break; 7134 } 7135 } 7136 7137 // Process right side 7138 for (node = endContainer; node; node = node.parentNode) { 7139 if (node === startContainer) { 7140 return walkBoundary(endContainer, ancestor); 7141 } 7142 7143 if (node === ancestor) { 7144 break; 7145 } 7146 } 7147 7148 // Find start/end point 7149 startPoint = findEndPoint(startContainer, ancestor) || startContainer; 7150 endPoint = findEndPoint(endContainer, ancestor) || endContainer; 7151 7152 // Walk left leaf 7153 walkBoundary(startContainer, startPoint, true); 7154 7155 // Walk the middle from start to end point 7156 siblings = collectSiblings( 7157 startPoint == startContainer ? startPoint : startPoint.nextSibling, 7158 'nextSibling', 7159 endPoint == endContainer ? endPoint.nextSibling : endPoint 7160 ); 7161 7162 if (siblings.length) { 7163 callback(exclude(siblings)); 7164 } 7165 7166 // Walk right leaf 7167 walkBoundary(endContainer, endPoint); 7168 }; 7169 7170 /** 7171 * Splits the specified range at it's start/end points. 7172 * 7173 * @private 7174 * @param {Range/RangeObject} rng Range to split. 7175 * @return {Object} Range position object. 7176 */ 7177 this.split = function(rng) { 7178 var startContainer = rng.startContainer, 7179 startOffset = rng.startOffset, 7180 endContainer = rng.endContainer, 7181 endOffset = rng.endOffset; 7182 7183 function splitText(node, offset) { 7184 return node.splitText(offset); 7185 } 7186 7187 // Handle single text node 7188 if (startContainer == endContainer && startContainer.nodeType == 3) { 7189 if (startOffset > 0 && startOffset < startContainer.nodeValue.length) { 7190 endContainer = splitText(startContainer, startOffset); 7191 startContainer = endContainer.previousSibling; 7192 7193 if (endOffset > startOffset) { 7194 endOffset = endOffset - startOffset; 7195 startContainer = endContainer = splitText(endContainer, endOffset).previousSibling; 7196 endOffset = endContainer.nodeValue.length; 7197 startOffset = 0; 7198 } else { 7199 endOffset = 0; 7200 } 7201 } 7202 } else { 7203 // Split startContainer text node if needed 7204 if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) { 7205 startContainer = splitText(startContainer, startOffset); 7206 startOffset = 0; 7207 } 7208 7209 // Split endContainer text node if needed 7210 if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) { 7211 endContainer = splitText(endContainer, endOffset).previousSibling; 7212 endOffset = endContainer.nodeValue.length; 7213 } 7214 } 7215 7216 return { 7217 startContainer: startContainer, 7218 startOffset: startOffset, 7219 endContainer: endContainer, 7220 endOffset: endOffset 7221 }; 7222 }; 7223 7224 /** 7225 * Normalizes the specified range by finding the closest best suitable caret location. 7226 * 7227 * @private 7228 * @param {Range} rng Range to normalize. 7229 * @return {Boolean} True/false if the specified range was normalized or not. 7230 */ 7231 this.normalize = function(rng) { 7232 var normalized, collapsed; 7233 7234 function normalizeEndPoint(start) { 7235 var container, offset, walker, body = dom.getRoot(), node, nonEmptyElementsMap; 7236 var directionLeft, isAfterNode; 7237 7238 function hasBrBeforeAfter(node, left) { 7239 var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body); 7240 7241 while ((node = walker[left ? 'prev' : 'next']())) { 7242 if (node.nodeName === "BR") { 7243 return true; 7244 } 7245 } 7246 } 7247 7248 function isPrevNode(node, name) { 7249 return node.previousSibling && node.previousSibling.nodeName == name; 7250 } 7251 7252 // Walks the dom left/right to find a suitable text node to move the endpoint into 7253 // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG 7254 function findTextNodeRelative(left, startNode) { 7255 var walker, lastInlineElement, parentBlockContainer; 7256 7257 startNode = startNode || container; 7258 parentBlockContainer = dom.getParent(startNode.parentNode, dom.isBlock) || body; 7259 7260 // Lean left before the BR element if it's the only BR within a block element. Gecko bug: #6680 7261 // This: <p><br>|</p> becomes <p>|<br></p> 7262 if (left && startNode.nodeName == 'BR' && isAfterNode && dom.isEmpty(parentBlockContainer)) { 7263 container = startNode.parentNode; 7264 offset = dom.nodeIndex(startNode); 7265 normalized = true; 7266 return; 7267 } 7268 7269 // Walk left until we hit a text node we can move to or a block/br/img 7270 walker = new TreeWalker(startNode, parentBlockContainer); 7271 while ((node = walker[left ? 'prev' : 'next']())) { 7272 // Break if we hit a non content editable node
7273 if (dom.getContentEditableParent(node) === "false") { 7274 return; 7275 } 7276 7277 // Found text node that has a length 7278 if (node.nodeType === 3 && node.nodeValue.length > 0) { 7279 container = node; 7280 offset = left ? node.nodeValue.length : 0; 7281 normalized = true; 7282 return; 7283 } 7284 7285 // Break if we find a block or a BR/IMG/INPUT etc 7286 if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 7287 return; 7288 } 7289 7290 lastInlineElement = node; 7291 } 7292 7293 // Only fetch the last inline element when in caret mode for now 7294 if (collapsed && lastInlineElement) { 7295 container = lastInlineElement; 7296 normalized = true; 7297 offset = 0; 7298 } 7299 } 7300 7301 container = rng[(start ? 'start' : 'end') + 'Container']; 7302 offset = rng[(start ? 'start' : 'end') + 'Offset']; 7303 isAfterNode = container.nodeType == 1 && offset === container.childNodes.length; 7304 nonEmptyElementsMap = dom.schema.getNonEmptyElements(); 7305 directionLeft = start; 7306 7307 if (container.nodeType == 1 && offset > container.childNodes.length - 1) { 7308 directionLeft = false; 7309 } 7310 7311 // If the container is a document move it to the body element 7312 if (container.nodeType === 9) { 7313 container = dom.getRoot(); 7314 offset = 0; 7315 } 7316 7317 // If the container is body try move it into the closest text node or position 7318 if (container === body) { 7319 // If start is before/after a image, table etc 7320 if (directionLeft) { 7321 node = container.childNodes[offset > 0 ? offset - 1 : 0]; 7322 if (node) { 7323 if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") { 7324 return; 7325 } 7326 } 7327 } 7328 7329 // Resolve the index 7330 if (container.hasChildNodes()) { 7331 offset = Math.min(!directionLeft && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1); 7332 container = container.childNodes[offset]; 7333 offset = 0; 7334 7335 // Don't walk into elements that doesn't have any child nodes like a IMG 7336 if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) { 7337 // Walk the DOM to find a text node to place the caret at or a BR 7338 node = container; 7339 walker = new TreeWalker(container, body); 7340 7341 do { 7342 // Found a text node use that position 7343 if (node.nodeType === 3 && node.nodeValue.length > 0) { 7344 offset = directionLeft ? 0 : node.nodeValue.length; 7345 container = node; 7346 normalized = true; 7347 break; 7348 } 7349 7350 // Found a BR/IMG element that we can place the caret before 7351 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 7352 offset = dom.nodeIndex(node); 7353 container = node.parentNode; 7354 7355 // Put caret after image when moving the end point 7356 if (node.nodeName == "IMG" && !directionLeft) { 7357 offset++; 7358 } 7359 7360 normalized = true; 7361 break; 7362 } 7363 } while ((node = (directionLeft ? walker.next() : walker.prev()))); 7364 } 7365 } 7366 } 7367 7368 // Lean the caret to the left if possible 7369 if (collapsed) { 7370 // So this: <b>x</b><i>|x</i> 7371 // Becomes: <b>x|</b><i>x</i> 7372 // Seems that only gecko has issues with this 7373 if (container.nodeType === 3 && offset === 0) { 7374 findTextNodeRelative(true); 7375 } 7376 7377 // Lean left into empty inline elements when the caret is before a BR 7378 // So this: <i><b></b><i>|<br></i> 7379 // Becomes: <i><b>|</b><i><br></i> 7380 // Seems that only gecko has issues with this. 7381 // Special edge case for <p><a>x</a>|<br></p> since we don't want <p><a>x|</a><br></p> 7382 if (container.nodeType === 1) { 7383 node = container.childNodes[offset]; 7384 7385 // Offset is after the containers last child 7386 // then use the previous child for normalization 7387 if (!node) { 7388 node = container.childNodes[offset - 1]; 7389 } 7390 7391 if (node && node.nodeName === 'BR' && !isPrevNode(node, 'A') && 7392 !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) { 7393 findTextNodeRelative(true, node); 7394 } 7395 } 7396 } 7397 7398 // Lean the start of the selection right if possible 7399 // So this: x[<b>x]</b> 7400 // Becomes: x<b>[x]</b> 7401 if (directionLeft && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) { 7402 findTextNodeRelative(false); 7403 } 7404 7405 // Set endpoint if it was normalized 7406 if (normalized) { 7407 rng['set' + (start ? 'Start' : 'End')](container, offset); 7408 } 7409 } 7410 7411 collapsed = rng.collapsed; 7412 7413 normalizeEndPoint(true); 7414 7415 if (!collapsed) { 7416 normalizeEndPoint(); 7417 } 7418 7419 // If it was collapsed then make sure it still is 7420 if (normalized && collapsed) { 7421 rng.collapse(true); 7422 } 7423 7424 return normalized; 7425 }; 7426 } 7427 7428 /** 7429 * Compares two ranges and checks if they are equal. 7430 * 7431 * @static 7432 * @method compareRanges 7433 * @param {DOMRange} rng1 First range to compare. 7434 * @param {DOMRange} rng2 First range to compare. 7435 * @return {Boolean} true/false if the ranges are equal. 7436 */ 7437 RangeUtils.compareRanges = function(rng1, rng2) { 7438 if (rng1 && rng2) { 7439 // Compare native IE ranges 7440 if (rng1.item || rng1.duplicate) { 7441 // Both are control ranges and the selected element matches 7442 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) { 7443 return true; 7444 } 7445 7446 // Both are text ranges and the range matches 7447 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) { 7448 return true; 7449 } 7450 } else { 7451 // Compare w3c ranges 7452 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset; 7453 } 7454 } 7455 7456 return false;
7457 }; 7458 7459 return RangeUtils; 7460}); 7461 7462// Included from: js/tinymce/classes/NodeChange.js 7463 7464/** 7465 * NodeChange.js 7466 * 7467 * Copyright, Moxiecode Systems AB 7468 * Released under LGPL License. 7469 * 7470 * License: http://www.tinymce.com/license 7471 * Contributing: http://www.tinymce.com/contributing 7472 */ 7473 7474/** 7475 * This class handles the nodechange event dispatching both manual and though selection change events. 7476 * 7477 * @class tinymce.NodeChange 7478 * @private 7479 */ 7480define("tinymce/NodeChange", [ 7481 "tinymce/dom/RangeUtils" 7482], function(RangeUtils) { 7483 return function(editor) { 7484 var lastRng, lastPath = []; 7485 7486 /** 7487 * Returns true/false if the current element path has been changed or not. 7488 * 7489 * @private 7490 * @return {Boolean} True if the element path is the same false if it's not. 7491 */ 7492 function isSameElementPath(startElm) { 7493 var i, currentPath; 7494 7495 currentPath = editor.$(startElm).parentsUntil(editor.getBody()).add(startElm); 7496 if (currentPath.length === lastPath.length) { 7497 for (i = currentPath.length; i >= 0; i--) { 7498 if (currentPath[i] !== lastPath[i]) { 7499 break; 7500 } 7501 } 7502 7503 if (i === -1) { 7504 lastPath = currentPath; 7505 return true; 7506 } 7507 } 7508 7509 lastPath = currentPath; 7510 7511 return false; 7512 } 7513 7514 // Gecko doesn't support the "selectionchange" event 7515 if (!('onselectionchange' in editor.getDoc())) { 7516 editor.on('NodeChange Click MouseUp KeyUp Focus', function(e) { 7517 var nativeRng, fakeRng; 7518 7519 // Since DOM Ranges mutate on modification 7520 // of the DOM we need to clone it's contents 7521 nativeRng = editor.selection.getRng(); 7522 fakeRng = { 7523 startContainer: nativeRng.startContainer, 7524 startOffset: nativeRng.startOffset, 7525 endContainer: nativeRng.endContainer, 7526 endOffset: nativeRng.endOffset 7527 }; 7528 7529 // Always treat nodechange as a selectionchange since applying 7530 // formatting to the current range wouldn't update the range but it's parent 7531 if (e.type == 'nodechange' || !RangeUtils.compareRanges(fakeRng, lastRng)) { 7532 editor.fire('SelectionChange'); 7533 } 7534 7535 lastRng = fakeRng; 7536 }); 7537 } 7538 7539 // IE has a bug where it fires a selectionchange on right click that has a range at the start of the body 7540 // When the contextmenu event fires the selection is located at the right location 7541 editor.on('contextmenu', function() { 7542 editor.fire('SelectionChange'); 7543 }); 7544 7545 editor.on('SelectionChange', function() { 7546 var startElm = editor.selection.getStart(true); 7547 7548 // Fire a nodechange only when the selection isn't collapsed since focusout will c
7548ollapse and remove the selection 7549 if (!editor.selection.isCollapsed() && !isSameElementPath(startElm) && editor.dom.isChildOf(startElm, editor.getBody())) { 7550 editor.nodeChanged({selectionChange: true}); 7551 } 7552 }); 7553 7554 // Fire an extra nodeChange on mouseup for compatibility reasons 7555 editor.on('MouseUp', function(e) { 7556 if (!e.isDefaultPrevented()) { 7557 // Delay nodeChanged call for WebKit edge case issue where the range 7558 // isn't updated until after you click outside a selected image 7559 setTimeout(function() { 7560 editor.nodeChanged(); 7561 }, 0); 7562 } 7563 }); 7564 7565 /** 7566 * Distpaches out a onNodeChange event to all observers. This method should be called when you 7567 * need to update the UI states or element path etc. 7568 * 7569 * @method nodeChanged 7570 * @param {Object} args Optional args to pass to NodeChange event handlers. 7571 */ 7572 this.nodeChanged = function(args) { 7573 var selection = editor.selection, node, parents, root; 7574 7575 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading 7576 if (editor.initialized && selection && !editor.settings.disable_nodechange && !editor.settings.readonly) { 7577 // Get start node 7578 root = editor.getBody(); 7579 node = selection.getStart() || root; 7580 node = node.ownerDocument != editor.getDoc() ? editor.getBody() : node; 7581 7582 // Edge case for <p>|<img></p> 7583 if (node.nodeName == 'IMG' && selection.isCollapsed()) { 7584 node = node.parentNode; 7585 } 7586 7587 // Get parents and add them to object 7588 parents = []; 7589 editor.dom.getParent(node, function(node) { 7590 if (node === root) { 7591 return true; 7592 } 7593 7594 parents.push(node); 7595 }); 7596 7597 args = args || {}; 7598 args.element = node; 7599 args.parents = parents; 7600 7601 editor.fire('NodeChange', args); 7602 } 7603 }; 7604 }; 7605}); 7606 7607// Included from: js/tinymce/classes/html/Node.js 7608 7609/** 7610 * Node.js 7611 * 7612 * Copyright, Moxiecode Systems AB 7613 * Released under LGPL License. 7614 * 7615 * License: http://www.tinymce.com/license 7616 * Contributing: http://www.tinymce.com/contributing 7617 */ 7618 7619/** 7620 * This class is a minimalistic implementation of a DOM like node used by the DomParser class. 7621 * 7622 * @example 7623 * var node = new tinymce.html.Node('strong', 1); 7624 * someRoot.append(node); 7625 * 7626 * @class tinymce.html.Node 7627 * @version 3.4 7628 */ 7629define("tinymce/html/Node", [], function() { 7630 var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = { 7631 '#text': 3, 7632 '#comment': 8, 7633 '#cdata': 4, 7634 '#pi': 7, 7635 '#doctype': 10, 7636 '#document-fragment': 11 7637 }; 7638 7639 // Walks the tree left/right 7640 function walk(node, root_node, prev) { 7641 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next'; 7642 7643 // Walk into nodes if it has a start 7644 if (node[startName]) { 7645 return node[startName]; 7646 } 7647 7648 // Return the sibling if it has one 7649 if (node !== root_node) { 7650 sibling = node[siblingName]; 7651 7652 if (sibling) { 7653 return sibling; 7654 } 7655 7656 // Walk up the parents to look for siblings 7657 for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) { 7658 sibling = parent[siblingName]; 7659 7660 if (sibling) { 7661 return sibling; 7662 } 7663 } 7664 } 7665 } 7666 7667 /** 7668 * Constructs a new Node instance. 7669 * 7670 * @constructor 7671 * @method Node 7672 * @param {String} name Name of the node type. 7673 * @param {Number} type Numeric type representing the node. 7674 */ 7675 function Node(name, type) { 7676 this.name = name; 7677 this.type = type; 7678 7679 if (type === 1) { 7680 this.attributes = []; 7681 this.attributes.map = {}; 7682 } 7683 } 7684 7685 Node.prototype = { 7686 /** 7687 * Replaces the current node with the specified one. 7688 * 7689 * @example 7690 * someNode.replace(someNewNode); 7691 * 7692 * @method replace 7693 * @param {tinymce.html.Node} node Node to replace the current node with. 7694 * @return {tinymce.html.Node} The old node that got replaced. 7695 */ 7696 replace: function(node) { 7697 var self = this; 7698 7699 if (node.parent) { 7700 node.remove(); 7701 } 7702 7703 self.insert(node, self); 7704 self.remove(); 7705 7706 return self; 7707 }, 7708 7709 /** 7710 * Gets/sets or removes an attribute by name. 7711 * 7712 * @example 7713 * someNode.attr("name", "value"); // Sets an attribute 7714 * console.log(someNode.attr("name")); // Gets an attribute 7715 * someNode.attr("name", null); // Removes an attribute 7716 * 7717 * @method attr 7718 * @param {String} name Attribute name to set or get. 7719 * @param {String} value Optional value to set. 7720 * @return {String/tinymce.html.Node} String or undefined on a get operation or the current node on a set operation. 7721 */ 7722 attr: function(name, value) { 7723 var self = this, attrs, i, undef; 7724 7725 if (typeof name !== "string") { 7726 for (i in name) { 7727 self.attr(i, name[i]); 7728 } 7729 7730 return self; 7731 } 7732 7733 if ((attrs = self.attributes)) { 7734 if (value !== undef) { 7735 // Remove attribute 7736 if (value === null) { 7737 if (name in attrs.map) { 7738 delete attrs.map[name]; 7739 7740 i = attrs.length; 7741 while (i--) { 7742 if (attrs[i].name === name) { 7743 attrs = attrs.splice(i, 1); 7744 return self; 7745 } 7746 } 7747 } 7748 7749 return self; 7750 } 7751 7752 // Set attribute 7753 if (name in attrs.map) { 7754 // Set attribute 7755 i = attrs.length; 7756 while (i--) { 7757 if (attrs[i].name === name) { 7758 attrs[i].value = value; 7759 break; 7760 } 7761 } 7762 } else { 7763 attrs.push({name: name, value: value}); 7764 } 7765 7766 attrs.map[name] = value; 7767 7768 return self; 7769 } else { 7770 return attrs.map[name]; 7771 } 7772 } 7773 }, 7774 7775 /**
7776 * Does a shallow clones the node into a new node. It will also exclude id attributes since 7777 * there should only be one id per document. 7778 * 7779 * @example 7780 * var clonedNode = node.clone(); 7781 * 7782 * @method clone 7783 * @return {tinymce.html.Node} New copy of the original node. 7784 */ 7785 clone: function() { 7786 var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs; 7787 7788 // Clone element attributes 7789 if ((selfAttrs = self.attributes)) { 7790 cloneAttrs = []; 7791 cloneAttrs.map = {}; 7792 7793 for (i = 0, l = selfAttrs.length; i < l; i++) { 7794 selfAttr = selfAttrs[i]; 7795 7796 // Clone everything except id 7797 if (selfAttr.name !== 'id') { 7798 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value}; 7799 cloneAttrs.map[selfAttr.name] = selfAttr.value; 7800 } 7801 } 7802 7803 clone.attributes = cloneAttrs; 7804 } 7805 7806 clone.value = self.value; 7807 clone.shortEnded = self.shortEnded; 7808 7809 return clone; 7810 }, 7811 7812 /** 7813 * Wraps the node in in another node. 7814 * 7815 * @example 7816 * node.wrap(wrapperNode); 7817 * 7818 * @method wrap 7819 */ 7820 wrap: function(wrapper) { 7821 var self = this; 7822 7823 self.parent.insert(wrapper, self); 7824 wrapper.append(self); 7825 7826 return self; 7827 }, 7828 7829 /** 7830 * Unwraps the node in other words it removes the node but keeps the children. 7831 * 7832 * @example 7833 * node.unwrap(); 7834 * 7835 * @method unwrap 7836 */ 7837 unwrap: function() { 7838 var self = this, node, next; 7839 7840 for (node = self.firstChild; node;) { 7841 next = node.next; 7842 self.insert(node, self, true); 7843 node = next; 7844 } 7845 7846 self.remove(); 7847 }, 7848 7849 /** 7850 * Removes the node from it's parent. 7851 * 7852 * @example 7853 * node.remove(); 7854 * 7855 * @method remove 7856 * @return {tinymce.html.Node} Current node that got removed. 7857 */ 7858 remove: function() { 7859 var self = this, parent = self.parent, next = self.next, prev = self.prev; 7860 7861 if (parent) { 7862 if (parent.firstChild === self) { 7863 parent.firstChild = next; 7864 7865 if (next) { 7866 next.prev = null; 7867 } 7868 } else { 7869 prev.next = next; 7870 } 7871 7872 if (parent.lastChild === self) { 7873 parent.lastChild = prev; 7874 7875 if (prev) { 7876 prev.next = null; 7877 } 7878 } else { 7879 next.prev = prev; 7880 } 7881 7882 self.parent = self.next = self.prev = null; 7883 } 7884 7885 return self; 7886 }, 7887 7888 /** 7889 * Appends a new node as a child of the current node. 7890 * 7891 * @example 7892 * node.append(someNode); 7893 * 7894 * @method append 7895 * @param {tinymce.html.Node} node Node to append as a child of the current one. 7896 * @return {tinymce.html.Node} The node that got appended. 7897 */ 7898 append: function(node) { 7899 var self = this, last; 7900 7901 if (node.parent) { 7902 node.remove(); 7903 } 7904 7905 last = self.lastChild; 7906 if (last) { 7907 last.next = node; 7908 node.prev = last; 7909 self.lastChild = node; 7910 } else { 7911 self.lastChild = self.firstChild = node; 7912 } 7913 7914 node.parent = self; 7915 7916 return node; 7917 }, 7918 7919 /** 7920 * Inserts a node at a specific position as a child of the current node. 7921 * 7922 * @example 7923 * parentNode.insert(newChildNode, oldChildNode); 7924 * 7925 * @method insert 7926 * @param {tinymce.html.Node} node Node to insert as a child of the current node. 7927 * @param {tinymce.html.Node} ref_node Reference node to set node before/after. 7928 * @param {Boolean} before Optional state to insert the node before the reference node. 7929 * @return {tinymce.html.Node} The node that got inserted. 7930 */ 7931 insert: function(node, ref_node, before) { 7932 var parent; 7933 7934 if (node.parent) { 7935 node.remove(); 7936 } 7937 7938 parent = ref_node.parent || this; 7939 7940 if (before) { 7941 if (ref_node === parent.firstChild) { 7942 parent.firstChild = node; 7943 } else { 7944 ref_node.prev.next = node; 7945 } 7946 7947 node.prev = ref_node.prev; 7948 node.next = ref_node; 7949 ref_node.prev = node; 7950 } else { 7951 if (ref_node === parent.lastChild) { 7952 parent.lastChild = node; 7953 } else { 7954 ref_node.next.prev = node; 7955 } 7956 7957 node.next = ref_node.next; 7958 node.prev = ref_node; 7959 ref_node.next = node; 7960 } 7961 7962 node.parent = parent; 7963 7964 return node; 7965 }, 7966 7967 /** 7968 * Get all children by name. 7969 * 7970 * @method getAll 7971 * @param {String} name Name of the child nodes to collect. 7972 * @return {Array} Array with child nodes matchin the specified name. 7973 */ 7974 getAll: function(name) { 7975 var self = this, node, collection = []; 7976 7977 for (node = self.firstChild; node; node = walk(node, self)) { 7978 if (node.name === name) { 7979 collection.push(node); 7980 } 7981 } 7982 7983 return collection; 7984 }, 7985 7986 /** 7987 * Removes all children of the current node. 7988 * 7989 * @method empty 7990 * @return {tinymce.html.Node} The current node that got cleared. 7991 */ 7992 empty: function() { 7993 var self = this, nodes, i, node; 7994 7995 // Remove all children 7996 if (self.firstChild) { 7997 nodes = []; 7998 7999 // Collect the children 8000 for (node = self.firstChild; node; node = walk(node, self)) { 8001 nodes.push(node); 8002 } 8003 8004 // Remove the children 8005 i = nodes.length; 8006 while (i--) { 8007 node = nodes[i]; 8008 node.parent = node.firstChild = node.lastChild = node.next = node.prev = null; 8009 } 8010 } 8011 8012 self.firstChild = self.lastChild = null; 8013 8014 return self; 8015 }, 8016 8017 /** 8018 * Returns true/false if the node is to be considered empty or not. 8019 * 8020 * @example 8021 * node.isEmpty({img: true}); 8022 * @method isEmpty 8023 * @param {Object} elements Name/value object with elements that are automatically treated as non empty elements. 8024 * @return {Boolean} true/false if the node is empty or not. 8025 */ 8026 isEmpty: function(elements) { 8027 var self = this, node = self.firstChild, i, name; 8028 8029 if (node) { 8030 do { 8031 if (node.type === 1) { 8032 // Ignore bogus elements 8033 if (node.attributes.map['data-mce-bogus']) { 8034 continue; 8035 } 8036 8037 // Keep empty elements like <img /> 8038 if (elements[node.name]) { 8039 return false;
8040 } 8041 8042 // Keep bookmark nodes and name attribute like <a name="1"></a> 8043 i = node.attributes.length; 8044 while (i--) { 8045 name = node.attributes[i].name; 8046 if (name === "name" || name.indexOf('data-mce-bookmark') === 0) { 8047 return false; 8048 } 8049 } 8050 } 8051 8052 // Keep comments 8053 if (node.type === 8) { 8054 return false; 8055 } 8056 8057 // Keep non whitespace text nodes 8058 if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) { 8059 return false; 8060 } 8061 } while ((node = walk(node, self))); 8062 } 8063 8064 return true; 8065 }, 8066 8067 /** 8068 * Walks to the next or previous node and returns that node or null if it wasn't found. 8069 * 8070 * @method walk 8071 * @param {Boolean} prev Optional previous node state defaults to false. 8072 * @return {tinymce.html.Node} Node that is next to or previous of the current node. 8073 */ 8074 walk: function(prev) { 8075 return walk(this, null, prev); 8076 } 8077 }; 8078 8079 /** 8080 * Creates a node of a specific type. 8081 * 8082 * @static 8083 * @method create 8084 * @param {String} name Name of the node type to create for example "b" or "#text". 8085 * @param {Object} attrs Name/value collection of attributes that will be applied to elements. 8086 */ 8087 Node.create = function(name, attrs) { 8088 var node, attrName; 8089 8090 // Create node 8091 node = new Node(name, typeLookup[name] || 1); 8092 8093 // Add attributes if needed 8094 if (attrs) { 8095 for (attrName in attrs) { 8096 node.attr(attrName, attrs[attrName]); 8097 } 8098 } 8099 8100 return node; 8101 }; 8102 8103 return Node; 8104}); 8105 8106// Included from: js/tinymce/classes/html/Schema.js 8107 8108/** 8109 * Schema.js 8110 * 8111 * Copyright, Moxiecode Systems AB 8112 * Released under LGPL License. 8113 * 8114 * License: http://www.tinymce.com/license 8115 * Contributing: http://www.tinymce.com/contributing 8116 */ 8117 8118/** 8119 * Schema validator class. 8120 * 8121 * @class tinymce.html.Schema 8122 * @example 8123 * if (tinymce.activeEditor.schema.isValidChild('p', 'span')) 8124 * alert('span is valid child of p.'); 8125 * 8126 * if (tinymce.activeEditor.schema.getElementRule('p')) 8127 * alert('P is a valid element.'); 8128 * 8129 * @class tinymce.html.Schema 8130 * @version 3.4 8131 */ 8132define("tinymce/html/Schema", [ 8133 "tinymce/util/Tools" 8134], function(Tools) { 8135 var mapCache = {}, dummyObj = {}; 8136 var makeMap = Tools.makeMap, each = Tools.each, extend = Tools.extend, explode = Tools.explode, inArray = Tools.inArray; 8137 8138 function split(items, delim) { 8139 return items ? items.split(delim || ' ') : []; 8140 } 8141 8142 /** 8143 * Builds a schema lookup table 8144 * 8145 * @private 8146 * @param {String} type html4, html5 or html5-strict schema type. 8147 * @return {Object} Schema lookup table. 8148 */ 8149 function compileSchema(type) { 8150 var schema = {}, globalAttributes, blockContent; 8151 var phrasingContent, flowContent, html4BlockContent, html4PhrasingContent; 8152 8153 function add(name, attributes, children) { 8154 var ni, i, attributesOrder, args = arguments; 8155 8156 function arrayToMap(array, obj) { 8157 var map = {}, i, l; 8158 8159 for (i = 0, l = array.length; i < l; i++) { 8160 map[array[i]] = obj || {}; 8161 } 8162 8163 return map; 8164 } 8165 8166 children = children || []; 8167 attributes = attributes || ""; 8168 8169 if (typeof(children) === "string") { 8170 children = split(children); 8171 } 8172 8173 // Split string children 8174 for (i = 3; i < args.length; i++) { 8175 if (typeof(args[i]) === "string") { 8176 args[i] = split(args[i]); 8177 } 8178 8179 children.push.apply(children, args[i]); 8180 } 8181 8182 name = split(name); 8183 ni = name.length; 8184 while (ni--) { 8185 attributesOrder = [].concat(globalAttributes, split(attributes)); 8186 schema[name[ni]] = { 8187 attributes: arrayToMap(attributesOrder), 8188 attributesOrder: attributesOrder, 8189 children: arrayToMap(children, dummyObj) 8190 }; 8191 } 8192 } 8193 8194 function addAttrs(name, attributes) { 8195 var ni, schemaItem, i, l; 8196 8197 name = split(name); 8198 ni = name.length; 8199 attributes = split(attributes); 8200 while (ni--) { 8201 schemaItem = schema[name[ni]]; 8202 for (i = 0, l = attributes.length; i < l; i++) { 8203 schemaItem.attributes[attributes[i]] = {}; 8204 schemaItem.attributesOrder.push(attributes[i]); 8205 } 8206 } 8207 } 8208 8209 // Use cached schema 8210 if (mapCache[type]) { 8211 return mapCache[type]; 8212 } 8213 8214 // Attributes present on all elements 8215 globalAttributes = split("id accesskey class dir lang style tabindex title"); 8216 8217 // Event attributes can be opt-in/opt-out 8218 /*eventAttributes = split("onabort onblur oncancel oncanplay oncanplaythrough onchange onclick onclose oncontextmenu oncuechange " + 8219 "ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended " + 8220 "onerror onfocus oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart " + 8221 "onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onpause onplay onplaying onprogress onratechange " + 8222 "onreset onscroll onseeked onseeking onseeking onselect onshow onstalled onsubmit onsuspend ontimeupdate onvolumechange " + 8223 "onwaiting" 8224 );*/ 8225 8226 // Block content elements 8227 blockContent = split( 8228 "address blockquote div dl fieldset form h1 h2 h3 h4 h5 h6 hr menu ol p pre table ul" 8229 ); 8230 8231 // Phrasing content elements from the HTML5 spec (inline) 8232 phrasingContent = split( 8233 "a abbr b bdo br button cite code del dfn em embed i iframe img input ins kbd " + 8234 "label map noscript object q s samp script select small span strong sub sup " + 8235 "textarea u var #text #comment" 8236 ); 8237 8238 // Add HTML5 items to globalAttributes, blockContent, phrasingContent 8239 if (type != "html4") { 8240 globalAttributes.push.apply(globalAttributes, split("contenteditable contextmenu draggable dropzone " + 8241 "hidden spellcheck translate")); 8242 blockContent.push.apply(blockContent, split("article aside details dialog figure header footer hgroup section nav"));
8243 phrasingContent.push.apply(phrasingContent, split("audio canvas command datalist mark meter output progress time wbr " + 8244 "video ruby bdi keygen")); 8245 } 8246 8247 // Add HTML4 elements unless it's html5-strict 8248 if (type != "html5-strict") { 8249 globalAttributes.push("xml:lang"); 8250 8251 html4PhrasingContent = split("acronym applet basefont big font strike tt"); 8252 phrasingContent.push.apply(phrasingContent, html4PhrasingContent); 8253 8254 each(html4PhrasingContent, function(name) { 8255 add(name, "", phrasingContent); 8256 }); 8257 8258 html4BlockContent = split("center dir isindex noframes"); 8259 blockContent.push.apply(blockContent, html4BlockContent); 8260 8261 // Flow content elements from the HTML5 spec (block+inline) 8262 flowContent = [].concat(blockContent, phrasingContent); 8263 8264 each(html4BlockContent, function(name) { 8265 add(name, "", flowContent); 8266 }); 8267 } 8268 8269 // Flow content elements from the HTML5 spec (block+inline) 8270 flowContent = flowContent || [].concat(blockContent, phrasingContent); 8271 8272 // HTML4 base schema TODO: Move HTML5 specific attributes to HTML5 specific if statement 8273 // Schema items <element name>, <specific attributes>, <children ..> 8274 add("html", "manifest", "head body"); 8275 add("head", "", "base command link meta noscript script style title"); 8276 add("title hr noscript br"); 8277 add("base", "href target"); 8278 add("link", "href rel media hreflang type sizes hreflang"); 8279 add("meta", "name http-equiv content charset"); 8280 add("style", "media type scoped"); 8281 add("script", "src async defer type charset"); 8282 add("body", "onafterprint onbeforeprint onbeforeunload onblur onerror onfocus " + 8283 "onhashchange onload onmessage onoffline ononline onpagehide onpageshow " + 8284 "onpopstate onresize onscroll onstorage onunload", flowContent); 8285 add("address dt dd div caption", "", flowContent); 8286 add("h1 h2 h3 h4 h5 h6 pre p abbr code var samp kbd sub sup i b u bdo span legend em strong small s cite dfn", "", phrasingContent); 8287 add("blockquote", "cite", flowContent); 8288 add("ol", "reversed start type", "li"); 8289 add("ul", "", "li"); 8290 add("li", "value", flowContent); 8291 add("dl", "", "dt dd"); 8292 add("a", "href target rel media hreflang type", phrasingContent); 8293 add("q", "cite", phrasingContent); 8294 add("ins del", "cite datetime", flowContent); 8295 add("img", "src srcset alt usemap ismap width height"); 8296 add("iframe", "src name width height", flowContent); 8297 add("embed", "src type width height"); 8298 add("object", "data type typemustmatch name usemap form width height", flowContent, "param"); 8299 add("param", "name value"); 8300 add("map", "name", flowContent, "area"); 8301 add("area", "alt coords shape href target rel media hreflang type"); 8302 add("table", "border", "caption colgroup thead tfoot tbody tr" + (type == "html4" ? " col" : "")); 8303 add("colgroup", "span", "col"); 8304 add("col", "span"); 8305 add("tbody thead tfoot", "", "tr"); 8306 add("tr", "", "td th"); 8307 add("td", "colspan rowspan headers", flowContent); 8308 add("th", "colspan rowspan headers scope abbr", flowContent); 8309 add("form", "accept-charset action autocomplete enctype method name novalidate target", flowContent); 8310 add("fieldset", "disabled form name", flowContent, "legend"); 8311 add("label", "form for", phrasingContent); 8312 add("input", "accept alt autocomplete checked dirname disabled form formaction formenctype formmethod formnovalidate " + 8313 "formtarget height list max maxlength min multiple name pattern readonly required size src step type value width" 8314 ); 8315 add("button", "disabled form formaction formenctype formmethod formnovalidate formtarget name type value", 8316 type == "html4" ? flowContent : phrasingContent); 8317 add("select", "disabled form multiple name required size", "option optgroup"); 8318 add("optgroup", "disabled label", "option"); 8319 add("option", "disabled label selected value"); 8320 add("textarea", "cols dirname disabled form maxlength name readonly required rows wrap"); 8321 add("menu", "type label", flowContent, "li"); 8322 add("noscript", "", flowContent); 8323 8324 // Extend with HTML5 elements 8325 if (type != "html4") { 8326 add("wbr"); 8327 add("ruby", "", phrasingContent, "rt rp"); 8328 add("figcaption", "", flowContent); 8329 add("mark rt rp summary bdi", "", phrasingContent); 8330 add("canvas", "width height", flowContent); 8331 add("video", "src crossorigin poster preload autoplay mediagroup loop " + 8332 "muted controls width height buffered", flowContent, "track source"); 8333 add("audio", "src crossorigin preload autoplay mediagroup loop muted controls buffered volume", flowContent, "track source"); 8334 add("picture", "", "img source"); 8335 add("source", "src srcset type media sizes"); 8336 add("track", "kind src srclang label default"); 8337 add("datalist", "", phrasingContent, "option"); 8338 add("article section nav aside header footer", "", flowContent); 8339 add("hgroup", "", "h1 h2 h3 h4 h5 h6"); 8340 add("figure", "", flowContent, "figcaption"); 8341 add("time", "datetime", phrasingContent); 8342 add("dialog", "open", flowContent); 8343 add("command", "type label icon disabled checked radiogroup command"); 8344 add("output", "for form name", phrasingContent); 8345 add("progress", "value max", phrasingContent); 8346 add("meter", "value min max low high optimum", phrasingContent); 8347 add("details", "open", flowContent, "summary"); 8348 add("keygen", "autofocus challenge disabled form keytype name"); 8349 } 8350 8351 // Extend with HTML4 attributes unless it's html5-strict 8352 if (type != "html5-strict") { 8353 addAttrs("script", "language xml:space"); 8354 addAttrs("style", "xml:space"); 8355 addAttrs("object", "declare classid code codebase codetype archive standby align border hspace vspace"); 8356 addAttrs("embed", "align name hspace vspace"); 8357 addAttrs("param", "valuetype type"); 8358 addAttrs("a", "charset name rev shape coords"); 8359 addAttrs("br", "clear"); 8360 addAttrs("applet", "codebase archive code object alt name width height align hspace vspace"); 8361 addAttrs("img", "name longdesc align border hspace vspace"); 8362 addAttrs("iframe", "longdesc frameborder marginwidth marginheight scrolling align"); 8363 addAttrs("font basefont", "size color face"); 8364 addAttrs("input", "usemap align"); 8365 addAttrs("select", "onchange"); 8366 addAttrs("textarea"); 8367 addAttrs("h1 h2 h3 h4 h5 h6 div p legend caption", "align"); 8368 addAttrs("ul", "type compact"); 8369 addAttrs("li", "type"); 8370 addAttrs("ol dl menu dir", "compact"); 8371 addAttrs("pre", "width xml:space"); 8372 addAttrs("hr", "align noshade size width"); 8373 addAttrs("isindex", "prompt"); 8374 addAttrs("table", "summary width frame rules cellspacing cellpadding align bgcolor"); 8375 addAttrs("col", "width align char charoff valign"); 8376 addAttrs("colgroup", "width align char charoff valign"); 8377 addAttrs("thead", "align char charoff valign"); 8378 addAttrs("tr", "align char charoff valign bgcolor"); 8379 addAttrs("th", "axis align char charoff valign nowrap bgcolor width height"); 8380 addAttrs("form", "accept"); 8381 addAttrs("td", "abbr axis scope align char charoff valign nowrap bgcolor width height"); 8382 addAttrs("tfoot", "align char charoff valign"); 8383 addAttrs("tbody", "align char charoff valign"); 8384 addAttrs("area", "nohref"); 8385 addAttrs("body", "background bgcolor text link vlink alink"); 8386 } 8387 8388 // Extend with HTML5 attributes unless it's html4 8389 if (type != "html4") { 8390 addAttrs("input button select textarea", "autofocus"); 8391 addAttrs("input textarea", "placeholder"); 8392 addAttrs("a", "download"); 8393 addAttrs("link script img", "crossorigin"); 8394 addAttrs("iframe", "sandbox seamless allowfullscreen"); // Excluded: srcdoc 8395 } 8396 8397 // Special: iframe, ruby, video, audio, label 8398 8399 // Delete children of the same name from it's parent 8400 // For example: form can't have a child of the name form 8401 each(split('a form meter progress dfn'), function(name) { 8402 if (schema[name]) { 8403 delete schema[name].children[name]; 8404 } 8405 }); 8406 8407 // Delete header, footer, sectioning and heading content descendants 8408 /*each('dt th address', function(name) { 8409 delete schema[name].children[name]; 8410 });*/ 8411 8412 // Caption can't have tables 8413 delete schema.caption.children.table; 8414 8415 // TODO: LI:s can only have value if parent is OL 8416 8417 // TODO: Handle transparent elements 8418 // a ins del canvas map 8419 8420 mapCache[type] = schema; 8421 8422 return schema; 8423 } 8424 8425 function compileElementMap(value, mode) { 8426 var styles; 8427 8428 if (value) { 8429 styles = {}; 8430 8431 if (typeof value == 'string') { 8432 value = { 8433 '*': value 8434 }; 8435 } 8436 8437 // Convert styles into a rule list 8438 each(value, function(value, key) { 8439 styles[key] = mode == 'map' ? makeMap(value, /[, ]/) : explode(value, /[, ]/); 8440 }); 8441 } 8442 8443 return styles; 8444 } 8445 8446 /** 8447 * Constructs a new Schema instance. 8448 * 8449 * @constructor 8450 * @method Schema 8451 * @param {Object} settings Name/value settings object. 8452 */ 8453 return function(settings) { 8454 var self = this, elements = {}, children = {}, patternElements = [], validStyles, invalidStyles, schemaItems; 8455 var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, validClasses; 8456 var blockElementsMap, nonEmptyElementsMap, textBlockElementsMap, textInlineElementsMap; 8457 var customElementsMap = {}, specialElements = {}; 8458 8459 // Creates an lookup table map object for the specified option or the default value 8460 function createLookupTable(option, default_value, extendWith) { 8461 var value = settings[option]; 8462 8463 if (!value) { 8464 // Get cached default map or make it if needed 8465 value = mapCache[option]; 8466 8467 if (!value) { 8468 value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' ')); 8469 value = extend(value, extendWith); 8470 8471 mapCache[option] = value; 8472 } 8473 } else { 8474 // Create custom map 8475 value = makeMap(value, /[, ]/, makeMap(value.toUpperCase(), /[, ]/)); 8476 } 8477 8478 return value; 8479 } 8480 8481 settings = settings || {}; 8482 schemaItems = compileSchema(settings.schema); 8483 8484 // Allow all elements and attributes if verify_html is set to false 8485 if (settings.verify_html === false) {
8486 settings.valid_elements = '*[*]'; 8487 } 8488 8489 validStyles = compileElementMap(settings.valid_styles); 8490 invalidStyles = compileElementMap(settings.invalid_styles, 'map'); 8491 validClasses = compileElementMap(settings.valid_classes, 'map'); 8492 8493 // Setup map objects 8494 whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea video audio iframe object'); 8495 selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr'); 8496 shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link ' + 8497 'meta param embed source wbr track'); 8498 boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize ' + 8499 'noshade nowrap readonly selected autoplay loop controls'); 8500 nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object script', shortEndedElementsMap); 8501 textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' + 8502 'blockquote center dir fieldset header footer article section hgroup aside nav figure'); 8503 blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' + 8504 'th tr td li ol ul caption dl dt dd noscript menu isindex option ' + 8505 'datalist select optgroup', textBlockElementsMap); 8506 textInlineElementsMap = createLookupTable('text_inline_elements', 'span strong b em i font strike u var cite ' + 8507 'dfn code mark q sup sub samp'); 8508 8509 each((settings.special || 'script noscript style textarea').split(' '), function(name) { 8510 specialElements[name] = new RegExp('<\/' + name + '[^>]*>', 'gi'); 8511 }); 8512 8513 // Converts a wildcard expression string to a regexp for example *a will become /.*a/. 8514 function patternToRegExp(str) { 8515 return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$'); 8516 } 8517 8518 // Parses the specified valid_elements string and adds to the current rules 8519 // This function is a bit hard to read since it's heavily optimized for speed 8520 function addValidElements(validElements) { 8521 var ei, el, ai, al, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder, 8522 prefix, outputName, globalAttributes, globalAttributesOrder, key, value, 8523 elementRuleRegExp = /^([#+\-])?([^\[!\/]+)(?:\/([^\[!]+))?(?:(!?)\[([^\]]+)\])?$/, 8524 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/, 8525 hasPatternsRegExp = /[*?+]/; 8526 8527 if (validElements) { 8528 // Split valid elements into an array with rules 8529 validElements = split(validElements, ','); 8530 8531 if (elements['@']) { 8532 globalAttributes = elements['@'].attributes; 8533 globalAttributesOrder = elements['@'].attributesOrder; 8534 } 8535 8536 // Loop all rules 8537 for (ei = 0, el = validElements.length; ei < el; ei++) { 8538 // Parse element rule 8539 matches = elementRuleRegExp.exec(validElements[ei]); 8540 if (matches) { 8541 // Setup local names for matches 8542 prefix = matches[1]; 8543 elementName = matches[2]; 8544 outputName = matches[3]; 8545 attrData = matches[5]; 8546 8547 // Create new attributes and attributesOrder 8548 attributes = {}; 8549 attributesOrder = []; 8550 8551 // Create the new element 8552 element = { 8553 attributes: attributes, 8554 attributesOrder: attributesOrder 8555 }; 8556 8557 // Padd empty elements prefix 8558 if (prefix === '#') { 8559 element.paddEmpty = true; 8560 } 8561 8562 // Remove empty elements prefix 8563 if (prefix === '-') { 8564 element.removeEmpty = true; 8565 } 8566 8567 if (matches[4] === '!') { 8568 element.removeEmptyAttrs = true; 8569 } 8570 8571 // Copy attributes from global rule into current rule 8572 if (globalAttributes) { 8573 for (key in globalAttributes) { 8574 attributes[key] = globalAttributes[key]; 8575 } 8576 8577 attributesOrder.push.apply(attributesOrder, globalAttributesOrder); 8578 } 8579 8580 // Attributes defined 8581 if (attrData) { 8582 attrData = split(attrData, '|'); 8583 for (ai = 0, al = attrData.length; ai < al; ai++) { 8584 matches = attrRuleRegExp.exec(attrData[ai]); 8585 if (matches) { 8586 attr = {}; 8587 attrType = matches[1]; 8588 attrName = matches[2].replace(/::/g, ':'); 8589 prefix = matches[3]; 8590 value = matches[4]; 8591 8592 // Required 8593 if (attrType === '!') { 8594 element.attributesRequired = element.attributesRequired || []; 8595 element.attributesRequired.push(attrName); 8596 attr.required = true; 8597 } 8598
8599 // Denied from global 8600 if (attrType === '-') { 8601 delete attributes[attrName]; 8602 attributesOrder.splice(inArray(attributesOrder, attrName), 1); 8603 continue; 8604 } 8605 8606 // Default value 8607 if (prefix) { 8608 // Default value 8609 if (prefix === '=') { 8610 element.attributesDefault = element.attributesDefault || []; 8611 element.attributesDefault.push({name: attrName, value: value}); 8612 attr.defaultValue = value; 8613 } 8614 8615 // Forced value 8616 if (prefix === ':') { 8617 element.attributesForced = element.attributesForced || []; 8618 element.attributesForced.push({name: attrName, value: value}); 8619 attr.forcedValue = value; 8620 } 8621 8622 // Required values 8623 if (prefix === '<') { 8624 attr.validValues = makeMap(value, '?'); 8625 } 8626 } 8627 8628 // Check for attribute patterns 8629 if (hasPatternsRegExp.test(attrName)) { 8630 element.attributePatterns = element.attributePatterns || []; 8631 attr.pattern = patternToRegExp(attrName); 8632 element.attributePatterns.push(attr); 8633 } else { 8634 // Add attribute to order list if it doesn't already exist 8635 if (!attributes[attrName]) { 8636 attributesOrder.push(attrName); 8637 } 8638 8639 attributes[attrName] = attr; 8640 } 8641 } 8642 } 8643 } 8644 8645 // Global rule, store away these for later usage 8646 if (!globalAttributes && elementName == '@') { 8647 globalAttributes = attributes; 8648 globalAttributesOrder = attributesOrder; 8649 } 8650 8651 // Handle substitute elements such as b/strong 8652 if (outputName) { 8653 element.outputName = elementName; 8654 elements[outputName] = element; 8655 } 8656 8657 // Add pattern or exact element 8658 if (hasPatternsRegExp.test(elementName)) { 8659 element.pattern = patternToRegExp(elementName); 8660 patternElements.push(element); 8661 } else { 8662 elements[elementName] = element; 8663 } 8664 } 8665 } 8666 } 8667 } 8668 8669 function setValidElements(validElements) { 8670 elements = {}; 8671 patternElements = []; 8672 8673 addValidElements(validElements); 8674 8675 each(schemaItems, function(element, name) { 8676 children[name] = element.children; 8677 }); 8678 } 8679 8680 // Adds custom non HTML elements to the schema 8681 function addCustomElements(customElements) { 8682 var customElementRegExp = /^(~)?(.+)$/; 8683 8684 if (customElements) { 8685 // Flush cached items since we are altering the default maps 8686 mapCache.text_block_elements = mapCache.block_elements = null; 8687 8688 each(split(customElements, ','), function(rule) { 8689 var matches = customElementRegExp.exec(rule), 8690 inline = matches[1] === '~', 8691 cloneName = inline ? 'span' : 'div', 8692 name = matches[2]; 8693 8694 children[name] = children[cloneName]; 8695 customElementsMap[name] = cloneName; 8696 8697 // If it's not marked as inline then add it to valid block elements 8698 if (!inline) { 8699 blockElementsMap[name.toUpperCase()] = {}; 8700 blockElementsMap[name] = {}; 8701 } 8702 8703 // Add elements clone if needed 8704 if (!elements[name]) { 8705 var customRule = elements[cloneName]; 8706 8707 customRule = extend({}, customRule); 8708 delete customRule.removeEmptyAttrs; 8709 delete customRule.removeEmpty; 8710 8711 elements[name] = customRule; 8712 } 8713 8714 // Add custom elements at span/div positions 8715 each(children, function(element, elmName) { 8716 if (element[cloneName]) { 8717 children[elmName] = element = extend({}, children[elmName]); 8718 element[name] = element[cloneName]; 8719 } 8720 }); 8721 }); 8722 } 8723 } 8724 8725 // Adds valid children to the schema object 8726 function addValidChildren(validChildren) { 8727 var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/; 8728 8729 if (validChildren) { 8730 each(split(validChildren, ','), function(rule) { 8731 var matches = childRuleRegExp.exec(rule), parent, prefix; 8732 8733 if (matches) { 8734 prefix = matches[1]; 8735 8736 // Add/remove items from default 8737 if (prefix) { 8738 parent = children[matches[2]]; 8739 } else { 8740 parent = children[matches[2]] = {'#comment': {}}; 8741 } 8742 8743 parent = children[matches[2]]; 8744 8745 each(split(matches[3], '|'), function(child) { 8746 if (prefix === '-') { 8747 // Clone the element before we delete 8748 // things in it to not mess up default schemas 8749 children[matches[2]] = parent = extend({}, children[matches[2]]); 8750 8751 delete parent[child]; 8752 } else { 8753 parent[child] = {}; 8754 } 8755 }); 8756 } 8757 }); 8758 } 8759 } 8760 8761 function getElementRule(name) { 8762 var element = elements[name], i; 8763 8764 // Exact match found 8765 if (element) { 8766 return element; 8767 } 8768 8769 // No exact match then try the patterns 8770 i = patternElements.length; 8771 while (i--) { 8772 element = patternElements[i]; 8773 8774 if (element.pattern.test(name)) { 8775 return element; 8776 } 8777 } 8778 } 8779
8780 if (!settings.valid_elements) { 8781 // No valid elements defined then clone the elements from the schema spec 8782 each(schemaItems, function(element, name) { 8783 elements[name] = { 8784 attributes: element.attributes, 8785 attributesOrder: element.attributesOrder 8786 }; 8787 8788 children[name] = element.children; 8789 }); 8790 8791 // Switch these on HTML4 8792 if (settings.schema != "html5") { 8793 each(split('strong/b em/i'), function(item) { 8794 item = split(item, '/'); 8795 elements[item[1]].outputName = item[0]; 8796 }); 8797 } 8798 8799 // Add default alt attribute for images 8800 elements.img.attributesDefault = [{name: 'alt', value: ''}]; 8801 8802 // Remove these if they are empty by default 8803 each(split('ol ul sub sup blockquote span font a table tbody tr strong em b i'), function(name) { 8804 if (elements[name]) { 8805 elements[name].removeEmpty = true; 8806 } 8807 }); 8808 8809 // Padd these by default 8810 each(split('p h1 h2 h3 h4 h5 h6 th td pre div address caption'), function(name) { 8811 elements[name].paddEmpty = true; 8812 }); 8813 8814 // Remove these if they have no attributes 8815 each(split('span'), function(name) { 8816 elements[name].removeEmptyAttrs = true; 8817 }); 8818 8819 // Remove these by default 8820 // TODO: Reenable in 4.1 8821 /*each(split('script style'), function(name) { 8822 delete elements[name]; 8823 });*/ 8824 } else { 8825 setValidElements(settings.valid_elements); 8826 } 8827 8828 addCustomElements(settings.custom_elements); 8829 addValidChildren(settings.valid_children); 8830 addValidElements(settings.extended_valid_elements); 8831 8832 // Todo: Remove this when we fix list handling to be valid 8833 addValidChildren('+ol[ul|ol],+ul[ul|ol]'); 8834 8835 // Delete invalid elements 8836 if (settings.invalid_elements) { 8837 each(explode(settings.invalid_elements), function(item) { 8838 if (elements[item]) { 8839 delete elements[item]; 8840 } 8841 }); 8842 } 8843 8844 // If the user didn't allow span only allow internal spans 8845 if (!getElementRule('span')) { 8846 addValidElements('span[!data-mce-type|*]'); 8847 } 8848 8849 /** 8850 * Name/value map object with valid parents and children to those parents. 8851 * 8852 * @example 8853 * children = { 8854 * div:{p:{}, h1:{}} 8855 * }; 8856 * @field children 8857 * @type Object 8858 */ 8859 self.children = children; 8860 8861 /** 8862 * Name/value map object with valid styles for each element. 8863 * 8864 * @method getValidStyles 8865 * @type Object 8866 */ 8867 self.getValidStyles = function() { 8868 return validStyles; 8869 }; 8870 8871 /** 8872 * Name/value map object with valid styles for each element. 8873 * 8874 * @method getInvalidStyles 8875 * @type Object 8876 */ 8877 self.getInvalidStyles = function() { 8878 return invalidStyles; 8879 }; 8880 8881 /** 8882 * Name/value map object with valid classes for each element. 8883 * 8884 * @method getValidClasses 8885 * @type Object 8886 */ 8887 self.getValidClasses = function() { 8888 return validClasses; 8889 }; 8890 8891 /** 8892 * Returns a map with boolean attributes. 8893 * 8894 * @method getBoolAttrs 8895 * @return {Object} Name/value lookup map for boolean attributes. 8896 */ 8897 self.getBoolAttrs = function() { 8898 return boolAttrMap; 8899 }; 8900 8901 /** 8902 * Returns a map with block elements. 8903 * 8904 * @method getBlockElements 8905 * @return {Object} Name/value lookup map for block elements. 8906 */ 8907 self.getBlockElements = function() { 8908 return blockElementsMap; 8909 }; 8910 8911 /** 8912 * Returns a map with text block elements. Such as: p,h1-h6,div,address 8913 * 8914 * @method getTextBlockElements 8915 * @return {Object} Name/value lookup map for block elements. 8916 */ 8917 self.getTextBlockElements = function() { 8918 return textBlockElementsMap; 8919 }; 8920 8921 /** 8922 * Returns a map of inline text format nodes for example strong/span or ins. 8923 * 8924 * @method getTextInlineElements 8925 * @return {Object} Name/value lookup map for text format elements. 8926 */ 8927 self.getTextInlineElements = function() { 8928 return textInlineElementsMap; 8929 }; 8930 8931 /** 8932 * Returns a map with short ended elements such as BR or IMG. 8933 * 8934 * @method getShortEndedElements 8935 * @return {Object} Name/value lookup map for short ended elements. 8936 */ 8937 self.getShortEndedElements = function() { 8938 return shortEndedElementsMap; 8939 }; 8940 8941 /** 8942 * Returns a map with self closing tags such as <li>. 8943 * 8944 * @method getSelfClosingElements 8945 * @return {Object} Name/value lookup map for self closing tags elements. 8946 */ 8947 self.getSelfClosingElements = function() { 8948 return selfClosingElementsMap; 8949 }; 8950 8951 /** 8952 * Returns a map with elements that should be treated as contents regardless if it has text 8953 * content in them or not such as TD, VIDEO or IMG. 8954 * 8955 * @method getNonEmptyElements 8956 * @return {Object} Name/value lookup map for non empty elements. 8957 */ 8958 self.getNonEmptyElements = function() { 8959 return nonEmptyElementsMap; 8960 }; 8961 8962 /** 8963 * Returns a map with elements where white space is to be preserved like PRE or SCRIPT. 8964 * 8965 * @method getWhiteSpaceElements 8966 * @return {Object} Name/value lookup map for white space elements. 8967 */ 8968 self.getWhiteSpaceElements = function() { 8969 return whiteSpaceElementsMap; 8970 }; 8971 8972 /** 8973 * Returns a map with special elements. These are elements that needs to be parsed 8974 * in a special way such as script, style, textarea etc. The map object values 8975 * are regexps used to find the end of the element. 8976 * 8977 * @method getSpecialElements 8978 * @return {Object} Name/value lookup map for special elements. 8979 */ 8980 self.getSpecialElements = function() { 8981 return specialElements; 8982 }; 8983 8984 /** 8985 * Returns true/false if the specified element and it's child is valid or not 8986 * according to the schema. 8987 * 8988 * @method isValidChild 8989 * @param {String} name Element name to check for. 8990 * @param {String} child Element child to verify. 8991 * @return {Boolean} True/false if the element is a valid child of the specified parent. 8992 */ 8993 self.isValidChild = function(name, child) { 8994 var parent = children[name]; 8995 8996 return !!(parent && parent[child]); 8997 }; 8998 8999 /** 9000 * Returns true/false if the specified element name and optional attribute is 9001 * valid according to the schema. 9002 * 9003 * @method isValid 9004 * @param {String} name Name of element to check. 9005 * @param {String} attr Optional attribute name to check for. 9006 * @return {Boolean} True/false if the element and attribute is valid. 9007 */ 9008 self.isValid = function(name, attr) { 9009 var attrPatterns, i, rule = getElementRule(name); 9010 9011 // Check if it's a valid element 9012 if (rule) { 9013 if (attr) { 9014 // Check if attribute name exists 9015 if (rule.attributes[attr]) { 9016 return true; 9017 } 9018 9019 // Check if attribute matches a regexp pattern 9020 attrPatterns = rule.attributePatterns; 9021 if (attrPatterns) { 9022 i = attrPatterns.length; 9023 while (i--) { 9024 if (attrPatterns[i].pattern.test(name)) { 9025 return true; 9026 } 9027 } 9028 } 9029 } else { 9030 return true; 9031 } 9032 } 9033 9034 // No match 9035 return false;
9036 }; 9037 9038 /** 9039 * Returns true/false if the specified element is valid or not 9040 * according to the schema. 9041 * 9042 * @method getElementRule 9043 * @param {String} name Element name to check for. 9044 * @return {Object} Element object or undefined if the element isn't valid. 9045 */ 9046 self.getElementRule = getElementRule; 9047 9048 /** 9049 * Returns an map object of all custom elements. 9050 * 9051 * @method getCustomElements 9052 * @return {Object} Name/value map object of all custom elements. 9053 */ 9054 self.getCustomElements = function() { 9055 return customElementsMap; 9056 }; 9057 9058 /** 9059 * Parses a valid elements string and adds it to the schema. The valid elements 9060 * format is for example "element[attr=default|otherattr]". 9061 * Existing rules will be replaced with the ones specified, so this extends the schema. 9062 * 9063 * @method addValidElements 9064 * @param {String} valid_elements String in the valid elements format to be parsed. 9065 */ 9066 self.addValidElements = addValidElements; 9067 9068 /** 9069 * Parses a valid elements string and sets it to the schema. The valid elements 9070 * format is for example "element[attr=default|otherattr]". 9071 * Existing rules will be replaced with the ones specified, so this extends the schema. 9072 * 9073 * @method setValidElements 9074 * @param {String} valid_elements String in the valid elements format to be parsed. 9075 */ 9076 self.setValidElements = setValidElements; 9077 9078 /** 9079 * Adds custom non HTML elements to the schema. 9080 * 9081 * @method addCustomElements 9082 * @param {String} custom_elements Comma separated list of custom elements to add. 9083 */ 9084 self.addCustomElements = addCustomElements; 9085 9086 /** 9087 * Parses a valid children string and adds them to the schema structure. The valid children 9088 * format is for example: "element[child1|child2]". 9089 * 9090 * @method addValidChildren 9091 * @param {String} valid_children Valid children elements string to parse 9092 */ 9093 self.addValidChildren = addValidChildren; 9094 9095 self.elements = elements; 9096 }; 9097}); 9098 9099// Included from: js/tinymce/classes/html/SaxParser.js 9100 9101/** 9102 * SaxParser.js 9103 * 9104 * Copyright, Moxiecode Systems AB 9105 * Released under LGPL License. 9106 * 9107 * License: http://www.tinymce.com/license 9108 * Contributing: http://www.tinymce.com/contributing 9109 */ 9110 9111/*eslint max-depth:[2, 9] */ 9112 9113/** 9114 * This class parses HTML code using pure JavaScript and executes various events for each item it finds. It will 9115 * always execute the events in the right order for tag soup code like <b><p></b></p>. It will also remove elements 9116 * and attributes that doesn't fit the schema if the validate setting is enabled. 9117 * 9118 * @example 9119 * var parser = new tinymce.html.SaxParser({ 9120 * validate: true, 9121 * 9122 * comment: function(text) { 9123 * console.log('Comment:', text); 9124 * }, 9125 * 9126 * cdata: function(text) { 9127 * console.log('CDATA:', text); 9128 * }, 9129 * 9130 * text: function(text, raw) { 9131 * console.log('Text:', text, 'Raw:', raw); 9132 * }, 9133 * 9134 * start: function(name, attrs, empty) { 9135 * console.log('Start:', name, attrs, empty); 9136 * }, 9137 * 9138 * end: function(name) { 9139 * console.log('End:', name); 9140 * }, 9141 * 9142 * pi: function(name, text) { 9143 * console.log('PI:', name, text); 9144 * }, 9145 * 9146 * doctype: function(text) { 9147 * console.log('DocType:', text); 9148 * } 9149 * }, schema); 9150 * @class tinymce.html.SaxParser 9151 * @version 3.4 9152 */ 9153define("tinymce/html/SaxParser", [ 9154 "tinymce/html/Schema", 9155 "tinymce/html/Entities", 9156 "tinymce/util/Tools" 9157], function(Schema, Entities, Tools) { 9158 var each = Tools.each; 9159 9160 /** 9161 * Returns the index of the end tag for a specific start tag. This can be 9162 * used to skip all children of a parent element from being processed. 9163 * 9164 * @private 9165 * @method findEndTag 9166 * @param {tinymce.html.Schema} schema Schema instance to use to match short ended elements. 9167 * @param {String} html HTML string to find the end tag in. 9168 * @param {Number} startIndex Indext to start searching at should be after the start tag. 9169 * @return {Number} Index of the end tag. 9170 */ 9171 function findEndTag(schema, html, startIndex) { 9172 var count = 1, index, matches, tokenRegExp, shortEndedElements; 9173 9174 shortEndedElements = schema.getShortEndedElements(); 9175 tokenRegExp = /<([!?\/])?([A-Za-z0-9\-_\:\.]+)((?:\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\/|\s+)>/g; 9176 tokenRegExp.lastIndex = index = startIndex; 9177 9178 while ((matches = tokenRegExp.exec(html))) { 9179 index = tokenRegExp.lastIndex; 9180 9181 if (matches[1] === '/') { // End element 9182 count--; 9183 } else if (!matches[1]) { // Start element 9184 if (matches[2] in shortEndedElements) { 9185 continue; 9186 } 9187 9188 count++; 9189 } 9190 9191 if (count === 0) { 9192 break; 9193 } 9194 } 9195 9196 return index; 9197 } 9198 9199 /** 9200 * Constructs a new SaxParser instance. 9201 * 9202 * @constructor 9203 * @method SaxParser 9204 * @param {Object}
9204 settings Name/value collection of settings. comment, cdata, text, start and end are callbacks. 9205 * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing. 9206 */ 9207 function SaxParser(settings, schema) { 9208 var self = this; 9209 9210 function noop() {} 9211 9212 settings = settings || {}; 9213 self.schema = schema = schema || new Schema(); 9214 9215 if (settings.fix_self_closing !== false) { 9216 settings.fix_self_closing = true; 9217 } 9218 9219 // Add handler functions from settings and setup default handlers 9220 each('comment cdata text start end pi doctype'.split(' '), function(name) { 9221 if (name) { 9222 self[name] = settings[name] || noop; 9223 } 9224 }); 9225 9226 /** 9227 * Parses the specified HTML string and executes the callbacks for each item it finds. 9228 * 9229 * @example 9230 * new SaxParser({...}).parse('<b>text</b>'); 9231 * @method parse 9232 * @param {String} html Html string to sax parse. 9233 */ 9234 self.parse = function(html) { 9235 var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name; 9236 var isInternalElement, removeInternalElements, shortEndedElements, fillAttrsMap, isShortEnded; 9237 var validate, elementRule, isValidElement, attr, attribsValue, validAttributesMap, validAttributePatterns; 9238 var attributesRequired, attributesDefault, attributesForced; 9239 var anyAttributesRequired, selfClosing, tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0; 9240 var decode = Entities.decode, fixSelfClosing, filteredUrlAttrs = Tools.makeMap('src,href,data,background,formaction,poster'); 9241 var scriptUriRegExp = /((java|vb)script|mhtml):/i, dataUriRegExp = /^data:/i; 9242 9243 function processEndTag(name) { 9244 var pos, i; 9245 9246 // Find position of parent of the same type 9247 pos = stack.length; 9248 while (pos--) { 9249 if (stack[pos].name === name) { 9250 break; 9251 } 9252 } 9253 9254 // Found parent 9255 if (pos >= 0) { 9256 // Close all the open elements 9257 for (i = stack.length - 1; i >= pos; i--) { 9258 name = stack[i]; 9259 9260 if (name.valid) { 9261 self.end(name.name); 9262 } 9263 } 9264 9265 // Remove the open elements from the stack 9266 stack.length = pos; 9267 } 9268 } 9269 9270 function parseAttribute(match, name, value, val2, val3) { 9271 var attrRule, i, trimRegExp = /[\s\u0000-\u001F]+/g; 9272 9273 name = name.toLowerCase(); 9274 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute 9275 9276 // Validate name and value pass through all data- attributes 9277 if (validate && !isInternalElement && name.indexOf('data-') !== 0) { 9278 attrRule = validAttributesMap[name]; 9279 9280 // Find rule by pattern matching 9281 if (!attrRule && validAttributePatterns) { 9282 i = validAttributePatterns.length; 9283 while (i--) { 9284 attrRule = validAttributePatterns[i]; 9285 if (attrRule.pattern.test(name)) { 9286 break; 9287 } 9288 } 9289 9290 // No rule matched 9291 if (i === -1) { 9292 attrRule = null; 9293 } 9294 } 9295 9296 // No attribute rule found 9297 if (!attrRule) { 9298 return; 9299 } 9300 9301 // Validate value 9302 if (attrRule.validValues && !(value in attrRule.validValues)) { 9303 return; 9304 } 9305 } 9306 9307 // Block any javascript: urls or non image data uris 9308 if (filteredUrlAttrs[name] && !settings.allow_script_urls) { 9309 var uri = value.replace(trimRegExp, ''); 9310 9311 try { 9312 // Might throw malformed URI sequence 9313 uri = decodeURIComponent(uri); 9314 } catch (ex) { 9315 // Fallback to non UTF-8 decoder 9316 uri = unescape(uri); 9317 } 9318 9319 if (scriptUriRegExp.test(uri)) { 9320 return; 9321 } 9322 9323 if (!settings.allow_html_data_urls && dataUriRegExp.test(uri) && !/^data:image\//i.test(uri)) { 9324 return; 9325 } 9326 } 9327 9328 // Add attribute to list and map 9329 attrList.map[name] = value; 9330 attrList.push({ 9331 name: name, 9332 value: value 9333 }); 9334 } 9335 9336 // Precompile RegExps and map objects 9337 tokenRegExp = new RegExp('<(?:' + 9338 '(?:!--([\\w\\W]*?)-->)|' + // Comment 9339 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA 9340 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE 9341 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI 9342 '(?:\\/([^>]+)>)|' + // End element 9343 '(?:([A-Za-z0-9\\-_\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element 9344 ')', 'g'); 9345 9346 attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g; 9347 9348 // Setup lookup tables for empty elements and boolean attributes 9349 shortEndedElements = schema.getShortEndedElements(); 9350 selfClosing = settings.self_closing_elements || schema.getSelfClosingElements(); 9351 fillAttrsMap = schema.getBoolAttrs();
9352 validate = settings.validate; 9353 removeInternalElements = settings.remove_internals; 9354 fixSelfClosing = settings.fix_self_closing; 9355 specialElements = schema.getSpecialElements(); 9356 9357 while ((matches = tokenRegExp.exec(html))) { 9358 // Text 9359 if (index < matches.index) { 9360 self.text(decode(html.substr(index, matches.index - index))); 9361 } 9362 9363 if ((value = matches[6])) { // End element 9364 value = value.toLowerCase(); 9365 9366 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements 9367 if (value.charAt(0) === ':') { 9368 value = value.substr(1); 9369 } 9370 9371 processEndTag(value); 9372 } else if ((value = matches[7])) { // Start element 9373 value = value.toLowerCase(); 9374 9375 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements 9376 if (value.charAt(0) === ':') { 9377 value = value.substr(1); 9378 } 9379 9380 isShortEnded = value in shortEndedElements; 9381 9382 // Is self closing tag for example an <li> after an open <li> 9383 if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) { 9384 processEndTag(value); 9385 } 9386 9387 // Validate element 9388 if (!validate || (elementRule = schema.getElementRule(value))) { 9389 isValidElement = true; 9390 9391 // Grab attributes map and patters when validation is enabled 9392 if (validate) { 9393 validAttributesMap = elementRule.attributes; 9394 validAttributePatterns = elementRule.attributePatterns; 9395 } 9396 9397 // Parse attributes 9398 if ((attribsValue = matches[8])) { 9399 isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element 9400 9401 // If the element has internal attributes then remove it if we are told to do so 9402 if (isInternalElement && removeInternalElements) { 9403 isValidElement = false; 9404 } 9405 9406 attrList = []; 9407 attrList.map = {}; 9408 9409 attribsValue.replace(attrRegExp, parseAttribute); 9410 } else { 9411 attrList = []; 9412 attrList.map = {}; 9413 } 9414 9415 // Process attributes if validation is enabled 9416 if (validate && !isInternalElement) { 9417 attributesRequired = elementRule.attributesRequired; 9418 attributesDefault = elementRule.attributesDefault; 9419 attributesForced = elementRule.attributesForced; 9420 anyAttributesRequired = elementRule.removeEmptyAttrs; 9421 9422 // Check if any attribute exists 9423 if (anyAttributesRequired && !attrList.length) { 9424 isValidElement = false; 9425 } 9426 9427 // Handle forced attributes 9428 if (attributesForced) { 9429 i = attributesForced.length; 9430 while (i--) { 9431 attr = attributesForced[i]; 9432 name = attr.name; 9433 attrValue = attr.value; 9434 9435 if (attrValue === '{$uid}') { 9436 attrValue = 'mce_' + idCount++; 9437 } 9438 9439 attrList.map[name] = attrValue; 9440 attrList.push({name: name, value: attrValue}); 9441 } 9442 } 9443 9444 // Handle default attributes 9445 if (attributesDefault) { 9446 i = attributesDefault.length; 9447 while (i--) { 9448 attr = attributesDefault[i]; 9449 name = attr.name; 9450 9451 if (!(name in attrList.map)) { 9452 attrValue = attr.value; 9453 9454 if (attrValue === '{$uid}') { 9455 attrValue = 'mce_' + idCount++; 9456 } 9457 9458 attrList.map[name] = attrValue; 9459 attrList.push({name: name, value: attrValue}); 9460 } 9461 } 9462 } 9463 9464 // Handle required attributes 9465 if (attributesRequired) { 9466 i = attributesRequired.length; 9467 while (i--) { 9468 if (attributesRequired[i] in attrList.map) { 9469 break; 9470 } 9471 } 9472 9473 // None of the required attributes where found 9474 if (i === -1) { 9475 isValidElement = false; 9476 } 9477 } 9478 9479 // Invalidate element if it's marked as bogus 9480 if ((attr = attrList.map['data-mce-bogus'])) { 9481 if (attr === 'all') { 9482 index = findEndTag(schema, html, tokenRegExp.lastIndex); 9483 tokenRegExp.lastIndex = index; 9484 continue; 9485 } 9486 9487 isValidElement = false;
9488 } 9489 } 9490 9491 if (isValidElement) { 9492 self.start(value, attrList, isShortEnded); 9493 } 9494 } else { 9495 isValidElement = false; 9496 } 9497 9498 // Treat script, noscript and style a bit different since they may include code that looks like elements 9499 if ((endRegExp = specialElements[value])) { 9500 endRegExp.lastIndex = index = matches.index + matches[0].length; 9501 9502 if ((matches = endRegExp.exec(html))) { 9503 if (isValidElement) { 9504 text = html.substr(index, matches.index - index); 9505 } 9506 9507 index = matches.index + matches[0].length; 9508 } else { 9509 text = html.substr(index); 9510 index = html.length; 9511 } 9512 9513 if (isValidElement) { 9514 if (text.length > 0) { 9515 self.text(text, true); 9516 } 9517 9518 self.end(value); 9519 } 9520 9521 tokenRegExp.lastIndex = index; 9522 continue; 9523 } 9524 9525 // Push value on to stack 9526 if (!isShortEnded) { 9527 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) { 9528 stack.push({name: value, valid: isValidElement}); 9529 } else if (isValidElement) { 9530 self.end(value); 9531 } 9532 } 9533 } else if ((value = matches[1])) { // Comment 9534 // Padd comment value to avoid browsers from parsing invalid comments as HTML 9535 if (value.charAt(0) === '>') { 9536 value = ' ' + value; 9537 } 9538 9539 if (!settings.allow_conditional_comments && value.substr(0, 3) === '[if') { 9540 value = ' ' + value; 9541 } 9542 9543 self.comment(value); 9544 } else if ((value = matches[2])) { // CDATA 9545 self.cdata(value); 9546 } else if ((value = matches[3])) { // DOCTYPE 9547 self.doctype(value); 9548 } else if ((value = matches[4])) { // PI 9549 self.pi(value, matches[5]); 9550 } 9551 9552 index = matches.index + matches[0].length; 9553 } 9554 9555 // Text 9556 if (index < html.length) { 9557 self.text(decode(html.substr(index))); 9558 } 9559 9560 // Close any open elements 9561 for (i = stack.length - 1; i >= 0; i--) { 9562 value = stack[i]; 9563 9564 if (value.valid) { 9565 self.end(value.name); 9566 } 9567 } 9568 }; 9569 } 9570 9571 SaxParser.findEndTag = findEndTag; 9572 9573 return SaxParser; 9574}); 9575 9576// Included from: js/tinymce/classes/html/DomParser.js 9577 9578/** 9579 * DomParser.js 9580 * 9581 * Copyright, Moxiecode Systems AB 9582 * Released under LGPL License. 9583 * 9584 * License: http://www.tinymce.com/license 9585 * Contributing: http://www.tinymce.com/contributing 9586 */ 9587 9588/** 9589 * This class parses HTML code into a DOM like structure of nodes it will remove redundant whitespace and make 9590 * sure that the node tree is valid according to the specified schema. 9591 * So for example: <p>a<p>b</p>c</p> will become <p>a</p><p>b</p><p>c</p> 9592 * 9593 * @example 9594 * var parser = new tinymce.html.DomParser({validate: true}, schema); 9595 * var rootNode = parser.parse('<h1>content</h1>'); 9596 * 9597 * @class tinymce.html.DomParser 9598 * @version 3.4 9599 */ 9600define("tinymce/html/DomParser", [ 9601 "tinymce/html/Node", 9602 "tinymce/html/Schema", 9603 "tinymce/html/SaxParser", 9604 "tinymce/util/Tools" 9605], function(Node, Schema, SaxParser, Tools) { 9606 var makeMap = Tools.makeMap, each = Tools.each, explode = Tools.explode, extend = Tools.extend; 9607 9608 /** 9609 * Constructs a new DomParser instance. 9610 * 9611 * @constructor 9612 * @method DomParser 9613 * @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks. 9614 * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing. 9615 */ 9616 return function(settings, schema) { 9617 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {}; 9618 9619 settings = settings || {}; 9620 settings.validate = "validate" in settings ? settings.validate : true; 9621 settings.root_name = settings.root_name || 'body'; 9622 self.schema = schema = schema || new Schema(); 9623 9624 function fixInvalidChildren(nodes) { 9625 var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i; 9626 var nonEmptyElements, nonSplitableElements, textBlockElements, sibling, nextNode; 9627 9628 nonSplitableElements = makeMap('tr,td,th,tbody,thead,tfoot,table'); 9629 nonEmptyElements = schema.getNonEmptyElements(); 9630 textBlockElements = schema.getTextBlockElements(); 9631 9632 for (ni = 0; ni < nodes.length; ni++) { 9633 node = nodes[ni]; 9634 9635 // Already removed or fixed 9636 if (!node.parent || node.fixed) { 9637 continue; 9638 } 9639 9640 // If the invalid element is a text block and the text block is within a parent LI element 9641 // Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office 9642 if (textBlockElements[node.name] && node.parent.name == 'li') { 9643 // Move sibling text blocks after LI element 9644 sibling = node.next; 9645 while (sibling) { 9646 if (textBlockElements[sibling.name]) { 9647 sibling.name = 'li'; 9648 sibling.fixed = true; 9649 node.parent.insert(sibling, node.parent); 9650 } else { 9651 break; 9652 } 9653 9654 sibling = sibling.next; 9655 } 9656 9657 // Unwrap current text block 9658 node.unwrap(node); 9659 continue; 9660 } 9661 9662 // Get list of all parent nodes until we find a valid parent to stick the child into 9663 parents = [node]; 9664 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && 9665 !nonSplitableElements[parent.name]; parent = parent.parent) { 9666 parents.push(parent); 9667 } 9668 9669 // Found a suitable parent 9670 if (parent && parents.length > 1) { 9671 // Reverse the array since it makes looping easier 9672 parents.reverse(); 9673 9674 // Clone the related parent and insert that after the moved node 9675 newParent = currentNode = self.filterNode(parents[0].clone()); 9676 9677 // Start cloning and moving children on the left side of the target node 9678 for (i = 0; i < parents.length - 1; i++) { 9679 if (schema.isValidChild(currentNode.name, parents[i].name)) { 9680 tempNode = self.filterNode(parents[i].clone()); 9681 currentNode.append(tempNode); 9682 } else { 9683 tempNode = currentNode; 9684 } 9685 9686 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1];) { 9687 nextNode = childNode.next; 9688 tempNode.append(childNode); 9689 childNode = nextNode; 9690 } 9691 9692 currentNode = tempNode; 9693 } 9694 9695 if (!newParent.isEmpty(nonEmptyElements)) { 9696 parent.insert(newParent, parents[0], true); 9697 parent.insert(node, newParent); 9698 } else { 9699 parent.insert(node, parents[0], true); 9700 } 9701 9702 // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p> 9703 parent = parents[0]; 9704 if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') { 9705 parent.empty().remove(); 9706 } 9707 } else if (node.parent) {
9708 // If it's an LI try to find a UL/OL for it or wrap it 9709 if (node.name === 'li') { 9710 sibling = node.prev; 9711 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { 9712 sibling.append(node); 9713 continue; 9714 } 9715 9716 sibling = node.next; 9717 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { 9718 sibling.insert(node, sibling.firstChild, true); 9719 continue; 9720 } 9721 9722 node.wrap(self.filterNode(new Node('ul', 1))); 9723 continue; 9724 } 9725 9726 // Try wrapping the element in a DIV 9727 if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) { 9728 node.wrap(self.filterNode(new Node('div', 1))); 9729 } else { 9730 // We failed wrapping it, then remove or unwrap it 9731 if (node.name === 'style' || node.name === 'script') { 9732 node.empty().remove(); 9733 } else { 9734 node.unwrap(); 9735 } 9736 } 9737 } 9738 } 9739 } 9740 9741 /** 9742 * Runs the specified node though the element and attributes filters. 9743 * 9744 * @method filterNode 9745 * @param {tinymce.html.Node} Node the node to run filters on. 9746 * @return {tinymce.html.Node} The passed in node. 9747 */ 9748 self.filterNode = function(node) { 9749 var i, name, list; 9750 9751 // Run element filters 9752 if (name in nodeFilters) { 9753 list = matchedNodes[name]; 9754 9755 if (list) { 9756 list.push(node); 9757 } else { 9758 matchedNodes[name] = [node]; 9759 } 9760 } 9761 9762 // Run attribute filters 9763 i = attributeFilters.length; 9764 while (i--) { 9765 name = attributeFilters[i].name; 9766 9767 if (name in node.attributes.map) { 9768 list = matchedAttributes[name]; 9769 9770 if (list) { 9771 list.push(node); 9772 } else { 9773 matchedAttributes[name] = [node]; 9774 } 9775 } 9776 } 9777 9778 return node; 9779 }; 9780 9781 /** 9782 * Adds a node filter function to the parser, the parser will collect the specified nodes by name 9783 * and then execute the callback ones it has finished parsing the document. 9784 * 9785 * @example 9786 * parser.addNodeFilter('p,h1', function(nodes, name) { 9787 * for (var i = 0; i < nodes.length; i++) { 9788 * console.log(nodes[i].name); 9789 * } 9790 * }); 9791 * @method addNodeFilter 9792 * @method {String} name Comma separated list of nodes to collect. 9793 * @param {function} callback Callback function to execute once it has collected nodes. 9794 */ 9795 self.addNodeFilter = function(name, callback) { 9796 each(explode(name), function(name) { 9797 var list = nodeFilters[name]; 9798 9799 if (!list) { 9800 nodeFilters[name] = list = []; 9801 } 9802 9803 list.push(callback); 9804 }); 9805 }; 9806 9807 /** 9808 * Adds a attribute filter function to the parser, the parser will collect nodes that has the specified attributes 9809 * and then execute the callback ones it has finished parsing the document. 9810 * 9811 * @example 9812 * parser.addAttributeFilter('src,href', function(nodes, name) { 9813 * for (var i = 0; i < nodes.length; i++) { 9814 * console.log(nodes[i].name); 9815 * } 9816 * }); 9817 * @method addAttributeFilter 9818 * @method {String} name Comma separated list of nodes to collect. 9819 * @param {function} callback Callback function to execute once it has collected nodes. 9820 */ 9821 self.addAttributeFilter = function(name, callback) { 9822 each(explode(name), function(name) { 9823 var i; 9824 9825 for (i = 0; i < attributeFilters.length; i++) { 9826 if (attributeFilters[i].name === name) { 9827 attributeFilters[i].callbacks.push(callback); 9828 return; 9829 } 9830 } 9831 9832 attributeFilters.push({name: name, callbacks: [callback]}); 9833 }); 9834 }; 9835 9836 /** 9837 * Parses the specified HTML string into a DOM like node tree and returns the result. 9838 * 9839 * @example 9840 * var rootNode = new DomParser({...}).parse('<b>text</b>'); 9841 * @method parse 9842 * @param {String} html Html string to sax parse. 9843 * @param {Object} args Optional args object that gets passed to all filter functions. 9844 * @return {tinymce.html.Node} Root node containing the tree. 9845 */ 9846 self.parse = function(html, args) { 9847 var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate; 9848 var blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement; 9849 var endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements; 9850 var children, nonEmptyElements, rootBlockName; 9851 9852 args = args || {}; 9853 matchedNodes = {}; 9854 matchedAttributes = {}; 9855 blockElements = extend(makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements()); 9856 nonEmptyElements = schema.getNonEmptyElements(); 9857 children = schema.children;
9858 validate = settings.validate; 9859 rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block; 9860 9861 whiteSpaceElements = schema.getWhiteSpaceElements(); 9862 startWhiteSpaceRegExp = /^[ \t\r\n]+/; 9863 endWhiteSpaceRegExp = /[ \t\r\n]+$/; 9864 allWhiteSpaceRegExp = /[ \t\r\n]+/g; 9865 isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/; 9866 9867 function addRootBlocks() { 9868 var node = rootNode.firstChild, next, rootBlockNode; 9869 9870 // Removes whitespace at beginning and end of block so: 9871 // <p> x </p> -> <p>x</p> 9872 function trim(rootBlockNode) { 9873 if (rootBlockNode) { 9874 node = rootBlockNode.firstChild; 9875 if (node && node.type == 3) { 9876 node.value = node.value.replace(startWhiteSpaceRegExp, ''); 9877 } 9878 9879 node = rootBlockNode.lastChild; 9880 if (node && node.type == 3) { 9881 node.value = node.value.replace(endWhiteSpaceRegExp, ''); 9882 } 9883 } 9884 } 9885 9886 // Check if rootBlock is valid within rootNode for example if P is valid in H1 if H1 is the contentEditabe root 9887 if (!schema.isValidChild(rootNode.name, rootBlockName.toLowerCase())) { 9888 return; 9889 } 9890 9891 while (node) { 9892 next = node.next; 9893 9894 if (node.type == 3 || (node.type == 1 && node.name !== 'p' && 9895 !blockElements[node.name] && !node.attr('data-mce-type'))) { 9896 if (!rootBlockNode) { 9897 // Create a new root block element 9898 rootBlockNode = createNode(rootBlockName, 1); 9899 rootBlockNode.attr(settings.forced_root_block_attrs); 9900 rootNode.insert(rootBlockNode, node); 9901 rootBlockNode.append(node); 9902 } else { 9903 rootBlockNode.append(node); 9904 } 9905 } else { 9906 trim(rootBlockNode); 9907 rootBlockNode = null; 9908 } 9909 9910 node = next; 9911 } 9912 9913 trim(rootBlockNode); 9914 } 9915 9916 function createNode(name, type) { 9917 var node = new Node(name, type), list; 9918 9919 if (name in nodeFilters) { 9920 list = matchedNodes[name]; 9921 9922 if (list) { 9923 list.push(node); 9924 } else { 9925 matchedNodes[name] = [node]; 9926 } 9927 } 9928 9929 return node; 9930 } 9931 9932 function removeWhitespaceBefore(node) { 9933 var textNode, textVal, sibling; 9934 9935 for (textNode = node.prev; textNode && textNode.type === 3;) { 9936 textVal = textNode.value.replace(endWhiteSpaceRegExp, ''); 9937 9938 if (textVal.length > 0) { 9939 textNode.value = textVal; 9940 textNode = textNode.prev; 9941 } else { 9942 sibling = textNode.prev; 9943 textNode.remove(); 9944 textNode = sibling; 9945 } 9946 } 9947 } 9948 9949 function cloneAndExcludeBlocks(input) { 9950 var name, output = {}; 9951 9952 for (name in input) { 9953 if (name !== 'li' && name != 'p') { 9954 output[name] = input[name]; 9955 } 9956 } 9957 9958 return output; 9959 } 9960 9961 parser = new SaxParser({ 9962 validate: validate, 9963 allow_script_urls: settings.allow_script_urls, 9964 allow_conditional_comments: settings.allow_conditional_comments, 9965 9966 // Exclude P and LI from DOM parsing since it's treated better by the DOM parser 9967 self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()), 9968 9969 cdata: function(text) { 9970 node.append(createNode('#cdata', 4)).value = text; 9971 }, 9972 9973 text: function(text, raw) { 9974 var textNode; 9975 9976 // Trim all redundant whitespace on non white space elements 9977 if (!isInWhiteSpacePreservedElement) { 9978 text = text.replace(allWhiteSpaceRegExp, ' '); 9979 9980 if (node.lastChild && blockElements[node.lastChild.name]) { 9981 text = text.replace(startWhiteSpaceRegExp, ''); 9982 } 9983 } 9984 9985 // Do we need to create the node 9986 if (text.length !== 0) { 9987 textNode = createNode('#text', 3); 9988 textNode.raw = !!raw; 9989 node.append(textNode).value = text; 9990 } 9991 }, 9992 9993 comment: function(text) { 9994 node.append(createNode('#comment', 8)).value = text; 9995 }, 9996 9997 pi: function(name, text) { 9998 node.append(createNode(name, 7)).value = text; 9999 removeWhitespaceBefore(node); 10000 }, 10001 10002 doctype: function(text) { 10003 var newNode; 10004 10005 newNode = node.append(createNode('#doctype', 10)); 10006 newNode.value = text; 10007 removeWhitespaceBefore(node); 10008 }, 10009 10010 start: function(name, attrs, empty) { 10011 var newNode, attrFiltersLen, elementRule, attrName, parent; 10012 10013 elementRule = validate ? schema.getElementRule(name) : {}; 10014 if (elementRule) {
10015 newNode = createNode(elementRule.outputName || name, 1); 10016 newNode.attributes = attrs; 10017 newNode.shortEnded = empty; 10018 10019 node.append(newNode); 10020 10021 // Check if node is valid child of the parent node is the child is 10022 // unknown we don't collect it since it's probably a custom element 10023 parent = children[node.name]; 10024 if (parent && children[newNode.name] && !parent[newNode.name]) { 10025 invalidChildren.push(newNode); 10026 } 10027 10028 attrFiltersLen = attributeFilters.length; 10029 while (attrFiltersLen--) { 10030 attrName = attributeFilters[attrFiltersLen].name; 10031 10032 if (attrName in attrs.map) { 10033 list = matchedAttributes[attrName]; 10034 10035 if (list) { 10036 list.push(newNode); 10037 } else { 10038 matchedAttributes[attrName] = [newNode]; 10039 } 10040 } 10041 } 10042 10043 // Trim whitespace before block 10044 if (blockElements[name]) { 10045 removeWhitespaceBefore(newNode); 10046 } 10047 10048 // Change current node if the element wasn't empty i.e not <br /> or <img /> 10049 if (!empty) { 10050 node = newNode; 10051 } 10052 10053 // Check if we are inside a whitespace preserved element 10054 if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { 10055 isInWhiteSpacePreservedElement = true; 10056 } 10057 } 10058 }, 10059 10060 end: function(name) { 10061 var textNode, elementRule, text, sibling, tempNode; 10062 10063 elementRule = validate ? schema.getElementRule(name) : {}; 10064 if (elementRule) { 10065 if (blockElements[name]) { 10066 if (!isInWhiteSpacePreservedElement) { 10067 // Trim whitespace of the first node in a block 10068 textNode = node.firstChild; 10069 if (textNode && textNode.type === 3) { 10070 text = textNode.value.replace(startWhiteSpaceRegExp, ''); 10071 10072 // Any characters left after trim or should we remove it 10073 if (text.length > 0) { 10074 textNode.value = text; 10075 textNode = textNode.next; 10076 } else { 10077 sibling = textNode.next; 10078 textNode.remove(); 10079 textNode = sibling; 10080 10081 // Remove any pure whitespace siblings 10082 while (textNode && textNode.type === 3) { 10083 text = textNode.value; 10084 sibling = textNode.next; 10085 10086 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { 10087 textNode.remove(); 10088 textNode = sibling; 10089 } 10090 10091 textNode = sibling; 10092 } 10093 } 10094 } 10095 10096 // Trim whitespace of the last node in a block 10097 textNode = node.lastChild; 10098 if (textNode && textNode.type === 3) { 10099 text = textNode.value.replace(endWhiteSpaceRegExp, ''); 10100 10101 // Any characters left after trim or should we remove it 10102 if (text.length > 0) { 10103 textNode.value = text; 10104 textNode = textNode.prev; 10105 } else { 10106 sibling = textNode.prev; 10107 textNode.remove(); 10108 textNode = sibling; 10109 10110 // Remove any pure whitespace siblings 10111 while (textNode && textNode.type === 3) { 10112 text = textNode.value; 10113 sibling = textNode.prev; 10114 10115 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { 10116 textNode.remove(); 10117 textNode = sibling; 10118 } 10119 10120 textNode = sibling; 10121 } 10122 } 10123 } 10124 } 10125 10126 // Trim start white space 10127 // Removed due to: #5424 10128 /*textNode = node.prev; 10129 if (textNode && textNode.type === 3) { 10130 text = textNode.value.replace(startWhiteSpaceRegExp, ''); 10131 10132 if (text.length > 0) 10133 textNode.value = text; 10134 else 10135 textNode.remove(); 10136 }*/ 10137 } 10138 10139 // Check if we exited a whitespace preserved element 10140 if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { 10141 isInWhiteSpacePreservedElement = false; 10142 } 10143 10144 // Handle empty nodes 10145 if (elementRule.removeEmpty || elementRule.paddEmpty) { 10146 if (node.isEmpty(nonEmptyElements)) { 10147 if (elementRule.paddEmpty) { 10148 node.empty().append(new Node('#text', '3')).value = '\u00a0'; 10149 } else { 10150 // Leave nodes that have a name like <a name="name"> 10151 if (!node.attributes.map.name && !node.attributes.map.id) { 10152 tempNode = node.parent; 10153 10154 if (blockElements[node.name]) { 10155 node.empty().remove(); 10156 } else { 10157 node.unwrap(); 10158 } 10159 10160 node = tempNode; 10161 return; 10162 } 10163 } 10164 } 10165 } 10166 10167 node = node.parent; 10168 } 10169 } 10170 }, schema); 10171 10172 rootNode = node = new Node(args.context || settings.root_name, 11); 10173 10174 parser.parse(html); 10175 10176 // Fix invalid children or report invalid children in a contextual parsing 10177 if (validate && invalidChildren.length) { 10178 if (!args.context) { 10179 fixInvalidChildren(invalidChildren); 10180 } else { 10181 args.invalid = true; 10182 } 10183 } 10184 10185 // Wrap nodes in the root into block elements if the root is body 10186 if (rootBlockName && (rootNode.name == 'body' || args.isRootContent)) { 10187 addRootBlocks(); 10188 } 10189 10190 // Run filters only when the contents is valid 10191 if (!args.invalid) { 10192 // Run node filters 10193 for (name in matchedNodes) { 10194 list = nodeFilters[name]; 10195 nodes = matchedNodes[name]; 10196 10197 // Remove already removed children 10198 fi = nodes.length; 10199 while (fi--) { 10200 if (!nodes[fi].parent) { 10201 nodes.splice(fi, 1); 10202 } 10203 } 10204 10205 for (i = 0, l = list.length; i < l; i++) { 10206 list[i](nodes, name, args); 10207 } 10208 } 10209 10210 // Run attribute filters 10211 for (i = 0, l = attributeFilters.length; i < l; i++) { 10212 list = attributeFilters[i]; 10213 10214 if (list.name in matchedAttributes) { 10215 nodes = matchedAttributes[list.name]; 10216 10217 // Remove already removed children 10218 fi = nodes.length; 10219 while (fi--) { 10220 if (!nodes[fi].parent) { 10221 nodes.splice(fi, 1); 10222 } 10223 } 10224 10225 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) { 10226 list.callbacks[fi](nodes, list.name, args); 10227 } 10228 } 10229 } 10230 } 10231 10232 return rootNode; 10233 }; 10234 10235 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to 10236 // make it possible to place the caret inside empty blocks. This logic tries to remove 10237 // these elements and keep br elements that where intended to be there intact 10238 if (settings.remove_trailing_brs) { 10239 self.addNodeFilter('br', function(nodes) { 10240 var i, l = nodes.length, node, blockElements = extend({}, schema.getBlockElements()); 10241 var nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName; 10242 var elementRule, textNode; 10243 10244 // Remove brs from body element as well 10245 blockElements.body = 1; 10246 10247 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p> 10248 for (i = 0; i < l; i++) { 10249 node = nodes[i]; 10250 parent = node.parent; 10251 10252 if (blockElements[node.parent.name] && node === parent.lastChild) { 10253 // Loop all nodes to the left of the current node and check for other BR elements 10254 // excluding bookmarks since they are invisible 10255 prev = node.prev; 10256 while (prev) { 10257 prevName = prev.name; 10258 10259 // Ignore bookmarks 10260 if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') { 10261 // Found a non BR element 10262 if (prevName !== "br") { 10263 break; 10264 } 10265 10266 // Found another br it's a <br><br> structure then don't remove anything 10267 if (prevName === 'br') { 10268 node = null; 10269 break; 10270 } 10271 } 10272 10273 prev = prev.prev; 10274 } 10275 10276 if (node) { 10277 node.remove(); 10278 10279 // Is the parent to be considered empty after we removed the BR 10280 if (parent.isEmpty(nonEmptyElements)) { 10281 elementRule = schema.getElementRule(parent.name); 10282 10283 // Remove or padd the element depending on schema rule 10284 if (elementRule) { 10285 if (elementRule.removeEmpty) { 10286 parent.remove(); 10287 } else if (elementRule.paddEmpty) { 10288 parent.empty().append(new Node('#text', 3)).value = '\u00a0'; 10289 } 10290 } 10291 } 10292 } 10293 } else {
10294 // Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> 10295 // so they become <p><b><i> </i></b></p> 10296 lastParent = node; 10297 while (parent && parent.firstChild === lastParent && parent.lastChild === lastParent) { 10298 lastParent = parent; 10299 10300 if (blockElements[parent.name]) { 10301 break; 10302 } 10303 10304 parent = parent.parent; 10305 } 10306 10307 if (lastParent === parent) { 10308 textNode = new Node('#text', 3); 10309 textNode.value = '\u00a0'; 10310 node.replace(textNode); 10311 } 10312 } 10313 } 10314 }); 10315 } 10316 10317 // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included. 10318 if (!settings.allow_html_in_named_anchor) { 10319 self.addAttributeFilter('id,name', function(nodes) { 10320 var i = nodes.length, sibling, prevSibling, parent, node; 10321 10322 while (i--) { 10323 node = nodes[i]; 10324 if (node.name === 'a' && node.firstChild && !node.attr('href')) { 10325 parent = node.parent; 10326 10327 // Move children after current node 10328 sibling = node.lastChild; 10329 do { 10330 prevSibling = sibling.prev; 10331 parent.insert(sibling, node); 10332 sibling = prevSibling; 10333 } while (sibling); 10334 } 10335 } 10336 }); 10337 } 10338 10339 if (settings.validate && schema.getValidClasses()) { 10340 self.addAttributeFilter('class', function(nodes) { 10341 var i = nodes.length, node, classList, ci, className, classValue; 10342 var validClasses = schema.getValidClasses(), validClassesMap, valid; 10343 10344 while (i--) { 10345 node = nodes[i]; 10346 classList = node.attr('class').split(' '); 10347 classValue = ''; 10348 10349 for (ci = 0; ci < classList.length; ci++) { 10350 className = classList[ci]; 10351 valid = false; 10352 10353 validClassesMap = validClasses['*']; 10354 if (validClassesMap && validClassesMap[className]) { 10355 valid = true; 10356 } 10357 10358 validClassesMap = validClasses[node.name]; 10359 if (!valid && validClassesMap && !validClassesMap[className]) { 10360 valid = true; 10361 } 10362 10363 if (valid) { 10364 if (classValue) { 10365 classValue += ' '; 10366 } 10367 10368 classValue += className; 10369 } 10370 } 10371 10372 if (!classValue.length) { 10373 classValue = null; 10374 } 10375 10376 node.attr('class', classValue); 10377 } 10378 }); 10379 } 10380 }; 10381}); 10382 10383// Included from: js/tinymce/classes/html/Writer.js 10384 10385/** 10386 * Writer.js 10387 * 10388 * Copyright, Moxiecode Systems AB 10389 * Released under LGPL License. 10390 * 10391 * License: http://www.tinymce.com/license 10392 * Contributing: http://www.tinymce.com/contributing 10393 */ 10394 10395/** 10396 * This class is used to write HTML tags out it can be used with the Serializer or the SaxParser. 10397 * 10398 * @class tinymce.html.Writer 10399 * @example 10400 * var writer = new tinymce.html.Writer({indent: true}); 10401 * var parser = new tinymce.html.SaxParser(writer).parse('<p><br></p>'); 10402 * console.log(writer.getContent()); 10403 * 10404 * @class tinymce.html.Writer 10405 * @version 3.4 10406 */ 10407define("tinymce/html/Writer", [ 10408 "tinymce/html/Entities", 10409 "tinymce/util/Tools" 10410], function(Entities, Tools) { 10411 var makeMap = Tools.makeMap; 10412 10413 /** 10414 * Constructs a new Writer instance. 10415 * 10416 * @constructor 10417 * @method Writer 10418 * @param {Object} settings Name/value settings object. 10419 */ 10420 return function(settings) { 10421 var html = [], indent, indentBefore, indentAfter, encode, htmlOutput; 10422 10423 settings = settings || {}; 10424 indent = settings.indent; 10425 indentBefore = makeMap(settings.indent_before || ''); 10426 indentAfter = makeMap(settings.indent_after || ''); 10427 encode = Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities); 10428 htmlOutput = settings.element_format == "html"; 10429 10430 return { 10431 /** 10432 * Writes the a start element such as <p id="a">. 10433 * 10434 * @method start 10435 * @param {String} name Name of the element. 10436 * @param {Array} attrs Optional attribute array or undefined if it hasn't any. 10437 * @param {Boolean} empty Optional empty state if the tag should end like <br />. 10438 */ 10439 start: function(name, attrs, empty) { 10440 var i, l, attr, value; 10441 10442 if (indent && indentBefore[name] && html.length > 0) { 10443 value = html[html.length - 1]; 10444 10445 if (value.length > 0 && value !== '\n') { 10446 html.push('\n'); 10447 } 10448 } 10449 10450 html.push('<', name); 10451 10452 if (attrs) { 10453 for (i = 0, l = attrs.length; i < l; i++) { 10454 attr = attrs[i]; 10455 html.push(' ', attr.name, '="', encode(attr.value, true), '"'); 10456 } 10457 } 10458 10459 if (!empty || htmlOutput) { 10460 html[html.length] = '>'; 10461 } else { 10462 html[html.length] = ' />'; 10463 } 10464 10465 if (empty && indent && indentAfter[name] && html.length > 0) { 10466 value = html[html.length - 1]; 10467 10468 if (value.length > 0 && value !== '\n') { 10469 html.push('\n'); 10470 } 10471 } 10472 }, 10473 10474 /** 10475 * Writes the a end element such as </p>. 10476 * 10477 * @method end 10478 * @param {String} name Name of the element. 10479 */ 10480 end: function(name) { 10481 var value; 10482 10483 /*if (indent && indentBefore[name] && html.length > 0) { 10484 value = html[html.length - 1]; 10485 10486 if (value.length > 0 && value !== '\n') 10487 html.push('\n'); 10488 }*/ 10489 10490 html.push('</', name, '>'); 10491 10492 if (indent && indentAfter[name] && html.length > 0) { 10493 value = html[html.length - 1]; 10494 10495 if (value.length > 0 && value !== '\n') { 10496 html.push('\n'); 10497 } 10498 } 10499 }, 10500 10501 /** 10502 * Writes a text node. 10503 * 10504 * @method text 10505 * @param {String} text String to write out. 10506 * @param {Boolean} raw Optional raw state if true the contents wont get encoded. 10507 */ 10508 text: function(text, raw) { 10509 if (text.length > 0) { 10510 html[html.length] = raw ? text : encode(text); 10511 } 10512 }, 10513 10514 /** 10515 * Writes a cdata node such as <![CDATA[data]]>. 10516 * 10517 * @method cdata 10518 * @param {String} text String to write out inside the cdata. 10519 */ 10520 cdata: function(text) { 10521 html.push('<![CDATA[', text, ']]>'); 10522 }, 10523 10524 /** 10525 * Writes a comment node such as <!-- Comment -->. 10526 * 10527 * @method cdata 10528 * @param {String} text String to write out inside the comment. 10529 */ 10530 comment: function(text) { 10531 html.push('<!--', text, '-->'); 10532 }, 10533 10534 /** 10535 * Writes a PI node such as <?xml attr="value" ?>. 10536 * 10537 * @method pi 10538 * @param {String} name Name of the pi. 10539 * @param {String} text String to write out inside the pi. 10540 */ 10541 pi: function(name, text) { 10542 if (text) { 10543 html.push('<?', name, ' ', text, '?>'); 10544 } else { 10545 html.push('<?', name, '?>'); 10546 } 10547 10548 if (indent) { 10549 html.push('\n'); 10550 } 10551 }, 10552 10553 /** 10554 * Writes a doctype node such as <!DOCTYPE data>. 10555 * 10556 * @method doctype 10557 * @param {String} text String to write out inside the doctype. 10558 */ 10559 doctype: function(text) { 10560 html.push('<!DOCTYPE', text, '>', indent ? '\n' : ''); 10561 }, 10562 10563 /** 10564 * Resets the internal buffer if one wants to reuse the writer. 10565 * 10566 * @method reset 10567 */ 10568 reset: function() { 10569 html.length = 0; 10570 }, 10571 10572 /** 10573 * Returns the contents that got serialized. 10574 * 10575 * @method getContent 10576 * @return {String} HTML contents that got written down. 10577 */ 10578 getContent: function() { 10579 return html.join('').replace(/\n$/, ''); 10580 } 10581 }; 10582 }; 10583}); 10584 10585// Included from: js/tinymce/classes/html/Serializer.js 10586 10587/** 10588 * Serializer.js 10589 * 10590 * Copyright, Moxiecode Systems AB 10591 * Released under LGPL License. 10592 * 10593 * License: http://www.tinymce.com/license 10594 * Contributing: http://www.tinymce.com/contributing 10595 */ 10596 10597/** 10598 * This class is used to serialize down the DOM tree into a string using a Writer instance. 10599 * 10600 * 10601 * @example 10602 * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>')); 10603 * @class tinymce.html.Serializer 10604 * @version 3.4 10605 */ 10606define("tinymce/html/Serializer", [ 10607 "tinymce/html/Writer", 10608 "tinymce/html/Schema" 10609], function(Writer, Schema) { 10610 /** 10611 * Constructs a new Serializer instance. 10612 * 10613 * @constructor 10614 * @method Serializer 10615 * @param {Object} settings Name/value settings object. 10616 * @param {tinymce.html.Schema} schema Schema instance to use. 10617 */ 10618 return function(settings, schema) { 10619 var self = this, writer = new Writer(settings); 10620 10621 settings = settings || {};
10622 settings.validate = "validate" in settings ? settings.validate : true; 10623 10624 self.schema = schema = schema || new Schema(); 10625 self.writer = writer; 10626 10627 /** 10628 * Serializes the specified node into a string. 10629 * 10630 * @example 10631 * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>')); 10632 * @method serialize 10633 * @param {tinymce.html.Node} node Node instance to serialize. 10634 * @return {String} String with HTML based on DOM tree. 10635 */ 10636 self.serialize = function(node) { 10637 var handlers, validate; 10638 10639 validate = settings.validate; 10640 10641 handlers = { 10642 // #text 10643 3: function(node) { 10644 writer.text(node.value, node.raw); 10645 }, 10646 10647 // #comment 10648 8: function(node) { 10649 writer.comment(node.value); 10650 }, 10651 10652 // Processing instruction 10653 7: function(node) { 10654 writer.pi(node.name, node.value); 10655 }, 10656 10657 // Doctype 10658 10: function(node) { 10659 writer.doctype(node.value); 10660 }, 10661 10662 // CDATA 10663 4: function(node) { 10664 writer.cdata(node.value); 10665 }, 10666 10667 // Document fragment 10668 11: function(node) { 10669 if ((node = node.firstChild)) { 10670 do { 10671 walk(node); 10672 } while ((node = node.next)); 10673 } 10674 } 10675 }; 10676 10677 writer.reset(); 10678 10679 function walk(node) { 10680 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule; 10681 10682 if (!handler) { 10683 name = node.name; 10684 isEmpty = node.shortEnded; 10685 attrs = node.attributes; 10686 10687 // Sort attributes 10688 if (validate && attrs && attrs.length > 1) { 10689 sortedAttrs = []; 10690 sortedAttrs.map = {}; 10691 10692 elementRule = schema.getElementRule(node.name); 10693 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) { 10694 attrName = elementRule.attributesOrder[i]; 10695 10696 if (attrName in attrs.map) { 10697 attrValue = attrs.map[attrName]; 10698 sortedAttrs.map[attrName] = attrValue; 10699 sortedAttrs.push({name: attrName, value: attrValue}); 10700 } 10701 } 10702 10703 for (i = 0, l = attrs.length; i < l; i++) { 10704 attrName = attrs[i].name; 10705 10706 if (!(attrName in sortedAttrs.map)) { 10707 attrValue = attrs.map[attrName]; 10708 sortedAttrs.map[attrName] = attrValue; 10709 sortedAttrs.push({name: attrName, value: attrValue}); 10710 } 10711 } 10712 10713 attrs = sortedAttrs; 10714 } 10715 10716 writer.start(node.name, attrs, isEmpty); 10717 10718 if (!isEmpty) { 10719 if ((node = node.firstChild)) { 10720 do { 10721 walk(node); 10722 } while ((node = node.next)); 10723 } 10724 10725 writer.end(name); 10726 } 10727 } else { 10728 handler(node); 10729 } 10730 } 10731 10732 // Serialize element and treat all non elements as fragments 10733 if (node.type == 1 && !settings.inner) { 10734 walk(node); 10735 } else { 10736 handlers[11](node); 10737 } 10738 10739 return writer.getContent(); 10740 }; 10741 }; 10742}); 10743 10744// Included from: js/tinymce/classes/dom/Serializer.js 10745 10746/** 10747 * Serializer.js 10748 * 10749 * Copyright, Moxiecode Systems AB 10750 * Released under LGPL License. 10751 * 10752 * License: http://www.tinymce.com/license 10753 * Contributing: http://www.tinymce.com/contributing 10754 */ 10755 10756/** 10757 * This class is used to serialize DOM trees into a string. Consult the TinyMCE Wiki API for 10758 * more details and examples on how to use this class. 10759 * 10760 * @class tinymce.dom.Serializer 10761 */ 10762define("tinymce/dom/Serializer", [ 10763 "tinymce/dom/DOMUtils", 10764 "tinymce/html/DomParser", 10765 "tinymce/html/Entities", 10766 "tinymce/html/Serializer", 10767 "tinymce/html/Node", 10768 "tinymce/html/Schema", 10769 "tinymce/Env", 10770 "tinymce/util/Tools" 10771], function(DOMUtils, DomParser, Entities, Serializer, Node, Schema, Env, Tools) { 10772 var each = Tools.each, trim = Tools.trim; 10773 var DOM = DOMUtils.DOM; 10774 10775 /** 10776 * Constructs a new DOM serializer class. 10777 * 10778 * @constructor 10779 * @method Serializer 10780 * @param {Object} settings Serializer settings object. 10781 * @param {tinymce.Editor} editor Optional editor to bind events to and get schema/dom from. 10782 */ 10783 return function(settings, editor) { 10784 var dom, schema, htmlParser; 10785 10786 if (editor) { 10787 dom = editor.dom; 10788 schema = editor.schema; 10789 } 10790 10791 // Default DOM and Schema if they are undefined 10792 dom = dom || DOM; 10793 schema = schema || new Schema(settings);
10794 settings.entity_encoding = settings.entity_encoding || 'named'; 10795 settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true; 10796 10797 htmlParser = new DomParser(settings, schema); 10798 10799 // Convert tabindex back to elements when serializing contents 10800 htmlParser.addAttributeFilter('data-mce-tabindex', function(nodes, name) { 10801 var i = nodes.length, node; 10802 10803 while (i--) { 10804 node = nodes[i]; 10805 node.attr('tabindex', node.attributes.map['data-mce-tabindex']); 10806 node.attr(name, null); 10807 } 10808 }); 10809 10810 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed 10811 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) { 10812 var i = nodes.length, node, value, internalName = 'data-mce-' + name; 10813 var urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef; 10814 10815 while (i--) { 10816 node = nodes[i]; 10817 10818 value = node.attributes.map[internalName]; 10819 if (value !== undef) { 10820 // Set external name to internal value and remove internal 10821 node.attr(name, value.length > 0 ? value : null); 10822 node.attr(internalName, null); 10823 } else { 10824 // No internal attribute found then convert the value we have in the DOM 10825 value = node.attributes.map[name]; 10826 10827 if (name === "style") { 10828 value = dom.serializeStyle(dom.parseStyle(value), node.name); 10829 } else if (urlConverter) { 10830 value = urlConverter.call(urlConverterScope, value, name, node.name); 10831 } 10832 10833 node.attr(name, value.length > 0 ? value : null); 10834 } 10835 } 10836 }); 10837 10838 // Remove internal classes mceItem<..> or mceSelected 10839 htmlParser.addAttributeFilter('class', function(nodes) { 10840 var i = nodes.length, node, value; 10841 10842 while (i--) { 10843 node = nodes[i]; 10844 value = node.attr('class'); 10845 10846 if (value) { 10847 value = node.attr('class').replace(/(?:^|\s)mce-item-\w+(?!\S)/g, ''); 10848 node.attr('class', value.length > 0 ? value : null); 10849 } 10850 } 10851 }); 10852 10853 // Remove bookmark elements 10854 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) { 10855 var i = nodes.length, node; 10856 10857 while (i--) { 10858 node = nodes[i]; 10859 10860 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) { 10861 node.remove(); 10862 } 10863 } 10864 }); 10865 10866 htmlParser.addNodeFilter('noscript', function(nodes) { 10867 var i = nodes.length, node; 10868 10869 while (i--) { 10870 node = nodes[i].firstChild; 10871 10872 if (node) { 10873 node.value = Entities.decode(node.value); 10874 } 10875 } 10876 }); 10877 10878 // Force script into CDATA sections and remove the mce- prefix also add comments around styles 10879 htmlParser.addNodeFilter('script,style', function(nodes, name) { 10880 var i = nodes.length, node, value, type; 10881 10882 function trim(value) { 10883 /*jshint maxlen:255 */ 10884 /*eslint max-len:0 */ 10885 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n') 10886 .replace(/^[\r\n]*|[\r\n]*$/g, '') 10887 .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '') 10888 .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, ''); 10889 } 10890 10891 while (i--) { 10892 node = nodes[i]; 10893 value = node.firstChild ? node.firstChild.value : ''; 10894 10895 if (name === "script") { 10896 // Remove mce- prefix from script elements and remove default type since the user specified 10897 // a script element without type attribute 10898 type = node.attr('type'); 10899 if (type) { 10900 node.attr('type', type == 'mce-no/type' ? null : type.replace(/^mce\-/, '')); 10901 } 10902 10903 if (value.length > 0) { 10904 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>'; 10905 } 10906 } else { 10907 if (value.length > 0) { 10908 node.firstChild.value = '<!--\n' + trim(value) + '\n-->'; 10909 } 10910 } 10911 } 10912 }); 10913 10914 // Convert comments to cdata and handle protected comments 10915 htmlParser.addNodeFilter('#comment', function(nodes) { 10916 var i = nodes.length, node; 10917 10918 while (i--) { 10919 node = nodes[i]; 10920 10921 if (node.value.indexOf('[CDATA[') === 0) { 10922 node.name = '#cdata'; 10923 node.type = 4; 10924 node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, ''); 10925 } else if (node.value.indexOf('mce:protected ') === 0) { 10926 node.name = "#text"; 10927 node.type = 3; 10928 node.raw = true; 10929 node.value = unescape(node.value).substr(14); 10930 } 10931 } 10932 }); 10933 10934 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) { 10935 var i = nodes.length, node; 10936 10937 while (i--) { 10938 node = nodes[i]; 10939 if (node.type === 7) { 10940 node.remove(); 10941 } else if (node.type === 1) { 10942 if (name === "input" && !("type" in node.attributes.map)) { 10943 node.attr('type', 'text'); 10944 } 10945 } 10946 } 10947 }); 10948 10949 // Fix list elements, TODO: Replace this later 10950 if (settings.fix_list_elements) { 10951 htmlParser.addNodeFilter('ul,ol', function(nodes) { 10952 var i = nodes.length, node, parentNode; 10953 10954 while (i--) { 10955 node = nodes[i]; 10956 parentNode = node.parent; 10957 10958 if (parentNode.name === 'ul' || parentNode.name === 'ol') { 10959 if (node.prev && node.prev.name === 'li') { 10960 node.prev.append(node); 10961 } 10962 } 10963 } 10964 }); 10965 } 10966 10967 // Remove internal data attributes 10968 htmlParser.addAttributeFilter( 10969 'data-mce-src,data-mce-href,data-mce-style,' + 10970 'data-mce-selected,data-mce-expando,' + 10971 'data-mce-type,data-mce-resize', 10972 10973 function(nodes, name) { 10974 var i = nodes.length; 10975 10976 while (i--) { 10977 nodes[i].attr(name, null); 10978 } 10979 } 10980 ); 10981 10982 // Return public methods 10983 return { 10984 /** 10985 * Schema instance that was used to when the Serializer was constructed. 10986 * 10987 * @field {tinymce.html.Schema} schema 10988 */ 10989 schema: schema, 10990 10991 /**
10992 * Adds a node filter function to the parser used by the serializer, the parser will collect the specified nodes by name 10993 * and then execute the callback ones it has finished parsing the document. 10994 * 10995 * @example 10996 * parser.addNodeFilter('p,h1', function(nodes, name) { 10997 * for (var i = 0; i < nodes.length; i++) { 10998 * console.log(nodes[i].name); 10999 * } 11000 * }); 11001 * @method addNodeFilter 11002 * @method {String} name Comma separated list of nodes to collect. 11003 * @param {function} callback Callback function to execute once it has collected nodes. 11004 */ 11005 addNodeFilter: htmlParser.addNodeFilter, 11006 11007 /** 11008 * Adds a attribute filter function to the parser used by the serializer, the parser will 11009 * collect nodes that has the specified attributes 11010 * and then execute the callback ones it has finished parsing the document. 11011 * 11012 * @example 11013 * parser.addAttributeFilter('src,href', function(nodes, name) { 11014 * for (var i = 0; i < nodes.length; i++) { 11015 * console.log(nodes[i].name); 11016 * } 11017 * }); 11018 * @method addAttributeFilter 11019 * @method {String} name Comma separated list of nodes to collect. 11020 * @param {function} callback Callback function to execute once it has collected nodes. 11021 */ 11022 addAttributeFilter: htmlParser.addAttributeFilter, 11023 11024 /** 11025 * Serializes the specified browser DOM node into a HTML string. 11026 * 11027 * @method serialize 11028 * @param {DOMNode} node DOM node to serialize. 11029 * @param {Object} args Arguments option that gets passed to event handlers. 11030 */ 11031 serialize: function(node, args) { 11032 var self = this, impl, doc, oldDoc, htmlSerializer, content; 11033 11034 // Explorer won't clone contents of script and style and the 11035 // selected index of select elements are cleared on a clone operation. 11036 if (Env.ie && dom.select('script,style,select,map').length > 0) { 11037 content = node.innerHTML; 11038 node = node.cloneNode(false); 11039 dom.setHTML(node, content); 11040 } else { 11041 node = node.cloneNode(true); 11042 } 11043 11044 // Nodes needs to be attached to something in WebKit/Opera 11045 // This fix will make DOM ranges and make Sizzle happy! 11046 impl = node.ownerDocument.implementation; 11047 if (impl.createHTMLDocument) { 11048 // Create an empty HTML document 11049 doc = impl.createHTMLDocument(""); 11050 11051 // Add the element or it's children if it's a body element to the new document 11052 each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) { 11053 doc.body.appendChild(doc.importNode(node, true)); 11054 }); 11055 11056 // Grab first child or body element for serialization 11057 if (node.nodeName != 'BODY') { 11058 node = doc.body.firstChild; 11059 } else { 11060 node = doc.body; 11061 } 11062 11063 // set the new document in DOMUtils so createElement etc works 11064 oldDoc = dom.doc; 11065 dom.doc = doc; 11066 } 11067 11068 args = args || {}; 11069 args.format = args.format || 'html'; 11070 11071 // Don't wrap content if we want selected html 11072 if (args.selection) { 11073 args.forced_root_block = ''; 11074 } 11075 11076 // Pre process 11077 if (!args.no_events) { 11078 args.node = node; 11079 self.onPreProcess(args); 11080 } 11081 11082 // Setup serializer 11083 htmlSerializer = new Serializer(settings, schema); 11084 11085 // Parse and serialize HTML 11086 args.content = htmlSerializer.serialize( 11087 htmlParser.parse(trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args) 11088 ); 11089 11090 // Replace all BOM characters for now until we can find a better solution 11091 if (!args.cleanup) { 11092 args.content = args.content.replace(/\uFEFF/g, ''); 11093 } 11094 11095 // Post process 11096 if (!args.no_events) { 11097 self.onPostProcess(args); 11098 } 11099 11100 // Restore the old document if it was changed 11101 if (oldDoc) { 11102 dom.doc = oldDoc; 11103 } 11104 11105 args.node = null; 11106 11107 return args.content; 11108 }, 11109 11110 /** 11111 * Adds valid elements rules to the serializers schema instance this enables you to specify things 11112 * like what elements should be outputted and what attributes specific elements might have. 11113 * Consult the Wiki for more details on this format. 11114 * 11115 * @method addRules 11116 * @param {String} rules Valid elements rules string to add to schema. 11117 */ 11118 addRules: function(rules) { 11119 schema.addValidElements(rules); 11120 }, 11121 11122 /** 11123 * Sets the valid elements rules to the serializers schema instance this enables you to specify things 11124 * like what elements should be outputted and what attributes specific elements might have. 11125 * Consult the Wiki for more details on this format. 11126 * 11127 * @method setRules 11128 * @param {String} rules Valid elements rules string. 11129 */ 11130 setRules: function(rules) { 11131 schema.setValidElements(rules); 11132 }, 11133 11134 onPreProcess: function(args) { 11135 if (editor) {
11136 editor.fire('PreProcess', args); 11137 } 11138 }, 11139 11140 onPostProcess: function(args) { 11141 if (editor) { 11142 editor.fire('PostProcess', args); 11143 } 11144 } 11145 }; 11146 }; 11147}); 11148 11149// Included from: js/tinymce/classes/dom/TridentSelection.js 11150 11151/** 11152 * TridentSelection.js 11153 * 11154 * Copyright, Moxiecode Systems AB 11155 * Released under LGPL License. 11156 * 11157 * License: http://www.tinymce.com/license 11158 * Contributing: http://www.tinymce.com/contributing 11159 */ 11160 11161/** 11162 * Selection class for old explorer versions. This one fakes the 11163 * native selection object available on modern browsers. 11164 * 11165 * @class tinymce.dom.TridentSelection 11166 */ 11167define("tinymce/dom/TridentSelection", [], function() { 11168 function Selection(selection) { 11169 var self = this, dom = selection.dom, FALSE = false; 11170 11171 function getPosition(rng, start) { 11172 var checkRng, startIndex = 0, endIndex, inside, 11173 children, child, offset, index, position = -1, parent; 11174 11175 // Setup test range, collapse it and get the parent 11176 checkRng = rng.duplicate(); 11177 checkRng.collapse(start); 11178 parent = checkRng.parentElement(); 11179 11180 // Check if the selection is within the right document 11181 if (parent.ownerDocument !== selection.dom.doc) { 11182 return; 11183 } 11184 11185 // IE will report non editable elements as it's parent so look for an editable one 11186 while (parent.contentEditable === "false") { 11187 parent = parent.parentNode; 11188 } 11189 11190 // If parent doesn't have any children then return that we are inside the element 11191 if (!parent.hasChildNodes()) { 11192 return {node: parent, inside: 1}; 11193 } 11194 11195 // Setup node list and endIndex 11196 children = parent.children; 11197 endIndex = children.length - 1; 11198 11199 // Perform a binary search for the position 11200 while (startIndex <= endIndex) { 11201 index = Math.floor((startIndex + endIndex) / 2); 11202 11203 // Move selection to node and compare the ranges 11204 child = children[index]; 11205 checkRng.moveToElementText(child); 11206 position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng); 11207 11208 // Before/after or an exact match 11209 if (position > 0) { 11210 endIndex = index - 1; 11211 } else if (position < 0) { 11212 startIndex = index + 1; 11213 } else { 11214 return {node: child}; 11215 } 11216 } 11217 11218 // Check if child position is before or we didn't find a position 11219 if (position < 0) { 11220 // No element child was found use the parent element and the offset inside that 11221 if (!child) { 11222 checkRng.moveToElementText(parent); 11223 checkRng.collapse(true); 11224 child = parent; 11225 inside = true; 11226 } else { 11227 checkRng.collapse(false); 11228 } 11229 11230 // Walk character by character in text node until we hit the selected range endpoint, 11231 // hit the end of document or parent isn't the right one 11232 // We need to walk char by char since rng.text or rng.htmlText will trim line endings 11233 offset = 0; 11234 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { 11235 if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) { 11236 break; 11237 } 11238 11239 offset++; 11240 } 11241 } else { 11242 // Child position is after the selection endpoint 11243 checkRng.collapse(true); 11244 11245 // Walk character by character in text node until we hit the selected range endpoint, hit 11246 // the end of document or parent isn't the right one 11247 offset = 0; 11248 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { 11249 if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) { 11250 break; 11251 } 11252 11253 offset++; 11254 } 11255 } 11256 11257 return {node: child, position: position, offset: offset, inside: inside}; 11258 } 11259 11260 // Returns a W3C DOM compatible range object by using the IE Range API 11261 function getRange() { 11262 var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark; 11263 11264 // If selection is outside the current document just return an empty range 11265 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); 11266 if (element.ownerDocument != dom.doc) { 11267 return domRange; 11268 } 11269 11270 collapsed = selection.isCollapsed(); 11271 11272 // Handle control selection 11273 if (ieRange.item) { 11274 domRange.setStart(element.parentNode, dom.nodeIndex(element)); 11275 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); 11276 11277 return domRange; 11278 } 11279 11280 function findEndPoint(start) { 11281 var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue; 11282 11283 container = endPoint.node;
11284 offset = endPoint.offset; 11285 11286 if (endPoint.inside && !container.hasChildNodes()) { 11287 domRange[start ? 'setStart' : 'setEnd'](container, 0); 11288 return; 11289 } 11290 11291 if (offset === undef) { 11292 domRange[start ? 'setStartBefore' : 'setEndAfter'](container); 11293 return; 11294 } 11295 11296 if (endPoint.position < 0) { 11297 sibling = endPoint.inside ? container.firstChild : container.nextSibling; 11298 11299 if (!sibling) { 11300 domRange[start ? 'setStartAfter' : 'setEndAfter'](container); 11301 return; 11302 } 11303 11304 if (!offset) { 11305 if (sibling.nodeType == 3) { 11306 domRange[start ? 'setStart' : 'setEnd'](sibling, 0); 11307 } else { 11308 domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling); 11309 } 11310 11311 return; 11312 } 11313 11314 // Find the text node and offset 11315 while (sibling) { 11316 if (sibling.nodeType == 3) { 11317 nodeValue = sibling.nodeValue; 11318 textNodeOffset += nodeValue.length; 11319 11320 // We are at or passed the position we where looking for 11321 if (textNodeOffset >= offset) { 11322 container = sibling; 11323 textNodeOffset -= offset; 11324 textNodeOffset = nodeValue.length - textNodeOffset; 11325 break; 11326 } 11327 } 11328 11329 sibling = sibling.nextSibling; 11330 } 11331 } else { 11332 // Find the text node and offset 11333 sibling = container.previousSibling; 11334 11335 if (!sibling) { 11336 return domRange[start ? 'setStartBefore' : 'setEndBefore'](container); 11337 } 11338 11339 // If there isn't any text to loop then use the first position 11340 if (!offset) { 11341 if (container.nodeType == 3) { 11342 domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length); 11343 } else { 11344 domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling); 11345 } 11346 11347 return; 11348 } 11349 11350 while (sibling) { 11351 if (sibling.nodeType == 3) { 11352 textNodeOffset += sibling.nodeValue.length; 11353 11354 // We are at or passed the position we where looking for 11355 if (textNodeOffset >= offset) { 11356 container = sibling; 11357 textNodeOffset -= offset; 11358 break; 11359 } 11360 } 11361 11362 sibling = sibling.previousSibling; 11363 } 11364 } 11365 11366 domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset); 11367 } 11368 11369 try { 11370 // Find start point 11371 findEndPoint(true); 11372 11373 // Find end point if needed 11374 if (!collapsed) { 11375 findEndPoint(); 11376 } 11377 } catch (ex) { 11378 // IE has a nasty bug where text nodes might throw "invalid argument" when you 11379 // access the nodeValue or other properties of text nodes. This seems to happend when 11380 // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it. 11381 if (ex.number == -2147024809) { 11382 // Get the current selection 11383 bookmark = self.getBookmark(2); 11384 11385 // Get start element 11386 tmpRange = ieRange.duplicate(); 11387 tmpRange.collapse(true); 11388 element = tmpRange.parentElement(); 11389 11390 // Get end element 11391 if (!collapsed) { 11392 tmpRange = ieRange.duplicate(); 11393 tmpRange.collapse(false); 11394 element2 = tmpRange.parentElement(); 11395 element2.innerHTML = element2.innerHTML; 11396 } 11397 11398 // Remove the broken elements 11399 element.innerHTML = element.innerHTML; 11400 11401 // Restore the selection 11402 self.moveToBookmark(bookmark); 11403 11404 // Since the range has moved we need to re-get it 11405 ieRange = selection.getRng(); 11406 11407 // Find start point 11408 findEndPoint(true); 11409 11410 // Find end point if needed 11411 if (!collapsed) { 11412 findEndPoint(); 11413 } 11414 } else { 11415 throw ex; // Throw other errors 11416 } 11417 } 11418 11419 return domRange; 11420 } 11421 11422 this.getBookmark = function(type) { 11423 var rng = selection.getRng(), bookmark = {}; 11424 11425 function getIndexes(node) { 11426 var parent, root, children, i, indexes = []; 11427 11428 parent = node.parentNode; 11429 root = dom.getRoot().parentNode; 11430 11431 while (parent != root && parent.nodeType !== 9) { 11432 children = parent.children; 11433 11434 i = children.length; 11435 while (i--) { 11436 if (node === children[i]) { 11437 indexes.push(i); 11438 break; 11439 } 11440 } 11441 11442 node = parent; 11443 parent = parent.parentNode; 11444 } 11445 11446 return indexes; 11447 } 11448 11449 function getBookmarkEndPoint(start) { 11450 var position; 11451 11452 position = getPosition(rng, start); 11453 if (position) { 11454 return { 11455 position: position.position, 11456 offset: position.offset, 11457 indexes: getIndexes(position.node), 11458 inside: position.inside 11459 }; 11460 } 11461 } 11462 11463 // Non ubstructive bookmark 11464 if (type === 2) { 11465 // Handle text selection 11466 if (!rng.item) { 11467 bookmark.start = getBookmarkEndPoint(true); 11468 11469 if (!selection.isCollapsed()) { 11470 bookmark.end = getBookmarkEndPoint(); 11471 } 11472 } else { 11473 bookmark.start = {ctrl: true, indexes: getIndexes(rng.item(0))}; 11474 } 11475 } 11476 11477 return bookmark; 11478 }; 11479 11480 this.moveToBookmark = function(bookmark) { 11481 var rng, body = dom.doc.body; 11482 11483 function resolveIndexes(indexes) { 11484 var node, i, idx, children; 11485 11486 node = dom.getRoot(); 11487 for (i = indexes.length - 1; i >= 0; i--) { 11488 children = node.children; 11489 idx = indexes[i]; 11490 11491 if (idx <= children.length - 1) { 11492 node = children[idx]; 11493 } 11494 } 11495 11496 return node; 11497 } 11498 11499 function setBookmarkEndPoint(start) { 11500 var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef, offset; 11501 11502 if (endPoint) { 11503 moveLeft = endPoint.position > 0; 11504 11505 moveRng = body.createTextRange(); 11506 moveRng.moveToElementText(resolveIndexes(endPoint.indexes)); 11507 11508 offset = endPoint.offset; 11509 if (offset !== undef) { 11510 moveRng.collapse(endPoint.inside || moveLeft); 11511 moveRng.moveStart('character', moveLeft ? -offset : offset); 11512 } else { 11513 moveRng.collapse(start); 11514 } 11515 11516 rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng); 11517 11518 if (start) { 11519 rng.collapse(true); 11520 } 11521 } 11522 } 11523 11524 if (bookmark.start) { 11525 if (bookmark.start.ctrl) { 11526 rng = body.createControlRange(); 11527 rng.addElement(resolveIndexes(bookmark.start.indexes)); 11528 rng.select(); 11529 } else { 11530 rng = body.createTextRange(); 11531 setBookmarkEndPoint(true); 11532 setBookmarkEndPoint(); 11533 rng.select(); 11534 } 11535 } 11536 }; 11537 11538 this.addRange = function(rng) { 11539 var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, 11540 doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm; 11541 11542 function setEndPoint(start) { 11543 var container, offset, marker, tmpRng, nodes; 11544 11545 marker = dom.create('a'); 11546 container = start ? startContainer : endContainer; 11547 offset = start ? startOffset : endOffset; 11548 tmpRng = ieRng.duplicate(); 11549 11550 if (container == doc || container == doc.documentElement) { 11551 container = body; 11552 offset = 0; 11553 } 11554 11555 if (container.nodeType == 3) { 11556 container.parentNode.insertBefore(marker, container); 11557 tmpRng.moveToElementText(marker); 11558 tmpRng.moveStart('character', offset); 11559 dom.remove(marker); 11560 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); 11561 } else { 11562 nodes = container.childNodes; 11563 11564 if (nodes.length) { 11565 if (offset >= nodes.length) { 11566 dom.insertAfter(marker, nodes[nodes.length - 1]); 11567 } else { 11568 container.insertBefore(marker, nodes[offset]); 11569 } 11570 11571 tmpRng.moveToElementText(marker); 11572 } else if (container.canHaveHTML) { 11573 // Empty node selection for example <div>|</div> 11574 // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open 11575 container.innerHTML = '<span></span>'; 11576 marker = container.firstChild; 11577 tmpRng.moveToElementText(marker); 11578 tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason 11579 } 11580 11581 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); 11582 dom.remove(marker); 11583 } 11584 } 11585 11586 // Setup some shorter versions 11587 startContainer = rng.startContainer; 11588 startOffset = rng.startOffset; 11589 endContainer = rng.endContainer; 11590 endOffset = rng.endOffset; 11591 ieRng = body.createTextRange(); 11592 11593 // If single element selection then try making a control selection out of it 11594 if (startContainer == endContainer && startContainer.nodeType == 1) { 11595 // Trick to place the caret inside an empty block element like <p></p> 11596 if (startOffset == endOffset && !startContainer.hasChildNodes()) { 11597 if (startContainer.canHaveHTML) {
11598 // Check if previous sibling is an empty block if it is then we need to render it 11599 // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236 11600 // Example this: <p></p><p>|</p> would become this: <p>|</p><p></p> 11601 sibling = startContainer.previousSibling; 11602 if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) { 11603 sibling.innerHTML = ''; 11604 } else { 11605 sibling = null; 11606 } 11607 11608 startContainer.innerHTML = '<span></span><span></span>'; 11609 ieRng.moveToElementText(startContainer.lastChild); 11610 ieRng.select(); 11611 dom.doc.selection.clear(); 11612 startContainer.innerHTML = ''; 11613 11614 if (sibling) { 11615 sibling.innerHTML = ''; 11616 } 11617 return; 11618 } else { 11619 startOffset = dom.nodeIndex(startContainer); 11620 startContainer = startContainer.parentNode; 11621 } 11622 } 11623 11624 if (startOffset == endOffset - 1) { 11625 try { 11626 ctrlElm = startContainer.childNodes[startOffset]; 11627 ctrlRng = body.createControlRange(); 11628 ctrlRng.addElement(ctrlElm); 11629 ctrlRng.select(); 11630 11631 // Check if the range produced is on the correct element and is a control range 11632 // On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398 11633 nativeRng = selection.getRng(); 11634 if (nativeRng.item && ctrlElm === nativeRng.item(0)) { 11635 return; 11636 } 11637 } catch (ex) { 11638 // Ignore 11639 } 11640 } 11641 } 11642 11643 // Set start/end point of selection 11644 setEndPoint(true); 11645 setEndPoint(); 11646 11647 // Select the new range and scroll it into view 11648 ieRng.select(); 11649 }; 11650 11651 // Expose range method 11652 this.getRangeAt = getRange; 11653 } 11654 11655 return Selection; 11656}); 11657 11658// Included from: js/tinymce/classes/util/VK.js 11659 11660/** 11661 * VK.js 11662 * 11663 * Copyright, Moxiecode Systems AB 11664 * Released under LGPL License. 11665 * 11666 * License: http://www.tinymce.com/license 11667 * Contributing: http://www.tinymce.com/contributing 11668 */ 11669 11670/** 11671 * This file exposes a set of the common KeyCodes for use. Please grow it as needed. 11672 */ 11673define("tinymce/util/VK", [ 11674 "tinymce/Env" 11675], function(Env) { 11676 return { 11677 BACKSPACE: 8, 11678 DELETE: 46, 11679 DOWN: 40, 11680 ENTER: 13, 11681 LEFT: 37, 11682 RIGHT: 39, 11683 SPACEBAR: 32, 11684 TAB: 9, 11685 UP: 38, 11686 11687 modifierPressed: function(e) { 11688 return e.shiftKey || e.ctrlKey || e.altKey || this.metaKeyPressed(e); 11689 }, 11690 11691 metaKeyPressed: function(e) { 11692 // Check if ctrl or meta key is pressed. Edge case for AltGr on Windows where it produces ctrlKey+altKey states 11693 return (Env.mac ? e.metaKey : e.ctrlKey && !e.altKey); 11694 } 11695 }; 11696}); 11697 11698// Included from: js/tinymce/classes/dom/ControlSelection.js 11699 11700/** 11701 * ControlSelection.js 11702 * 11703 * Copyright, Moxiecode Systems AB 11704 * Released under LGPL License. 11705 * 11706 * License: http://www.tinymce.com/license 11707 * Contributing: http://www.tinymce.com/contributing 11708 */ 11709 11710/** 11711 * This class handles control selection of elements. Controls are elements 11712 * that can be resized and needs to be selected as a whole. It adds custom resize handles 11713 * to all browser engines that support properly disabling the built in resize logic. 11714 * 11715 * @class tinymce.dom.ControlSelection 11716 */ 11717define("tinymce/dom/ControlSelection", [ 11718 "tinymce/util/VK", 11719 "tinymce/util/Tools", 11720 "tinymce/Env" 11721], function(VK, Tools, Env) { 11722 return function(selection, editor) { 11723 var dom = editor.dom, each = Tools.each; 11724 var selectedElm, selectedElmGhost, resizeHelper, resizeHandles, selectedHandle, lastMouseDownEvent; 11725 var startX, startY, selectedElmX, selectedElmY, startW, startH, ratio, resizeStarted; 11726 var width, height, editableDoc = editor.getDoc(), rootDocument = document, isIE = Env.ie && Env.ie < 11; 11727 var abs = Math.abs, round = Math.round, rootElement = editor.getBody(), startScrollWidth, startScrollHeight; 11728 11729 // Details about each resize handle how to scale etc 11730 resizeHandles = { 11731 // Name: x multiplier, y multiplier, delta size x, delta size y 11732 n: [0.5, 0, 0, -1], 11733 e: [1, 0.5, 1, 0], 11734 s: [0.5, 1, 0, 1], 11735 w: [0, 0.5, -1, 0], 11736 nw: [0, 0, -1, -1], 11737 ne: [1, 0, 1, -1], 11738 se: [1, 1, 1, 1], 11739 sw: [0, 1, -1, 1] 11740 }; 11741 11742 // Add CSS for resize handles, cloned element and selected 11743 var rootClass = '.mce-content-body'; 11744 editor.contentStyles.push( 11745 rootClass + ' div.mce-resizehandle {' + 11746 'position: absolute;' + 11747 'border: 1px solid black;' + 11748 'background: #FFF;' + 11749 'width: 5px;' + 11750 'height: 5px;' + 11751 'z-index: 10000' + 11752 '}' + 11753 rootClass + ' .mce-resizehandle:hover {' + 11754 'background: #000' + 11755 '}' + 11756 rootClass + ' img[data-mce-selected], hr[data-mce-selected] {' + 11757 'outline: 1px solid black;' + 11758 'resize: none' + // Have been talks about implementing this in browsers 11759 '}' + 11760 rootClass + ' .mce-clonedresizable {' + 11761 'position: absolute;' + 11762 (Env.gecko ? '' : 'outline: 1px dashed black;') + // Gecko produces trails while resizing 11763 'opacity: .5;' + 11764 'filter: alpha(opacity=50);' + 11765 'z-index: 10000' + 11766 '}' + 11767 rootClass + ' .mce-resize-helper {' + 11768 'background: #555;' + 11769 'background: rgba(0,0,0,0.75);' + 11770 'border-radius: 3px;' + 11771 'border: 1px;' + 11772 'color: white;' + 11773 'display: none;' + 11774 'font-family: sans-serif;' + 11775 'font-size: 12px;' + 11776 'white-space: nowrap;' + 11777 'line-height: 14px;' + 11778 'margin: 5px 10px;' + 11779 'padding: 5px;' + 11780 'position: absolute;' + 11781 'z-index: 10001' + 11782 '}' 11783 ); 11784 11785 function isResizable(elm) { 11786 var selector = editor.settings.object_resizing; 11787 11788 if (selector === false || Env.iOS) { 11789 return false;
11790 } 11791 11792 if (typeof selector != 'string') { 11793 selector = 'table,img,div'; 11794 } 11795 11796 if (elm.getAttribute('data-mce-resize') === 'false') { 11797 return false; 11798 } 11799 11800 return editor.dom.is(elm, selector); 11801 } 11802 11803 function resizeGhostElement(e) { 11804 var deltaX, deltaY, proportional; 11805 var resizeHelperX, resizeHelperY; 11806 11807 // Calc new width/height 11808 deltaX = e.screenX - startX; 11809 deltaY = e.screenY - startY; 11810 11811 // Calc new size 11812 width = deltaX * selectedHandle[2] + startW; 11813 height = deltaY * selectedHandle[3] + startH; 11814 11815 // Never scale down lower than 5 pixels 11816 width = width < 5 ? 5 : width; 11817 height = height < 5 ? 5 : height; 11818 11819 if (selectedElm.nodeName == "IMG" && editor.settings.resize_img_proportional !== false) { 11820 proportional = !VK.modifierPressed(e); 11821 } else { 11822 proportional = VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0); 11823 } 11824 11825 // Constrain proportions 11826 if (proportional) { 11827 if (abs(deltaX) > abs(deltaY)) { 11828 height = round(width * ratio); 11829 width = round(height / ratio); 11830 } else { 11831 width = round(height / ratio); 11832 height = round(width * ratio); 11833 } 11834 } 11835 11836 // Update ghost size 11837 dom.setStyles(selectedElmGhost, { 11838 width: width, 11839 height: height 11840 }); 11841 11842 // Update resize helper position 11843 resizeHelperX = selectedHandle.startPos.x + deltaX; 11844 resizeHelperY = selectedHandle.startPos.y + deltaY; 11845 resizeHelperX = resizeHelperX > 0 ? resizeHelperX : 0; 11846 resizeHelperY = resizeHelperY > 0 ? resizeHelperY : 0; 11847 11848 dom.setStyles(resizeHelper, { 11849 left: resizeHelperX, 11850 top: resizeHelperY, 11851 display: 'block' 11852 }); 11853 11854 resizeHelper.innerHTML = width + ' × ' + height; 11855 11856 // Update ghost X position if needed 11857 if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) { 11858 dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width)); 11859 } 11860 11861 // Update ghost Y position if needed 11862 if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) { 11863 dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height)); 11864 } 11865 11866 // Calculate how must overflow we got 11867 deltaX = rootElement.scrollWidth - startScrollWidth; 11868 deltaY = rootElement.scrollHeight - startScrollHeight; 11869 11870 // Re-position the resize helper based on the overflow 11871 if (deltaX + deltaY !== 0) { 11872 dom.setStyles(resizeHelper, { 11873 left: resizeHelperX - deltaX, 11874 top: resizeHelperY - deltaY 11875 }); 11876 } 11877 11878 if (!resizeStarted) { 11879 editor.fire('ObjectResizeStart', {target: selectedElm, width: startW, height: startH}); 11880 resizeStarted = true; 11881 } 11882 } 11883 11884 function endGhostResize() { 11885 resizeStarted = false; 11886 11887 function setSizeProp(name, value) { 11888 if (value) { 11889 // Resize by using style or attribute 11890 if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) { 11891 dom.setStyle(selectedElm, name, value); 11892 } else { 11893 dom.setAttrib(selectedElm, name, value); 11894 } 11895 } 11896 } 11897 11898 // Set width/height properties 11899 setSizeProp('width', width); 11900 setSizeProp('height', height); 11901 11902 dom.unbind(editableDoc, 'mousemove', resizeGhostElement); 11903 dom.unbind(editableDoc, 'mouseup', endGhostResize); 11904 11905 if (rootDocument != editableDoc) { 11906 dom.unbind(rootDocument, 'mousemove', resizeGhostElement); 11907 dom.unbind(rootDocument, 'mouseup', endGhostResize); 11908 } 11909 11910 // Remove ghost/helper and update resize handle positions 11911 dom.remove(selectedElmGhost); 11912 dom.remove(resizeHelper); 11913 11914 if (!isIE || selectedElm.nodeName == "TABLE") { 11915 showResizeRect(selectedElm); 11916 } 11917 11918 editor.fire('ObjectResized', {target: selectedElm, width: width, height: height}); 11919 dom.setAttrib(selectedElm, 'style', dom.getAttrib(selectedElm, 'style')); 11920 editor.nodeChanged(); 11921 } 11922 11923 function showResizeRect(targetElm, mouseDownHandleName, mouseDownEvent) { 11924 var position, targetWidth, targetHeight, e, rect; 11925 11926 unbindResizeHandleEvents(); 11927 11928 // Get position and size of target 11929 position = dom.getPos(targetElm, rootElement); 11930 selectedElmX = position.x; 11931 selectedElmY = position.y; 11932 rect = targetElm.getBoundingClientRect(); // Fix for Gecko offsetHeight for table with caption 11933 targetWidth = rect.width || (rect.right - rect.left); 11934 targetHeight = rect.height || (rect.bottom - rect.top); 11935 11936 // Reset width/height if user selects a new image/table 11937 if (selectedElm != targetElm) { 11938 detachResizeStartListener(); 11939 selectedElm = targetElm; 11940 width = height = 0; 11941 } 11942 11943 // Makes it possible to disable resizing 11944 e = editor.fire('ObjectSelected', {target: targetElm}); 11945 11946 if (isResizable(targetElm) && !e.isDefaultPrevented()) { 11947 each(resizeHandles, function(handle, name) { 11948 var handleElm, handlerContainerElm; 11949 11950 function startDrag(e) { 11951 startX = e.screenX; 11952 startY = e.screenY; 11953 startW = selectedElm.clientWidth; 11954 startH = selectedElm.clientHeight; 11955 ratio = startH / startW; 11956 selectedHandle = handle; 11957 11958 handle.startPos = { 11959 x: targetWidth * handle[0] + selectedElmX, 11960 y: targetHeight * handle[1] + selectedElmY 11961 }; 11962 11963 startScrollWidth = rootElement.scrollWidth; 11964 startScrollHeight = rootElement.scrollHeight; 11965 11966 selectedElmGhost = selectedElm.cloneNode(true); 11967 dom.addClass(selectedElmGhost, 'mce-clonedresizable'); 11968 dom.setAttrib(selectedElmGhost, 'data-mce-bogus', 'all'); 11969 selectedElmGhost.contentEditable = false; // Hides IE move layer cursor 11970 selectedElmGhost.unSelectabe = true; 11971 dom.setStyles(selectedElmGhost, { 11972 left: selectedElmX, 11973 top: selectedElmY, 11974 margin: 0 11975 }); 11976 11977 selectedElmGhost.removeAttribute('data-mce-selected'); 11978 rootElement.appendChild(selectedElmGhost); 11979 11980 dom.bind(editableDoc, 'mousemove', resizeGhostElement); 11981 dom.bind(editableDoc, 'mouseup', endGhostResize); 11982 11983 if (rootDocument != editableDoc) { 11984 dom.bind(rootDocument, 'mousemove', resizeGhostElement); 11985 dom.bind(rootDocument, 'mouseup', endGhostResize); 11986 } 11987 11988 resizeHelper = dom.add(rootElement, 'div', { 11989 'class': 'mce-resize-helper', 11990 'data-mce-bogus': 'all' 11991 }, startW + ' × ' + startH); 11992 } 11993 11994 if (mouseDownHandleName) { 11995 // Drag started by IE native resizestart 11996 if (name == mouseDownHandleName) { 11997 startDrag(mouseDownEvent); 11998 } 11999 12000 return; 12001 } 12002 12003 // Get existing or render resize handle 12004 handleElm = dom.get('mceResizeHandle' + name); 12005 if (!handleElm) { 12006 handlerContainerElm = rootElement; 12007 12008 handleElm = dom.add(handlerContainerElm, 'div', { 12009 id: 'mceResizeHandle' + name, 12010 'data-mce-bogus': 'all', 12011 'class': 'mce-resizehandle', 12012 unselectable: true, 12013 style: 'cursor:' + name + '-resize; margin:0; padding:0' 12014 }); 12015 12016 // Hides IE move layer cursor 12017 // If we set it on Chrome we get this wounderful bug: #6725 12018 if (Env.ie) { 12019 handleElm.contentEditable = false;
12020 } 12021 } else { 12022 dom.show(handleElm); 12023 } 12024 12025 if (!handle.elm) { 12026 dom.bind(handleElm, 'mousedown', function(e) { 12027 e.stopImmediatePropagation(); 12028 e.preventDefault(); 12029 startDrag(e); 12030 }); 12031 12032 handle.elm = handleElm; 12033 } 12034 12035 // Position element 12036 dom.setStyles(handleElm, { 12037 left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2), 12038 top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2) 12039 }); 12040 }); 12041 } else { 12042 hideResizeRect(); 12043 } 12044 12045 selectedElm.setAttribute('data-mce-selected', '1'); 12046 } 12047 12048 function hideResizeRect() { 12049 var name, handleElm; 12050 12051 unbindResizeHandleEvents(); 12052 12053 if (selectedElm) { 12054 selectedElm.removeAttribute('data-mce-selected'); 12055 } 12056 12057 for (name in resizeHandles) { 12058 handleElm = dom.get('mceResizeHandle' + name); 12059 if (handleElm) { 12060 dom.unbind(handleElm); 12061 dom.remove(handleElm); 12062 } 12063 } 12064 } 12065 12066 function updateResizeRect(e) { 12067 var startElm, controlElm; 12068 12069 function isChildOrEqual(node, parent) { 12070 if (node) { 12071 do { 12072 if (node === parent) { 12073 return true; 12074 } 12075 } while ((node = node.parentNode)); 12076 } 12077 } 12078 12079 // Ignore all events while resizing 12080 if (resizeStarted) { 12081 return; 12082 } 12083 12084 // Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v 12085 each(dom.select('img[data-mce-selected],hr[data-mce-selected]'), function(img) { 12086 img.removeAttribute('data-mce-selected'); 12087 }); 12088 12089 controlElm = e.type == 'mousedown' ? e.target : selection.getNode(); 12090 controlElm = dom.$(controlElm).closest(isIE ? 'table' : 'table,img,hr')[0]; 12091 12092 if (isChildOrEqual(controlElm, rootElement)) { 12093 disableGeckoResize(); 12094 startElm = selection.getStart(true); 12095 12096 if (isChildOrEqual(startElm, controlElm) && isChildOrEqual(selection.getEnd(true), controlElm)) { 12097 if (!isIE || (controlElm != startElm && startElm.nodeName !== 'IMG')) { 12098 showResizeRect(controlElm); 12099 return; 12100 } 12101 } 12102 } 12103 12104 hideResizeRect(); 12105 } 12106 12107 function attachEvent(elm, name, func) { 12108 if (elm && elm.attachEvent) { 12109 elm.attachEvent('on' + name, func); 12110 } 12111 } 12112 12113 function detachEvent(elm, name, func) { 12114 if (elm && elm.detachEvent) { 12115 elm.detachEvent('on' + name, func); 12116 } 12117 } 12118 12119 function resizeNativeStart(e) { 12120 var target = e.srcElement, pos, name, corner, cornerX, cornerY, relativeX, relativeY; 12121 12122 pos = target.getBoundingClientRect(); 12123 relativeX = lastMouseDownEvent.clientX - pos.left; 12124 relativeY = lastMouseDownEvent.clientY - pos.top; 12125 12126 // Figure out what corner we are draging on 12127 for (name in resizeHandles) { 12128 corner = resizeHandles[name]; 12129 12130 cornerX = target.offsetWidth * corner[0]; 12131 cornerY = target.offsetHeight * corner[1]; 12132 12133 if (abs(cornerX - relativeX) < 8 && abs(cornerY - relativeY) < 8) { 12134 selectedHandle = corner; 12135 break; 12136 } 12137 } 12138 12139 // Remove native selection and let the magic begin 12140 resizeStarted = true; 12141 editor.fire('ObjectResizeStart', { 12142 target: selectedElm, 12143 width: selectedElm.clientWidth, 12144 height: selectedElm.clientHeight 12145 }); 12146 editor.getDoc().selection.empty(); 12147 showResizeRect(target, name, lastMouseDownEvent); 12148 } 12149 12150 function nativeControlSelect(e) { 12151 var target = e.srcElement; 12152 12153 if (target != selectedElm) { 12154 editor.fire('ObjectSelected', {target: target}); 12155 detachResizeStartListener(); 12156 12157 if (target.id.indexOf('mceResizeHandle') === 0) { 12158 e.returnValue = false; 12159 return; 12160 } 12161 12162 if (target.nodeName == 'IMG' || target.nodeName == 'TABLE') { 12163 hideResizeRect(); 12164 selectedElm = target; 12165 attachEvent(target, 'resizestart', resizeNativeStart); 12166 } 12167 } 12168 } 12169 12170 function detachResizeStartListener() { 12171 detachEvent(selectedElm, 'resizestart', resizeNativeStart); 12172 } 12173 12174 function unbindResizeHandleEvents() { 12175 for (var name in resizeHandles) { 12176 var handle = resizeHandles[name]; 12177 12178 if (handle.elm) { 12179 dom.unbind(handle.elm); 12180 delete handle.elm; 12181 } 12182 } 12183 } 12184 12185 function disableGeckoResize() { 12186 try { 12187 // Disable object resizing on Gecko 12188 editor.getDoc().execCommand('enableObjectResizing', false, false); 12189 } catch (ex) { 12190 // Ignore 12191 } 12192 } 12193 12194 function controlSelect(elm) { 12195 var ctrlRng; 12196 12197 if (!isIE) { 12198 return; 12199 } 12200 12201 ctrlRng = editableDoc.body.createControlRange(); 12202 12203 try { 12204 ctrlRng.addElement(elm); 12205 ctrlRng.select(); 12206 return true; 12207 } catch (ex) { 12208 // Ignore since the element can't be control selected for example a P tag 12209 } 12210 } 12211 12212 editor.on('init', function() { 12213 if (isIE) { 12214 // Hide the resize rect on resize and reselect the image
12215 editor.on('ObjectResized', function(e) { 12216 if (e.target.nodeName != 'TABLE') { 12217 hideResizeRect(); 12218 controlSelect(e.target); 12219 } 12220 }); 12221 12222 attachEvent(rootElement, 'controlselect', nativeControlSelect); 12223 12224 editor.on('mousedown', function(e) { 12225 lastMouseDownEvent = e; 12226 }); 12227 } else { 12228 disableGeckoResize(); 12229 12230 if (Env.ie >= 11) { 12231 // TODO: Drag/drop doesn't work 12232 editor.on('mouseup', function(e) { 12233 var nodeName = e.target.nodeName; 12234 12235 if (!resizeStarted && /^(TABLE|IMG|HR)$/.test(nodeName)) { 12236 editor.selection.select(e.target, nodeName == 'TABLE'); 12237 editor.nodeChanged(); 12238 } 12239 }); 12240 12241 editor.dom.bind(rootElement, 'mscontrolselect', function(e) { 12242 if (/^(TABLE|IMG|HR)$/.test(e.target.nodeName)) { 12243 e.preventDefault(); 12244 12245 // This moves the selection from being a control selection to a text like selection like in WebKit #6753 12246 // TODO: Fix this the day IE works like other browsers without this nasty native ugly control selections. 12247 if (e.target.tagName == 'IMG') { 12248 window.setTimeout(function() { 12249 editor.selection.select(e.target); 12250 }, 0); 12251 } 12252 } 12253 }); 12254 } 12255 } 12256 12257 editor.on('nodechange ResizeEditor', updateResizeRect); 12258 12259 // Update resize rect while typing in a table 12260 editor.on('keydown keyup', function(e) { 12261 if (selectedElm && selectedElm.nodeName == "TABLE") { 12262 updateResizeRect(e); 12263 } 12264 }); 12265 12266 editor.on('hide', hideResizeRect); 12267 12268 // Hide rect on focusout since it would float on top of windows otherwise 12269 //editor.on('focusout', hideResizeRect); 12270 }); 12271 12272 editor.on('remove', unbindResizeHandleEvents); 12273 12274 function destroy() { 12275 selectedElm = selectedElmGhost = null; 12276 12277 if (isIE) { 12278 detachResizeStartListener(); 12279 detachEvent(rootElement, 'controlselect', nativeControlSelect); 12280 } 12281 } 12282 12283 return { 12284 isResizable: isResizable, 12285 showResizeRect: showResizeRect, 12286 hideResizeRect: hideResizeRect, 12287 updateResizeRect: updateResizeRect, 12288 controlSelect: controlSelect, 12289 destroy: destroy 12290 }; 12291 }; 12292}); 12293 12294// Included from: js/tinymce/classes/dom/BookmarkManager.js 12295 12296/** 12297 * BookmarkManager.js 12298 * 12299 * Copyright, Moxiecode Systems AB 12300 * Released under LGPL License. 12301 * 12302 * License: http://www.tinymce.com/license 12303 * Contributing: http://www.tinymce.com/contributing 12304 */ 12305 12306/** 12307 * This class handles selection bookmarks. 12308 * 12309 * @class tinymce.dom.BookmarkManager 12310 */ 12311define("tinymce/dom/BookmarkManager", [ 12312 "tinymce/Env", 12313 "tinymce/util/Tools" 12314], function(Env, Tools) { 12315 /** 12316 * Constructs a new BookmarkManager instance for a specific selection instance. 12317 * 12318 * @constructor 12319 * @method BookmarkManager 12320 * @param {tinymce.dom.Selection} selection Selection instance to handle bookmarks for. 12321 */ 12322 function BookmarkManager(selection) { 12323 var dom = selection.dom; 12324 12325 /** 12326 * Returns a bookmark location for the current selection. This bookmark object 12327 * can then be used to restore the selection after some content modification to the document. 12328 * 12329 * @method getBookmark 12330 * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex. 12331 * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization. 12332 * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection. 12333 * @example 12334 * // Stores a bookmark of the current selection 12335 * var bm = tinymce.activeEditor.selection.getBookmark(); 12336 * 12337 * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content'); 12338 * 12339 * // Restore the selection bookmark 12340 * tinymce.activeEditor.selection.moveToBookmark(bm); 12341 */ 12342 this.getBookmark = function(type, normalized) { 12343 var rng, rng2, id, collapsed, name, element, chr = '', styles; 12344 12345 function findIndex(name, element) { 12346 var index = 0; 12347 12348 Tools.each(dom.select(name), function(node, i) { 12349 if (node == element) { 12350 index = i; 12351 } 12352 }); 12353 12354 return index; 12355 } 12356 12357 function normalizeTableCellSelection(rng) { 12358 function moveEndPoint(start) { 12359 var container, offset, childNodes, prefix = start ? 'start' : 'end'; 12360 12361 container = rng[prefix + 'Container']; 12362 offset = rng[prefix + 'Offset']; 12363 12364 if (container.nodeType == 1 && container.nodeName == "TR") { 12365 childNodes = container.childNodes; 12366 container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)]; 12367 if (container) { 12368 offset = start ? 0 : container.childNodes.length; 12369 rng['set' + (start ? 'Start' : 'End')](container, offset); 12370 } 12371 } 12372 } 12373 12374 moveEndPoint(true); 12375 moveEndPoint(); 12376 12377 return rng; 12378 } 12379 12380 function getLocation() { 12381 var rng = selection.getRng(true), root = dom.getRoot(), bookmark = {}; 12382 12383 function getPoint(rng, start) { 12384 var container = rng[start ? 'startContainer' : 'endContainer'], 12385 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; 12386 12387 if (container.nodeType == 3) { 12388 if (normalized) { 12389 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) { 12390 offset += node.nodeValue.length; 12391 } 12392 } 12393 12394 point.push(offset); 12395 } else { 12396 childNodes = container.childNodes; 12397 12398 if (offset >= childNodes.length && childNodes.length) { 12399 after = 1; 12400 offset = Math.max(0, childNodes.length - 1); 12401 } 12402 12403 point.push(dom.nodeIndex(childNodes[offset], normalized) + after); 12404 } 12405 12406 for (; container && container != root; container = container.parentNode) { 12407 point.push(dom.nodeIndex(container, normalized)); 12408 } 12409 12410 return point; 12411 } 12412 12413 bookmark.start = getPoint(rng, true); 12414 12415 if (!selection.isCollapsed()) { 12416 bookmark.end = getPoint(rng); 12417 } 12418 12419 return bookmark; 12420 } 12421 12422 if (type == 2) { 12423 element = selection.getNode(); 12424 name = element ? element.nodeName : null; 12425 12426 if (name == 'IMG') { 12427 return {name: name, index: findIndex(name, element)}; 12428 } 12429 12430 if (selection.tridentSel) { 12431 return selection.tridentSel.getBookmark(type); 12432 } 12433 12434 return getLocation(); 12435 } 12436 12437 // Handle simple range 12438 if (type) { 12439 return {rng: selection.getRng()}; 12440 } 12441 12442 rng = selection.getRng(); 12443 id = dom.uniqueId(); 12444 collapsed = selection.isCollapsed(); 12445 styles = 'overflow:hidden;line-height:0px'; 12446 12447 // Explorer method 12448 if (rng.duplicate || rng.item) { 12449 // Text selection 12450 if (!rng.item) { 12451 rng2 = rng.duplicate(); 12452 12453 try { 12454 // Insert start marker 12455 rng.collapse(); 12456 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>'); 12457 12458 // Insert end marker 12459 if (!collapsed) { 12460 rng2.collapse(false); 12461 12462 // Detect the empty space after block elements in IE and move the 12463 // end back one character <p></p>] becomes <p>]</p> 12464 rng.moveToElementText(rng2.parentElement()); 12465 if (rng.compareEndPoints('StartToEnd', rng2) === 0) { 12466 rng2.move('character', -1); 12467 } 12468 12469 rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>'); 12470 } 12471 } catch (ex) { 12472 // IE might throw unspecified error so lets ignore it 12473 return null; 12474 } 12475 } else { 12476 // Control selection 12477 element = rng.item(0); 12478 name = element.nodeName; 12479 12480 return {name: name, index: findIndex(name, element)}; 12481 } 12482 } else { 12483 element = selection.getNode(); 12484 name = element.nodeName; 12485 if (name == 'IMG') { 12486 return {name: name, index: findIndex(name, element)}; 12487 } 12488 12489 // W3C method 12490 rng2 = normalizeTableCellSelection(rng.cloneRange()); 12491 12492 // Insert end marker 12493 if (!collapsed) { 12494 rng2.collapse(false);
12495 rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr)); 12496 } 12497 12498 rng = normalizeTableCellSelection(rng); 12499 rng.collapse(true); 12500 rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr)); 12501 } 12502 12503 selection.moveToBookmark({id: id, keep: 1}); 12504 12505 return {id: id}; 12506 }; 12507 12508 /** 12509 * Restores the selection to the specified bookmark. 12510 * 12511 * @method moveToBookmark 12512 * @param {Object} bookmark Bookmark to restore selection from. 12513 * @return {Boolean} true/false if it was successful or not. 12514 * @example 12515 * // Stores a bookmark of the current selection 12516 * var bm = tinymce.activeEditor.selection.getBookmark(); 12517 * 12518 * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content'); 12519 * 12520 * // Restore the selection bookmark 12521 * tinymce.activeEditor.selection.moveToBookmark(bm); 12522 */ 12523 this.moveToBookmark = function(bookmark) { 12524 var rng, root, startContainer, endContainer, startOffset, endOffset; 12525 12526 function setEndPoint(start) { 12527 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; 12528 12529 if (point) { 12530 offset = point[0]; 12531 12532 // Find container node 12533 for (node = root, i = point.length - 1; i >= 1; i--) { 12534 children = node.childNodes; 12535 12536 if (point[i] > children.length - 1) { 12537 return; 12538 } 12539 12540 node = children[point[i]]; 12541 } 12542 12543 // Move text offset to best suitable location 12544 if (node.nodeType === 3) { 12545 offset = Math.min(point[0], node.nodeValue.length); 12546 } 12547 12548 // Move element offset to best suitable location 12549 if (node.nodeType === 1) { 12550 offset = Math.min(point[0], node.childNodes.length); 12551 } 12552 12553 // Set offset within container node 12554 if (start) { 12555 rng.setStart(node, offset); 12556 } else { 12557 rng.setEnd(node, offset); 12558 } 12559 } 12560 12561 return true; 12562 } 12563 12564 function restoreEndPoint(suffix) { 12565 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; 12566 12567 if (marker) { 12568 node = marker.parentNode; 12569 12570 if (suffix == 'start') { 12571 if (!keep) { 12572 idx = dom.nodeIndex(marker); 12573 } else { 12574 node = marker.firstChild; 12575 idx = 1; 12576 } 12577 12578 startContainer = endContainer = node; 12579 startOffset = endOffset = idx; 12580 } else { 12581 if (!keep) { 12582 idx = dom.nodeIndex(marker); 12583 } else { 12584 node = marker.firstChild; 12585 idx = 1; 12586 } 12587 12588 endContainer = node; 12589 endOffset = idx; 12590 } 12591 12592 if (!keep) { 12593 prev = marker.previousSibling; 12594 next = marker.nextSibling; 12595 12596 // Remove all marker text nodes 12597 Tools.each(Tools.grep(marker.childNodes), function(node) { 12598 if (node.nodeType == 3) { 12599 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); 12600 } 12601 }); 12602 12603 // Remove marker but keep children if for example contents where inserted into the marker 12604 // Also remove duplicated instances of the marker for example by a 12605 // split operation or by WebKit auto split on paste feature 12606 while ((marker = dom.get(bookmark.id + '_' + suffix))) { 12607 dom.remove(marker, 1); 12608 } 12609 12610 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node 12611 // and we are sniffing since adding a lot of detection code for a browser with 3% of the market 12612 // isn't worth the effort. Sorry, Opera but it's just a fact 12613 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !Env.opera) { 12614 idx = prev.nodeValue.length; 12615 prev.appendData(next.nodeValue); 12616 dom.remove(next); 12617 12618 if (suffix == 'start') { 12619 startContainer = endContainer = prev; 12620 startOffset = endOffset = idx; 12621 } else { 12622 endContainer = prev; 12623 endOffset = idx; 12624 } 12625 } 12626 } 12627 } 12628 } 12629 12630 function addBogus(node) { 12631 // Adds a bogus BR element for empty block elements 12632 if (dom.isBlock(node) && !node.innerHTML && !Env.ie) { 12633 node.innerHTML = '<br data-mce-bogus="1" />'; 12634 } 12635 12636 return node; 12637 } 12638 12639 if (bookmark) { 12640 if (bookmark.start) { 12641 rng = dom.createRng(); 12642 root = dom.getRoot(); 12643 12644 if (selection.tridentSel) { 12645 return selection.tridentSel.moveToBookmark(bookmark); 12646 } 12647 12648 if (setEndPoint(true) && setEndPoint()) { 12649 selection.setRng(rng); 12650 } 12651 } else if (bookmark.id) { 12652 // Restore start/end points 12653 restoreEndPoint('start'); 12654 restoreEndPoint('end'); 12655 12656 if (startContainer) { 12657 rng = dom.createRng(); 12658 rng.setStart(addBogus(startContainer), startOffset); 12659 rng.setEnd(addBogus(endContainer), endOffset); 12660 selection.setRng(rng); 12661 } 12662 } else if (bookmark.name) { 12663 selection.select(dom.select(bookmark.name)[bookmark.index]); 12664 } else if (bookmark.rng) { 12665 selection.setRng(bookmark.rng); 12666 } 12667 } 12668 }; 12669 } 12670 12671 /** 12672 * Returns true/false if the specified node is a bookmark node or not. 12673 * 12674 * @static 12675 * @method isBookmarkNode 12676 * @param {DOMNode} node DOM Node to check if it's a bookmark node or not. 12677 * @return {Boolean} true/false if the node is a bookmark node or not. 12678 */ 12679 BookmarkManager.isBookmarkNode = function(node) { 12680 return node && node.tagName === 'SPAN' && node.getAttribute('data-mce-type') === 'bookmark'; 12681 }; 12682 12683 return BookmarkManager; 12684}); 12685 12686// Included from: js/tinymce/classes/dom/Selection.js 12687 12688/** 12689 * Selection.js 12690 * 12691 * Copyright, Moxiecode Systems AB 12692 * Released under LGPL License. 12693 * 12694 * License: http://www.tinymce.com/license 12695 * Contributing: http://www.tinymce.com/contributing 12696 */ 12697 12698/** 12699 * This class handles text and control selection it's an crossbrowser utility class. 12700 * Consult the TinyMCE Wiki API for more details and examples on how to use this class. 12701 * 12702 * @class tinymce.dom.Selection 12703 * @example 12704 * // Getting the currently selected node for the active editor 12705 * alert(tinymce.activeEditor.selection.getNode().nodeName); 12706 */ 12707define("tinymce/dom/Selection", [ 12708 "tinymce/dom/TreeWalker", 12709 "tinymce/dom/TridentSelection", 12710 "tinymce/dom/ControlSelection", 12711 "tinymce/dom/RangeUtils",
12712 "tinymce/dom/BookmarkManager", 12713 "tinymce/Env", 12714 "tinymce/util/Tools" 12715], function(TreeWalker, TridentSelection, ControlSelection, RangeUtils, BookmarkManager, Env, Tools) { 12716 var each = Tools.each, trim = Tools.trim; 12717 var isIE = Env.ie; 12718 12719 /** 12720 * Constructs a new selection instance. 12721 * 12722 * @constructor 12723 * @method Selection 12724 * @param {tinymce.dom.DOMUtils} dom DOMUtils object reference. 12725 * @param {Window} win Window to bind the selection object to. 12726 * @param {tinymce.dom.Serializer} serializer DOM serialization class to use for getContent. 12727 */ 12728 function Selection(dom, win, serializer, editor) { 12729 var self = this; 12730 12731 self.dom = dom; 12732 self.win = win; 12733 self.serializer = serializer; 12734 self.editor = editor; 12735 self.bookmarkManager = new BookmarkManager(self); 12736 self.controlSelection = new ControlSelection(self, editor); 12737 12738 // No W3C Range support 12739 if (!self.win.getSelection) { 12740 self.tridentSel = new TridentSelection(self); 12741 } 12742 } 12743 12744 Selection.prototype = { 12745 /** 12746 * Move the selection cursor range to the specified node and offset. 12747 * If there is no node specified it will move it to the first suitable location within the body. 12748 * 12749 * @method setCursorLocation 12750 * @param {Node} node Optional node to put the cursor in. 12751 * @param {Number} offset Optional offset from the start of the node to put the cursor at. 12752 */ 12753 setCursorLocation: function(node, offset) { 12754 var self = this, rng = self.dom.createRng(); 12755 12756 if (!node) { 12757 self._moveEndPoint(rng, self.editor.getBody(), true); 12758 self.setRng(rng); 12759 } else { 12760 rng.setStart(node, offset); 12761 rng.setEnd(node, offset); 12762 self.setRng(rng); 12763 self.collapse(false); 12764 } 12765 }, 12766 12767 /** 12768 * Returns the selected contents using the DOM serializer passed in to this class. 12769 * 12770 * @method getContent 12771 * @param {Object} s Optional settings class with for example output format text or html. 12772 * @return {String} Selected contents in for example HTML format. 12773 * @example 12774 * // Alerts the currently selected contents 12775 * alert(tinymce.activeEditor.selection.getContent()); 12776 * 12777 * // Alerts the currently selected contents as plain text 12778 * alert(tinymce.activeEditor.selection.getContent({format: 'text'})); 12779 */ 12780 getContent: function(args) { 12781 var self = this, rng = self.getRng(), tmpElm = self.dom.create("body"); 12782 var se = self.getSel(), whiteSpaceBefore, whiteSpaceAfter, fragment; 12783 12784 args = args || {}; 12785 whiteSpaceBefore = whiteSpaceAfter = ''; 12786 args.get = true; 12787 args.format = args.format || 'html'; 12788 args.selection = true; 12789 self.editor.fire('BeforeGetContent', args); 12790 12791 if (args.format == 'text') { 12792 return self.isCollapsed() ? '' : (rng.text || (se.toString ? se.toString() : '')); 12793 } 12794 12795 if (rng.cloneContents) { 12796 fragment = rng.cloneContents(); 12797 12798 if (fragment) { 12799 tmpElm.appendChild(fragment); 12800 } 12801 } else if (rng.item !== undefined || rng.htmlText !== undefined) { 12802 // IE will produce invalid markup if elements are present that 12803 // it doesn't understand like custom elements or HTML5 elements. 12804 // Adding a BR in front of the contents and then remoiving it seems to fix it though. 12805 tmpElm.innerHTML = '<br>' + (rng.item ? rng.item(0).outerHTML : rng.htmlText); 12806 tmpElm.removeChild(tmpElm.firstChild); 12807 } else { 12808 tmpElm.innerHTML = rng.toString(); 12809 } 12810 12811 // Keep whitespace before and after 12812 if (/^\s/.test(tmpElm.innerHTML)) { 12813 whiteSpaceBefore = ' '; 12814 } 12815 12816 if (/\s+$/.test(tmpElm.innerHTML)) { 12817 whiteSpaceAfter = ' '; 12818 } 12819 12820 args.getInner = true; 12821 12822 args.content = self.isCollapsed() ? '' : whiteSpaceBefore + self.serializer.serialize(tmpElm, args) + whiteSpaceAfter; 12823 self.editor.fire('GetContent', args); 12824 12825 return args.content; 12826 }, 12827 12828 /** 12829 * Sets the current selection to the specified content. If any contents is selected it will be replaced 12830 * with the contents passed in to this function. If there is no selection the contents will be inserted 12831 * where the caret is placed in the editor/page. 12832 * 12833 * @method setContent 12834 * @param {String} content HTML contents to set could also be other formats depending on settings. 12835 * @param {Object} args Optional settings object with for example data format. 12836 * @example 12837 * // Inserts some HTML contents at the current selection 12838 * tinymce.activeEditor.selection.setContent('<strong>Some contents</strong>'); 12839 */ 12840 setContent: function(content, args) {
12841 var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp; 12842 12843 args = args || {format: 'html'}; 12844 args.set = true; 12845 args.selection = true; 12846 content = args.content = content; 12847 12848 // Dispatch before set content event 12849 if (!args.no_events) { 12850 self.editor.fire('BeforeSetContent', args); 12851 } 12852 12853 content = args.content; 12854 12855 if (rng.insertNode) { 12856 // Make caret marker since insertNode places the caret in the beginning of text after insert 12857 content += '<span id="__caret">_</span>'; 12858 12859 // Delete and insert new node 12860 if (rng.startContainer == doc && rng.endContainer == doc) { 12861 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents 12862 doc.body.innerHTML = content; 12863 } else { 12864 rng.deleteContents(); 12865 12866 if (doc.body.childNodes.length === 0) { 12867 doc.body.innerHTML = content; 12868 } else { 12869 // createContextualFragment doesn't exists in IE 9 DOMRanges 12870 if (rng.createContextualFragment) { 12871 rng.insertNode(rng.createContextualFragment(content)); 12872 } else { 12873 // Fake createContextualFragment call in IE 9 12874 frag = doc.createDocumentFragment(); 12875 temp = doc.createElement('div'); 12876 12877 frag.appendChild(temp); 12878 temp.outerHTML = content; 12879 12880 rng.insertNode(frag); 12881 } 12882 } 12883 } 12884 12885 // Move to caret marker
12886 caretNode = self.dom.get('__caret'); 12887 12888 // Make sure we wrap it compleatly, Opera fails with a simple select call 12889 rng = doc.createRange(); 12890 rng.setStartBefore(caretNode); 12891 rng.setEndBefore(caretNode); 12892 self.setRng(rng); 12893 12894 // Remove the caret position 12895 self.dom.remove('__caret'); 12896 12897 try { 12898 self.setRng(rng); 12899 } catch (ex) { 12900 // Might fail on Opera for some odd reason 12901 } 12902 } else { 12903 if (rng.item) { 12904 // Delete content and get caret text selection 12905 doc.execCommand('Delete', false, null); 12906 rng = self.getRng(); 12907 } 12908 12909 // Explorer removes spaces from the beginning of pasted contents 12910 if (/^\s+/.test(content)) { 12911 rng.pasteHTML('<span id="__mce_tmp">_</span>' + content); 12912 self.dom.remove('__mce_tmp'); 12913 } else { 12914 rng.pasteHTML(content); 12915 } 12916 } 12917 12918 // Dispatch set content event 12919 if (!args.no_events) { 12920 self.editor.fire('SetContent', args); 12921 } 12922 }, 12923 12924 /** 12925 * Returns the start element of a selection range. If the start is in a text 12926 * node the parent element will be returned. 12927 * 12928 * @method getStart 12929 * @param {Boolean} real Optional state to get the real parent when the selection is collapsed not the closest element. 12930 * @return {Element} Start element of selection range. 12931 */ 12932 getStart: function(real) { 12933 var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node; 12934 12935 if (rng.duplicate || rng.item) { 12936 // Control selection, return first item 12937 if (rng.item) { 12938 return rng.item(0); 12939 } 12940 12941 // Get start element 12942 checkRng = rng.duplicate(); 12943 checkRng.collapse(1); 12944 startElement = checkRng.parentElement(); 12945 if (startElement.ownerDocument !== self.dom.doc) { 12946 startElement = self.dom.getRoot(); 12947 } 12948 12949 // Check if range parent is inside the start element, then return the inner parent element 12950 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element 12951 parentElement = node = rng.parentElement(); 12952 while ((node = node.parentNode)) { 12953 if (node == startElement) { 12954 startElement = parentElement; 12955 break; 12956 } 12957 } 12958 12959 return startElement; 12960 } else { 12961 startElement = rng.startContainer; 12962 12963 if (startElement.nodeType == 1 && startElement.hasChildNodes()) { 12964 if (!real || !rng.collapsed) { 12965 startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)]; 12966 } 12967 } 12968 12969 if (startElement && startElement.nodeType == 3) { 12970 return startElement.parentNode; 12971 } 12972 12973 return startElement; 12974 } 12975 }, 12976 12977 /** 12978 * Returns the end element of a selection range. If the end is in a text 12979 * node the parent element will be returned. 12980 * 12981 * @method getEnd 12982 * @param {Boolean} real Optional state to get the real parent when the selection is collapsed not the closest element. 12983 * @return {Element} End element of selection range. 12984 */ 12985 getEnd: function(real) { 12986 var self = this, rng = self.getRng(), endElement, endOffset; 12987 12988 if (rng.duplicate || rng.item) { 12989 if (rng.item) { 12990 return rng.item(0); 12991 } 12992 12993 rng = rng.duplicate(); 12994 rng.collapse(0); 12995 endElement = rng.parentElement(); 12996 if (endElement.ownerDocument !== self.dom.doc) { 12997 endElement = self.dom.getRoot(); 12998 } 12999 13000 if (endElement && endElement.nodeName == 'BODY') { 13001 return endElement.lastChild || endElement; 13002 } 13003 13004 return endElement; 13005 } else { 13006 endElement = rng.endContainer; 13007 endOffset = rng.endOffset; 13008 13009 if (endElement.nodeType == 1 && endElement.hasChildNodes()) { 13010 if (!real || !rng.collapsed) { 13011 endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset]; 13012 } 13013 } 13014 13015 if (endElement && endElement.nodeType == 3) { 13016 return endElement.parentNode; 13017 } 13018 13019 return endElement; 13020 } 13021 }, 13022 13023 /** 13024 * Returns a bookmark location for the current selection. This bookmark object 13025 * can then be used to restore the selection after some content modification to the document. 13026 * 13027 * @method getBookmark 13028 * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex. 13029 * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization. 13030 * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection. 13031 * @example
13032 * // Stores a bookmark of the current selection 13033 * var bm = tinymce.activeEditor.selection.getBookmark(); 13034 * 13035 * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content'); 13036 * 13037 * // Restore the selection bookmark 13038 * tinymce.activeEditor.selection.moveToBookmark(bm); 13039 */ 13040 getBookmark: function(type, normalized) { 13041 return this.bookmarkManager.getBookmark(type, normalized); 13042 }, 13043 13044 /** 13045 * Restores the selection to the specified bookmark. 13046 * 13047 * @method moveToBookmark 13048 * @param {Object} bookmark Bookmark to restore selection from. 13049 * @return {Boolean} true/false if it was successful or not. 13050 * @example 13051 * // Stores a bookmark of the current selection 13052 * var bm = tinymce.activeEditor.selection.getBookmark(); 13053 * 13054 * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content'); 13055 * 13056 * // Restore the selection bookmark 13057 * tinymce.activeEditor.selection.moveToBookmark(bm); 13058 */ 13059 moveToBookmark: function(bookmark) { 13060 return this.bookmarkManager.moveToBookmark(bookmark); 13061 }, 13062 13063 /** 13064 * Selects the specified element. This will place the start and end of the selection range around the element. 13065 * 13066 * @method select 13067 * @param {Element} node HMTL DOM element to select. 13068 * @param {Boolean} content Optional bool state if the contents should be selected or not on non IE browser. 13069 * @return {Element} Selected element the same element as the one that got passed in. 13070 * @example 13071 * // Select the first paragraph in the active editor 13072 * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]); 13073 */ 13074 select: function(node, content) { 13075 var self = this, dom = self.dom, rng = dom.createRng(), idx; 13076 13077 // Clear stored range set by FocusManager 13078 self.lastFocusBookmark = null; 13079 13080 if (node) { 13081 if (!content && self.controlSelection.controlSelect(node)) { 13082 return; 13083 } 13084 13085 idx = dom.nodeIndex(node); 13086 rng.setStart(node.parentNode, idx); 13087 rng.setEnd(node.parentNode, idx + 1); 13088 13089 // Find first/last text node or BR element 13090 if (content) { 13091 self._moveEndPoint(rng, node, true); 13092 self._moveEndPoint(rng, node); 13093 } 13094 13095 self.setRng(rng); 13096 } 13097 13098 return node; 13099 }, 13100 13101 /** 13102 * Returns true/false if the selection range is collapsed or not. Collapsed means if it's a caret or a larger selection. 13103 * 13104 * @method isCollapsed 13105 * @return {Boolean} true/false state if the selection range is collapsed or not. 13106 * Collapsed means if it's a caret or a larger selection. 13107 */ 13108 isCollapsed: function() { 13109 var self = this, rng = self.getRng(), sel = self.getSel(); 13110 13111 if (!rng || rng.item) { 13112 return false; 13113 } 13114 13115 if (rng.compareEndPoints) { 13116 return rng.compareEndPoints('StartToEnd', rng) === 0; 13117 } 13118 13119 return !sel || rng.collapsed; 13120 }, 13121 13122 /** 13123 * Collapse the selection to start or end of range. 13124 * 13125 * @method collapse 13126 * @param {Boolean} toStart Optional boolean state if to collapse to end or not. Defaults to start. 13127 */ 13128 collapse: function(toStart) { 13129 var self = this, rng = self.getRng(), node; 13130 13131 // Control range on IE 13132 if (rng.item) { 13133 node = rng.item(0); 13134 rng = self.win.document.body.createTextRange(); 13135 rng.moveToElementText(node); 13136 } 13137 13138 rng.collapse(!!toStart); 13139 self.setRng(rng); 13140 }, 13141 13142 /** 13143 * Returns the browsers internal selection object. 13144 * 13145 * @method getSel 13146 * @return {Selection} Internal browser selection object. 13147 */ 13148 getSel: function() { 13149 var win = this.win; 13150 13151 return win.getSelection ? win.getSelection() : win.document.selection; 13152 }, 13153 13154 /** 13155 * Returns the browsers internal range object. 13156 * 13157 * @method getRng 13158 * @param {Boolean} w3c Forces a compatible W3C range on IE. 13159 * @return {Range} Internal browser range object. 13160 * @see http://www.quirksmode.org/dom/range_intro.html 13161 * @see http://www.dotvoid.com/2001/03/using-the-range-object-in-mozilla/ 13162 */ 13163 getRng: function(w3c) { 13164 var self = this, selection, rng, elm, doc = self.win.document, ieRng; 13165 13166 function tryCompareBoundaryPoints(how, sourceRange, destinationRange) { 13167 try { 13168 return sourceRange.compareBoundaryPoints(how, destinationRange); 13169 } catch (ex) { 13170 // Gecko throws wrong document exception if the range points 13171 // to nodes that where removed from the dom #6690 13172 // Browsers should mutate existing DOMRange instances so that they always point 13173 // to something in the document this is not the case in Gecko works fine in IE/WebKit/Blink 13174 // For performance reasons just return -1 13175 return -1; 13176 } 13177 } 13178 13179 // Use last rng passed from FocusManager if it's available this enables 13180 // calls to editor.selection.getStart() to work when caret focus is lost on IE 13181 if (!w3c && self.lastFocusBookmark) { 13182 var bookmark = self.lastFocusBookmark; 13183 13184 // Convert bookmark to range IE 11 fix 13185 if (bookmark.startContainer) { 13186 rng = doc.createRange(); 13187 rng.setStart(bookmark.startContainer, bookmark.startOffset); 13188 rng.setEnd(bookmark.endContainer, bookmark.endOffset); 13189 } else { 13190 rng = bookmark; 13191 } 13192 13193 return rng; 13194 } 13195 13196 // Found tridentSel object then we need to use that one 13197 if (w3c && self.tridentSel) { 13198 return self.tridentSel.getRangeAt(0); 13199 } 13200 13201 try { 13202 if ((selection = self.getSel())) { 13203 if (selection.rangeCount > 0) { 13204 rng = selection.getRangeAt(0); 13205 } else { 13206 rng = selection.createRange ? selection.createRange() : doc.createRange(); 13207 } 13208 } 13209 } catch (ex) { 13210 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe 13211 } 13212 13213 // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet 13214 // IE 11 doesn't support the selection object so we check for that as well 13215 if (isIE && rng && rng.setStart && doc.selection) { 13216 try { 13217 // IE will sometimes throw an exception here 13218 ieRng = doc.selection.createRange(); 13219 } catch (ex) { 13220 13221 } 13222 13223 if (ieRng && ieRng.item) { 13224 elm = ieRng.item(0); 13225 rng = doc.createRange(); 13226 rng.setStartBefore(elm); 13227 rng.setEndAfter(elm); 13228 } 13229 } 13230 13231 // No range found then create an empty one 13232 // This can occur when the editor is placed in a hidden container element on Gecko 13233 // Or on IE when there was an exception 13234 if (!rng) { 13235 rng = doc.createRange ? doc.createRange() : doc.body.createTextRange(); 13236 } 13237 13238 // If range is at start of document then move it to start of body 13239 if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) { 13240 elm = self.dom.getRoot(); 13241 rng.setStart(elm, 0); 13242 rng.setEnd(elm, 0); 13243 } 13244 13245 if (self.selectedRange && self.explicitRange) { 13246 if (tryCompareBoundaryPoints(rng.START_TO_START, rng, self.selectedRange) === 0 && 13247 tryCompareBoundaryPoints(rng.END_TO_END, rng, self.selectedRange) === 0) { 13248 // Safari, Opera and Chrome only ever select text which causes the range to change. 13249 // This lets us use the originally set range if the selection hasn't been changed by the user. 13250 rng = self.explicitRange; 13251 } else { 13252 self.selectedRange = null; 13253 self.explicitRange = null; 13254 } 13255 } 13256 13257 return rng; 13258 }, 13259 13260 /** 13261 * Changes the selection to the specified DOM range. 13262 * 13263 * @method setRng 13264 * @param {Range} rng Range to select. 13265 */ 13266 setRng: function(rng, forward) { 13267 var self = this, sel; 13268 13269 if (!rng) { 13270 return; 13271 } 13272 13273 // Is IE specific range 13274 if (rng.select) { 13275 try { 13276 rng.select(); 13277 } catch (ex) { 13278 // Needed for some odd IE bug #1843306 13279 } 13280 13281 return; 13282 } 13283 13284 if (!self.tridentSel) { 13285 sel = self.getSel(); 13286 13287 if (sel) { 13288 self.explicitRange = rng; 13289 13290 try { 13291 sel.removeAllRanges(); 13292 sel.addRange(rng); 13293 } catch (ex) { 13294 // IE might throw errors here if the editor is within a hidden container and selection is changed 13295 } 13296 13297 // Forward is set to false and we have an extend function 13298 if (forward === false && sel.extend) { 13299 sel.collapse(rng.endContainer, rng.endOffset); 13300 sel.extend(rng.startContainer, rng.startOffset); 13301 } 13302 13303 // adding range isn't always successful so we need to check range count otherwise an exception can occur 13304 self.selectedRange = sel.rangeCount > 0 ? sel.getRangeAt(0) : null; 13305 } 13306 } else { 13307 // Is W3C Range fake range on IE 13308 if (rng.cloneRange) { 13309 try { 13310 self.tridentSel.addRange(rng); 13311 return; 13312 } catch (ex) { 13313 //IE9 throws an error here if called before selection is placed in the editor
13314 } 13315 } 13316 } 13317 }, 13318 13319 /** 13320 * Sets the current selection to the specified DOM element. 13321 * 13322 * @method setNode 13323 * @param {Element} elm Element to set as the contents of the selection. 13324 * @return {Element} Returns the element that got passed in. 13325 * @example 13326 * // Inserts a DOM node at current selection/caret location 13327 * tinymce.activeEditor.selection.setNode(tinymce.activeEditor.dom.create('img', {src: 'some.gif', title: 'some title'})); 13328 */ 13329 setNode: function(elm) { 13330 var self = this; 13331 13332 self.setContent(self.dom.getOuterHTML(elm)); 13333 13334 return elm; 13335 }, 13336 13337 /** 13338 * Returns the currently selected element or the common ancestor element for both start and end of the selection. 13339 * 13340 * @method getNode 13341 * @return {Element} Currently selected element or common ancestor element. 13342 * @example 13343 * // Alerts the currently selected elements node name 13344 * alert(tinymce.activeEditor.selection.getNode().nodeName); 13345 */ 13346 getNode: function() { 13347 var self = this, rng = self.getRng(), elm; 13348 var startContainer = rng.startContainer, endContainer = rng.endContainer; 13349 var startOffset = rng.startOffset, endOffset = rng.endOffset, root = self.dom.getRoot(); 13350 13351 function skipEmptyTextNodes(node, forwards) { 13352 var orig = node; 13353 13354 while (node && node.nodeType === 3 && node.length === 0) { 13355 node = forwards ? node.nextSibling : node.previousSibling; 13356 } 13357 13358 return node || orig; 13359 } 13360 13361 // Range maybe lost after the editor is made visible again 13362 if (!rng) { 13363 return root; 13364 } 13365 13366 if (rng.setStart) { 13367 elm = rng.commonAncestorContainer; 13368 13369 // Handle selection a image or other control like element such as anchors 13370 if (!rng.collapsed) { 13371 if (startContainer == endContainer) { 13372 if (endOffset - startOffset < 2) { 13373 if (startContainer.hasChildNodes()) { 13374 elm = startContainer.childNodes[startOffset]; 13375 } 13376 } 13377 } 13378 13379 // If the anchor node is a element instead of a text node then return this element 13380 //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) 13381 // return sel.anchorNode.childNodes[sel.anchorOffset]; 13382 13383 // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent. 13384 // This happens when you double click an underlined word in FireFox. 13385 if (startContainer.nodeType === 3 && endContainer.nodeType === 3) { 13386 if (startContainer.length === startOffset) { 13387 startContainer = skipEmptyTextNodes(startContainer.nextSibling, true); 13388 } else { 13389 startContainer = startContainer.parentNode; 13390 } 13391 13392 if (endOffset === 0) { 13393 endContainer = skipEmptyTextNodes(endContainer.previousSibling, false); 13394 } else { 13395 endContainer = endContainer.parentNode; 13396 } 13397 13398 if (startContainer && startContainer === endContainer) { 13399 return startContainer; 13400 } 13401 } 13402 } 13403 13404 if (elm && elm.nodeType == 3) { 13405 return elm.parentNode; 13406 } 13407 13408 return elm; 13409 } 13410 13411 elm = rng.item ? rng.item(0) : rng.parentElement(); 13412 13413 // IE 7 might return elements outside the iframe 13414 if (elm.ownerDocument !== self.win.document) { 13415 elm = root; 13416 } 13417 13418 return elm; 13419 }, 13420 13421 getSelectedBlocks: function(startElm, endElm) { 13422 var self = this, dom = self.dom, node, root, selectedBlocks = []; 13423 13424 root = dom.getRoot(); 13425 startElm = dom.getParent(startElm || self.getStart(), dom.isBlock); 13426 endElm = dom.getParent(endElm || self.getEnd(), dom.isBlock); 13427 13428 if (startElm && startElm != root) { 13429 selectedBlocks.push(startElm); 13430 } 13431 13432 if (startElm && endElm && startElm != endElm) { 13433 node = startElm; 13434 13435 var walker = new TreeWalker(startElm, root); 13436 while ((node = walker.next()) && node != endElm) { 13437 if (dom.isBlock(node)) { 13438 selectedBlocks.push(node); 13439 } 13440 } 13441 } 13442 13443 if (endElm && startElm != endElm && endElm != root) { 13444 selectedBlocks.push(endElm); 13445 } 13446 13447 return selectedBlocks; 13448 }, 13449 13450 isForward: function() { 13451 var dom = this.dom, sel = this.getSel(), anchorRange, focusRange; 13452 13453 // No support for selection direction then always return true 13454 if (!sel || !sel.anchorNode || !sel.focusNode) { 13455 return true; 13456 } 13457 13458 anchorRange = dom.createRng(); 13459 anchorRange.setStart(sel.anchorNode, sel.anchorOffset); 13460 anchorRange.collapse(true); 13461 13462 focusRange = dom.createRng(); 13463 focusRange.setStart(sel.focusNode, sel.focusOffset); 13464 focusRange.collapse(true); 13465 13466 return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0; 13467 }, 13468 13469 normalize: function() { 13470 var self = this, rng = self.getRng(); 13471 13472 if (!isIE && new RangeUtils(self.dom).normalize(rng)) { 13473 self.setRng(rng, self.isForward()); 13474 } 13475 13476 return rng; 13477 }, 13478 13479 /** 13480 * Executes callback of the current selection matches the specified selector or not and passes the state and args to the callback. 13481 * 13482 * @method selectorChanged 13483 * @param {String} selector CSS selector to check for. 13484 * @param {function} callback Callback with state and args when the selector is matches or not. 13485 */ 13486 selectorChanged: function(selector, callback) { 13487 var self = this, currentSelectors; 13488 13489 if (!self.selectorChangedData) { 13490 self.selectorChangedData = {}; 13491 currentSelectors = {}; 13492 13493 self.editor.on('NodeChange', function(e) { 13494 var node = e.element, dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {}; 13495 13496 // Check for new matching selectors 13497 each(self.selectorChangedData, function(callbacks, selector) { 13498 each(parents, function(node) { 13499 if (dom.is(node, selector)) { 13500 if (!currentSelectors[selector]) { 13501 // Execute callbacks 13502 each(callbacks, function(callback) { 13503 callback(true, {node: node, selector: selector, parents: parents}); 13504 }); 13505 13506 currentSelectors[selector] = callbacks; 13507 } 13508 13509 matchedSelectors[selector] = callbacks; 13510 return false;
13511 } 13512 }); 13513 }); 13514 13515 // Check if current selectors still match 13516 each(currentSelectors, function(callbacks, selector) { 13517 if (!matchedSelectors[selector]) { 13518 delete currentSelectors[selector]; 13519 13520 each(callbacks, function(callback) { 13521 callback(false, {node: node, selector: selector, parents: parents}); 13522 }); 13523 } 13524 }); 13525 }); 13526 } 13527 13528 // Add selector listeners 13529 if (!self.selectorChangedData[selector]) { 13530 self.selectorChangedData[selector] = []; 13531 } 13532 13533 self.selectorChangedData[selector].push(callback); 13534 13535 return self; 13536 }, 13537 13538 getScrollContainer: function() { 13539 var scrollContainer, node = this.dom.getRoot(); 13540 13541 while (node && node.nodeName != 'BODY') { 13542 if (node.scrollHeight > node.clientHeight) { 13543 scrollContainer = node; 13544 break; 13545 } 13546 13547 node = node.parentNode; 13548 } 13549 13550 return scrollContainer; 13551 }, 13552 13553 scrollIntoView: function(elm) { 13554 var y, viewPort, self = this, dom = self.dom, root = dom.getRoot(), viewPortY, viewPortH; 13555 13556 function getPos(elm) { 13557 var x = 0, y = 0; 13558 13559 var offsetParent = elm; 13560 while (offsetParent && offsetParent.nodeType) { 13561 x += offsetParent.offsetLeft || 0; 13562 y += offsetParent.offsetTop || 0; 13563 offsetParent = offsetParent.offsetParent; 13564 } 13565 13566 return {x: x, y: y}; 13567 } 13568 13569 if (root.nodeName != 'BODY') { 13570 var scrollContainer = self.getScrollContainer(); 13571 if (scrollContainer) { 13572 y = getPos(elm).y - getPos(scrollContainer).y; 13573 viewPortH = scrollContainer.clientHeight; 13574 viewPortY = scrollContainer.scrollTop; 13575 if (y < viewPortY || y + 25 > viewPortY + viewPortH) { 13576 scrollContainer.scrollTop = y < viewPortY ? y : y - viewPortH + 25; 13577 } 13578 13579 return; 13580 } 13581 } 13582 13583 viewPort = dom.getViewPort(self.editor.getWin()); 13584 y = dom.getPos(elm).y; 13585 viewPortY = viewPort.y; 13586 viewPortH = viewPort.h; 13587 if (y < viewPort.y || y + 25 > viewPortY + viewPortH) { 13588 self.editor.getWin().scrollTo(0, y < viewPortY ? y : y - viewPortH + 25); 13589 } 13590 }, 13591 13592 placeCaretAt: function(clientX, clientY) { 13593 var doc = this.editor.getDoc(), rng, point; 13594 13595 if (doc.caretPositionFromPoint) { 13596 point = doc.caretPositionFromPoint(clientX, clientY); 13597 rng = doc.createRange(); 13598 rng.setStart(point.offsetNode, point.offset); 13599 rng.collapse(true); 13600 } else if (doc.caretRangeFromPoint) { 13601 rng = doc.caretRangeFromPoint(clientX, clientY); 13602 } else if (doc.body.createTextRange) { 13603 rng = doc.body.createTextRange(); 13604 13605 try { 13606 rng.moveToPoint(clientX, clientY); 13607 rng.collapse(true); 13608 } catch (ex) { 13609 rng.collapse(clientY < doc.body.clientHeight); 13610 } 13611 } 13612 13613 this.setRng(rng); 13614 }, 13615 13616 _moveEndPoint: function(rng, node, start) { 13617 var root = node, walker = new TreeWalker(node, root); 13618 var nonEmptyElementsMap = this.dom.schema.getNonEmptyElements(); 13619 13620 do { 13621 // Text node 13622 if (node.nodeType == 3 && trim(node.nodeValue).length !== 0) { 13623 if (start) { 13624 rng.setStart(node, 0); 13625 } else { 13626 rng.setEnd(node, node.nodeValue.length); 13627 } 13628 13629 return; 13630 } 13631 13632 // BR/IMG/INPUT elements but not table cells 13633 if (nonEmptyElementsMap[node.nodeName] && !/^(TD|TH)$/.test(node.nodeName)) { 13634 if (start) { 13635 rng.setStartBefore(node); 13636 } else { 13637 if (node.nodeName == 'BR') { 13638 rng.setEndBefore(node); 13639 } else { 13640 rng.setEndAfter(node); 13641 } 13642 } 13643 13644 return; 13645 } 13646 13647 // Found empty text block old IE can place the selection inside those 13648 if (Env.ie && Env.ie < 11 && this.dom.isBlock(node) && this.dom.isEmpty(node)) { 13649 if (start) { 13650 rng.setStart(node, 0); 13651 } else { 13652 rng.setEnd(node, 0); 13653 } 13654 13655 return; 13656 } 13657 } while ((node = (start ? walker.next() : walker.prev()))); 13658 13659 // Failed to find any text node or other suitable location then move to the root of body 13660 if (root.nodeName == 'BODY') { 13661 if (start) { 13662 rng.setStart(root, 0); 13663 } else { 13664 rng.setEnd(root, root.childNodes.length); 13665 } 13666 } 13667 }, 13668 13669 destroy: function() { 13670 this.win = null; 13671 this.controlSelection.destroy(); 13672 } 13673 }; 13674 13675 return Selection; 13676}); 13677 13678// Included from: js/tinymce/classes/dom/ElementUtils.js 13679 13680/** 13681 * ElementUtils.js 13682 * 13683 * Copyright, Moxiecode Systems AB 13684 * Released under LGPL License. 13685 * 13686 * License: http://www.tinymce.com/license 13687 * Contributing: http://www.tinymce.com/contributing 13688 */ 13689 13690/** 13691 * Utility class for various element specific functions. 13692 * 13693 * @private 13694 */ 13695define("tinymce/dom/ElementUtils", [
13696 "tinymce/dom/BookmarkManager", 13697 "tinymce/util/Tools" 13698], function(BookmarkManager, Tools) { 13699 var each = Tools.each; 13700 13701 function ElementUtils(dom) { 13702 /** 13703 * Compares two nodes and checks if it's attributes and styles matches. 13704 * This doesn't compare classes as items since their order is significant. 13705 * 13706 * @method compare 13707 * @param {Node} node1 First node to compare with. 13708 * @param {Node} node2 Second node to compare with. 13709 * @return {boolean} True/false if the nodes are the same or not. 13710 */ 13711 this.compare = function(node1, node2) { 13712 // Not the same name 13713 if (node1.nodeName != node2.nodeName) { 13714 return false; 13715 } 13716 13717 /** 13718 * Returns all the nodes attributes excluding internal ones, styles and classes. 13719 * 13720 * @private 13721 * @param {Node} node Node to get attributes from. 13722 * @return {Object} Name/value object with attributes and attribute values. 13723 */ 13724 function getAttribs(node) { 13725 var attribs = {}; 13726 13727 each(dom.getAttribs(node), function(attr) { 13728 var name = attr.nodeName.toLowerCase(); 13729 13730 // Don't compare internal attributes or style 13731 if (name.indexOf('_') !== 0 && name !== 'style' && name !== 'data-mce-style') { 13732 attribs[name] = dom.getAttrib(node, name); 13733 } 13734 }); 13735 13736 return attribs; 13737 } 13738 13739 /** 13740 * Compares two objects checks if it's key + value exists in the other one. 13741 * 13742 * @private 13743 * @param {Object} obj1 First object to compare. 13744 * @param {Object} obj2 Second object to compare. 13745 * @return {boolean} True/false if the objects matches or not. 13746 */ 13747 function compareObjects(obj1, obj2) { 13748 var value, name; 13749 13750 for (name in obj1) { 13751 // Obj1 has item obj2 doesn't have 13752 if (obj1.hasOwnProperty(name)) { 13753 value = obj2[name]; 13754 13755 // Obj2 doesn't have obj1 item 13756 if (typeof value == "undefined") { 13757 return false; 13758 } 13759 13760 // Obj2 item has a different value 13761 if (obj1[name] != value) { 13762 return false; 13763 } 13764 13765 // Delete similar value 13766 delete obj2[name]; 13767 } 13768 } 13769 13770 // Check if obj 2 has something obj 1 doesn't have 13771 for (name in obj2) { 13772 // Obj2 has item obj1 doesn't have 13773 if (obj2.hasOwnProperty(name)) { 13774 return false; 13775 } 13776 } 13777 13778 return true; 13779 } 13780 13781 // Attribs are not the same 13782 if (!compareObjects(getAttribs(node1), getAttribs(node2))) { 13783 return false; 13784 } 13785 13786 // Styles are not the same 13787 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) { 13788 return false; 13789 } 13790 13791 return !BookmarkManager.isBookmarkNode(node1) && !BookmarkManager.isBookmarkNode(node2); 13792 }; 13793 } 13794 13795 return ElementUtils; 13796}); 13797 13798// Included from: js/tinymce/classes/fmt/Preview.js 13799 13800/** 13801 * Preview.js 13802 * 13803 * Copyright, Moxiecode Systems AB 13804 * Released under LGPL License. 13805 * 13806 * License: http://www.tinymce.com/license 13807 * Contributing: http://www.tinymce.com/contributing 13808 */ 13809 13810/** 13811 * Internal class for generating previews styles for formats. 13812 * 13813 * Example: 13814 * Preview.getCssText(editor, 'bold'); 13815 * 13816 * @class tinymce.fmt.Preview 13817 * @private 13818 */ 13819define("tinymce/fmt/Preview", [ 13820 "tinymce/util/Tools" 13821], function(Tools) { 13822 var each = Tools.each; 13823 13824 function getCssText(editor, format) { 13825 var name, previewElm, dom = editor.dom; 13826 var previewCss = '', parentFontSize, previewStyles; 13827 13828 previewStyles = editor.settings.preview_styles; 13829 13830 // No preview forced 13831 if (previewStyles === false) { 13832 return ''; 13833 } 13834 13835 // Default preview 13836 if (!previewStyles) { 13837 previewStyles = 'font-family font-size font-weight font-style text-decoration ' + 13838 'text-transform color background-color border border-radius outline text-shadow'; 13839 } 13840 13841 // Removes any variables since these can't be previewed 13842 function removeVars(val) { 13843 return val.replace(/%(\w+)/g, ''); 13844 } 13845 13846 // Create block/inline element to use for preview 13847 if (typeof(format) == "string") { 13848 format = editor.formatter.get(format); 13849 if (!format) { 13850 return; 13851 } 13852 13853 format = format[0]; 13854 } 13855 13856 name = format.block || format.inline || 'span'; 13857 previewElm = dom.create(name); 13858 13859 // Add format styles to preview element 13860 each(format.styles, function(value, name) { 13861 value = removeVars(value); 13862 13863 if (value) { 13864 dom.setStyle(previewElm, name, value); 13865 } 13866 }); 13867 13868 // Add attributes to preview element 13869 each(format.attributes, function(value, name) { 13870 value = removeVars(value); 13871 13872 if (value) { 13873 dom.setAttrib(previewElm, name, value); 13874 } 13875 }); 13876 13877 // Add classes to preview element 13878 each(format.classes, function(value) { 13879 value = removeVars(value); 13880 13881 if (!dom.hasClass(previewElm, value)) { 13882 dom.addClass(previewElm, value); 13883 } 13884 }); 13885 13886 editor.fire('PreviewFormats'); 13887 13888 // Add the previewElm outside the visual area 13889 dom.setStyles(previewElm, {position: 'absolute', left: -0xFFFF}); 13890 editor.getBody().appendChild(previewElm); 13891 13892 // Get parent container font size so we can compute px values out of em/% for older IE:s 13893 parentFontSize = dom.getStyle(editor.getBody(), 'fontSize', true); 13894 parentFontSize = /px$/.test(parentFontSize) ? parseInt(parentFontSize, 10) : 0; 13895 13896 each(previewStyles.split(' '), function(name) { 13897 var value = dom.getStyle(previewElm, name, true); 13898 13899 // If background is transparent then check if the body has a background color we can use
13900 if (name == 'background-color' && /transparent|rgba\s*\([^)]+,\s*0\)/.test(value)) { 13901 value = dom.getStyle(editor.getBody(), name, true); 13902 13903 // Ignore white since it's the default color, not the nicest fix 13904 // TODO: Fix this by detecting runtime style 13905 if (dom.toHex(value).toLowerCase() == '#ffffff') { 13906 return; 13907 } 13908 } 13909 13910 if (name == 'color') { 13911 // Ignore black since it's the default color, not the nicest fix 13912 // TODO: Fix this by detecting runtime style 13913 if (dom.toHex(value).toLowerCase() == '#000000') { 13914 return; 13915 } 13916 } 13917 13918 // Old IE won't calculate the font size so we need to do that manually 13919 if (name == 'font-size') { 13920 if (/em|%$/.test(value)) { 13921 if (parentFontSize === 0) { 13922 return; 13923 } 13924 13925 // Convert font size from em/% to px 13926 value = parseFloat(value, 10) / (/%$/.test(value) ? 100 : 1); 13927 value = (value * parentFontSize) + 'px'; 13928 } 13929 } 13930 13931 if (name == "border" && value) { 13932 previewCss += 'padding:0 2px;'; 13933 } 13934 13935 previewCss += name + ':' + value + ';'; 13936 }); 13937 13938 editor.fire('AfterPreviewFormats'); 13939 13940 //previewCss += 'line-height:normal'; 13941 13942 dom.remove(previewElm); 13943 13944 return previewCss; 13945 } 13946 13947 return { 13948 getCssText: getCssText 13949 }; 13950}); 13951 13952// Included from: js/tinymce/classes/Formatter.js 13953 13954/** 13955 * Formatter.js 13956 * 13957 * Copyright, Moxiecode Systems AB 13958 * Released under LGPL License. 13959 * 13960 * License: http://www.tinymce.com/license 13961 * Contributing: http://www.tinymce.com/contributing 13962 */ 13963 13964/** 13965 * Text formatter engine class. This class is used to apply formats like bold, italic, font size 13966 * etc to the current selection or specific nodes. This engine was build to replace the browsers 13967 * default formatting logic for execCommand due to it's inconsistent and buggy behavior. 13968 * 13969 * @class tinymce.Formatter 13970 * @example 13971 * tinymce.activeEditor.formatter.register('mycustomformat', { 13972 * inline: 'span', 13973 * styles: {color: '#ff0000'} 13974 * }); 13975 * 13976 * tinymce.activeEditor.formatter.apply('mycustomformat'); 13977 */ 13978define("tinymce/Formatter", [ 13979 "tinymce/dom/TreeWalker", 13980 "tinymce/dom/RangeUtils", 13981 "tinymce/dom/BookmarkManager", 13982 "tinymce/dom/ElementUtils", 13983 "tinymce/util/Tools", 13984 "tinymce/fmt/Preview" 13985], function(TreeWalker, RangeUtils, BookmarkManager, ElementUtils, Tools, Preview) { 13986 /** 13987 * Constructs a new formatter instance. 13988 * 13989 * @constructor Formatter 13990 * @param {tinymce.Editor} ed Editor instance to construct the formatter engine to. 13991 */ 13992 return function(ed) { 13993 var formats = {}, 13994 dom = ed.dom, 13995 selection = ed.selection, 13996 rangeUtils = new RangeUtils(dom), 13997 isValid = ed.schema.isValidChild, 13998 isBlock = dom.isBlock, 13999 forcedRootBlock = ed.settings.forced_root_block, 14000 nodeIndex = dom.nodeIndex, 14001 INVISIBLE_CHAR = '\uFEFF', 14002 MCE_ATTR_RE = /^(src|href|style)$/, 14003 FALSE = false, 14004 TRUE = true, 14005 formatChangeData, 14006 undef, 14007 getContentEditable = dom.getContentEditable, 14008 disableCaretContainer, 14009 markCaretContainersBogus, 14010 isBookmarkNode = BookmarkManager.isBookmarkNode; 14011 14012 var each = Tools.each, 14013 grep = Tools.grep, 14014 walk = Tools.walk, 14015 extend = Tools.extend; 14016 14017 function isTextBlock(name) { 14018 if (name.nodeType) { 14019 name = name.nodeName; 14020 } 14021 14022 return !!ed.schema.getTextBlockElements()[name.toLowerCase()]; 14023 } 14024 14025 function getParents(node, selector) { 14026 return dom.getParents(node, selector, dom.getRoot()); 14027 } 14028 14029 function isCaretNode(node) { 14030 return node.nodeType === 1 && node.id === '_mce_caret'; 14031 } 14032 14033 function defaultFormats() { 14034 register({ 14035 valigntop: [ 14036 {selector: 'td,th', styles: {'verticalAlign': 'top'}} 14037 ], 14038 14039 valignmiddle: [ 14040 {selector: 'td,th', styles: {'verticalAlign': 'middle'}} 14041 ], 14042 14043 valignbottom: [ 14044 {selector: 'td,th', styles: {'verticalAlign': 'bottom'}} 14045 ], 14046 14047 alignleft: [ 14048 {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'left'}, defaultBlock: 'div'}, 14049 {selector: 'img,table', collapsed: false, styles: {'float': 'left'}} 14050 ], 14051 14052 aligncenter: [ 14053 {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'center'}, defaultBlock: 'div'},
14054 {selector: 'img', collapsed: false, styles: {display: 'block', marginLeft: 'auto', marginRight: 'auto'}}, 14055 {selector: 'table', collapsed: false, styles: {marginLeft: 'auto', marginRight: 'auto'}} 14056 ], 14057 14058 alignright: [ 14059 {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'right'}, defaultBlock: 'div'}, 14060 {selector: 'img,table', collapsed: false, styles: {'float': 'right'}} 14061 ], 14062 14063 alignjustify: [ 14064 {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'justify'}, defaultBlock: 'div'} 14065 ], 14066 14067 bold: [ 14068 {inline: 'strong', remove: 'all'}, 14069 {inline: 'span', styles: {fontWeight: 'bold'}}, 14070 {inline: 'b', remove: 'all'} 14071 ], 14072 14073 italic: [ 14074 {inline: 'em', remove: 'all'}, 14075 {inline: 'span', styles: {fontStyle: 'italic'}}, 14076 {inline: 'i', remove: 'all'} 14077 ], 14078 14079 underline: [ 14080 {inline: 'span', styles: {textDecoration: 'underline'}, exact: true}, 14081 {inline: 'u', remove: 'all'} 14082 ], 14083 14084 strikethrough: [ 14085 {inline: 'span', styles: {textDecoration: 'line-through'}, exact: true}, 14086 {inline: 'strike', remove: 'all'} 14087 ], 14088 14089 forecolor: {inline: 'span', styles: {color: '%value'}, links: true, remove_similar: true}, 14090 hilitecolor: {inline: 'span', styles: {backgroundColor: '%value'}, links: true, remove_similar: true}, 14091 fontname: {inline: 'span', styles: {fontFamily: '%value'}}, 14092 fontsize: {inline: 'span', styles: {fontSize: '%value'}}, 14093 fontsize_class: {inline: 'span', attributes: {'class': '%value'}}, 14094 blockquote: {block: 'blockquote', wrapper: 1, remove: 'all'}, 14095 tabledataresponsive: {block: 'div', wrapper: 1, classes: 'tabledataresponsive'}, 14096 subscript: {inline: 'sub'}, 14097 superscript: {inline: 'sup'}, 14098 code: {inline: 'code'}, 14099 14100 link: {inline: 'a', selector: 'a', remove: 'all', split: true, deep: true, 14101 onmatch: function() { 14102 return true; 14103 }, 14104 14105 onformat: function(elm, fmt, vars) { 14106 each(vars, function(value, key) { 14107 dom.setAttrib(elm, key, value); 14108 }); 14109 } 14110 }, 14111 14112 removeformat: [ 14113 { 14114 selector: 'b,strong,em,i,font,u,strike,sub,sup,dfn,code,samp,kbd,var,cite,mark,q', 14115 remove: 'all', 14116 split: true, 14117 expand: false, 14118 block_expand: true, 14119 deep: true 14120 }, 14121 {selector: 'span', attributes: ['style', 'class'], remove: 'empty', split: true, expand: false, deep: true}, 14122 {selector: '*', attributes: ['style', 'class'], split: false, expand: false, deep: true} 14123 ] 14124 }); 14125 14126 // Register default block formats 14127 each('p h1 h2 h3 h4 h5 h6 div address pre div dt dd samp'.split(/\s/), function(name) { 14128 register(name, {block: name, remove: 'all'}); 14129 }); 14130 14131 // Register user defined formats 14132 register(ed.settings.formats); 14133 } 14134 14135 function addKeyboardShortcuts() { 14136 // Add some inline shortcuts 14137 ed.addShortcut('ctrl+b', 'bold_desc', 'Bold'); 14138 ed.addShortcut('ctrl+i', 'italic_desc', 'Italic'); 14139 ed.addShortcut('ctrl+u', 'underline_desc', 'Underline'); 14140 14141 // BlockFormat shortcuts keys 14142 for (var i = 1; i <= 6; i++) { 14143 ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]); 14144 } 14145 14146 ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']); 14147 ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']); 14148 ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']); 14149 } 14150 14151 // Public functions 14152 14153 /** 14154 * Returns the format by name or all formats if no name is specified. 14155 * 14156 * @method get 14157 * @param {String} name Optional name to retrive by. 14158 * @return {Array/Object} Array/Object with all registred formats or a specific format. 14159 */ 14160 function get(name) { 14161 return name ? formats[name] : formats; 14162 } 14163 14164 /** 14165 * Registers a specific format by name. 14166 * 14167 * @method register 14168 * @param {Object/String} name Name of the format for example "bold". 14169 * @param {Object/Array} format Optional format object or array of format variants 14170 * can only be omitted if the first arg is an object. 14171 */ 14172 function register(name, format) { 14173 if (name) { 14174 if (typeof(name) !== 'string') { 14175 each(name, function(format, name) { 14176 register(name, format); 14177 }); 14178 } else {
14179 // Force format into array and add it to internal collection 14180 format = format.length ? format : [format]; 14181 14182 each(format, function(format) { 14183 // Set deep to false by default on selector formats this to avoid removing 14184 // alignment on images inside paragraphs when alignment is changed on paragraphs 14185 if (format.deep === undef) { 14186 format.deep = !format.selector; 14187 } 14188 14189 // Default to true 14190 if (format.split === undef) { 14191 format.split = !format.selector || format.inline; 14192 } 14193 14194 // Default to true 14195 if (format.remove === undef && format.selector && !format.inline) { 14196 format.remove = 'none'; 14197 } 14198 14199 // Mark format as a mixed format inline + block level 14200 if (format.selector && format.inline) { 14201 format.mixed = true; 14202 format.block_expand = true; 14203 } 14204 14205 // Split classes if needed 14206 if (typeof(format.classes) === 'string') { 14207 format.classes = format.classes.split(/\s+/); 14208 } 14209 }); 14210 14211 formats[name] = format; 14212 } 14213 } 14214 } 14215 14216 /** 14217 * Unregister a specific format by name. 14218 * 14219 * @method unregister 14220 * @param {String} name Name of the format for example "bold". 14221 */ 14222 function unregister(name) { 14223 if (name && formats[name]) { 14224 delete formats[name]; 14225 } 14226 14227 return formats; 14228 } 14229 14230 function getTextDecoration(node) { 14231 var decoration; 14232 14233 ed.dom.getParent(node, function(n) { 14234 decoration = ed.dom.getStyle(n, 'text-decoration'); 14235 return decoration && decoration !== 'none'; 14236 }); 14237 14238 return decoration; 14239 } 14240 14241 function processUnderlineAndColor(node) { 14242 var textDecoration; 14243 if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) { 14244 textDecoration = getTextDecoration(node.parentNode); 14245 if (ed.dom.getStyle(node, 'color') && textDecoration) { 14246 ed.dom.setStyle(node, 'text-decoration', textDecoration); 14247 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) { 14248 ed.dom.setStyle(node, 'text-decoration', null); 14249 } 14250 } 14251 } 14252 14253 /** 14254 * Applies the specified format to the current selection or specified node. 14255 * 14256 * @method apply 14257 * @param {String} name Name of format to apply. 14258 * @param {Object} vars Optional list of variables to replace within format before applying it. 14259 * @param {Node} node Optional node to apply the format to defaults to current selection. 14260 */ 14261 function apply(name, vars, node) { 14262 var formatList = get(name), format = formatList[0], bookmark, rng, isCollapsed = !node && selection.isCollapsed(); 14263 14264 function setElementFormat(elm, fmt) { 14265 fmt = fmt || format; 14266 14267 if (elm) { 14268 if (fmt.onformat) { 14269 fmt.onformat(elm, fmt, vars, node); 14270 } 14271 14272 each(fmt.styles, function(value, name) { 14273 dom.setStyle(elm, name, replaceVars(value, vars)); 14274 }); 14275 14276 // Needed for the WebKit span spam bug 14277 // TODO: Remove this once WebKit/Blink fixes this 14278 if (fmt.styles) { 14279 var styleVal = dom.getAttrib(elm, 'style'); 14280 14281 if (styleVal) { 14282 elm.setAttribute('data-mce-style', styleVal); 14283 } 14284 } 14285 14286 each(fmt.attributes, function(value, name) { 14287 dom.setAttrib(elm, name, replaceVars(value, vars)); 14288 }); 14289 14290 each(fmt.classes, function(value) { 14291 value = replaceVars(value, vars); 14292 14293 if (!dom.hasClass(elm, value)) { 14294 dom.addClass(elm, value); 14295 } 14296 }); 14297 } 14298 } 14299 14300 function adjustSelectionToVisibleSelection() { 14301 function findSelectionEnd(start, end) { 14302 var walker = new TreeWalker(end); 14303 for (node = walker.current(); node; node = walker.prev()) { 14304 if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') { 14305 return node; 14306 } 14307 } 14308 } 14309 14310 // Adjust selection so that a end container with a end offset of zero is not included in the selection 14311 // as this isn't visible to the user. 14312 var rng = ed.selection.getRng(); 14313 var start = rng.startContainer; 14314 var end = rng.endContainer; 14315 14316 if (start != end && rng.endOffset === 0) { 14317 var newEnd = findSelectionEnd(start, end); 14318 var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length; 14319 14320 rng.setEnd(newEnd, endOffset); 14321 } 14322 14323 return rng; 14324 } 14325 14326 function applyRngStyle(rng, bookmark, node_specific) { 14327 var newWrappers = [], wrapName, wrapElm, contentEditable = true; 14328 14329 // Setup wrapper element 14330 wrapName = format.inline || format.block; 14331 wrapElm = dom.create(wrapName); 14332 setElementFormat(wrapElm); 14333 14334 rangeUtils.walk(rng, function(nodes) { 14335 var currentWrapElm; 14336 14337 /** 14338 * Process a list of nodes wrap them. 14339 */ 14340 function process(node) { 14341 var nodeName, parentName, found, hasContentEditableState, lastContentEditable; 14342 14343 lastContentEditable = contentEditable; 14344 nodeName = node.nodeName.toLowerCase(); 14345 parentName = node.parentNode.nodeName.toLowerCase(); 14346 14347 // Node has a contentEditable value 14348 if (node.nodeType === 1 && getContentEditable(node)) { 14349 lastContentEditable = contentEditable; 14350 contentEditable = getContentEditable(node) === "true"; 14351 hasContentEditableState = true; // We don't want to wrap the container only it's children 14352 } 14353 14354 // Stop wrapping on br elements 14355 if (isEq(nodeName, 'br')) { 14356 currentWrapElm = 0; 14357 14358 // Remove any br elements when we wrap things 14359 if (format.block) { 14360 dom.remove(node); 14361 } 14362 14363 return; 14364 } 14365 14366 // If node is wrapper type 14367 if (format.wrapper && matchNode(node, name, vars)) { 14368 currentWrapElm = 0; 14369 return; 14370 } 14371 14372 // Can we rename the block 14373 // TODO: Break this if up, too complex 14374 if (contentEditable && !hasContentEditableState && format.block && 14375 !format.wrapper && isTextBlock(nodeName) && isValid(parentName, wrapName)) { 14376 node = dom.rename(node, wrapName); 14377 setElementFormat(node); 14378 newWrappers.push(node); 14379 currentWrapElm = 0; 14380 return; 14381 } 14382 14383 // Handle selector patterns 14384 if (format.selector) { 14385 // Look for matching formats 14386 each(formatList, function(format) {
14387 // Check collapsed state if it exists 14388 if ('collapsed' in format && format.collapsed !== isCollapsed) { 14389 return; 14390 } 14391 14392 if (dom.is(node, format.selector) && !isCaretNode(node)) { 14393 setElementFormat(node, format); 14394 found = true; 14395 } 14396 }); 14397 14398 // Continue processing if a selector match wasn't found and a inline element is defined 14399 if (!format.inline || found) { 14400 currentWrapElm = 0; 14401 return; 14402 } 14403 } 14404 14405 // Is it valid to wrap this item 14406 // TODO: Break this if up, too complex 14407 if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) && 14408 !(!node_specific && node.nodeType === 3 && 14409 node.nodeValue.length === 1 && 14410 node.nodeValue.charCodeAt(0) === 65279) && 14411 !isCaretNode(node) && 14412 (!format.inline || !isBlock(node))) { 14413 // Start wrapping 14414 if (!currentWrapElm) { 14415 // Wrap the node 14416 currentWrapElm = dom.clone(wrapElm, FALSE); 14417 node.parentNode.insertBefore(currentWrapElm, node); 14418 newWrappers.push(currentWrapElm); 14419 } 14420 14421 currentWrapElm.appendChild(node); 14422 } else { 14423 // Start a new wrapper for possible children 14424 currentWrapElm = 0; 14425 14426 each(grep(node.childNodes), process); 14427 14428 if (hasContentEditableState) { 14429 contentEditable = lastContentEditable; // Restore last contentEditable state from stack 14430 } 14431 14432 // End the last wrapper 14433 currentWrapElm = 0; 14434 } 14435 } 14436 14437 // Process siblings from range 14438 each(nodes, process); 14439 }); 14440 14441 // Apply formats to links as well to get the color of the underline to change as well 14442 if (format.links === true) { 14443 each(newWrappers, function(node) { 14444 function process(node) { 14445 if (node.nodeName === 'A') { 14446 setElementFormat(node, format); 14447 } 14448 14449 each(grep(node.childNodes), process); 14450 } 14451 14452 process(node); 14453 }); 14454 } 14455 14456 // Cleanup 14457 each(newWrappers, function(node) { 14458 var childCount; 14459 14460 function getChildCount(node) { 14461 var count = 0; 14462 14463 each(node.childNodes, function(node) { 14464 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) { 14465 count++; 14466 } 14467 }); 14468 14469 return count; 14470 } 14471 14472 function mergeStyles(node) { 14473 var child, clone; 14474 14475 each(node.childNodes, function(node) { 14476 if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) { 14477 child = node; 14478 return FALSE; // break loop 14479 } 14480 }); 14481 14482 // If child was found and of the same type as the current node 14483 if (child && !isBookmarkNode(child) && matchName(child, format)) { 14484 clone = dom.clone(child, FALSE); 14485 setElementFormat(clone); 14486 14487 dom.replace(clone, node, TRUE); 14488 dom.remove(child, 1); 14489 } 14490 14491 return clone || node; 14492 } 14493 14494 childCount = getChildCount(node); 14495 14496 // Remove empty nodes but only if there is multiple wrappers and they are not block 14497 // elements so never remove single <h1></h1> since that would remove the 14498 // currrent empty block element where the caret is at 14499 if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) { 14500 dom.remove(node, 1); 14501 return; 14502 } 14503 14504 if (format.inline || format.wrapper) { 14505 // Merges the current node with it's children of similar type to reduce the number of elements 14506 if (!format.exact && childCount === 1) { 14507 node = mergeStyles(node); 14508 } 14509 14510 // Remove/merge children 14511 each(formatList, function(format) { 14512 // Merge all children of similar type will move styles from child to parent 14513 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span> 14514 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span> 14515 each(dom.select(format.inline, node), function(child) { 14516 if (isBookmarkNode(child)) { 14517 return; 14518 } 14519 14520 removeFormat(format, vars, child, format.exact ? child : null); 14521 }); 14522 }); 14523 14524 // Remove child if direct parent is of same type 14525 if (matchNode(node.parentNode, name, vars)) { 14526 dom.remove(node, 1); 14527 node = 0; 14528 return TRUE; 14529 } 14530 14531 // Look for parent with similar style format 14532 if (format.merge_with_parents) { 14533 dom.getParent(node.parentNode, function(parent) { 14534 if (matchNode(parent, name, vars)) { 14535 dom.remove(node, 1); 14536 node = 0; 14537 return TRUE; 14538 } 14539 }); 14540 } 14541 14542 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b> 14543 if (node && format.merge_siblings !== false) { 14544 node = mergeSiblings(getNonWhiteSpaceSibling(node), node); 14545 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE)); 14546 } 14547 } 14548 }); 14549 } 14550 14551 if (format) { 14552 if (node) { 14553 if (node.nodeType) { 14554 rng = dom.createRng(); 14555 rng.setStartBefore(node); 14556 rng.setEndAfter(node); 14557 applyRngStyle(expandRng(rng, formatList), null, true); 14558 } else { 14559 applyRngStyle(node, null, true); 14560 } 14561 } else {
14562 if (!isCollapsed || !format.inline || dom.select('td.mce-item-selected,th.mce-item-selected').length) { 14563 // Obtain selection node before selection is unselected by applyRngStyle() 14564 var curSelNode = ed.selection.getNode(); 14565 14566 // If the formats have a default block and we can't find a parent block then 14567 // start wrapping it with a DIV this is for forced_root_blocks: false 14568 // It's kind of a hack but people should be using the default block type P since all desktop editors work that way 14569 if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) { 14570 apply(formatList[0].defaultBlock); 14571 } 14572 14573 // Apply formatting to selection 14574 ed.selection.setRng(adjustSelectionToVisibleSelection()); 14575 bookmark = selection.getBookmark(); 14576 applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark); 14577 14578 // Colored nodes should be underlined so that the color of the underline matches the text color. 14579 if (format.styles && (format.styles.color || format.styles.textDecoration)) { 14580 walk(curSelNode, processUnderlineAndColor, 'childNodes'); 14581 processUnderlineAndColor(curSelNode); 14582 } 14583 14584 selection.moveToBookmark(bookmark); 14585 moveStart(selection.getRng(TRUE)); 14586 ed.nodeChanged(); 14587 } else { 14588 performCaretAction('apply', name, vars); 14589 } 14590 } 14591 } 14592 } 14593 14594 /** 14595 * Removes the specified format from the current selection or specified node. 14596 * 14597 * @method remove 14598 * @param {String} name Name of format to remove. 14599 * @param {Object} vars Optional list of variables to replace within format before removing it. 14600 * @param {Node/Range} node Optional node or DOM range to remove the format from defaults to current selection. 14601 */ 14602 function remove(name, vars, node, similar) { 14603 var formatList = get(name), format = formatList[0], bookmark, rng, contentEditable = true; 14604 14605 // Merges the styles for each node 14606 function process(node) { 14607 var children, i, l, lastContentEditable, hasContentEditableState; 14608 14609 // Node has a contentEditable value 14610 if (node.nodeType === 1 && getContentEditable(node)) { 14611 lastContentEditable = contentEditable; 14612 contentEditable = getContentEditable(node) === "true"; 14613 hasContentEditableState = true; // We don't want to wrap the container only it's children 14614 } 14615 14616 // Grab the children first since the nodelist might be changed 14617 children = grep(node.childNodes); 14618 14619 // Process current node 14620 if (contentEditable && !hasContentEditableState) { 14621 for (i = 0, l = formatList.length; i < l; i++) { 14622 if (removeFormat(formatList[i], vars, node, node)) { 14623 break; 14624 } 14625 } 14626 } 14627 14628 // Process the children 14629 if (format.deep) { 14630 if (children.length) { 14631 for (i = 0, l = children.length; i < l; i++) { 14632 process(children[i]); 14633 } 14634 14635 if (hasContentEditableState) { 14636 contentEditable = lastContentEditable; // Restore last contentEditable state from stack 14637 } 14638 } 14639 } 14640 } 14641 14642 function findFormatRoot(container) { 14643 var formatRoot; 14644 14645 // Find format root 14646 each(getParents(container.parentNode).reverse(), function(parent) { 14647 var format; 14648 14649 // Find format root element 14650 if (!formatRoot && parent.id != '_start' && parent.id != '_end') { 14651 // Is the node matching the format we are looking for 14652 format = matchNode(parent, name, vars, similar); 14653 if (format && format.split !== false) { 14654 formatRoot = parent; 14655 } 14656 } 14657 }); 14658 14659 return formatRoot; 14660 } 14661 14662 function wrapAndSplit(formatRoot, container, target, split) { 14663 var parent, clone, lastClone, firstClone, i, formatRootParent; 14664 14665 // Format root found then clone formats and split it 14666 if (formatRoot) { 14667 formatRootParent = formatRoot.parentNode; 14668 14669 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) { 14670 clone = dom.clone(parent, FALSE); 14671 14672 for (i = 0; i < formatList.length; i++) { 14673 if (removeFormat(formatList[i], vars, clone, clone)) { 14674 clone = 0; 14675 break; 14676 } 14677 } 14678 14679 // Build wrapper node 14680 if (clone) { 14681 if (lastClone) { 14682 clone.appendChild(lastClone); 14683 } 14684 14685 if (!firstClone) { 14686 firstClone = clone; 14687 } 14688 14689 lastClone = clone; 14690 } 14691 } 14692 14693 // Never split block elements if the format is mixed 14694 if (split && (!format.mixed || !isBlock(formatRoot))) { 14695 container = dom.split(formatRoot, container); 14696 } 14697 14698 // Wrap container in cloned formats 14699 if (lastClone) { 14700 target.parentNode.insertBefore(lastClone, target); 14701 firstClone.appendChild(target); 14702 } 14703 } 14704 14705 return container; 14706 } 14707 14708 function splitToFormatRoot(container) { 14709 return wrapAndSplit(findFormatRoot(container), container, container, true); 14710 } 14711 14712 function unwrap(start) { 14713 var node = dom.get(start ? '_start' : '_end'), 14714 out = node[start ? 'firstChild' : 'lastChild']; 14715 14716 // If the end is placed within the start the result will be removed 14717 // So this checks if the out node is a bookmark node if it is it 14718 // checks for another more suitable node 14719 if (isBookmarkNode(out)) { 14720 out = out[start ? 'firstChild' : 'lastChild']; 14721 } 14722 14723 dom.remove(node, true); 14724 14725 return out; 14726 } 14727 14728 function removeRngStyle(rng) { 14729 var startContainer, endContainer; 14730 var commonAncestorContainer = rng.commonAncestorContainer; 14731 14732 rng = expandRng(rng, formatList, TRUE); 14733 14734 if (format.split) { 14735 startContainer = getContainer(rng, TRUE); 14736 endContainer = getContainer(rng); 14737 14738 if (startContainer != endContainer) { 14739 // WebKit will render the table incorrectly if we wrap a TH or TD in a SPAN 14740 // so let's see if we can use the first child instead 14741 // This will happen if you triple click a table cell and use remove formatting 14742 if (/^(TR|TH|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) { 14743 if (startContainer.nodeName == "TR") { 14744 startContainer = startContainer.firstChild.firstChild || startContainer; 14745 } else { 14746 startContainer = startContainer.firstChild || startContainer; 14747 } 14748 } 14749 14750 // Try to adjust endContainer as well if cells on the same row were selected - bug #6410 14751 if (commonAncestorContainer && 14752 /^T(HEAD|BODY|FOOT|R)$/.test(commonAncestorContainer.nodeName) && 14753 /^(TH|TD)$/.test(endContainer.nodeName) && endContainer.firstChild) { 14754 endContainer = endContainer.firstChild || endContainer; 14755 } 14756 14757 // Wrap start/end nodes in span element since these might be cloned/moved 14758 startContainer = wrap(startContainer, 'span', {id: '_start', 'data-mce-type': 'bookmark'});
14759 endContainer = wrap(endContainer, 'span', {id: '_end', 'data-mce-type': 'bookmark'}); 14760 14761 // Split start/end 14762 splitToFormatRoot(startContainer); 14763 splitToFormatRoot(endContainer); 14764 14765 // Unwrap start/end to get real elements again 14766 startContainer = unwrap(TRUE); 14767 endContainer = unwrap(); 14768 } else { 14769 startContainer = endContainer = splitToFormatRoot(startContainer); 14770 } 14771 14772 // Update range positions since they might have changed after the split operations 14773 rng.startContainer = startContainer.parentNode; 14774 rng.startOffset = nodeIndex(startContainer); 14775 rng.endContainer = endContainer.parentNode; 14776 rng.endOffset = nodeIndex(endContainer) + 1; 14777 } 14778 14779 // Remove items between start/end 14780 rangeUtils.walk(rng, function(nodes) { 14781 each(nodes, function(node) { 14782 process(node); 14783 14784 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined. 14785 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && 14786 node.parentNode && getTextDecoration(node.parentNode) === 'underline') { 14787 removeFormat({ 14788 'deep': false, 14789 'exact': true, 14790 'inline': 'span', 14791 'styles': { 14792 'textDecoration': 'underline' 14793 } 14794 }, null, node); 14795 } 14796 }); 14797 }); 14798 } 14799 14800 // Handle node 14801 if (node) { 14802 if (node.nodeType) { 14803 rng = dom.createRng(); 14804 rng.setStartBefore(node); 14805 rng.setEndAfter(node); 14806 removeRngStyle(rng); 14807 } else { 14808 removeRngStyle(node); 14809 } 14810 14811 return; 14812 } 14813 14814 if (!selection.isCollapsed() || !format.inline || dom.select('td.mce
14814-item-selected,th.mce-item-selected').length) { 14815 bookmark = selection.getBookmark(); 14816 removeRngStyle(selection.getRng(TRUE)); 14817 selection.moveToBookmark(bookmark); 14818 14819 // Check if start element still has formatting then we are at: "<b>text|</b>text" 14820 // and need to move the start into the next text node 14821 if (format.inline && match(name, vars, selection.getStart())) { 14822 moveStart(selection.getRng(true)); 14823 } 14824 14825 ed.nodeChanged(); 14826 } else { 14827 performCaretAction('remove', name, vars, similar); 14828 } 14829 } 14830 14831 /** 14832 * Toggles the specified format on/off. 14833 * 14834 * @method toggle 14835 * @param {String} name Name of format to apply/remove. 14836 * @param {Object} vars Optional list of variables to replace within format before applying/removing it. 14837 * @param {Node} node Optional node to apply the format to or remove from. Defaults to current selection. 14838 */ 14839 function toggle(name, vars, node) { 14840 var fmt = get(name); 14841 14842 if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) { 14843 remove(name, vars, node); 14844 } else { 14845 apply(name, vars, node); 14846 } 14847 } 14848 14849 /** 14850 * Return true/false if the specified node has the specified format. 14851 * 14852 * @method matchNode 14853 * @param {Node} node Node to check the format on. 14854 * @param {String} name Format name to check. 14855 * @param {Object} vars Optional list of variables to replace before checking it. 14856 * @param {Boolean} similar Match format that has similar properties. 14857 * @return {Object} Returns the format object it matches or undefined if it doesn't match. 14858 */ 14859 function matchNode(node, name, vars, similar) { 14860 var formatList = get(name), format, i, classes; 14861 14862 function matchItems(node, format, item_name) { 14863 var key, value, items = format[item_name], i; 14864 14865 // Custom match 14866 if (format.onmatch) { 14867 return format.onmatch(node, format, item_name); 14868 } 14869 14870 // Check all items 14871 if (items) { 14872 // Non indexed object 14873 if (items.length === undef) { 14874 for (key in items) { 14875 if (items.hasOwnProperty(key)) { 14876 if (item_name === 'attributes') { 14877 value = dom.getAttrib(node, key); 14878 } else { 14879 value = getStyle(node, key); 14880 } 14881 14882 if (similar && !value && !format.exact) { 14883 return; 14884 } 14885 14886 if ((!similar || format.exact) && !isEq(value, normalizeStyleValue(replaceVars(items[key], vars), key))) { 14887 return; 14888 } 14889 } 14890 } 14891 } else { 14892 // Only one match needed for indexed arrays 14893 for (i = 0; i < items.length; i++) { 14894 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) { 14895 return format; 14896 } 14897 } 14898 } 14899 } 14900 14901 return format; 14902 } 14903 14904 if (formatList && node) { 14905 // Check each format in list 14906 for (i = 0; i < formatList.length; i++) { 14907 format = formatList[i]; 14908 14909 // Name name, attributes, styles and classes 14910 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) { 14911 // Match classes 14912 if ((classes = format.classes)) { 14913 for (i = 0; i < classes.length; i++) { 14914 if (!dom.hasClass(node, classes[i])) { 14915 return; 14916 } 14917 } 14918 } 14919 14920 return format; 14921 } 14922 } 14923 } 14924 } 14925 14926 /** 14927 * Matches the current selection or specified node against the specified format name. 14928 * 14929 * @method match 14930 * @param {String} name Name of format to match. 14931 * @param {Object} vars Optional list of variables to replace before checking it. 14932 * @param {Node} node Optional node to check. 14933 * @return {boolean} true/false if the specified selection/node matches the format. 14934 */ 14935 function match(name, vars, node) { 14936 var startNode; 14937 14938 function matchParents(node) { 14939 var root = dom.getRoot(); 14940 14941 if (node === root) { 14942 return false; 14943 } 14944 14945 // Find first node with similar format settings 14946 node = dom.getParent(node, function(node) { 14947 return node.parentNode === root || !!matchNode(node, name, vars, true); 14948 }); 14949 14950 // Do an exact check on the similar format element 14951 return matchNode(node, name, vars); 14952 } 14953
14954 // Check specified node 14955 if (node) { 14956 return matchParents(node); 14957 } 14958 14959 // Check selected node 14960 node = selection.getNode(); 14961 if (matchParents(node)) { 14962 return TRUE; 14963 } 14964 14965 // Check start node if it's different 14966 startNode = selection.getStart(); 14967 if (startNode != node) { 14968 if (matchParents(startNode)) { 14969 return TRUE; 14970 } 14971 } 14972 14973 return FALSE; 14974 } 14975 14976 /** 14977 * Matches the current selection against the array of formats and returns a new array with matching formats. 14978 * 14979 * @method matchAll 14980 * @param {Array} names Name of format to match. 14981 * @param {Object} vars Optional list of variables to replace before checking it. 14982 * @return {Array} Array with matched formats. 14983 */ 14984 function matchAll(names, vars) { 14985 var startElement, matchedFormatNames = [], checkedMap = {}; 14986 14987 // Check start of selection for formats 14988 startElement = selection.getStart(); 14989 dom.getParent(startElement, function(node) { 14990 var i, name; 14991 14992 for (i = 0; i < names.length; i++) { 14993 name = names[i]; 14994 14995 if (!checkedMap[name] && matchNode(node, name, vars)) { 14996 checkedMap[name] = true; 14997 matchedFormatNames.push(name); 14998 } 14999 } 15000 }, dom.getRoot()); 15001 15002 return matchedFormatNames; 15003 } 15004 15005 /** 15006 * Returns true/false if the specified format can be applied to the current selection or not. It 15007 * will currently only check the state for selector formats, it returns true on all other format types. 15008 * 15009 * @method canApply 15010 * @param {String} name Name of format to check. 15011 * @return {boolean} true/false if the specified format can be applied to the current selection/node. 15012 */ 15013 function canApply(name) { 15014 var formatList = get(name), startNode, parents, i, x, selector; 15015 15016 if (formatList) { 15017 startNode = selection.getStart(); 15018 parents = getParents(startNode); 15019 15020 for (x = formatList.length - 1; x >= 0; x--) { 15021 selector = formatList[x].selector; 15022 15023 // Format is not selector based then always return TRUE 15024 // Is it has a defaultBlock then it's likely it can be applied for example align on a non block element line 15025 if (!selector || formatList[x].defaultBlock) { 15026 return TRUE; 15027 } 15028 15029 for (i = parents.length - 1; i >= 0; i--) { 15030 if (dom.is(parents[i], selector)) { 15031 return TRUE; 15032 } 15033 } 15034 } 15035 } 15036 15037 return FALSE; 15038 } 15039 15040 /** 15041 * Executes the specified callback when the current selection matches the formats or not. 15042 * 15043 * @method formatChanged 15044 * @param {String} formats Comma separated list of formats to check for. 15045 * @param {function} callback Callback with state and args when the format is changed/toggled on/off. 15046 * @param {Boolean} similar True/false state if the match should handle similar or exact formats. 15047 */ 15048 function formatChanged(formats, callback, similar) { 15049 var currentFormats; 15050 15051 // Setup format node change logic 15052 if (!formatChangeData) { 15053 formatChangeData = {}; 15054 currentFormats = {}; 15055 15056 ed.on('NodeChange', function(e) { 15057 var parents = getParents(e.element), matchedFormats = {}; 15058 15059 // Ignore bogus nodes like the <a> tag created by moveStart() 15060 parents = Tools.grep(parents, function(node) { 15061 return node.nodeType == 1 && !node.getAttribute('data-mce-bogus'); 15062 }); 15063 15064 // Check for new formats 15065 each(formatChangeData, function(callbacks, format) { 15066 each(parents, function(node) { 15067 if (matchNode(node, format, {}, callbacks.similar)) { 15068 if (!currentFormats[format]) { 15069 // Execute callbacks 15070 each(callbacks, function(callback) { 15071 callback(true, {node: node, format: format, parents: parents}); 15072 }); 15073 15074 currentFormats[format] = callbacks; 15075 } 15076 15077 matchedFormats[format] = callbacks; 15078 return false; 15079 } 15080 }); 15081 }); 15082 15083 // Check if current formats still match 15084 each(currentFormats, function(callbacks, format) { 15085 if (!matchedFormats[format]) { 15086 delete currentFormats[format]; 15087 15088 each(callbacks, function(callback) { 15089 callback(false, {node: e.element, format: format, parents: parents}); 15090 }); 15091 } 15092 }); 15093 }); 15094 } 15095 15096 // Add format listeners 15097 each(formats.split(','), function(format) { 15098 if (!formatChangeData[format]) { 15099 formatChangeData[format] = []; 15100 formatChangeData[format].similar = similar; 15101 } 15102 15103 formatChangeData[format].push(callback); 15104 }); 15105 15106 return this; 15107 } 15108 15109 /** 15110 * Returns a preview css text for the specified format. 15111 * 15112 * @method getCssText 15113 * @param {String/Object} format Format to generate preview css text for. 15114 * @return {String} Css text for the specified format. 15115 * @example 15116 * var cssText1 = editor.formatter.getCssText('bold'); 15117 * var cssText2 = editor.formatter.getCssText({inline: 'b'}); 15118 */ 15119 function getCssText(format) { 15120 return Preview.getCssText(ed, format); 15121 } 15122 15123 // Expose to public 15124 extend(this, { 15125 get: get, 15126 register: register, 15127 unregister: unregister, 15128 apply: apply, 15129 remove: remove, 15130 toggle: toggle, 15131 match: match, 15132 matchAll: matchAll, 15133 matchNode: matchNode, 15134 canApply: canApply, 15135 formatChanged: formatChanged, 15136 getCssText: getCssText 15137 }); 15138 15139 // Initialize 15140 defaultFormats(); 15141 addKeyboardShortcuts(); 15142 ed.on('BeforeGetContent', function(e) { 15143 if (markCaretContainersBogus && e.format != 'raw') { 15144 markCaretContainersBogus(); 15145 } 15146 }); 15147 ed.on('mouseup keydown', function(e) { 15148 if (disableCaretContainer) { 15149 disableCaretContainer(e); 15150 } 15151 }); 15152 15153 // Private functions 15154 15155 /** 15156 * Checks if the specified nodes name matches the format inline/block or selector. 15157 * 15158 * @private 15159 * @param {Node} node Node to match against the specified format. 15160 * @param {Object} format Format object o match with. 15161 * @return {boolean} true/false if the format matches. 15162 */ 15163 function matchName(node, format) { 15164 // Check for inline match 15165 if (isEq(node, format.inline)) { 15166 return TRUE; 15167 } 15168 15169 // Check for block match 15170 if (isEq(node, format.block)) { 15171 return TRUE; 15172 } 15173 15174 // Check for selector match 15175 if (format.selector) { 15176 return node.nodeType == 1 && dom.is(node, format.selector); 15177 } 15178 } 15179 15180 /** 15181 * Compares two string/nodes regardless of their case. 15182 * 15183 * @private 15184 * @param {String/Node} Node or string to compare. 15185 * @param {String/Node} Node or string to compare. 15186 * @return {boolean} True/false if they match. 15187 */ 15188 function isEq(str1, str2) { 15189 str1 = str1 || ''; 15190 str2 = str2 || ''; 15191 15192 str1 = '' + (str1.nodeName || str1); 15193 str2 = '' + (str2.nodeName || str2); 15194 15195 return str1.toLowerCase() == str2.toLowerCase(); 15196 } 15197 15198 /** 15199 * Returns the style by name on the specified node. This method modifies the style 15200 * contents to make it more easy to match. This will resolve a few browser issues. 15201 * 15202 * @private 15203 * @param {Node} node to get style from. 15204 * @param {String} name Style name to get. 15205 * @return {String} Style item value. 15206 */ 15207 function getStyle(node, name) { 15208 return normalizeStyleValue(dom.getStyle(node, name), name); 15209 } 15210 15211 /** 15212 * Normalize style value by name. This method modifies the style contents 15213 * to make it more easy to match. This will resolve a few browser issues. 15214 * 15215 * @private 15216 * @param {Node} node to get style from. 15217 * @param {String} name Style name to get. 15218 * @return {String} Style item value. 15219 */ 15220 function normalizeStyleValue(value, name) { 15221 // Force the format to hex 15222 if (name == 'color' || name == 'backgroundColor') { 15223 value = dom.toHex(value); 15224 } 15225 15226 // Opera will return bold as 700 15227 if (name == 'fontWeight' && value == 700) { 15228 value = 'bold'; 15229 } 15230
15231 // Normalize fontFamily so "'Font name', Font" becomes: "Font name,Font" 15232 if (name == 'fontFamily') { 15233 value = value.replace(/[\'\"]/g, '').replace(/,\s+/g, ','); 15234 } 15235 15236 return '' + value; 15237 } 15238 15239 /** 15240 * Replaces variables in the value. The variable format is %var. 15241 * 15242 * @private 15243 * @param {String} value Value to replace variables in. 15244 * @param {Object} vars Name/value array with variables to replace. 15245 * @return {String} New value with replaced variables. 15246 */ 15247 function replaceVars(value, vars) { 15248 if (typeof(value) != "string") { 15249 value = value(vars); 15250 } else if (vars) { 15251 value = value.replace(/%(\w+)/g, function(str, name) { 15252 return vars[name] || str; 15253 }); 15254 } 15255 15256 return value; 15257 } 15258 15259 function isWhiteSpaceNode(node) { 15260 return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue); 15261 } 15262 15263 function wrap(node, name, attrs) { 15264 var wrapper = dom.create(name, attrs); 15265 15266 node.parentNode.insertBefore(wrapper, node); 15267 wrapper.appendChild(node); 15268 15269 return wrapper; 15270 } 15271 15272 /** 15273 * Expands the specified range like object to depending on format. 15274 * 15275 * For example on block formats it will move the start/end position 15276 * to the beginning of the current block. 15277 * 15278 * @private 15279 * @param {Object} rng Range like object. 15280 * @param {Array} formats Array with formats to expand by. 15281 * @return {Object} Expanded range like object. 15282 */ 15283 function expandRng(rng, format, remove) { 15284 var lastIdx, leaf, endPoint, 15285 startContainer = rng.startContainer, 15286 startOffset = rng.startOffset, 15287 endContainer = rng.endContainer, 15288 endOffset = rng.endOffset; 15289 15290 // This function walks up the tree if there is no siblings before/after the node 15291 function findParentContainer(start) { 15292 var container, parent, sibling, siblingName, root; 15293 15294 container = parent = start ? startContainer : endContainer; 15295 siblingName = start ? 'previousSibling' : 'nextSibling'; 15296 root = dom.getRoot(); 15297 15298 function isBogusBr(node) { 15299 return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling; 15300 } 15301 15302 // If it's a text node and the offset is inside the text 15303 if (container.nodeType == 3 && !isWhiteSpaceNode(container)) { 15304 if (start ? startOffset > 0 : endOffset < container.nodeValue.length) { 15305 return container; 15306 } 15307 } 15308 15309 /*eslint no-constant-condition:0 */ 15310 while (true) { 15311 // Stop expanding on block elements 15312 if (!format[0].block_expand && isBlock(parent)) { 15313 return parent; 15314 } 15315 15316 // Walk left/right 15317 for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) { 15318 if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) { 15319 return parent; 15320 } 15321 } 15322 15323 // Check if we can move up are we at root level or body level 15324 if (parent.parentNode == root) { 15325 container = parent; 15326 break; 15327 } 15328 15329 parent = parent.parentNode; 15330 } 15331 15332 return container; 15333 } 15334 15335 // This function walks down the tree to find the leaf at the selection. 15336 // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node. 15337 function findLeaf(node, offset) { 15338 if (offset === undef) { 15339 offset = node.nodeType === 3 ? node.length : node.childNodes.length; 15340 } 15341 15342 while (node && node.hasChildNodes()) { 15343 node = node.childNodes[offset]; 15344 if (node) { 15345 offset = node.nodeType === 3 ? node.length : node.childNodes.length; 15346 } 15347 } 15348 return {node: node, offset: offset}; 15349 } 15350 15351 // If index based start position then resolve it 15352 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { 15353 lastIdx = startContainer.childNodes.length - 1; 15354 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset]; 15355 15356 if (startContainer.nodeType == 3) { 15357 startOffset = 0; 15358 } 15359 } 15360 15361 // If index based end position then resolve it 15362 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { 15363 lastIdx = endContainer.childNodes.length - 1; 15364 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1]; 15365 15366 if (endContainer.nodeType == 3) { 15367 endOffset = endContainer.nodeValue.length; 15368 } 15369 } 15370 15371 // Expands the node to the closes contentEditable false element if it exists 15372 function findParentContentEditable(node) { 15373 var parent = node; 15374 15375 while (parent) { 15376 if (parent.nodeType === 1 && getContentEditable(parent)) {
15377 return getContentEditable(parent) === "false" ? parent : node; 15378 } 15379 15380 parent = parent.parentNode; 15381 } 15382 15383 return node; 15384 } 15385 15386 function findWordEndPoint(container, offset, start) { 15387 var walker, node, pos, lastTextNode; 15388 15389 function findSpace(node, offset) { 15390 var pos, pos2, str = node.nodeValue; 15391 15392 if (typeof(offset) == "undefined") { 15393 offset = start ? str.length : 0; 15394 } 15395 15396 if (start) { 15397 pos = str.lastIndexOf(' ', offset); 15398 pos2 = str.lastIndexOf('\u00a0', offset); 15399 pos = pos > pos2 ? pos : pos2; 15400 15401 // Include the space on remove to avoid tag soup 15402 if (pos !== -1 && !remove) { 15403 pos++; 15404 } 15405 } else { 15406 pos = str.indexOf(' ', offset); 15407 pos2 = str.indexOf('\u00a0', offset); 15408 pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2; 15409 } 15410 15411 return pos; 15412 } 15413 15414 if (container.nodeType === 3) { 15415 pos = findSpace(container, offset); 15416 15417 if (pos !== -1) { 15418 return {container: container, offset: pos}; 15419 } 15420 15421 lastTextNode = container; 15422 } 15423 15424 // Walk the nodes inside the block 15425 walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody()); 15426 while ((node = walker[start ? 'prev' : 'next']())) { 15427 if (node.nodeType === 3) { 15428 lastTextNode = node; 15429 pos = findSpace(node); 15430 15431 if (pos !== -1) { 15432 return {container: node, offset: pos}; 15433 } 15434 } else if (isBlock(node)) { 15435 break; 15436 } 15437 } 15438 15439 if (lastTextNode) { 15440 if (start) { 15441 offset = 0; 15442 } else { 15443 offset = lastTextNode.length; 15444 } 15445 15446 return {container: lastTextNode, offset: offset}; 15447 } 15448 } 15449 15450 function findSelectorEndPoint(container, sibling_name) { 15451 var parents, i, y, curFormat; 15452 15453 if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name]) { 15454 container = container[sibling_name]; 15455 } 15456 15457 parents = getParents(container); 15458 for (i = 0; i < parents.length; i++) { 15459 for (y = 0; y < format.length; y++) { 15460 curFormat = format[y]; 15461 15462 // If collapsed state is set then skip formats that doesn't match that 15463 if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) { 15464 continue; 15465 } 15466 15467 if (dom.is(parents[i], curFormat.selector)) { 15468 return parents[i]; 15469 } 15470 } 15471 } 15472 15473 return container; 15474 } 15475 15476 function findBlockEndPoint(container, sibling_name) { 15477 var node, root = dom.getRoot(); 15478 15479 // Expand to block of similar type 15480 if (!format[0].wrapper) { 15481 node = dom.getParent(container, format[0].block, root); 15482 } 15483 15484 // Expand to first wrappable block element or any block element 15485 if (!node) { 15486 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, function(node) { 15487 // Fixes #6183 where it would expand to editable parent element in inline mode 15488 return node != root && isTextBlock(node); 15489 }); 15490 } 15491 15492 // Exclude inner lists from wrapping 15493 if (node && format[0].wrapper) { 15494 node = getParents(node, 'ul,ol').reverse()[0] || node; 15495 } 15496 15497 // Didn't find a block element look for first/last wrappable element 15498 if (!node) { 15499 node = container; 15500 15501 while (node[sibling_name] && !isBlock(node[sibling_name])) { 15502 node = node[sibling_name]; 15503 15504 // Break on BR but include it will be removed later on 15505 // we can't remove it now since we need to check if it can be wrapped 15506 if (isEq(node, 'br')) { 15507 break; 15508 } 15509 } 15510 } 15511 15512 return node || container; 15513 } 15514 15515 // Expand to closest contentEditable element 15516 startContainer = findParentContentEditable(startContainer); 15517 endContainer = findParentContentEditable(endContainer); 15518 15519 // Exclude bookmark nodes if possible 15520 if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) { 15521 startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode; 15522 startContainer = startContainer.nextSibling || startContainer; 15523 15524 if (startContainer.nodeType == 3) { 15525 startOffset = 0; 15526 } 15527 } 15528 15529 if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) { 15530 endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode; 15531 endContainer = endContainer.previousSibling || endContainer; 15532 15533 if (endContainer.nodeType == 3) { 15534 endOffset = endContainer.length; 15535 } 15536 } 15537 15538 if (format[0].inline) { 15539 if (rng.collapsed) { 15540 // Expand left to closest word boundary 15541 endPoint = findWordEndPoint(startContainer, startOffset, true); 15542 if (endPoint) { 15543 startContainer = endPoint.container; 15544 startOffset = endPoint.offset; 15545 } 15546 15547 // Expand right to closest word boundary 15548 endPoint = findWordEndPoint(endContainer, endOffset); 15549 if (endPoint) { 15550 endContainer = endPoint.container; 15551 endOffset = endPoint.offset; 15552 } 15553 } 15554 15555 // Avoid applying formatting to a trailing space. 15556 leaf = findLeaf(endContainer, endOffset); 15557 if (leaf.node) { 15558 while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) { 15559 leaf = findLeaf(leaf.node.previousSibling); 15560 } 15561 15562 if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 && 15563 leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') { 15564 15565 if (leaf.offset > 1) { 15566 endContainer = leaf.node;
15567 endContainer.splitText(leaf.offset - 1); 15568 } 15569 } 15570 } 15571 } 15572 15573 // Move start/end point up the tree if the leaves are sharp and if we are in different containers 15574 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>! 15575 // This will reduce the number of wrapper elements that needs to be created 15576 // Move start point up the tree 15577 if (format[0].inline || format[0].block_expand) { 15578 if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) { 15579 startContainer = findParentContainer(true); 15580 } 15581 15582 if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) { 15583 endContainer = findParentContainer(); 15584 } 15585 } 15586 15587 // Expand start/end container to matching selector 15588 if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { 15589 // Find new startContainer/endContainer if there is better one 15590 startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); 15591 endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); 15592 } 15593 15594 // Expand start/end container to matching block element or text node 15595 if (format[0].block || format[0].selector) { 15596 // Find new startContainer/endContainer if there is better one 15597 startContainer = findBlockEndPoint(startContainer, 'previousSibling'); 15598 endContainer = findBlockEndPoint(endContainer, 'nextSibling'); 15599 15600 // Non block element then try to expand up the leaf 15601 if (format[0].block) { 15602 if (!isBlock(startContainer)) { 15603 startContainer = findParentContainer(true); 15604 } 15605 15606 if (!isBlock(endContainer)) { 15607 endContainer = findParentContainer(); 15608 } 15609 } 15610 } 15611 15612 // Setup index for startContainer 15613 if (startContainer.nodeType == 1) { 15614 startOffset = nodeIndex(startContainer); 15615 startContainer = startContainer.parentNode; 15616 } 15617 15618 // Setup index for endContainer 15619 if (endContainer.nodeType == 1) { 15620 endOffset = nodeIndex(endContainer) + 1; 15621 endContainer = endContainer.parentNode; 15622 } 15623 15624 // Return new range like object 15625 return { 15626 startContainer: startContainer, 15627 startOffset: startOffset, 15628 endContainer: endContainer, 15629 endOffset: endOffset 15630 }; 15631 } 15632 15633 function isColorFormatAndAnchor(node, format) { 15634 return format.links && node.tagName == 'A'; 15635 } 15636 15637 /** 15638 * Removes the specified format for the specified node. It will also remove the node if it doesn't have 15639 * any attributes if the format specifies it to do so. 15640 * 15641 * @private 15642 * @param {Object} format Format object with items to remove from node. 15643 * @param {Object} vars Name/value object with variables to apply to format. 15644 * @param {Node} node Node to remove the format styles on. 15645 * @param {Node} compare_node Optional compare node, if specified the styles will be compared to that node. 15646 * @return {Boolean} True/false if the node was removed or not. 15647 */ 15648 function removeFormat(format, vars, node, compare_node) { 15649 var i, attrs, stylesModified; 15650 15651 // Check if node matches format 15652 if (!matchName(node, format) && !isColorFormatAndAnchor(node, format)) { 15653 return FALSE; 15654 } 15655 15656 // Should we compare with format attribs and styles 15657 if (format.remove != 'all') { 15658 // Remove styles 15659 each(format.styles, function(value, name) { 15660 value = normalizeStyleValue(replaceVars(value, vars), name); 15661 15662 // Indexed array 15663 if (typeof(name) === 'number') { 15664 name = value; 15665 compare_node = 0; 15666 } 15667 15668 if (format.remove_similar || (!compare_node || isEq(getStyle(compare_node, name), value))) { 15669 dom.setStyle(node, name, ''); 15670 } 15671 15672 stylesModified = 1; 15673 }); 15674 15675 // Remove style attribute if it's empty 15676 if (stylesModified && dom.getAttrib(node, 'style') === '') { 15677 node.removeAttribute('style'); 15678 node.removeAttribute('data-mce-style'); 15679 } 15680 15681 // Remove attributes 15682 each(format.attributes, function(value, name) { 15683 var valueOut; 15684 15685 value = replaceVars(value, vars); 15686 15687 // Indexed array 15688 if (typeof(name) === 'number') { 15689 name = value; 15690 compare_node = 0; 15691 } 15692 15693 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
15694 // Keep internal classes 15695 if (name == 'class') { 15696 value = dom.getAttrib(node, name); 15697 if (value) { 15698 // Build new class value where everything is removed except the internal prefixed classes 15699 valueOut = ''; 15700 each(value.split(/\s+/), function(cls) { 15701 if (/mce\w+/.test(cls)) { 15702 valueOut += (valueOut ? ' ' : '') + cls; 15703 } 15704 }); 15705 15706 // We got some internal classes left 15707 if (valueOut) { 15708 dom.setAttrib(node, name, valueOut); 15709 return; 15710 } 15711 } 15712 } 15713 15714 // IE6 has a bug where the attribute doesn't get removed correctly 15715 if (name == "class") { 15716 node.removeAttribute('className'); 15717 } 15718 15719 // Remove mce prefixed attributes 15720 if (MCE_ATTR_RE.test(name)) { 15721 node.removeAttribute('data-mce-' + name); 15722 } 15723 15724 node.removeAttribute(name); 15725 } 15726 }); 15727 15728 // Remove classes 15729 each(format.classes, function(value) { 15730 value = replaceVars(value, vars); 15731 15732 if (!compare_node || dom.hasClass(compare_node, value)) { 15733 dom.removeClass(node, value); 15734 } 15735 }); 15736 15737 // Check for non internal attributes 15738 attrs = dom.getAttribs(node); 15739 for (i = 0; i < attrs.length; i++) { 15740 if (attrs[i].nodeName.indexOf('_') !== 0) { 15741 return FALSE; 15742 } 15743 } 15744 } 15745 15746 // Remove the inline child if it's empty for example <b> or <span> 15747 if (format.remove != 'none') { 15748 removeNode(node, format); 15749 return TRUE; 15750 } 15751 } 15752 15753 /** 15754 * Removes the node and wrap it's children in paragraphs before doing so or 15755 * appends BR elements to the beginning/end of the block element if forcedRootBlocks is disabled. 15756 * 15757 * If the div in the node below gets removed: 15758 * text<div>text</div>text 15759 * 15760 * Output becomes: 15761 * text<div><br />text<br /></div>text 15762 * 15763 * So when the div is removed the result is: 15764 * text<br />text<br />text 15765 * 15766 * @private 15767 * @param {Node} node Node to remove + apply BR/P elements to. 15768 * @param {Object} format Format rule. 15769 * @return {Node} Input node. 15770 */ 15771 function removeNode(node, format) { 15772 var parentNode = node.parentNode, rootBlockElm; 15773 15774 function find(node, next, inc) { 15775 node = getNonWhiteSpaceSibling(node, next, inc); 15776 15777 return !node || (node.nodeName == 'BR' || isBlock(node)); 15778 } 15779 15780 if (format.block) { 15781 if (!forcedRootBlock) { 15782 // Append BR elements if needed before we remove the block 15783 if (isBlock(node) && !isBlock(parentNode)) { 15784 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) { 15785 node.insertBefore(dom.create('br'), node.firstChild); 15786 } 15787 15788 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) { 15789 node.appendChild(dom.create('br')); 15790 } 15791 } 15792 } else { 15793 // Wrap the block in a forcedRootBlock if we are at the root of document 15794 if (parentNode == dom.getRoot()) { 15795 if (!format.list_block || !isEq(node, format.list_block)) { 15796 each(grep(node.childNodes), function(node) { 15797 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) { 15798 if (!rootBlockElm) { 15799 rootBlockElm = wrap(node, forcedRootBlock); 15800 dom.setAttribs(rootBlockElm, ed.settings.forced_root_block_attrs); 15801 } else { 15802 rootBlockElm.appendChild(node); 15803 } 15804 } else { 15805 rootBlockElm = 0; 15806 } 15807 }); 15808 } 15809 } 15810 } 15811 } 15812 15813 // Never remove nodes that isn't the specified inline element if a selector is specified too 15814 if (format.selector && format.inline && !isEq(format.inline, node)) { 15815 return; 15816 } 15817 15818 dom.remove(node, 1); 15819 } 15820 15821 /** 15822 * Returns the next/previous non whitespace node. 15823 * 15824 * @private 15825 * @param {Node} node Node to start at. 15826 * @param {boolean} next (Optional) Include next or previous node defaults to previous. 15827 * @param {boolean} inc (Optional) Include the current node in checking. Defaults to false. 15828 * @return {Node} Next or previous node or undefined if it wasn't found. 15829 */ 15830 function getNonWhiteSpaceSibling(node, next, inc) { 15831 if (node) { 15832 next = next ? 'nextSibling' : 'previousSibling'; 15833 15834 for (node = inc ? node : node[next]; node; node = node[next]) { 15835 if (node.nodeType == 1 || !isWhiteSpaceNode(node)) { 15836 return node; 15837 } 15838 } 15839 } 15840 } 15841 15842 /** 15843 * Merges the next/previous sibling element if they match. 15844 * 15845 * @private 15846 * @param {Node} prev Previous node to compare/merge. 15847 * @param {Node} next Next node to compare/merge. 15848 * @return {Node} Next node if we didn't merge and prev node if we did. 15849 */ 15850 function mergeSiblings(prev, next) { 15851 var sibling, tmpSibling, elementUtils = new ElementUtils(dom); 15852 15853 function findElementSibling(node, sibling_name) { 15854 for (sibling = node; sibling; sibling = sibling[sibling_name]) { 15855 if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) { 15856 return node; 15857 } 15858 15859 if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) { 15860 return sibling; 15861 } 15862 } 15863 15864 return node; 15865 } 15866 15867 // Check if next/prev exists and that they are elements 15868 if (prev && next) { 15869 // If previous sibling is empty then jump over it 15870 prev = findElementSibling(prev, 'previousSibling'); 15871 next = findElementSibling(next, 'nextSibling'); 15872 15873 // Compare next and previous nodes 15874 if (elementUtils.compare(prev, next)) { 15875 // Append nodes between 15876 for (sibling = prev.nextSibling; sibling && sibling != next;) { 15877 tmpSibling = sibling; 15878 sibling = sibling.nextSibling; 15879 prev.appendChild(tmpSibling); 15880 } 15881 15882 // Remove next node 15883 dom.remove(next); 15884 15885 // Move children into prev node 15886 each(grep(next.childNodes), function(node) { 15887 prev.appendChild(node); 15888 }); 15889 15890 return prev; 15891 } 15892 } 15893 15894 return next; 15895 } 15896 15897 function getContainer(rng, start) { 15898 var container, offset, lastIdx; 15899 15900 container = rng[start ? 'startContainer' : 'endContainer']; 15901 offset = rng[start ? 'startOffset' : 'endOffset']; 15902 15903 if (container.nodeType == 1) { 15904 lastIdx = container.childNodes.length - 1; 15905 15906 if (!start && offset) { 15907 offset--; 15908 } 15909 15910 container = container.childNodes[offset > lastIdx ? lastIdx : offset]; 15911 } 15912 15913 // If start text node is excluded then walk to the next node 15914 if (container.nodeType === 3 && start && offset >= container.nodeValue.length) { 15915 container = new TreeWalker(container, ed.getBody()).next() || container; 15916 } 15917 15918 // If end text node is excluded then walk to the previous node 15919 if (container.nodeType === 3 && !start && offset === 0) { 15920 container = new TreeWalker(container, ed.getBody()).prev() || container; 15921 } 15922 15923 return container; 15924 } 15925 15926 function performCaretAction(type, name, vars, similar) { 15927 var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug; 15928 15929 // Creates a caret container bogus element 15930 function createCaretContainer(fill) { 15931 var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''}); 15932 15933 if (fill) { 15934 caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR)); 15935 } 15936 15937 return caretContainer; 15938 } 15939 15940 function isCaretContainerEmpty(node, nodes) { 15941 while (node) { 15942 if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) { 15943 return false;
15944 } 15945 15946 // Collect nodes 15947 if (nodes && node.nodeType === 1) { 15948 nodes.push(node); 15949 } 15950 15951 node = node.firstChild; 15952 } 15953 15954 return true; 15955 } 15956 15957 // Returns any parent caret container element 15958 function getParentCaretContainer(node) { 15959 while (node) { 15960 if (node.id === caretContainerId) { 15961 return node; 15962 } 15963 15964 node = node.parentNode; 15965 } 15966 } 15967 15968 // Finds the first text node in the specified node 15969 function findFirstTextNode(node) { 15970 var walker; 15971 15972 if (node) { 15973 walker = new TreeWalker(node, node); 15974 15975 for (node = walker.current(); node; node = walker.next()) { 15976 if (node.nodeType === 3) { 15977 return node; 15978 } 15979 } 15980 } 15981 } 15982 15983 // Removes the caret container for the specified node or all on the current document 15984 function removeCaretContainer(node, move_caret) { 15985 var child, rng; 15986 15987 if (!node) { 15988 node = getParentCaretContainer(selection.getStart()); 15989 15990 if (!node) { 15991 while ((node = dom.get(caretContainerId))) { 15992 removeCaretContainer(node, false); 15993 } 15994 } 15995 } else { 15996 rng = selection.getRng(true); 15997 15998 if (isCaretContainerEmpty(node)) { 15999 if (move_caret !== false) { 16000 rng.setStartBefore(node); 16001 rng.setEndBefore(node); 16002 } 16003 16004 dom.remove(node); 16005 } else { 16006 child = findFirstTextNode(node); 16007 16008 if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) { 16009 child.deleteData(0, 1); 16010 16011 // Fix for bug #6976 16012 if (rng.startContainer == child && rng.startOffset > 0) { 16013 rng.setStart(child, rng.startOffset - 1); 16014 } 16015 16016 if (rng.endContainer == child && rng.endOffset > 0) { 16017 rng.setEnd(child, rng.endOffset - 1); 16018 } 16019 } 16020 16021 dom.remove(node, 1); 16022 } 16023 16024 selection.setRng(rng); 16025 } 16026 } 16027 16028 // Applies formatting to the caret postion 16029 function applyCaretFormat() { 16030 var rng, caretContainer, textNode, offset, bookmark, container, text; 16031 16032 rng = selection.getRng(true); 16033 offset = rng.startOffset; 16034 container = rng.startContainer; 16035 text = container.nodeValue; 16036 16037 caretContainer = getParentCaretContainer(selection.getStart()); 16038 if (caretContainer) { 16039 textNode = findFirstTextNode(caretContainer); 16040 } 16041 16042 // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character 16043 if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) { 16044 // Get bookmark of caret position 16045 bookmark = selection.getBookmark(); 16046 16047 // Collapse bookmark range (WebKit) 16048 rng.collapse(true); 16049 16050 // Expand the range to the closest word and split it at those points 16051 rng = expandRng(rng, get(name)); 16052 rng = rangeUtils.split(rng); 16053 16054 // Apply the format to the range 16055 apply(name, vars, rng); 16056 16057 // Move selection back to caret position 16058 selection.moveToBookmark(bookmark); 16059 } else { 16060 if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) { 16061 caretContainer = createCaretContainer(true); 16062 textNode = caretContainer.firstChild; 16063 16064 rng.insertNode(caretContainer); 16065 offset = 1; 16066 16067 apply(name, vars, caretContainer); 16068 } else { 16069 apply(name, vars, caretContainer); 16070 } 16071 16072 // Move selection to text node 16073 selection.setCursorLocation(textNode, offset); 16074 } 16075 } 16076 16077 function removeCaretFormat() { 16078 var rng = selection.getRng(true), container, offset, bookmark, 16079 hasContentAfter, node, formatNode, parents = [], i, caretContainer; 16080 16081 container = rng.startContainer; 16082 offset = rng.startOffset; 16083 node = container; 16084 16085 if (container.nodeType == 3) { 16086 if (offset != container.nodeValue.length) { 16087 hasContentAfter = true; 16088 } 16089 16090 node = node.parentNode; 16091 } 16092 16093 while (node) { 16094 if (matchNode(node, name, vars, similar)) { 16095 formatNode = node; 16096 break; 16097 } 16098 16099 if (node.nextSibling) { 16100 hasContentAfter = true; 16101 } 16102 16103 parents.push(node); 16104 node = node.parentNode; 16105 } 16106 16107 // Node doesn't have the specified format 16108 if (!formatNode) { 16109 return; 16110 } 16111 16112 // Is there contents after the caret then remove the format on the element 16113 if (hasContentAfter) { 16114 // Get bookmark of caret position 16115 bookmark = selection.getBookmark(); 16116 16117 // Collapse bookmark range (WebKit) 16118 rng.collapse(true); 16119 16120 // Expand the range to the closest word and split it at those points 16121 rng = expandRng(rng, get(name), true); 16122 rng = rangeUtils.split(rng); 16123 16124 // Remove the format from the range 16125 remove(name, vars, rng); 16126 16127 // Move selection back to caret position 16128 selection.moveToBookmark(bookmark); 16129 } else { 16130 caretContainer = createCaretContainer(); 16131 16132 node = caretContainer; 16133 for (i = parents.length - 1; i >= 0; i--) { 16134 node.appendChild(dom.clone(parents[i], false)); 16135 node = node.firstChild; 16136 } 16137 16138 // Insert invisible character into inner most format element 16139 node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR)); 16140 node = node.firstChild; 16141 16142 var block = dom.getParent(formatNode, isTextBlock); 16143 16144 if (block && dom.isEmpty(block)) { 16145 // Replace formatNode with caretContainer when removing format from empty block like <p><b>|</b></p> 16146 formatNode.parentNode.replaceChild(caretContainer, formatNode); 16147 } else { 16148 // Insert caret container after the formated node 16149 dom.insertAfter(caretContainer, formatNode); 16150 } 16151 16152 // Move selection to text node 16153 selection.setCursorLocation(node, 1); 16154 16155 // If the formatNode is empty, we can remove it safely. 16156 if (dom.isEmpty(formatNode)) { 16157 dom.remove(formatNode); 16158 } 16159 } 16160 } 16161 16162 // Checks if the parent caret container node isn't empty if that is the case it 16163 // will remove the bogus state on all children that isn't empty 16164 function unmarkBogusCaretParents() { 16165 var caretContainer; 16166 16167 caretContainer = getParentCaretContainer(selection.getStart()); 16168 if (caretContainer && !dom.isEmpty(caretContainer)) { 16169 walk(caretContainer, function(node) { 16170 if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) { 16171 dom.setAttrib(node, 'data-mce-bogus', null); 16172 } 16173 }, 'childNodes'); 16174 } 16175 } 16176 16177 // Only bind the caret events once 16178 if (!ed._hasCaretEvents) { 16179 // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements 16180 markCaretContainersBogus = function() { 16181 var nodes = [], i; 16182 16183 if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) { 16184 // Mark children 16185 i = nodes.length; 16186 while (i--) { 16187 dom.setAttrib(nodes[i], 'data-mce-bogus', '1'); 16188 } 16189 } 16190 }; 16191 16192 disableCaretContainer = function(e) { 16193 var keyCode = e.keyCode; 16194 16195 removeCaretContainer(); 16196 16197 // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys 16198 if (keyCode == 8 || keyCode == 37 || keyCode == 39) { 16199 removeCaretContainer(getParentCaretContainer(selection.getStart())); 16200 } 16201 16202 unmarkBogusCaretParents(); 16203 }; 16204 16205 // Remove bogus state if they got filled by contents using editor.selection.setContent 16206 ed.on('SetContent', function(e) { 16207 if (e.selection) { 16208 unmarkBogusCaretParents(); 16209 } 16210 }); 16211 ed._hasCaretEvents = true; 16212 } 16213 16214 // Do apply or remove caret format 16215 if (type == "apply") { 16216 applyCaretFormat(); 16217 } else { 16218 removeCaretFormat(); 16219 } 16220 } 16221 16222 /** 16223 * Moves the start to the first suitable text node. 16224 */ 16225 function moveStart(rng) { 16226 var container = rng.startContainer, 16227 offset = rng.startOffset, isAtEndOfText, 16228 walker, node, nodes, tmpNode; 16229 16230 // Convert text node into index if possible 16231 if (container.nodeType == 3 && offset >= container.nodeValue.length) { 16232 // Get the parent container location and walk from there 16233 offset = nodeIndex(container); 16234 container = container.parentNode; 16235 isAtEndOfText = true; 16236 } 16237 16238 // Move startContainer/startOffset in to a suitable node 16239 if (container.nodeType == 1) { 16240 nodes = container.childNodes; 16241 container = nodes[Math.min(offset, nodes.length - 1)]; 16242 walker = new TreeWalker(container, dom.getParent(container, dom.isBlock)); 16243 16244 // If offset is at end of the parent node walk to the next one 16245 if (offset >
16245 nodes.length - 1 || isAtEndOfText) { 16246 walker.next(); 16247 } 16248 16249 for (node = walker.current(); node; node = walker.next()) { 16250 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { 16251 // IE has a "neat" feature where it moves the start node into the closest element 16252 // we can avoid this by inserting an element before it and then remove it after we set the selection 16253 tmpNode = dom.create('a', {'data-mce-bogus': 'all'}, INVISIBLE_CHAR); 16254 node.parentNode.insertBefore(tmpNode, node); 16255 16256 // Set selection and remove tmpNode 16257 rng.setStart(node, 0); 16258 selection.setRng(rng); 16259 dom.remove(tmpNode); 16260 16261 return; 16262 } 16263 } 16264 } 16265 } 16266 }; 16267}); 16268 16269// Included from: js/tinymce/classes/UndoManager.js 16270 16271/** 16272 * UndoManager.js 16273 * 16274 * Copyright, Moxiecode Systems AB 16275 * Released under LGPL License. 16276 * 16277 * License: http://www.tinymce.com/license 16278 * Contributing: http://www.tinymce.com/contributing 16279 */ 16280 16281/** 16282 * This class handles the undo/redo history levels for the editor. Since the build in undo/redo has major drawbacks a custom one was needed. 16283 * 16284 * @class tinymce.UndoManager 16285 */ 16286define("tinymce/UndoManager", [ 16287 "tinymce/util/VK", 16288 "tinymce/Env", 16289 "tinymce/util/Tools", 16290 "tinymce/html/SaxParser" 16291], function(VK, Env, Tools, SaxParser) { 16292 var trim = Tools.trim, trimContentRegExp; 16293 16294 trimContentRegExp = new RegExp([ 16295 '<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\\/span>', // Trim bogus spans like caret containers 16296 '\\s?data-mce-selected="[^"]+"' // Trim temporaty data-mce prefixed attributes like data-mce-selected 16297 ].join('|'), 'gi'); 16298 16299 return function(editor) { 16300 var self = this, index = 0, data = [], beforeBookmark, isFirstTypedCharacter, locks = 0; 16301 16302 /** 16303 * Returns a trimmed version of the editor contents to be used for the undo level. This 16304 * will remove any data-mce-bogus="all" marked elements since these are used for UI it will also 16305 * remove the data-mce-selected attributes used for selection of objects and caret containers. 16306 * It will keep all data-mce-bogus="1" elements since these can be used to place the caret etc and will 16307 * be removed by the serialization logic when you save. 16308 * 16309 * @private 16310 * @return {String} HTML contents of the editor excluding some internal bogus elements. 16311 */ 16312 function getContent() { 16313 var content = editor.getContent({format: 'raw', no_events: 1}); 16314 var bogusAllRegExp = /<(\w+) [^>]*data-mce-bogus="all"[^>]*>/g; 16315 var endTagIndex, index, matchLength, matches, shortEndedElements, schema = editor.schema; 16316 16317 content = content.replace(trimContentRegExp, ''); 16318 shortEndedElements = schema.getShortEndedElements(); 16319 16320 // Remove all bogus elements marked with "all" 16321 while ((matches = bogusAllRegExp.exec(content))) { 16322 index = bogusAllRegExp.lastIndex; 16323 matchLength = matches[0].length; 16324 16325 if (shortEndedElements[matches[1]]) { 16326 endTagIndex = index; 16327 } else { 16328 endTagIndex = SaxParser.findEndTag(schema, content, index); 16329 } 16330 16331 content = content.substring(0, index - matchLength) + content.substring(endTagIndex); 16332 bogusAllRegExp.lastIndex = index - matchLength; 16333 } 16334 16335 return trim(content); 16336 } 16337 16338 function addNonTypingUndoLevel(e) { 16339 self.typing = false; 16340 self.add({}, e); 16341 } 16342 16343 // Add initial undo level when the editor is initialized 16344 editor.on('init', function() { 16345 self.add(); 16346 }); 16347 16348 // Get position before an execCommand is processed 16349 editor.on('BeforeExecCommand', function(e) { 16350 var cmd = e.command; 16351 16352 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') { 16353 self.beforeChange(); 16354 } 16355 }); 16356 16357 // Add undo level after an execCommand call was made 16358 editor.on('ExecCommand', function(e) { 16359 var cmd = e.command; 16360 16361 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') { 16362 addNonTypingUndoLevel(e); 16363 } 16364 }); 16365
16366 editor.on('ObjectResizeStart', function() { 16367 self.beforeChange(); 16368 }); 16369 16370 editor.on('SaveContent ObjectResized blur', addNonTypingUndoLevel); 16371 editor.on('DragEnd', addNonTypingUndoLevel); 16372 16373 editor.on('KeyUp', function(e) { 16374 var keyCode = e.keyCode; 16375 16376 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) { 16377 addNonTypingUndoLevel(); 16378 editor.nodeChanged(); 16379 } 16380 16381 if (keyCode == 46 || keyCode == 8 || (Env.mac && (keyCode == 91 || keyCode == 93))) { 16382 editor.nodeChanged(); 16383 } 16384 16385 // Fire a TypingUndo event on the first character entered 16386 if (isFirstTypedCharacter && self.typing) { 16387 // Make the it dirty if the content was changed after typing the first character 16388 if (!editor.isDirty()) { 16389 editor.isNotDirty = !data[0] || getContent() == data[0].content; 16390 16391 // Fire initial change event 16392 if (!editor.isNotDirty) { 16393 editor.fire('change', {level: data[0], lastLevel: null}); 16394 } 16395 } 16396 16397 editor.fire('TypingUndo'); 16398 isFirstTypedCharacter = false; 16399 editor.nodeChanged(); 16400 } 16401 }); 16402 16403 editor.on('KeyDown', function(e) { 16404 var keyCode = e.keyCode; 16405 16406 // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter 16407 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) { 16408 if (self.typing) { 16409 addNonTypingUndoLevel(e); 16410 } 16411 16412 return; 16413 } 16414 16415 // If key isn't shift,ctrl,alt,capslock,metakey 16416 var modKey = VK.modifierPressed(e); 16417 if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing && !modKey) { 16418 self.beforeChange(); 16419 self.typing = true; 16420 self.add({}, e); 16421 isFirstTypedCharacter = true; 16422 } 16423 }); 16424 16425 editor.on('MouseDown', function(e) { 16426 if (self.typing) { 16427 addNonTypingUndoLevel(e); 16428 } 16429 }); 16430 16431 // Add keyboard shortcuts for undo/redo keys 16432 editor.addShortcut('ctrl+z', '', 'Undo'); 16433 editor.addShortcut('ctrl+y,ctrl+shift+z', '', 'Redo'); 16434 16435 editor.on('AddUndo Undo Redo ClearUndos', function(e) { 16436 if (!e.isDefaultPrevented()) { 16437 editor.nodeChanged(); 16438 } 16439 }); 16440 16441 self = { 16442 // Explose for debugging reasons 16443 data: data, 16444 16445 /** 16446 * State if the user is currently typing or not. This will add a typing operation into one undo 16447 * level instead of one new level for each keystroke. 16448 * 16449 * @field {Boolean} typing 16450 */ 16451 typing: false, 16452 16453 /** 16454 * Stores away a bookmark to be used when performing an undo action so that the selection is before 16455 * the change has been made. 16456 * 16457 * @method beforeChange 16458 */ 16459 beforeChange: function() { 16460 if (!locks) { 16461 beforeBookmark = editor.selection.getBookmark(2, true); 16462 } 16463 }, 16464 16465 /** 16466 * Adds a new undo level/snapshot to the undo list. 16467 * 16468 * @method add 16469 * @param {Object} level Optional undo level object to add. 16470 * @param {DOMEvent} Event Optional event responsible for the creation of the undo level. 16471 * @return {Object} Undo level that got added or null it a level wasn't needed. 16472 */ 16473 add: function(level, event) { 16474 var i, settings = editor.settings, lastLevel; 16475 16476 level = level || {}; 16477 level.content = getContent(); 16478 16479 if (locks || editor.removed) { 16480 return null; 16481 } 16482 16483 lastLevel = data[index]; 16484 if (editor.fire('BeforeAddUndo', {level: level, lastLevel: lastLevel, originalEvent: event}).isDefaultPrevented()) { 16485 return null; 16486 } 16487 16488 // Add undo level if needed 16489 if (lastLevel && lastLevel.content == level.content) { 16490 return null; 16491 } 16492 16493 // Set before bookmark on previous level 16494 if (data[index]) { 16495 data[index].beforeBookmark = beforeBookmark; 16496 } 16497 16498 // Time to compress 16499 if (settings.custom_undo_redo_levels) { 16500 if (data.length > settings.custom_undo_redo_levels) { 16501 for (i = 0; i < data.length - 1; i++) { 16502 data[i] = data[i + 1]; 16503 } 16504 16505 data.length--; 16506 index = data.length; 16507 } 16508 } 16509 16510 // Get a non intrusive normalized bookmark 16511 level.bookmark = editor.selection.getBookmark(2, true); 16512 16513 // Crop array if needed 16514 if (index < data.length - 1) { 16515 data.length = index + 1; 16516 } 16517 16518 data.push(level); 16519 index = data.length - 1; 16520 16521 var args = {level: level, lastLevel: lastLevel, originalEvent: event}; 16522 16523 editor.fire('AddUndo', args); 16524 16525 if (index > 0) { 16526 editor.isNotDirty = false;
16527 editor.fire('change', args); 16528 } 16529 16530 return level; 16531 }, 16532 16533 /** 16534 * Undoes the last action. 16535 * 16536 * @method undo 16537 * @return {Object} Undo level or null if no undo was performed. 16538 */ 16539 undo: function() { 16540 var level; 16541 16542 if (self.typing) { 16543 self.add(); 16544 self.typing = false; 16545 } 16546 16547 if (index > 0) { 16548 level = data[--index]; 16549 16550 // Undo to first index then set dirty state to false 16551 if (index === 0) { 16552 editor.isNotDirty = true; 16553 } 16554 16555 editor.setContent(level.content, {format: 'raw'}); 16556 editor.selection.moveToBookmark(level.beforeBookmark); 16557 16558 editor.fire('undo', {level: level}); 16559 } 16560 16561 return level; 16562 }, 16563 16564 /** 16565 * Redoes the last action. 16566 * 16567 * @method redo 16568 * @return {Object} Redo level or null if no redo was performed. 16569 */ 16570 redo: function() { 16571 var level; 16572 16573 if (index < data.length - 1) { 16574 level = data[++index]; 16575 16576 editor.setContent(level.content, {format: 'raw'}); 16577 editor.selection.moveToBookmark(level.bookmark); 16578 16579 editor.fire('redo', {level: level}); 16580 } 16581 16582 return level; 16583 }, 16584 16585 /** 16586 * Removes all undo levels. 16587 * 16588 * @method clear 16589 */ 16590 clear: function() { 16591 data = []; 16592 index = 0; 16593 self.typing = false; 16594 editor.fire('ClearUndos'); 16595 }, 16596 16597 /** 16598 * Returns true/false if the undo manager has any undo levels. 16599 * 16600 * @method hasUndo 16601 * @return {Boolean} true/false if the undo manager has any undo levels. 16602 */ 16603 hasUndo: function() { 16604 // Has undo levels or typing and content isn't the same as the initial level 16605 return index > 0 || (self.typing && data[0] && getContent() != data[0].content); 16606 }, 16607 16608 /** 16609 * Returns true/false if the undo manager has any redo levels. 16610 * 16611 * @method hasRedo 16612 * @return {Boolean} true/false if the undo manager has any redo levels. 16613 */ 16614 hasRedo: function() { 16615 return index < data.length - 1 && !this.typing; 16616 }, 16617 16618 /** 16619 * Executes the specified function in an undo transation. The selection 16620 * before the modification will be stored to the undo stack and if the DOM changes 16621 * it will add a new undo level. Any methods within the transation that adds undo levels will 16622 * be ignored. So a transation can include calls to execCommand or editor.insertContent. 16623 * 16624 * @method transact 16625 * @param {function} callback Function to execute dom manipulation logic in. 16626 */ 16627 transact: function(callback) { 16628 self.beforeChange(); 16629 16630 try { 16631 locks++; 16632 callback(); 16633 } finally { 16634 locks--; 16635 } 16636 16637 self.add(); 16638 } 16639 }; 16640 16641 return self; 16642 }; 16643}); 16644 16645// Included from: js/tinymce/classes/EnterKey.js 16646 16647/** 16648 * EnterKey.js 16649 * 16650 * Copyright, Moxiecode Systems AB 16651 * Released under LGPL License. 16652 * 16653 * License: http://www.tinymce.com/license 16654 * Contributing: http://www.tinymce.com/contributing 16655 */ 16656 16657/** 16658 * Contains logic for handling the enter key to split/generate block elements. 16659 */ 16660define("tinymce/EnterKey", [ 16661 "tinymce/dom/TreeWalker", 16662 "tinymce/dom/RangeUtils", 16663 "tinymce/Env" 16664], function(TreeWalker, RangeUtils, Env) { 16665 var isIE = Env.ie && Env.ie < 11; 16666 16667 return function(editor) { 16668 var dom = editor.dom, selection = editor.selection, settings = editor.settings; 16669 var undoManager = editor.undoManager, schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements(); 16670 16671 function handleEnterKey(evt) { 16672 var rng, tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey, 16673 newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer; 16674 16675 // Returns true if the block can be split into two blocks or not 16676 function canSplitBlock(node) { 16677 return node && 16678 dom.isBlock(node) && 16679 !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) && 16680 !/^(fixed|absolute)/i.test(node.style.position) && 16681 dom.getContentEditable(node) !== "true"; 16682 } 16683 16684 // Renders empty block on IE 16685 function renderBlockOnIE(block) { 16686 var oldRng; 16687 16688 if (dom.isBlock(block)) { 16689 oldRng = selection.getRng(); 16690 block.appendChild(dom.create('span', null, '\u00a0')); 16691 selection.select(block); 16692 block.lastChild.outerHTML = ''; 16693 selection.setRng(oldRng); 16694 } 16695 } 16696 16697 // Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>
16697x</p> 16698 function trimInlineElementsOnLeftSideOfBlock(block) { 16699 var node = block, firstChilds = [], i; 16700 16701 if (!node) { 16702 return; 16703 } 16704 16705 // Find inner most first child ex: <p><i><b>*</b></i></p> 16706 while ((node = node.firstChild)) { 16707 if (dom.isBlock(node)) { 16708 return; 16709 } 16710 16711 if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 16712 firstChilds.push(node); 16713 } 16714 } 16715 16716 i = firstChilds.length; 16717 while (i--) { 16718 node = firstChilds[i]; 16719 if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) { 16720 dom.remove(node); 16721 } else { 16722 // Remove <a> </a> see #5381 16723 if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') { 16724 dom.remove(node); 16725 } 16726 } 16727 } 16728 } 16729 16730 // Moves the caret to a suitable position within the root for example in the first non 16731 // pure whitespace text node or before an image 16732 function moveToCaretPosition(root) { 16733 var walker, node, rng, lastNode = root, tempElm; 16734 16735 function firstNonWhiteSpaceNodeSibling(node) { 16736 while (node) { 16737 if (node.nodeType == 1 || (node.nodeType == 3 && node.data && /[\r\n\s]/.test(node.data))) { 16738 return node; 16739 } 16740 16741 node = node.nextSibling; 16742 } 16743 } 16744 16745 if (!root) { 16746 return; 16747 } 16748 16749 // Old IE versions doesn't properly render blocks with br elements in them 16750 // For example <p><br></p> wont be rendered correctly in a contentEditable area 16751 // until you remove the br producing <p></p> 16752 if (Env.ie && Env.ie < 9 && parentBlock && parentBlock.firstChild) { 16753 if (parentBlock.firstChild == parentBlock.lastChild && parentBlock.firstChild.tagName == 'BR') { 16754 dom.remove(parentBlock.firstChild); 16755 } 16756 } 16757 16758 if (/^(LI|DT|DD)$/.test(root.nodeName)) { 16759 var firstChild = firstNonWhiteSpaceNodeSibling(root.firstChild); 16760 16761 if (firstChild && /^(UL|OL|DL)$/.test(firstChild.nodeName)) { 16762 root.insertBefore(dom.doc.createTextNode('\u00a0'), root.firstChild); 16763 } 16764 } 16765 16766 rng = dom.createRng(); 16767 16768 // Normalize whitespace to remove empty text nodes. Fix for: #6904 16769 // Gecko will be able to place the caret in empty text nodes but it won't render propery 16770 // Older IE versions will sometimes crash so for now ignore all IE versions 16771 if (!Env.ie) { 16772 root.normalize(); 16773 } 16774 16775 if (root.hasChildNodes()) { 16776 walker = new TreeWalker(root, root); 16777 16778 while ((node = walker.current())) { 16779 if (node.nodeType == 3) { 16780 rng.setStart(node, 0); 16781 rng.setEnd(node, 0); 16782 break; 16783 } 16784 16785 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 16786 rng.setStartBefore(node); 16787 rng.setEndBefore(node); 16788 break; 16789 } 16790 16791 lastNode = node; 16792 node = walker.next(); 16793 } 16794 16795 if (!node) { 16796 rng.setStart(lastNode, 0); 16797 rng.setEnd(lastNode, 0); 16798 } 16799 } else { 16800 if (root.nodeName == 'BR') { 16801 if (root.nextSibling && dom.isBlock(root.nextSibling)) { 16802 // Trick on older IE versions to render the caret before the BR between two lists 16803 if (!documentMode || documentMode < 9) { 16804 tempElm = dom.create('br'); 16805 root.parentNode.insertBefore(tempElm, root); 16806 } 16807 16808 rng.setStartBefore(root); 16809 rng.setEndBefore(root); 16810 } else { 16811 rng.setStartAfter(root); 16812 rng.setEndAfter(root); 16813 } 16814 } else { 16815 rng.setStart(root, 0); 16816 rng.setEnd(root, 0); 16817 } 16818 } 16819 16820 selection.setRng(rng); 16821 16822 // Remove tempElm created for old IE:s 16823 dom.remove(tempElm); 16824 selection.scrollIntoView(root); 16825 } 16826 16827 function setForcedBlockAttrs(node) { 16828 var forcedRootBlockName = settings.forced_root_block; 16829 16830 if (forcedRootBlockName && forcedRootBlockName.toLowerCase() === node.tagName.toLowerCase()) { 16831 dom.setAttribs(node, settings.forced_root_block_attrs); 16832 } 16833 } 16834 16835 // Creates a new block element by cloning the current one or creating a new one if the name is specified 16836 // This function will also copy any text formatting from the parent block and add it to the new one 16837 function createNewBlock(name) {
16838 var node = container, block, clonedNode, caretNode, textInlineElements = schema.getTextInlineElements(); 16839 16840 if (name || parentBlockName == "TABLE") { 16841 block = dom.create(name || newBlockName); 16842 setForcedBlockAttrs(block); 16843 } else { 16844 block = parentBlock.cloneNode(false); 16845 } 16846 16847 caretNode = block; 16848 16849 // Clone any parent styles 16850 if (settings.keep_styles !== false) { 16851 do { 16852 if (textInlineElements[node.nodeName]) { 16853 // Never clone a caret containers 16854 if (node.id == '_mce_caret') { 16855 continue; 16856 } 16857 16858 clonedNode = node.cloneNode(false); 16859 dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique 16860 16861 if (block.hasChildNodes()) { 16862 clonedNode.appendChild(block.firstChild); 16863 block.appendChild(clonedNode); 16864 } else { 16865 caretNode = clonedNode; 16866 block.appendChild(clonedNode); 16867 } 16868 } 16869 } while ((node = node.parentNode)); 16870 } 16871 16872 // BR is needed in empty blocks on non IE browsers 16873 if (!isIE) { 16874 caretNode.innerHTML = '<br data-mce-bogus="1">'; 16875 } 16876 16877 return block; 16878 } 16879 16880 // Returns true/false if the caret is at the start/end of the parent block element 16881 function isCaretAtStartOrEndOfBlock(start) { 16882 var walker, node, name; 16883 16884 // Caret is in the middle of a text node like "a|b" 16885 if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) { 16886 return false; 16887 } 16888 16889 // If after the last element in block node edge case for #5091 16890 if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) { 16891 return true; 16892 } 16893 16894 // If the caret if before the first element in parentBlock 16895 if (start && container.nodeType == 1 && container == parentBlock.firstChild) { 16896 return true; 16897 } 16898 16899 // Caret can be before/after a table 16900 if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) { 16901 return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start); 16902 } 16903 16904 // Walk the DOM and look for text nodes or non empty elements 16905 walker = new TreeWalker(container, parentBlock); 16906 16907 // If caret is in beginning or end of a text block then jump to the next/previous node 16908 if (container.nodeType == 3) { 16909 if (start && offset === 0) { 16910 walker.prev(); 16911 } else if (!start && offset == container.nodeValue.length) { 16912 walker.next(); 16913 } 16914 } 16915 16916 while ((node = walker.current())) { 16917 if (node.nodeType === 1) { 16918 // Ignore bogus elements 16919 if (!node.getAttribute('data-mce-bogus')) { 16920 // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p> 16921 name = node.nodeName.toLowerCase(); 16922 if (nonEmptyElementsMap[name] && name !== 'br') { 16923 return false; 16924 } 16925 } 16926 } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) { 16927 return false; 16928 } 16929 16930 if (start) { 16931 walker.prev(); 16932 } else { 16933 walker.next(); 16934 } 16935 } 16936 16937 return true; 16938 } 16939 16940 // Wraps any text nodes or inline elements in the specified forced root block name 16941 function wrapSelfAndSiblingsInDefaultBlock(container, offset) { 16942 var newBlock, parentBlock, startNode, node, next, rootBlockName, blockName = newBlockName || 'P'; 16943 16944 // Not in a block element or in a table cell or caption 16945 parentBlock = dom.getParent(container, dom.isBlock); 16946 rootBlockName = editor.getBody().nodeName.toLowerCase(); 16947 if (!parentBlock || !canSplitBlock(parentBlock)) { 16948 parentBlock = parentBlock || editableRoot; 16949 16950 if (!parentBlock.hasChildNodes()) { 16951 newBlock = dom.create(blockName); 16952 setForcedBlockAttrs(newBlock); 16953 parentBlock.appendChild(newBlock); 16954 rng.setStart(newBlock, 0); 16955 rng.setEnd(newBlock, 0); 16956 return newBlock; 16957 } 16958 16959 // Find parent that is the first child of parentBlock 16960 node = container; 16961 while (node.parentNode != parentBlock) { 16962 node = node.parentNode; 16963 } 16964 16965 // Loop left to find start node start wrapping at 16966 while (node && !dom.isBlock(node)) { 16967 startNode = node; 16968 node = node.previousSibling; 16969 } 16970 16971 if (startNode && schema.isValidChild(rootBlockName, blockName.toLowerCase())) { 16972 newBlock = dom.create(blockName); 16973 setForcedBlockAttrs(newBlock); 16974 startNode.parentNode.insertBefore(newBlock, startNode); 16975 16976 // Start wrapping until we hit a block 16977 node = startNode;
16978 while (node && !dom.isBlock(node)) { 16979 next = node.nextSibling; 16980 newBlock.appendChild(node); 16981 node = next; 16982 } 16983 16984 // Restore range to it's past location 16985 rng.setStart(container, offset); 16986 rng.setEnd(container, offset); 16987 } 16988 } 16989 16990 return container; 16991 } 16992 16993 // Inserts a block or br before/after or in the middle of a split list of the LI is empty 16994 function handleEmptyListItem() { 16995 function isFirstOrLastLi(first) { 16996 var node = containerBlock[first ? 'firstChild' : 'lastChild']; 16997 16998 // Find first/last element since there might be whitespace there 16999 while (node) { 17000 if (node.nodeType == 1) { 17001 break; 17002 } 17003 17004 node = node[first ? 'nextSibling' : 'previousSibling']; 17005 } 17006 17007 return node === parentBlock; 17008 } 17009 17010 function getContainerBlock() { 17011 var containerBlockParent = containerBlock.parentNode; 17012 17013 if (/^(LI|DT|DD)$/.test(containerBlockParent.nodeName)) { 17014 return containerBlockParent; 17015 } 17016 17017 return containerBlock; 17018 } 17019 17020 // Check if we are in an nested list 17021 var containerBlockParentName = containerBlock.parentNode.nodeName; 17022 if (/^(OL|UL|LI)$/.test(containerBlockParentName)) { 17023 newBlockName = 'LI'; 17024 } 17025 17026 newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR'); 17027 17028 if (isFirstOrLastLi(true) && isFirstOrLastLi()) { 17029 if (containerBlockParentName == 'LI') { 17030 // Nested list is inside a LI 17031 dom.insertAfter(newBlock, getContainerBlock()); 17032 } else { 17033 // Is first and last list item then replace the OL/UL with a text block 17034 dom.replace(newBlock, containerBlock); 17035 } 17036 } else if (isFirstOrLastLi(true)) { 17037 if (containerBlockParentName == 'LI') { 17038 // List nested in an LI then move the list to a new sibling LI 17039 dom.insertAfter(newBlock, getContainerBlock()); 17040 newBlock.appendChild(dom.doc.createTextNode(' ')); // Needed for IE so the caret can be placed 17041 newBlock.appendChild(containerBlock); 17042 } else { 17043 // First LI in list then remove LI and add text block before list 17044 containerBlock.parentNode.insertBefore(newBlock, containerBlock); 17045 } 17046 } else if (isFirstOrLastLi()) { 17047 // Last LI in list then remove LI and add text block after list 17048 dom.insertAfter(newBlock, getContainerBlock()); 17049 renderBlockOnIE(newBlock); 17050 } else { 17051 // Middle LI in list the split the list and insert a text block in the middle 17052 // Extract after fragment and insert it after the current block 17053 containerBlock = getContainerBlock(); 17054 tmpRng = rng.cloneRange(); 17055 tmpRng.setStartAfter(parentBlock); 17056 tmpRng.setEndAfter(containerBlock); 17057 fragment = tmpRng.extractContents(); 17058 17059 if (newBlockName == 'LI' && fragment.firstChild.nodeName == 'LI') { 17060 newBlock = fragment.firstChild; 17061 dom.insertAfter(fragment, containerBlock); 17062 } else { 17063 dom.insertAfter(fragment, containerBlock); 17064 dom.insertAfter(newBlock, containerBlock); 17065 } 17066 } 17067 17068 dom.remove(parentBlock); 17069 moveToCaretPosition(newBlock); 17070 undoManager.add(); 17071 } 17072 17073 // Inserts a BR element if the forced_root_block option is set to false or empty string 17074 function insertBr() { 17075 editor.execCommand("InsertLineBreak", false, evt); 17076 } 17077 17078 // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element 17079 function trimLeadingLineBreaks(node) { 17080 do { 17081 if (node.nodeType === 3) { 17082 node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, ''); 17083 } 17084 17085 node = node.firstChild; 17086 } while (node); 17087 } 17088 17089 function getEditableRoot(node) { 17090 var root = dom.getRoot(), parent, editableRoot; 17091 17092 // Get all parents until we hit a non editable parent or the root 17093 parent = node; 17094 while (parent !== root && dom.getContentEditable(parent) !== "false") { 17095 if (dom.getContentEditable(parent) === "true") { 17096 editableRoot = parent; 17097 } 17098 17099 parent = parent.parentNode; 17100 } 17101 17102 return parent !== root ? editableRoot : root; 17103 } 17104 17105 // Adds a BR at the end of blocks that only contains an IMG or INPUT since 17106 // these might be floated and then they won't expand the block 17107 function addBrToBlockIfNeeded(block) { 17108 var lastChild; 17109 17110 // IE will render the blocks correctly other browsers needs a BR 17111 if (!isIE) { 17112 block.normalize(); // Remove empty text nodes that got left behind by the extract 17113 17114 // Check if the block is empty or contains a floated last child 17115 lastChild = block.lastChild; 17116 if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) { 17117 dom.add(block, 'br'); 17118 } 17119 } 17120 } 17121 17122 rng = selection.getRng(true); 17123 17124 // Event is blocked by some other handler for example the lists plugin 17125 if (evt.isDefaultPrevented()) { 17126 return; 17127 } 17128 17129 // Delete any selected contents 17130 if (!rng.collapsed) { 17131 editor.execCommand('Delete'); 17132 return; 17133 } 17134 17135 // Setup range items and newBlockName 17136 new RangeUtils(dom).normalize(rng); 17137 container = rng.startContainer; 17138 offset = rng.startOffset; 17139 newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block; 17140 newBlockName = newBlockName ? newBlockName.toUpperCase() : ''; 17141 documentMode = dom.doc.documentMode; 17142 shiftKey = evt.shiftKey; 17143 17144 // Resolve node index 17145 if (container.nodeType == 1 && container.hasChildNodes()) { 17146 isAfterLastNodeInContainer = offset > container.childNodes.length - 1; 17147 17148 container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container; 17149 if (isAfterLastNodeInContainer && container.nodeType == 3) { 17150 offset = container.nodeValue.length; 17151 } else { 17152 offset = 0; 17153 } 17154 } 17155 17156 // Get editable root node normaly the body element but sometimes a div or span 17157 editableRoot = getEditableRoot(container); 17158 17159 // If there is no editable root then enter is done inside a contentEditable false element 17160 if (!editableRoot) { 17161 return; 17162 } 17163 17164 undoManager.beforeChange(); 17165 17166 // If editable root isn't block nor the root of the editor
17167 if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) { 17168 if (!newBlockName || shiftKey) { 17169 insertBr(); 17170 } 17171 17172 return; 17173 } 17174 17175 // Wrap the current node and it's sibling in a default block if it's needed. 17176 // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td> 17177 // This won't happen if root blocks are disabled or the shiftKey is pressed 17178 if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) { 17179 container = wrapSelfAndSiblingsInDefaultBlock(container, offset); 17180 } 17181 17182 // Find parent block and setup empty block paddings 17183 parentBlock = dom.getParent(container, dom.isBlock); 17184 containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null; 17185 17186 // Setup block names 17187 parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 17188 containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 17189 17190 // Enter inside block contained within a LI then split or insert before/after LI 17191 if (containerBlockName == 'LI' && !evt.ctrlKey) { 17192 parentBlock = containerBlock; 17193 parentBlockName = containerBlockName; 17194 } 17195 17196 // Handle enter in list item 17197 if (/^(LI|DT|DD)$/.test(parentBlockName)) { 17198 if (!newBlockName && shiftKey) { 17199 insertBr(); 17200 return; 17201 } 17202 17203 // Handle enter inside an empty list item 17204 if (dom.isEmpty(parentBlock)) { 17205 handleEmptyListItem(); 17206 return; 17207 } 17208 } 17209 17210 // Don't split PRE tags but insert a BR instead easier when writing code samples etc 17211 if (parentBlockName == 'PRE' && settings.br_in_pre !== false) { 17212 if (!shiftKey) { 17213 insertBr(); 17214 return; 17215 } 17216 } else { 17217 // If no root block is configured then insert a BR by default or if the shiftKey is pressed 17218 if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) { 17219 insertBr(); 17220 return; 17221 } 17222 } 17223 17224 // If parent block is root then never insert new blocks 17225 if (newBlockName && parentBlock === editor.getBody()) { 17226 return; 17227 } 17228 17229 // Default block name if it's not configured 17230 newBlockName = newBlockName || 'P'; 17231 17232 // Insert new block before/after the parent block depending on caret location 17233 if (isCaretAtStartOrEndOfBlock()) { 17234 // If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup 17235 if (/^(H[1-6]|PRE|FIGURE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') { 17236 newBlock = createNewBlock(newBlockName); 17237 } else { 17238 newBlock = createNewBlock(); 17239 } 17240 17241 // Split the current container block element if enter is pressed inside an empty inner block element 17242 if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) { 17243 // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P 17244 newBlock = dom.split(containerBlock, parentBlock); 17245 } else { 17246 dom.insertAfter(newBlock, parentBlock); 17247 } 17248 17249 moveToCaretPosition(newBlock); 17250 } else if (isCaretAtStartOrEndOfBlock(true)) { 17251 // Insert new block before 17252 newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock); 17253 renderBlockOnIE(newBlock); 17254 moveToCaretPosition(parentBlock); 17255 } else { 17256 // Extract after fragment and insert it after the current block 17257 tmpRng = rng.cloneRange(); 17258 tmpRng.setEndAfter(parentBlock); 17259 fragment = tmpRng.extractContents(); 17260 trimLeadingLineBreaks(fragment); 17261 newBlock = fragment.firstChild; 17262 dom.insertAfter(fragment, parentBlock); 17263 trimInlineElementsOnLeftSideOfBlock(newBlock); 17264 addBrToBlockIfNeeded(parentBlock); 17265 moveToCaretPosition(newBlock); 17266 } 17267 17268 dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique 17269 17270 // Allow custom handling of new blocks 17271 editor.fire('NewBlock', {newBlock: newBlock}); 17272 17273 undoManager.add(); 17274 } 17275 17276 editor.on('keydown', function(evt) { 17277 if (evt.keyCode == 13) { 17278 if (handleEnterKey(evt) !== false) { 17279 evt.preventDefault(); 17280 } 17281 } 17282 }); 17283 }; 17284}); 17285 17286// Included from: js/tinymce/classes/ForceBlocks.js 17287 17288/** 17289 * ForceBlocks.js 17290 * 17291 * Copyright, Moxiecode Systems AB 17292 * Released under LGPL License. 17293 * 17294 * License: http://www.tinymce.com/license 17295 * Contributing: http://www.tinymce.com/contributing 17296 */ 17297 17298define("tinymce/ForceBlocks", [], function() { 17299 return function(editor) { 17300 var settings = editor.settings, dom = editor.dom, selection = editor.selection; 17301 var schema = editor.schema, blockElements = schema.getBlockElements(); 17302 17303 function addRootBlocks() { 17304 var node = selection.getStart(), rootNode = editor.getBody(), rng; 17305 var startContainer, startOffset, endContainer, endOffset, rootBlockNode; 17306 var tempNode, offset = -0xFFFFFF, wrapped, restoreSelection; 17307 var tmpRng, rootNodeName, forcedRootBlock; 17308 17309 forcedRootBlock = settings.forced_root_block; 17310 17311 if (!node || node.nodeType !== 1 || !forcedRootBlock) { 17312 return; 17313 } 17314 17315 // Check if node is wrapped in block 17316 while (node && node != rootNode) { 17317 if (blockElements[node.nodeName]) { 17318 return; 17319 } 17320 17321 node = node.parentNode; 17322 } 17323 17324 // Get current selection 17325 rng = selection.getRng(); 17326 if (rng.setStart) { 17327 startContainer = rng.startContainer; 17328 startOffset = rng.startOffset; 17329 endContainer = rng.endContainer; 17330 endOffset = rng.endOffset; 17331 17332 try { 17333 restoreSelection = editor.getDoc().activeElement === rootNode; 17334 } catch (ex) { 17335 // IE throws unspecified error here sometimes 17336 } 17337 } else { 17338 // Force control range into text range 17339 if (rng.item) { 17340 node = rng.item(0); 17341 rng = editor.getDoc().body.createTextRange(); 17342 rng.moveToElementText(node); 17343 } 17344 17345 restoreSelection = rng.parentElement().ownerDocument === editor.getDoc(); 17346 tmpRng = rng.duplicate(); 17347 tmpRng.collapse(true); 17348 startOffset = tmpRng.move('character', offset) * -1; 17349 17350 if (!tmpRng.collapsed) { 17351 tmpRng = rng.duplicate(); 17352 tmpRng.collapse(false); 17353 endOffset = (tmpRng.move('character', offset) * -1) - startOffset; 17354 } 17355 } 17356 17357 // Wrap non block elements and text nodes 17358 node = rootNode.firstChild; 17359 rootNodeName = rootNode.nodeName.toLowerCase(); 17360 while (node) { 17361 // TODO: Break this up, too complex 17362 if (((node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName]))) && 17363 schema.isValidChild(rootNodeName, forcedRootBlock.toLowerCase())) { 17364 // Remove empty text nodes 17365 if (node.nodeType === 3 && node.nodeValue.length === 0) { 17366 tempNode = node; 17367 node = node.nextSibling; 17368 dom.remove(tempNode); 17369 continue; 17370 } 17371 17372 if (!rootBlockNode) { 17373 rootBlockNode = dom.create(forcedRootBlock, editor.settings.forced_root_block_attrs); 17374 node.parentNode.insertBefore(rootBlockNode, node); 17375 wrapped = true; 17376 } 17377 17378 tempNode = node; 17379 node = node.nextSibling; 17380 rootBlockNode.appendChild(tempNode); 17381 } else { 17382 rootBlockNode = null; 17383 node = node.nextSibling; 17384 } 17385 } 17386 17387 if (wrapped && restoreSelection) { 17388 if (rng.setStart) { 17389 rng.setStart(startContainer, startOffset); 17390 rng.setEnd(endContainer, endOffset); 17391 selection.setRng(rng); 17392 } else { 17393 // Only select if the previous selection was inside the document to prevent auto focus in quirks mode 17394 try { 17395 rng = editor.getDoc().body.createTextRange(); 17396 rng.moveToElementText(rootNode); 17397 rng.collapse(true); 17398 rng.moveStart('character', startOffset); 17399 17400 if (endOffset > 0) { 17401 rng.moveEnd('character', endOffset); 17402 } 17403 17404 rng.select(); 17405 } catch (ex) { 17406 // Ignore 17407 } 17408 } 17409 17410 editor.nodeChanged(); 17411 } 17412 } 17413 17414 // Force root blocks 17415 if (settings.forced_root_block) { 17416 editor.on('NodeChange', addRootBlocks); 17417 } 17418 }; 17419}); 17420 17421// Included from: js/tinymce/classes/EditorCommands.js 17422 17423/** 17424 * EditorCommands.js 17425 * 17426 * Copyright, Moxiecode Systems AB 17427 * Released under LGPL License. 17428 * 17429 * License: http://www.tinymce.com/license 17430 * Contributing: http://www.tinymce.com/contributing 17431 */ 17432 17433/** 17434 * This class enables you to add custom editor commands and it contains 17435 * overrides for native browser commands to address various bugs and issues. 17436 * 17437 * @class tinymce.EditorCommands 17438 */ 17439define("tinymce/EditorCommands", [ 17440 "tinymce/html/Serializer", 17441 "tinymce/Env", 17442 "tinymce/util/Tools", 17443 "tinymce/dom/ElementUtils", 17444 "tinymce/dom/RangeUtils", 17445 "tinymce/dom/TreeWalker" 17446], function(Serializer, Env, Tools, ElementUtils, RangeUtils, TreeWalker) {
17447 // Added for compression purposes 17448 var each = Tools.each, extend = Tools.extend; 17449 var map = Tools.map, inArray = Tools.inArray, explode = Tools.explode; 17450 var isGecko = Env.gecko, isIE = Env.ie, isOldIE = Env.ie && Env.ie < 11; 17451 var TRUE = true, FALSE = false; 17452 17453 return function(editor) { 17454 var dom = editor.dom, 17455 selection = editor.selection, 17456 commands = {state: {}, exec: {}, value: {}}, 17457 settings = editor.settings, 17458 formatter = editor.formatter, 17459 bookmark; 17460 17461 /** 17462 * Executes the specified command. 17463 * 17464 * @method execCommand 17465 * @param {String} command Command to execute. 17466 * @param {Boolean} ui Optional user interface state. 17467 * @param {Object} value Optional value for command. 17468 * @return {Boolean} true/false if the command was found or not. 17469 */ 17470 function execCommand(command, ui, value) { 17471 var func; 17472 17473 command = command.toLowerCase(); 17474 if ((func = commands.exec[command])) { 17475 func(command, ui, value); 17476 return TRUE; 17477 } 17478 17479 return FALSE; 17480 } 17481 17482 /** 17483 * Queries the current state for a command for example if the current selection is "bold". 17484 * 17485 * @method queryCommandState 17486 * @param {String} command Command to check the state of. 17487 * @return {Boolean/Number} true/false if the selected contents is bold or not, -1 if it's not found. 17488 */ 17489 function queryCommandState(command) { 17490 var func; 17491 17492 command = command.toLowerCase(); 17493 if ((func = commands.state[command])) { 17494 return func(command); 17495 } 17496 17497 return -1; 17498 } 17499 17500 /** 17501 * Queries the command value for example the current fontsize. 17502 * 17503 * @method queryCommandValue 17504 * @param {String} command Command to check the value of. 17505 * @return {Object} Command value of false if it's not found. 17506 */ 17507 function queryCommandValue(command) { 17508 var func; 17509 17510 command = command.toLowerCase(); 17511 if ((func = commands.value[command])) { 17512 return func(command); 17513 } 17514 17515 return FALSE; 17516 } 17517 17518 /** 17519 * Adds commands to the command collection. 17520 * 17521 * @method addCommands 17522 * @param {Object} command_list Name/value collection with commands to add, the names can also be c
17522omma separated. 17523 * @param {String} type Optional type to add, defaults to exec. Can be value or state as well. 17524 */ 17525 function addCommands(command_list, type) { 17526 type = type || 'exec'; 17527 17528 each(command_list, function(callback, command) { 17529 each(command.toLowerCase().split(','), function(command) { 17530 commands[type][command] = callback; 17531 }); 17532 }); 17533 } 17534 17535 // Expose public methods 17536 extend(this, { 17537 execCommand: execCommand, 17538 queryCommandState: queryCommandState, 17539 queryCommandValue: queryCommandValue, 17540 addCommands: addCommands 17541 }); 17542 17543 // Private methods 17544 17545 function execNativeCommand(command, ui, value) { 17546 if (ui === undefined) { 17547 ui = FALSE; 17548 } 17549 17550 if (value === undefined) { 17551 value = null; 17552 } 17553 17554 return editor.getDoc().execCommand(command, ui, value); 17555 } 17556 17557 function isFormatMatch(name) { 17558 return formatter.match(name); 17559 } 17560 17561 function toggleFormat(name, value) { 17562 formatter.toggle(name, value ? {value: value} : undefined); 17563 editor.nodeChanged(); 17564 } 17565 17566 function storeSelection(type) { 17567 bookmark = selection.getBookmark(type); 17568 } 17569 17570 function restoreSelection() { 17571 selection.moveToBookmark(bookmark); 17572 } 17573 17574 // Add execCommand overrides 17575 addCommands({ 17576 // Ignore these, added for compatibility 17577 'mceResetDesignMode,mceBeginUndoLevel': function() {}, 17578 17579 // Add undo manager logic 17580 'mceEndUndoLevel,mceAddUndoLevel': function() { 17581 editor.undoManager.add(); 17582 }, 17583 17584 'Cut,Copy,Paste': function(command) { 17585 var doc = editor.getDoc(), failed; 17586 17587 // Try executing the native command 17588 try { 17589 execNativeCommand(command); 17590 } catch (ex) { 17591 // Command failed 17592 failed = TRUE; 17593 } 17594 17595 // Present alert message about clipboard access not being available 17596 if (failed || !doc.queryCommandSupported(command)) { 17597 var msg = editor.translate( 17598 "Your browser doesn't support direct access to the clipboard. " + 17599 "Please use the Ctrl+X/C/V keyboard shortcuts instead." 17600 ); 17601 17602 if (Env.mac) { 17603 msg = msg.replace(/Ctrl\+/g, '\u2318+'); 17604 } 17605 17606 editor.windowManager.alert(msg); 17607 } 17608 }, 17609 17610 // Override unlink command 17611 unlink: function() { 17612 if (selection.isCollapsed()) { 17613 var elm = selection.getNode(); 17614 if (elm.tagName == 'A') { 17615 editor.dom.remove(elm, true); 17616 } 17617 17618 return; 17619 } 17620 17621 formatter.remove("link"); 17622 }, 17623 17624 // Override justify commands to use the text formatter engine 17625 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) { 17626 var align = command.substring(7); 17627 17628 if (align == 'full') {
17629 align = 'justify'; 17630 } 17631 17632 // Remove all other alignments first 17633 each('left,center,right,justify'.split(','), function(name) { 17634 if (align != name) { 17635 formatter.remove('align' + name); 17636 } 17637 }); 17638 17639 toggleFormat('align' + align); 17640 execCommand('mceRepaint'); 17641 }, 17642 17643 // Override list commands to fix WebKit bug 17644 'InsertUnorderedList,InsertOrderedList': function(command) { 17645 var listElm, listParent; 17646 17647 execNativeCommand(command); 17648 17649 // WebKit produces lists within block elements so we need to split them 17650 // we will replace the native list creation logic to custom logic later on 17651 // TODO: Remove this when the list creation logic is removed 17652 listElm = dom.getParent(selection.getNode(), 'ol,ul'); 17653 if (listElm) { 17654 listParent = listElm.parentNode; 17655 17656 // If list is within a text block then split that block 17657 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) { 17658 storeSelection(); 17659 dom.split(listParent, listElm); 17660 restoreSelection(); 17661 } 17662 } 17663 }, 17664 17665 // Override commands to use the text formatter engine 17666 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) { 17667 toggleFormat(command); 17668 }, 17669 17670 // Override commands to use the text formatter engine 17671 'ForeColor,HiliteColor,FontName': function(command, ui, value) { 17672 toggleFormat(command, value); 17673 }, 17674 17675 FontSize: function(command, ui, value) { 17676 var fontClasses, fontSizes; 17677 17678 // Convert font size 1-7 to styles 17679 if (value >= 1 && value <= 7) { 17680 fontSizes = explode(settings.font_size_style_values); 17681 fontClasses = explode(settings.font_size_classes); 17682 17683 if (fontClasses) { 17684 value = fontClasses[value - 1] || value; 17685 } else { 17686 value = fontSizes[value - 1] || value; 17687 } 17688 } 17689 17690 toggleFormat(command, value); 17691 }, 17692 17693 RemoveFormat: function(command) { 17694 formatter.remove(command); 17695 }, 17696 17697 mceBlockQuote: function() { 17698 toggleFormat('blockquote'); 17699 }, 17700 17701 FormatBlock: function(command, ui, value) { 17702 return toggleFormat(value || 'p'); 17703 }, 17704 17705 mceCleanup: function() { 17706 var bookmark = selection.getBookmark(); 17707 17708 editor.setContent(editor.getContent({cleanup: TRUE}), {cleanup: TRUE}); 17709 17710 selection.moveToBookmark(bookmark); 17711 }, 17712 17713 mceRemoveNode: function(command, ui, value) { 17714 var node = value || selection.getNode(); 17715 17716 // Make sure that the body node isn't removed 17717 if (node != editor.getBody()) { 17718 storeSelection(); 17719 editor.dom.remove(node, TRUE); 17720 restoreSelection(); 17721 } 17722 }, 17723 17724 mceSelectNodeDepth: function(command, ui, value) { 17725 var counter = 0; 17726 17727 dom.getParent(selection.getNode(), function(node) { 17728 if (node.nodeType == 1 && counter++ == value) { 17729 selection.select(node); 17730 return FALSE; 17731 } 17732 }, editor.getBody()); 17733 }, 17734 17735 mceSelectNode: function(command, ui, value) { 17736 selection.select(value); 17737 }, 17738 17739 mceInsertContent: function(command, ui, value) { 17740 var parser, serializer, parentNode, rootNode, fragment, args; 17741 var marker, rng, node, node2, bookmarkHtml, merge; 17742 var textInlineElements = editor.schema.getTextInlineElements(); 17743 17744 function trimOrPaddLeftRight(html) { 17745 var rng, container, offset; 17746 17747 rng = selection.getRng(true); 17748 container = rng.startContainer; 17749 offset = rng.startOffset; 17750 17751 function hasSiblingText(siblingName) { 17752 return container[siblingName] && container[siblingName].nodeType == 3; 17753 } 17754 17755 if (container.nodeType == 3) { 17756 if (offset > 0) { 17757 html = html.replace(/^ /, ' '); 17758 } else if (!hasSiblingText('previousSibling')) { 17759 html = html.replace(/^ /, ' '); 17760 } 17761 17762 if (offset < container.length) { 17763 html = html.replace(/ (<br>|)$/, ' '); 17764 } else if (!hasSiblingText('nextSibling')) { 17765 html = html.replace(/( | )(<br>|)$/, ' '); 17766 } 17767 } 17768 17769 return html; 17770 } 17771 17772 function markInlineFormatElements(fragment) { 17773 if (merge) { 17774 for (node = fragment.firstChild; node; node = node.walk(true)) { 17775 if (textInlineElements[node.name]) { 17776 node.attr('data-mce-new', "true"); 17777 } 17778 } 17779 } 17780 } 17781 17782 function reduceInlineTextElements() { 17783 if (merge) { 17784 var root = editor.getBody(), elementUtils = new ElementUtils(dom); 17785 17786 each(dom.select('*[data-mce-new]'), function(node) { 17787 node.removeAttribute('data-mce-new'); 17788 17789 for (var testNode = node.parentNode; testNode && testNode != root; testNode = testNode.parentNode) { 17790 if (elementUtils.compare(testNode, node)) { 17791 dom.remove(node, true); 17792 } 17793 } 17794 }); 17795 } 17796 } 17797 17798 if (typeof(value) != 'string') { 17799 merge = value.merge; 17800 value = value.content; 17801 } 17802 17803 // Check for whitespace before/after value 17804 if (/^ | $/.test(value)) { 17805 value = trimOrPaddLeftRight(value); 17806 } 17807 17808 // Setup parser and serializer 17809 parser = editor.parser; 17810 serializer = new Serializer({}, editor.schema); 17811 bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">
17811​</span>'; 17812 17813 // Run beforeSetContent handlers on the HTML to be inserted 17814 args = {content: value, format: 'html', selection: true}; 17815 editor.fire('BeforeSetContent', args); 17816 value = args.content; 17817 17818 // Add caret at end of contents if it's missing 17819 if (value.indexOf('{$caret}') == -1) { 17820 value += '{$caret}'; 17821 } 17822 17823 // Replace the caret marker with a span bookmark element 17824 value = value.replace(/\{\$caret\}/, bookmarkHtml); 17825 17826 // If selection is at <body>|<p></p> then move it into <body><p>|</p> 17827 rng = selection.getRng(); 17828 var caretElement = rng.startContainer || (rng.parentElement ? rng.parentElement() : null); 17829 var body = editor.getBody(); 17830 if (caretElement === body && selection.isCollapsed()) { 17831 if (dom.isBlock(body.firstChild) && dom.isEmpty(body.firstChild)) { 17832 rng = dom.createRng(); 17833 rng.setStart(body.firstChild, 0); 17834 rng.setEnd(body.firstChild, 0); 17835 selection.setRng(rng); 17836 } 17837 } 17838 17839 // Insert node maker where we will insert the new HTML and get it's parent 17840 if (!selection.isCollapsed()) { 17841 editor.getDoc().execCommand('Delete', false, null); 17842 } 17843 17844 parentNode = selection.getNode(); 17845 17846 // Parse the fragment within the context of the parent node 17847 var parserArgs = {context: parentNode.nodeName.toLowerCase()}; 17848 fragment = parser.parse(value, parserArgs); 17849 17850 markInlineFormatElements(fragment); 17851 17852 // Move the caret to a more suitable location 17853 node = fragment.lastChild; 17854 if (node.attr('id') == 'mce_marker') { 17855 marker = node; 17856 17857 for (node = node.prev; node; node = node.walk(true)) { 17858 if (node.type == 3 || !dom.isBlock(node.name)) { 17859 if (editor.schema.isValidChild(node.parent.name, 'span')) { 17860 node.parent.insert(marker, node, node.name === 'br'); 17861 } 17862 break; 17863 } 17864 } 17865 } 17866 17867 // If parser says valid we can insert the contents into that parent 17868 if (!parserArgs.invalid) { 17869 value = serializer.serialize(fragment); 17870 17871 // Check if parent is empty or only has one BR element then set the innerHTML of that parent 17872 node = parentNode.firstChild; 17873 node2 = parentNode.lastChild; 17874 if (!node || (node === node2 && node.nodeName === 'BR')) { 17875 dom.setHTML(parentNode, value); 17876 } else { 17877 selection.setContent(value); 17878 } 17879 } else {
17880 // If the fragment was invalid within that context then we need 17881 // to parse and process the parent it's inserted into 17882 17883 // Insert bookmark node and get the parent 17884 selection.setContent(bookmarkHtml); 17885 parentNode = selection.getNode(); 17886 rootNode = editor.getBody(); 17887 17888 // Opera will return the document node when selection is in root 17889 if (parentNode.nodeType == 9) { 17890 parentNode = node = rootNode; 17891 } else { 17892 node = parentNode; 17893 } 17894 17895 // Find the ancestor just before the root element 17896 while (node !== rootNode) { 17897 parentNode = node; 17898 node = node.parentNode; 17899 } 17900 17901 // Get the outer/inner HTML depending on if we are in the root and parser and serialize that 17902 value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode); 17903 value = serializer.serialize( 17904 parser.parse( 17905 // Need to replace by using a function since $ in the contents would otherwise be a problem 17906 value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() { 17907 return serializer.serialize(fragment); 17908 }) 17909 ) 17910 ); 17911 17912 // Set the inner/outer HTML depending on if we are in the root or not 17913 if (parentNode == rootNode) { 17914 dom.setHTML(rootNode, value); 17915 } else { 17916 dom.setOuterHTML(parentNode, value); 17917 } 17918 } 17919 17920 reduceInlineTextElements(); 17921 17922 marker = dom.get('mce_marker'); 17923 selection.scrollIntoView(marker); 17924 17925 // Move selection before marker and remove it 17926 rng = dom.createRng(); 17927 17928 // If previous sibling is a text node set the selection to the end of that node 17929 node = marker.previousSibling; 17930 if (node && node.nodeType == 3) { 17931 rng.setStart(node, node.nodeValue.length); 17932 17933 // TODO: Why can't we normalize on IE 17934 if (!isIE) { 17935 node2 = marker.nextSibling; 17936 if (node2 && node2.nodeType == 3) { 17937 node.appendData(node2.data); 17938 node2.parentNode.removeChild(node2); 17939 } 17940 } 17941 } else { 17942 // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node 17943 rng.setStartBefore(marker); 17944 rng.setEndBefore(marker); 17945 } 17946 17947 // Remove the marker node and set the new range 17948 dom.remove(marker); 17949 selection.setRng(rng); 17950 17951 // Dispatch after event and add any visual elements needed 17952 editor.fire('SetContent', args); 17953 editor.addVisual(); 17954 }, 17955 17956 mceInsertRawHTML: function(command, ui, value) { 17957 selection.setContent('tiny_mce_marker'); 17958 editor.setContent( 17959 editor.getContent().replace(/tiny_mce_marker/g, function() { 17960 return value; 17961 }) 17962 ); 17963 }, 17964 17965 mceToggleFormat: function(command, ui, value) { 17966 toggleFormat(value); 17967 }, 17968 17969 mceSetContent: function(command, ui, value) { 17970 editor.setContent(value); 17971 }, 17972 17973 'Indent,Outdent': function(command) { 17974 var intentValue, indentUnit, value; 17975 17976 // Setup indent level 17977 intentValue = settings.indentation; 17978 indentUnit = /[a-z%]+$/i.exec(intentValue); 17979 intentValue = parseInt(intentValue, 10); 17980 17981 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { 17982 // If forced_root_blocks is set to false we don't have a block to indent so lets create a div 17983 if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) { 17984 formatter.apply('div'); 17985 } 17986 17987 each(selection.getSelectedBlocks(), function(element) { 17988 if (element.nodeName != "LI") { 17989 var indentStyleName = editor.getParam('indent_use_margin', false) ? 'margin' : 'padding'; 17990 17991 indentStyleName += dom.getStyle(element, 'direction', true) == 'rtl' ? 'Right' : 'Left'; 17992 17993 if (command == 'outdent') { 17994 value = Math.max(0, parseInt(element.style[indentStyleName] || 0, 10) - intentValue); 17995 dom.setStyle(element, indentStyleName, value ? value + indentUnit : ''); 17996 } else { 17997 value = (parseInt(element.style[indentStyleName] || 0, 10) + intentValue) + indentUnit; 17998 dom.setStyle(element, indentStyleName, value); 17999 } 18000 } 18001 }); 18002 } else { 18003 execNativeCommand(command); 18004 } 18005 }, 18006 18007 mceRepaint: function() { 18008 if (isGecko) { 18009 try { 18010 storeSelection(TRUE); 18011 18012 if (selection.getSel()) { 18013 selection.getSel().selectAllChildren(editor.getBody()); 18014 } 18015 18016 selection.collapse(TRUE); 18017 restoreSelection(); 18018 } catch (ex) { 18019 // Ignore 18020 } 18021 } 18022 }, 18023 18024 InsertHorizontalRule: function() { 18025 editor.execCommand('mceInsertContent', false, '<hr />'); 18026 }, 18027 18028 mceToggleVisualAid: function() { 18029 editor.hasVisual = !editor.hasVisual; 18030 editor.addVisual(); 18031 }, 18032 18033 mceReplaceContent: function(command, ui, value) { 18034 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format: 'text'}))); 18035 }, 18036 18037 mceInsertLink: function(command, ui, value) { 18038 var anchor; 18039 18040 if (typeof(value) == 'string') { 18041 value = {href: value}; 18042 } 18043 18044 anchor = dom.getParent(selection.getNode(), 'a'); 18045 18046 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here. 18047 value.href = value.href.replace(' ', '%20'); 18048 18049 // Remove existing links if there could be child links or that the href isn't specified 18050 if (!anchor || !value.href) { 18051 formatter.remove('link'); 18052 } 18053 18054 // Apply new link to selection 18055 if (value.href) { 18056 formatter.apply('link', value, anchor); 18057 } 18058 }, 18059 18060 selectAll: function() { 18061 var root = dom.getRoot(), rng; 18062 18063 if (selection.getRng().setStart) { 18064 rng = dom.createRng(); 18065 rng.setStart(root, 0); 18066 rng.setEnd(root, root.childNodes.length); 18067 selection.setRng(rng); 18068 } else {
18069 // IE will render it's own root level block elements and sometimes 18070 // even put font elements in them when the user starts typing. So we need to 18071 // move the selection to a more suitable element from this: 18072 // <body>|<p></p></body> to this: <body><p>|</p></body> 18073 rng = selection.getRng(); 18074 if (!rng.item) { 18075 rng.moveToElementText(root); 18076 rng.select(); 18077 } 18078 } 18079 }, 18080 18081 "delete": function() { 18082 execNativeCommand("Delete"); 18083 18084 // Check if body is empty after the delete call if so then set the contents 18085 // to an empty string and move the caret to any block produced by that operation 18086 // this fixes the issue with root blocks not being properly produced after a delete call on IE 18087 var body = editor.getBody(); 18088 18089 if (dom.isEmpty(body)) { 18090 editor.setContent(''); 18091 18092 if (body.firstChild && dom.isBlock(body.firstChild)) { 18093 editor.selection.setCursorLocation(body.firstChild, 0); 18094 } else { 18095 editor.selection.setCursorLocation(body, 0); 18096 } 18097 } 18098 }, 18099 18100 mceNewDocument: function() { 18101 editor.setContent(''); 18102 }, 18103 18104 InsertLineBreak: function(command, ui, value) { 18105 // We load the current event in from EnterKey.js when appropriate to heed 18106 // certain event-specific variations such as ctrl-enter in a list 18107 var evt = value; 18108 var brElm, extraBr, marker; 18109 var rng = selection.getRng(true); 18110 new RangeUtils(dom).normalize(rng); 18111 18112 var offset = rng.startOffset; 18113 var container = rng.startContainer; 18114 18115 // Resolve node index 18116 if (container.nodeType == 1 && container.hasChildNodes()) { 18117 var isAfterLastNodeInContainer = offset > container.childNodes.length - 1; 18118 18119 container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container; 18120 if (isAfterLastNodeInContainer && container.nodeType == 3) { 18121 offset = container.nodeValue.length; 18122 } else { 18123 offset = 0; 18124 } 18125 } 18126 18127 var parentBlock = dom.getParent(container, dom.isBlock); 18128 var parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 18129 var containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null; 18130 var containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 18131 18132 // Enter inside block contained within a LI then split or insert before/after LI 18133 var isControlKey = evt && evt.ctrlKey; 18134 if (containerBlockName == 'LI' && !isControlKey) { 18135 parentBlock = containerBlock; 18136 parentBlockName = containerBlockName; 18137 } 18138 18139 // Walks the parent block to the right and look for BR elements 18140 function hasRightSideContent() { 18141 var walker = new TreeWalker(container, parentBlock), node; 18142 var nonEmptyElementsMap = editor.schema.getNonEmptyElements(); 18143 18144 while ((node = walker.next())) { 18145 if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) { 18146 return true; 18147 } 18148 } 18149 } 18150 18151 if (container && container.nodeType == 3 && offset >= container.nodeValue.length) { 18152 // Insert extra BR element at the end block elements 18153 if (!isOldIE && !hasRightSideContent()) { 18154 brElm = dom.create('br'); 18155 rng.insertNode(brElm); 18156 rng.setStartAfter(brElm); 18157 rng.setEndAfter(brElm); 18158 extraBr = true; 18159 } 18160 } 18161 18162 brElm = dom.create('br'); 18163 rng.insertNode(brElm); 18164 18165 // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it 18166 var documentMode = dom.doc.documentMode; 18167 if (isOldIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) { 18168 brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm); 18169 } 18170 18171 // Insert temp marker and scroll to that 18172 marker = dom.create('span', {}, ' '); 18173 brElm.parentNode.insertBefore(marker, brElm); 18174 selection.scrollIntoView(marker); 18175 dom.remove(marker); 18176 18177 if (!extraBr) { 18178 rng.setStartAfter(brElm); 18179 rng.setEndAfter(brElm); 18180 } else { 18181 rng.setStartBefore(brElm); 18182 rng.setEndBefore(brElm); 18183 } 18184 18185 selection.setRng(rng); 18186 editor.undoManager.add(); 18187 18188 return TRUE; 18189 } 18190 }); 18191 18192 // Add queryCommandState overrides 18193 addCommands({ 18194 // Override justify commands 18195 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) { 18196 var name = 'align' + command.substring(7); 18197 var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks(); 18198 var matches = map(nodes, function(node) { 18199 return !!formatter.matchNode(node, name); 18200 }); 18201 return inArray(matches, TRUE) !== -1; 18202 }, 18203 18204 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) { 18205 return isFormatMatch(command); 18206 }, 18207 18208 mceBlockQuote: function() { 18209 return isFormatMatch('blockquote'); 18210 }, 18211 18212 Outdent: function() { 18213 var node; 18214 18215 if (settings.inline_styles) { 18216 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) { 18217 return TRUE; 18218 } 18219 18220 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) { 18221 return TRUE; 18222 } 18223 } 18224 18225 return ( 18226 queryCommandState('InsertUnorderedList') || 18227 queryCommandState('InsertOrderedList') || 18228 (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')) 18229 ); 18230 }, 18231 18232 'InsertUnorderedList,InsertOrderedList': function(command) { 18233 var list = dom.getParent(selection.getNode(), 'ul,ol'); 18234 18235 return list && 18236 ( 18237 command === 'insertunorderedlist' && list.tagName === 'UL' || 18238 command === 'insertorderedlist' && list.tagName === 'OL' 18239 ); 18240 } 18241 }, 'state'); 18242 18243 // Add queryCommandValue overrides 18244 addCommands({ 18245 'FontSize,FontName': function(command) { 18246 var value = 0, parent; 18247 18248 if ((parent = dom.getParent(selection.getNode(), 'span'))) { 18249 if (command == 'fontsize') { 18250 value = parent.style.fontSize; 18251 } else { 18252 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); 18253 } 18254 } 18255 18256 return value; 18257 } 18258 }, 'value'); 18259 18260 // Add undo manager logic 18261 addCommands({ 18262 Undo: function() { 18263 editor.undoManager.undo(); 18264 }, 18265 18266 Redo: function() { 18267 editor.undoManager.redo(); 18268 } 18269 }); 18270 }; 18271}); 18272 18273// Included from: js/tinymce/classes/util/URI.js 18274 18275/** 18276 * URI.js 18277 * 18278 * Copyright, Moxiecode Systems AB 18279 * Released under LGPL License. 18280 * 18281 * License: http://www.tinymce.com/license 18282 * Contributing: http://www.tinymce.com/contributing 18283 */ 18284 18285/** 18286 * This class handles parsing, modification and serialization of URI/URL strings. 18287 * @class tinymce.util.URI 18288 */ 18289define("tinymce/util/URI", [ 18290 "tinymce/util/Tools" 18291], function(Tools) { 18292 var each = Tools.each, trim = Tools.trim; 18293 var queryParts = "source protocol authority userInfo user password host port relative path directory file query anchor".split(' '); 18294 var DEFAULT_PORTS = { 18295 'ftp': 21, 18296 'http': 80, 18297 'https': 443, 18298 'mailto': 25 18299 }; 18300 18301 /** 18302 * Constructs a new URI instance. 18303 * 18304 * @constructor 18305 * @method URI 18306 * @param {String} url URI string to parse. 18307 * @param {Object} settings Optional settings object. 18308 */ 18309 function URI(url, settings) { 18310 var self = this, baseUri, base_url; 18311 18312 url = trim(url); 18313 settings = self.settings = settings || {}; 18314 baseUri = settings.base_uri; 18315 18316 // Strange app protocol that isn't http/https or local anchor 18317 // For example: mailto,skype,tel etc. 18318 if (/^([\w\-]+):([^\/]{2})/i.test(url) || /^\s*#/.test(url)) { 18319 self.source = url; 18320 return; 18321 } 18322 18323 var isProtocolRelative = url.indexOf('//') === 0; 18324
18325 // Absolute path with no host, fake host and protocol 18326 if (url.indexOf('/') === 0 && !isProtocolRelative) { 18327 url = (baseUri ? baseUri.protocol || 'http' : 'http') + '://mce_host' + url; 18328 } 18329 18330 // Relative path http:// or protocol relative //path 18331 if (!/^[\w\-]*:?\/\//.test(url)) { 18332 base_url = settings.base_uri ? settings.base_uri.path : new URI(location.href).directory; 18333 if (settings.base_uri.protocol === "") { 18334 url = '//mce_host' + self.toAbsPath(base_url, url); 18335 } else { 18336 url = /([^#?]*)([#?]?.*)/.exec(url); 18337 url = ((baseUri && baseUri.protocol) || 'http') + '://mce_host' + self.toAbsPath(base_url, url[1]) + url[2]; 18338 } 18339 } 18340 18341 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri) 18342 url = url.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something 18343 18344 /*jshint maxlen: 255 */ 18345 /*eslint max-len: 0 */ 18346 url = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(url); 18347 18348 each(queryParts, function(v, i) { 18349 var part = url[i]; 18350 18351 // Zope 3 workaround, they use @@something 18352 if (part) { 18353 part = part.replace(/\(mce_at\)/g, '@@'); 18354 } 18355 18356 self[v] = part; 18357 }); 18358 18359 if (baseUri) { 18360 if (!self.protocol) { 18361 self.protocol = baseUri.protocol; 18362 } 18363 18364 if (!self.userInfo) { 18365 self.userInfo = baseUri.userInfo; 18366 } 18367 18368 if (!self.port && self.host === 'mce_host') { 18369 self.port = baseUri.port; 18370 } 18371 18372 if (!self.host || self.host === 'mce_host') { 18373 self.host = baseUri.host; 18374 } 18375 18376 self.source = ''; 18377 } 18378 18379 if (isProtocolRelative) { 18380 self.protocol = ''; 18381 } 18382 18383 //t.path = t.path || '/'; 18384 } 18385 18386 URI.prototype = { 18387 /** 18388 * Sets the internal path part of the URI. 18389 * 18390 * @method setPath 18391 * @param {string} path Path string to set. 18392 */ 18393 setPath: function(path) { 18394 var self = this; 18395 18396 path = /^(.*?)\/?(\w+)?$/.exec(path); 18397 18398 // Update path parts 18399 self.path = path[0]; 18400 self.directory = path[1]; 18401 self.file = path[2]; 18402 18403 // Rebuild source 18404 self.source = ''; 18405 self.getURI(); 18406 }, 18407 18408 /** 18409 * Converts the specified URI into a relative URI based on the current URI instance location. 18410 * 18411 * @method toRelative 18412 * @param {String} uri URI to convert into a relative path/URI. 18413 * @return {String} Relative URI from the point specified in the current URI instance. 18414 * @example 18415 * // Converts an absolute URL to an relative URL url will be somedir/somefile.htm 18416 * var url = new tinymce.util.URI('http://www.site.com/dir/').toRelative('http://www.site.com/dir/somedir/somefile.htm'); 18417 */ 18418 toRelative: function(uri) { 18419 var self = this, output; 18420 18421 if (uri === "./") { 18422 return uri; 18423 } 18424 18425 uri = new URI(uri, {base_uri: self}); 18426 18427 // Not on same domain/port or protocol 18428 if ((uri.host != 'mce_host' && self.host != uri.host && uri.host) || self.port != uri.port || 18429 (self.protocol != uri.protocol && uri.protocol !== "")) { 18430 return uri.getURI(); 18431 } 18432 18433 var tu = self.getURI(), uu = uri.getURI(); 18434 18435 // Allow usage of the base_uri when relative_urls = true 18436 if (tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) { 18437 return tu; 18438 } 18439 18440 output = self.toRelPath(self.path, uri.path); 18441 18442 // Add query 18443 if (uri.query) { 18444 output += '?' + uri.query; 18445 } 18446 18447 // Add anchor 18448 if (uri.anchor) { 18449 output += '#' + uri.anchor; 18450 } 18451 18452 return output; 18453 }, 18454 18455 /** 18456 * Converts the specified URI into a absolute URI based on the current URI instance location. 18457 * 18458 * @method toAbsolute 18459 * @param {String} uri URI to convert into a relative path/URI. 18460 * @param {Boolean} noHost No host and protocol prefix. 18461 * @return {String} Absolute URI from the point specified in the current URI instance. 18462 * @example 18463 * // Converts an relative URL to an absolute URL url will be http://www.site.com/dir/somedir/somefile.htm 18464 * var url = new tinymce.util.URI('http://www.site.com/dir/').toAbsolute('somedir/somefile.htm'); 18465 */ 18466 toAbsolute: function(uri, noHost) { 18467 uri = new URI(uri, {base_uri: this}); 18468 18469 return uri.getURI(noHost && this.isSameOrigin(uri)); 18470 }, 18471 18472 /** 18473 * Determine whether the given URI has the same origin as this URI. Based on RFC-6454. 18474 * Supports default ports for protocols listed in DEFAULT_PORTS. Unsupported protocols will fail safe: they 18475 * won't match, if the port specifications differ. 18476 * 18477 * @method isSameOrigin 18478 * @param {tinymce.util.URI} uri Uri instance to compare. 18479 * @returns {Boolean} True if the origins are the same. 18480 */ 18481 isSameOrigin: function(uri) { 18482 if (this.host == uri.host && this.protocol == uri.protocol) { 18483 if (this.port == uri.port) { 18484 return true; 18485 } 18486 18487 var defaultPort = DEFAULT_PORTS[this.protocol]; 18488 if (defaultPort && ((this.port || defaultPort) == (uri.port || defaultPort))) { 18489 return true; 18490 } 18491 } 18492 18493 return false;
18494 }, 18495 18496 /** 18497 * Converts a absolute path into a relative path. 18498 * 18499 * @method toRelPath 18500 * @param {String} base Base point to convert the path from. 18501 * @param {String} path Absolute path to convert into a relative path. 18502 */ 18503 toRelPath: function(base, path) { 18504 var items, breakPoint = 0, out = '', i, l; 18505 18506 // Split the paths 18507 base = base.substring(0, base.lastIndexOf('/')); 18508 base = base.split('/'); 18509 items = path.split('/'); 18510 18511 if (base.length >= items.length) { 18512 for (i = 0, l = base.length; i < l; i++) { 18513 if (i >= items.length || base[i] != items[i]) { 18514 breakPoint = i + 1; 18515 break; 18516 } 18517 } 18518 } 18519 18520 if (base.length < items.length) { 18521 for (i = 0, l = items.length; i < l; i++) { 18522 if (i >= base.length || base[i] != items[i]) { 18523 breakPoint = i + 1; 18524 break; 18525 } 18526 } 18527 } 18528 18529 if (breakPoint === 1) { 18530 return path; 18531 } 18532 18533 for (i = 0, l = base.length - (breakPoint - 1); i < l; i++) { 18534 out += "../"; 18535 } 18536 18537 for (i = breakPoint - 1, l = items.length; i < l; i++) { 18538 if (i != breakPoint - 1) { 18539 out += "/" + items[i]; 18540 } else { 18541 out += items[i]; 18542 } 18543 } 18544 18545 return out; 18546 }, 18547 18548 /** 18549 * Converts a relative path into a absolute path. 18550 * 18551 * @method toAbsPath 18552 * @param {String} base Base point to convert the path from. 18553 * @param {String} path Relative path to convert into an absolute path. 18554 */ 18555 toAbsPath: function(base, path) { 18556 var i, nb = 0, o = [], tr, outPath; 18557 18558 // Split paths 18559 tr = /\/$/.test(path) ? '/' : ''; 18560 base = base.split('/'); 18561 path = path.split('/'); 18562 18563 // Remove empty chunks 18564 each(base, function(k) { 18565 if (k) { 18566 o.push(k); 18567 } 18568 }); 18569 18570 base = o; 18571 18572 // Merge relURLParts chunks 18573 for (i = path.length - 1, o = []; i >= 0; i--) { 18574 // Ignore empty or . 18575 if (path[i].length === 0 || path[i] === ".") { 18576 continue; 18577 } 18578 18579 // Is parent 18580 if (path[i] === '..') { 18581 nb++; 18582 continue; 18583 } 18584 18585 // Move up 18586 if (nb > 0) { 18587 nb--; 18588 continue; 18589 } 18590 18591 o.push(path[i]); 18592 } 18593 18594 i = base.length - nb; 18595 18596 // If /a/b/c or / 18597 if (i <= 0) { 18598 outPath = o.reverse().join('/'); 18599 } else { 18600 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/'); 18601 } 18602 18603 // Add front / if it's needed 18604 if (outPath.indexOf('/') !== 0) { 18605 outPath = '/' + outPath; 18606 } 18607 18608 // Add traling / if it's needed 18609 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) { 18610 outPath += tr; 18611 } 18612 18613 return outPath; 18614 }, 18615 18616 /** 18617 * Returns the full URI of the internal structure. 18618 * 18619 * @method getURI 18620 * @param {Boolean} noProtoHost Optional no host and protocol part. Defaults to false. 18621 */ 18622 getURI: function(noProtoHost) { 18623 var s, self = this; 18624 18625 // Rebuild source 18626 if (!self.source || noProtoHost) { 18627 s = ''; 18628 18629 if (!noProtoHost) { 18630 if (self.protocol) { 18631 s += self.protocol + '://'; 18632 } else { 18633 s += '//'; 18634 } 18635 18636 if (self.userInfo) { 18637 s += self.userInfo + '@'; 18638 } 18639 18640 if (self.host) { 18641 s += self.host; 18642 } 18643 18644 if (self.port) { 18645 s += ':' + self.port; 18646 } 18647 } 18648 18649 if (self.path) { 18650 s += self.path; 18651 } 18652 18653 if (self.query) { 18654 s += '?' + self.query; 18655 } 18656 18657 if (self.anchor) { 18658 s += '#' + self.anchor; 18659 } 18660 18661 self.source = s; 18662 } 18663 18664 return self.source; 18665 } 18666 }; 18667 18668 return URI; 18669}); 18670 18671// Included from: js/tinymce/classes/util/Class.js 18672 18673/** 18674 * Class.js 18675 * 18676 * Copyright 2003-2012, Moxiecode Systems AB, All rights reserved. 18677 */ 18678 18679/** 18680 * This utilitiy class is used for easier inheritage. 18681 * 18682 * Features: 18683 * * Exposed super functions: this._super(); 18684 * * Mixins 18685 * * Dummy functions 18686 * * Property functions: var value = object.value(); and object.value(newValue); 18687 * * Static functions 18688 * * Defaults settings 18689 */ 18690define("tinymce/util/Class", [ 18691 "tinymce/util/Tools" 18692], function(Tools) { 18693 var each = Tools.each, extend = Tools.extend; 18694 18695 var extendClass, initializing; 18696 18697 function Class() { 18698 } 18699 18700 // Provides classical inheritance, based on code made by John Resig 18701 Class.extend = extendClass = function(prop) { 18702 var self = this, _super = self.prototype, prototype, name, member; 18703 18704 // The dummy class constructor 18705 function Class() { 18706 var i, mixins, mixin, self = this; 18707 18708 // All construction is actually done in the init method 18709 if (!initializing) { 18710 // Run class constuctor 18711 if (self.init) {
18712 self.init.apply(self, arguments); 18713 } 18714 18715 // Run mixin constructors 18716 mixins = self.Mixins; 18717 if (mixins) { 18718 i = mixins.length; 18719 while (i--) { 18720 mixin = mixins[i]; 18721 if (mixin.init) { 18722 mixin.init.apply(self, arguments); 18723 } 18724 } 18725 } 18726 } 18727 } 18728 18729 // Dummy function, needs to be extended in order to provide functionality 18730 function dummy() { 18731 return this; 18732 } 18733 18734 // Creates a overloaded method for the class 18735 // this enables you to use this._super(); to call the super function 18736 function createMethod(name, fn) { 18737 return function() { 18738 var self = this, tmp = self._super, ret; 18739 18740 self._super = _super[name]; 18741 ret = fn.apply(self, arguments); 18742 self._super = tmp; 18743 18744 return ret; 18745 }; 18746 } 18747 18748 // Instantiate a base class (but only create the instance, 18749 // don't run the init constructor) 18750 initializing = true; 18751 18752 /*eslint new-cap:0 */ 18753 prototype = new self(); 18754 initializing = false; 18755 18756 // Add mixins 18757 if (prop.Mixins) { 18758 each(prop.Mixins, function(mixin) { 18759 mixin = mixin; 18760 18761 for (var name in mixin) { 18762 if (name !== "init") { 18763 prop[name] = mixin[name]; 18764 } 18765 } 18766 }); 18767 18768 if (_super.Mixins) { 18769 prop.Mixins = _super.Mixins.concat(prop.Mixins); 18770 } 18771 } 18772 18773 // Generate dummy methods 18774 if (prop.Methods) { 18775 each(prop.Methods.split(','), function(name) { 18776 prop[name] = dummy; 18777 }); 18778 } 18779 18780 // Generate property methods 18781 if (prop.Properties) { 18782 each(prop.Properties.split(','), function(name) { 18783 var fieldName = '_' + name; 18784 18785 prop[name] = function(value) { 18786 var self = this, undef; 18787 18788 // Set value 18789 if (value !== undef) { 18790 self[fieldName] = value; 18791 18792 return self; 18793 } 18794 18795 // Get value 18796 return self[fieldName]; 18797 }; 18798 }); 18799 } 18800 18801 // Static functions 18802 if (prop.Statics) { 18803 each(prop.Statics, function(func, name) { 18804 Class[name] = func; 18805 }); 18806 } 18807 18808 // Default settings 18809 if (prop.Defaults && _super.Defaults) { 18810 prop.Defaults = extend({}, _super.Defaults, prop.Defaults); 18811 } 18812 18813 // Copy the properties over onto the new prototype 18814 for (name in prop) { 18815 member = prop[name]; 18816 18817 if (typeof member == "function" && _super[name]) { 18818 prototype[name] = createMethod(name, member); 18819 } else { 18820 prototype[name] = member; 18821 } 18822 } 18823 18824 // Populate our constructed prototype object 18825 Class.prototype = prototype; 18826 18827 // Enforce the constructor to be what we expect 18828 Class.constructor = Class; 18829 18830 // And make this class extendible 18831 Class.extend = extendClass; 18832 18833 return Class; 18834 }; 18835 18836 return Class; 18837}); 18838 18839// Included from: js/tinymce/classes/util/EventDispatcher.js 18840 18841/** 18842 * EventDispatcher.js 18843 * 18844 * Copyright, Moxiecode Systems AB 18845 * Released under LGPL License. 18846 * 18847 * License: http://www.tinymce.com/license 18848 * Contributing: http://www.tinymce.com/contributing 18849 */ 18850 18851/** 18852 * This class lets you add/remove and fire events by name on the specified scope. This makes 18853 * it easy to add event listener logic to any class. 18854 * 18855 * @class tinymce.util.EventDispatcher 18856 * @example 18857 * var eventDispatcher = new EventDispatcher(); 18858 * 18859 * eventDispatcher.on('click', function() {console.log('data');}); 18860 * eventDispatcher.fire('click', {data: 123}); 18861 */ 18862define("tinymce/util/EventDispatcher", [ 18863 "tinymce/util/Tools" 18864], function(Tools) { 18865 var nativeEvents = Tools.makeMap( 18866 "focus blur focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange " + 18867 "mouseout mouseenter mouseleave wheel keydown keypress keyup input contextmenu dragstart dragend dragover " + 18868 "draggesture dragdrop drop drag submit " + 18869 "compositionstart compositionend compositionupdate touchstart touchend", 18870 ' ' 18871 ); 18872 18873 function Dispatcher(settings) { 18874 var self = this, scope, bindings = {}, toggleEvent; 18875 18876 function returnFalse() { 18877 return false; 18878 } 18879 18880 function returnTrue() { 18881 return true; 18882 } 18883 18884 settings = settings || {}; 18885 scope = settings.scope || self; 18886 toggleEvent = settings.toggleEvent || returnFalse; 18887 18888 /** 18889 * Fires the specified event by name. 18890 * 18891 * @method fire 18892 * @param {String} name Name of the event to fire. 18893 * @param {Object?} args Event arguments. 18894 * @return {Object} Event args instance passed in. 18895 * @example 18896 * instance.fire('event', {...}); 18897 */ 18898 function fire(name, args) { 18899 var handlers, i, l, callback; 18900 18901 name = name.toLowerCase(); 18902 args = args || {}; 18903 args.type = name; 18904 18905 // Setup target is there isn't one 18906 if (!args.target) { 18907 args.target = scope; 18908 } 18909 18910 // Add event delegation methods if they are missing 18911 if (!args.preventDefault) { 18912 // Add preventDefault method 18913 args.preventDefault = function() { 18914 args.isDefaultPrevented = returnTrue; 18915 }; 18916 18917 // Add stopPropagation 18918 args.stopPropagation = function() { 18919 args.isPropagationStopped = returnTrue; 18920 }; 18921 18922 // Add stopImmediatePropagation 18923 args.stopImmediatePropagation = function() { 18924 args.isImmediatePropagationStopped = returnTrue; 18925 }; 18926 18927 // Add event delegation states 18928 args.isDefaultPrevented = returnFalse; 18929 args.isPropagationStopped = returnFalse; 18930 args.isImmediatePropagationStopped = returnFalse; 18931 } 18932 18933 if (settings.beforeFire) { 18934 settings.beforeFire(args); 18935 } 18936 18937 handlers = bindings[name]; 18938 if (handlers) { 18939 for (i = 0, l = handlers.length; i < l; i++) { 18940 callback = handlers[i]; 18941 18942 // Unbind handlers marked with "once" 18943 if (callback.once) { 18944 off(name, callback.func); 18945 } 18946 18947 // Stop immediate propagation if needed 18948 if (args.isImmediatePropagationStopped()) { 18949 args.stopPropagation(); 18950 return args; 18951 } 18952 18953 // If callback returns false then prevent default and stop all propagation 18954 if (callback.func.call(scope, args) === false) { 18955 args.preventDefault(); 18956 return args; 18957 } 18958 } 18959 } 18960 18961 return args; 18962 } 18963 18964 /** 18965 * Binds an event listener to a specific event by name. 18966 * 18967 * @method on 18968 * @param {String} name Event name or space separated list of events to bind. 18969 * @param {callback} callback Callback to be executed when the event occurs. 18970 * @param {Boolean} first Optional flag if the event should be prepended. Use this with care. 18971 * @return {Object} Current class instance. 18972 * @example 18973 * instance.on('event', function(e) { 18974 * // Callback logic 18975 * }); 18976 */ 18977 function on(name, callback, prepend, extra) { 18978 var handlers, names, i; 18979 18980 if (callback === false) { 18981 callback = returnFalse; 18982 } 18983 18984 if (callback) { 18985 callback = { 18986 func: callback 18987 }; 18988 18989 if (extra) { 18990 Tools.extend(callback, extra); 18991 } 18992 18993 names = name.toLowerCase().split(' '); 18994 i = names.length; 18995 while (i--) { 18996 name = names[i]; 18997 handlers = bindings[name]; 18998 if (!handlers) { 18999 handlers = bindings[name] = []; 19000 toggleEvent(name, true); 19001 } 19002 19003 if (prepend) { 19004 handlers.unshift(callback); 19005 } else { 19006 handlers.push(callback); 19007 } 19008 } 19009 } 19010 19011 return self; 19012 } 19013 19014 /** 19015 * Unbinds an event listener to a specific event by name. 19016 * 19017 * @method off 19018 * @param {String?} name Name of the event to unbind. 19019 * @param {callback?} callback Callback to unbind. 19020 * @return {Object} Current class instance. 19021 * @example 19022 * // Unbind specific callback 19023 * instance.off('event', handler); 19024 * 19025 * // Unbind all listeners by name 19026 * instance.off('event'); 19027 * 19028 * // Unbind all events 19029 * instance.off(); 19030 */ 19031 function off(name, callback) { 19032 var i, handlers, bindingName, names, hi; 19033 19034 if (name) { 19035 names = name.toLowerCase().split(' '); 19036 i = names.length; 19037 while (i--) { 19038 name = names[i]; 19039 handlers = bindings[name]; 19040 19041 // Unbind all handlers 19042 if (!name) { 19043 for (bindingName in bindings) { 19044 toggleEvent(bindingName, false); 19045 delete bindings[bindingName]; 19046 } 19047 19048 return self; 19049 } 19050 19051 if (handlers) { 19052 // Unbind all by name 19053 if (!callback) { 19054 handlers.length = 0; 19055 } else { 19056 // Unbind specific ones 19057 hi = handlers.length; 19058 while (hi--) { 19059 if (handlers[hi].func === callback) { 19060 handlers = handlers.slice(0, hi).concat(handlers.slice(hi + 1)); 19061 bindings[name] = handlers; 19062 } 19063 } 19064 } 19065 19066 if (!handlers.length) { 19067 toggleEvent(name, false); 19068 delete bindings[name]; 19069 } 19070 } 19071 } 19072 } else { 19073 for (name in bindings) { 19074 toggleEvent(name, false); 19075 } 19076 19077 bindings = {}; 19078 } 19079 19080 return self; 19081 } 19082 19083 /** 19084 * Binds an event listener to a specific event by name 19085 * and automatically unbind the event once the callback fires. 19086 * 19087 * @method once 19088 * @param {String} name Event name or space separated list of events to bind. 19089 * @param {callback} callback Callback to be executed when the event occurs. 19090 * @param {Boolean} first Optional flag if the event should be prepended. Use this with care. 19091 * @return {Object} Current class instance. 19092 * @example 19093 * instance.once('event', function(e) { 19094 * // Callback logic 19095 * }); 19096 */ 19097 function once(name, callback, prepend) { 19098 return on(name, callback, prepend, {once: true}); 19099 } 19100 19101 /** 19102 * Returns true/false if the dispatcher has a event of the specified name. 19103 * 19104 * @method has 19105 * @param {String} name Name of the event to check for. 19106 * @return {Boolean} true/false if the event exists or not. 19107 */ 19108 function has(name) { 19109 name = name.toLowerCase(); 19110 return !(!bindings[name] || bindings[name].length === 0); 19111 } 19112 19113 // Expose 19114 self.fire = fire; 19115 self.on = on; 19116 self.off = off; 19117 self.once = once; 19118 self.has = has; 19119 } 19120 19121 /** 19122 * Returns true/false if the specified event name is a native browser event or not. 19123 * 19124 * @method isNative 19125 * @param {String} name Name to check if it's native. 19126 * @return {Boolean} true/false if the event is native or not. 19127 * @static 19128 */ 19129 Dispatcher.isNative = function(name) { 19130 return !!nativeEvents[name.toLowerCase()]; 19131 }; 19132 19133 return Dispatcher; 19134}); 19135 19136// Included from: js/tinymce/classes/ui/Selector.js 19137 19138/** 19139 * Selector.js 19140 * 19141 * Copyright, Moxiecode Systems AB 19142 * Released under LGPL License. 19143 * 19144 * License: http://www.tinymce.com/license 19145 * Contributing: http://www.tinymce.com/contributing 19146 */ 19147 19148/*eslint no-nested-ternary:0 */ 19149 19150/** 19151 * Selector engine, enables you to select controls by using CSS like expressions. 19152 * We currently only support basic CSS expressions to reduce the size of the core 19153 * and the ones we support should be enough for most cases. 19154 * 19155 * @example 19156 * Supported expressions: 19157 * element 19158 * element#name 19159 * element.class 19160 * element[attr] 19161 * element[attr*=value] 19162 * element[attr~=value] 19163 * element[attr!=value] 19164 * element[attr^=value] 19165 * element[attr$=value] 19166 * element:<state> 19167 * element:not(<expression>) 19168 * element:first 19169 * element:last 19170 * element:odd 19171 * element:even 19172 * element element 19173 * element > element 19174 * 19175 * @class tinymce.ui.Selector 19176 */
19177define("tinymce/ui/Selector", [ 19178 "tinymce/util/Class" 19179], function(Class) { 19180 "use strict"; 19181 19182 /** 19183 * Produces an array with a unique set of objects. It will not compare the values 19184 * but the references of the objects. 19185 * 19186 * @private 19187 * @method unqiue 19188 * @param {Array} array Array to make into an array with unique items. 19189 * @return {Array} Array with unique items. 19190 */ 19191 function unique(array) { 19192 var uniqueItems = [], i = array.length, item; 19193 19194 while (i--) { 19195 item = array[i]; 19196 19197 if (!item.__checked) { 19198 uniqueItems.push(item); 19199 item.__checked = 1; 19200 } 19201 } 19202 19203 i = uniqueItems.length; 19204 while (i--) { 19205 delete uniqueItems[i].__checked; 19206 } 19207 19208 return uniqueItems; 19209 } 19210 19211 var expression = /^([\w\\*]+)?(?:#([\w\\]+))?(?:\.([\w\\\.]+))?(?:\[\@?([\w\\]+)([\^\$\*!~]?=)([\w\\]+)\])?(?:\:(.+))?/i; 19212 19213 /*jshint maxlen:255 */ 19214 /*eslint max-len:0 */ 19215 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, 19216 whiteSpace = /^\s*|\s*$/g, 19217 Collection; 19218 19219 var Selector = Class.extend({ 19220 /** 19221 * Constructs a new Selector instance. 19222 * 19223 * @constructor 19224 * @method init 19225 * @param {String} selector CSS like selector expression. 19226 */ 19227 init: function(selector) { 19228 var match = this.match; 19229 19230 function compileNameFilter(name) { 19231 if (name) { 19232 name = name.toLowerCase(); 19233 19234 return function(item) { 19235 return name === '*' || item.type === name; 19236 }; 19237 } 19238 } 19239 19240 function compileIdFilter(id) { 19241 if (id) { 19242 return function(item) { 19243 return item._name === id; 19244 }; 19245 } 19246 } 19247 19248 function compileClassesFilter(classes) { 19249 if (classes) { 19250 classes = classes.split('.'); 19251 19252 return function(item) { 19253 var i = classes.length; 19254 19255 while (i--) {
19256 if (!item.hasClass(classes[i])) { 19257 return false; 19258 } 19259 } 19260 19261 return true; 19262 }; 19263 } 19264 } 19265 19266 function compileAttrFilter(name, cmp, check) { 19267 if (name) { 19268 return function(item) { 19269 var value = item[name] ? item[name]() : ''; 19270 19271 return !cmp ? !!check : 19272 cmp === "=" ? value === check : 19273 cmp === "*=" ? value.indexOf(check) >= 0 : 19274 cmp === "~=" ? (" " + value + " ").indexOf(" " + check + " ") >= 0 : 19275 cmp === "!=" ? value != check : 19276 cmp === "^=" ? value.indexOf(check) === 0 : 19277 cmp === "$=" ? value.substr(value.length - check.length) === check : 19278 false; 19279 }; 19280 } 19281 } 19282 19283 function compilePsuedoFilter(name) { 19284 var notSelectors; 19285 19286 if (name) { 19287 name = /(?:not\((.+)\))|(.+)/i.exec(name); 19288 19289 if (!name[1]) { 19290 name = name[2]; 19291 19292 return function(item, index, length) { 19293 return name === 'first' ? index === 0 : 19294 name === 'last' ? index === length - 1 : 19295 name === 'even' ? index % 2 === 0 : 19296 name === 'odd' ? index % 2 === 1 : 19297 item[name] ? item[name]() : 19298 false; 19299 }; 19300 } else { 19301 // Compile not expression 19302 notSelectors = parseChunks(name[1], []); 19303 19304 return function(item) { 19305 return !match(item, notSelectors); 19306 }; 19307 } 19308 } 19309 } 19310 19311 function compile(selector, filters, direct) { 19312 var parts; 19313 19314 function add(filter) { 19315 if (filter) { 19316 filters.push(filter); 19317 } 19318 } 19319 19320 // Parse expression into parts 19321 parts = expression.exec(selector.replace(whiteSpace, '')); 19322 19323 add(compileNameFilter(parts[1])); 19324 add(compileIdFilter(parts[2])); 19325 add(compileClassesFilter(parts[3])); 19326 add(compileAttrFilter(parts[4], parts[5], parts[6])); 19327 add(compilePsuedoFilter(parts[7])); 19328 19329 // Mark the filter with psuedo for performance 19330 filters.psuedo = !!parts[7]; 19331 filters.direct = direct; 19332 19333 return filters; 19334 } 19335 19336 // Parser logic based on Sizzle by John Resig 19337 function parseChunks(selector, selectors) { 19338 var parts = [], extra, matches, i; 19339 19340 do { 19341 chunker.exec(""); 19342 matches = chunker.exec(selector); 19343 19344 if (matches) { 19345 selector = matches[3]; 19346 parts.push(matches[1]); 19347 19348 if (matches[2]) { 19349 extra = matches[3]; 19350 break; 19351 } 19352 } 19353 } while (matches); 19354 19355 if (extra) { 19356 parseChunks(extra, selectors); 19357 } 19358 19359 selector = []; 19360 for (i = 0; i < parts.length; i++) { 19361 if (parts[i] != '>') { 19362 selector.push(compile(parts[i], [], parts[i - 1] === '>')); 19363 } 19364 } 19365 19366 selectors.push(selector); 19367 19368 return selectors; 19369 } 19370 19371 this._selectors = parseChunks(selector, []); 19372 }, 19373 19374 /** 19375 * Returns true/false if the selector matches the specified control. 19376 * 19377 * @method match 19378 * @param {tinymce.ui.Control} control Control to match agains the selector. 19379 * @param {Array} selectors Optional array of selectors, mostly used internally. 19380 * @return {Boolean} true/false state if the control matches or not. 19381 */ 19382 match: function(control, selectors) { 19383 var i, l, si, sl, selector, fi, fl, filters, index, length, siblings, count, item; 19384 19385 selectors = selectors || this._selectors; 19386 for (i = 0, l = selectors.length; i < l; i++) { 19387 selector = selectors[i]; 19388 sl = selector.length; 19389 item = control; 19390 count = 0; 19391 19392 for (si = sl - 1; si >= 0; si--) { 19393 filters = selector[si]; 19394 19395 while (item) { 19396 // Find the index and length since a psuedo filter like :first needs it 19397 if (filters.psuedo) { 19398 siblings = item.parent().items(); 19399 index = length = siblings.length; 19400 while (index--) { 19401 if (siblings[index] === item) { 19402 break; 19403 } 19404 } 19405 } 19406 19407 for (fi = 0, fl = filters.length; fi < fl; fi++) { 19408 if (!filters[fi](item, index, length)) { 19409 fi = fl + 1; 19410 break; 19411 } 19412 } 19413 19414 if (fi === fl) { 19415 count++; 19416 break; 19417 } else { 19418 // If it didn't match the right most expression then 19419 // break since it's no point looking at the parents 19420 if (si === sl - 1) { 19421 break; 19422 } 19423 } 19424 19425 item = item.parent(); 19426 } 19427 } 19428 19429 // If we found all selectors then return true otherwise continue looking 19430 if (count === sl) { 19431 return true; 19432 } 19433 } 19434 19435 return false;
19436 }, 19437 19438 /** 19439 * Returns a tinymce.ui.Collection with matches of the specified selector inside the specified container. 19440 * 19441 * @method find 19442 * @param {tinymce.ui.Control} container Container to look for items in. 19443 * @return {tinymce.ui.Collection} Collection with matched elements. 19444 */ 19445 find: function(container) { 19446 var matches = [], i, l, selectors = this._selectors; 19447 19448 function collect(items, selector, index) { 19449 var i, l, fi, fl, item, filters = selector[index]; 19450 19451 for (i = 0, l = items.length; i < l; i++) { 19452 item = items[i]; 19453 19454 // Run each filter agains the item 19455 for (fi = 0, fl = filters.length; fi < fl; fi++) { 19456 if (!filters[fi](item, i, l)) { 19457 fi = fl + 1; 19458 break; 19459 } 19460 } 19461 19462 // All filters matched the item 19463 if (fi === fl) { 19464 // Matched item is on the last expression like: panel toolbar [button] 19465 if (index == selector.length - 1) { 19466 matches.push(item); 19467 } else { 19468 // Collect next expression type 19469 if (item.items) { 19470 collect(item.items(), selector, index + 1); 19471 } 19472 } 19473 } else if (filters.direct) { 19474 return; 19475 } 19476 19477 // Collect child items 19478 if (item.items) { 19479 collect(item.items(), selector, index); 19480 } 19481 } 19482 } 19483 19484 if (container.items) { 19485 for (i = 0, l = selectors.length; i < l; i++) { 19486 collect(container.items(), selectors[i], 0); 19487 } 19488 19489 // Unique the matches if needed 19490 if (l > 1) { 19491 matches = unique(matches); 19492 } 19493 } 19494 19495 // Fix for circular reference 19496 if (!Collection) { 19497 // TODO: Fix me! 19498 Collection = Selector.Collection; 19499 } 19500 19501 return new Collection(matches); 19502 } 19503 }); 19504 19505 return Selector; 19506}); 19507 19508// Included from: js/tinymce/classes/ui/Collection.js 19509 19510/** 19511 * Collection.js 19512 * 19513 * Copyright, Moxiecode Systems AB 19514 * Released under LGPL License. 19515 * 19516 * License: http://www.tinymce.com/license 19517 * Contributing: http://www.tinymce.com/contributing 19518 */ 19519 19520/** 19521 * Control collection, this class contains control instances and it enables you to 19522 * perform actions on all the contained items. This is very similar to how jQuery works. 19523 * 19524 * @example 19525 * someCollection.show().disabled(true); 19526 * 19527 * @class tinymce.ui.Collection 19528 */ 19529define("tinymce/ui/Collection", [ 19530 "tinymce/util/Tools", 19531 "tinymce/ui/Selector", 19532 "tinymce/util/Class" 19533], function(Tools, Selector, Class) { 19534 "use strict"; 19535 19536 var Collection, proto, push = Array.prototype.push, slice = Array.prototype.slice; 19537 19538 proto = { 19539 /** 19540 * Current number of contained control instances. 19541 * 19542 * @field length 19543 * @type Number 19544 */ 19545 length: 0, 19546 19547 /** 19548 * Constructor for the collection. 19549 * 19550 * @constructor 19551 * @method init 19552 * @param {Array} items Optional array with items to add. 19553 */ 19554 init: function(items) { 19555 if (items) { 19556 this.add(items); 19557 } 19558 }, 19559 19560 /** 19561 * Adds new items to the control collection. 19562 * 19563 * @method add 19564 * @param {Array} items Array if items to add to collection. 19565 * @return {tinymce.ui.Collection} Current collection instance. 19566 */ 19567 add: function(items) { 19568 var self = this; 19569 19570 // Force single item into array 19571 if (!Tools.isArray(items)) { 19572 if (items instanceof Collection) { 19573 self.add(items.toArray()); 19574 } else { 19575 push.call(self, items); 19576 } 19577 } else { 19578 push.apply(self, items); 19579 } 19580 19581 return self; 19582 }, 19583 19584 /** 19585 * Sets the contents of the collection. This will remove any existing items 19586 * and replace them with the ones specified in the input array. 19587 * 19588 * @method set 19589 * @param {Array} items Array with items to set into the Collection. 19590 * @return {tinymce.ui.Collection} Collection instance. 19591 */ 19592 set: function(items) { 19593 var self = this, len = self.length, i; 19594 19595 self.length = 0; 19596 self.add(items); 19597 19598 // Remove old entries 19599 for (i = self.length; i < len; i++) { 19600 delete self[i]; 19601 } 19602 19603 return self; 19604 }, 19605 19606 /** 19607 * Filters the collection item based on the specified selector expression or selector function. 19608 * 19609 * @method filter 19610 * @param {String} selector Selector expression to filter items by. 19611 * @return {tinymce.ui.Collection} Collection containing the filtered items. 19612 */ 19613 filter: function(selector) { 19614 var self = this, i, l, matches = [], item, match; 19615 19616 // Compile string into selector expression 19617 if (typeof(selector) === "string") { 19618 selector = new Selector(selector); 19619 19620 match = function(item) { 19621 return selector.match(item); 19622 }; 19623 } else { 19624 // Use selector as matching function 19625 match = selector; 19626 } 19627 19628 for (i = 0, l = self.length; i < l; i++) { 19629 item = self[i]; 19630 19631 if (match(item)) { 19632 matches.push(item); 19633 } 19634 } 19635 19636 return new Collection(matches); 19637 }, 19638 19639 /** 19640 * Slices the items within the collection. 19641 * 19642 * @method slice 19643 * @param {Number} index Index to slice at. 19644 * @param {Number} len Optional length to slice. 19645 * @return {tinymce.ui.Collection} Current collection. 19646 */ 19647 slice: function() { 19648 return new Collection(slice.apply(this, arguments)); 19649 }, 19650 19651 /** 19652 * Makes the current collection equal to the specified index. 19653 * 19654 * @method eq 19655 * @param {Number} index Index of the item to set the collection to. 19656 * @return {tinymce.ui.Collection} Current collection. 19657 */ 19658 eq: function(index) { 19659 return index === -1 ? this.slice(index) : this.slice(index, +index + 1); 19660 }, 19661 19662 /** 19663 * Executes the specified callback on each item in collection. 19664 * 19665 * @method each 19666 * @param {function} callback Callback to execute for each item in collection. 19667 * @return {tinymce.ui.Collection} Current collection instance. 19668 */ 19669 each: function(callback) { 19670 Tools.each(this, callback); 19671 19672 return this; 19673 }, 19674 19675 /** 19676 * Returns an JavaScript array object of the contents inside the collection. 19677 * 19678 * @method toArray 19679 * @return {Array} Array with all items from collection. 19680 */ 19681 toArray: function() { 19682 return Tools.toArray(this); 19683 }, 19684 19685 /** 19686 * Finds the index of the specified control or return -1 if it isn't in the collection. 19687 * 19688 * @method indexOf 19689 * @param {Control} ctrl Control instance to look for. 19690 * @return {Number} Index of the specified control or -1. 19691 */ 19692 indexOf: function(ctrl) { 19693 var self = this, i = self.length; 19694 19695 while (i--) { 19696 if (self[i] === ctrl) { 19697 break; 19698 } 19699 } 19700 19701 return i; 19702 }, 19703 19704 /** 19705 * Returns a new collection of the contents in reverse order. 19706 * 19707 * @method reverse 19708 * @return {tinymce.ui.Collection} Collection instance with reversed items. 19709 */ 19710 reverse: function() { 19711 return new Collection(Tools.toArray(this).reverse()); 19712 }, 19713 19714 /** 19715 * Returns true/false if the class exists or not. 19716 * 19717 * @method hasClass 19718 * @param {String} cls Class to check for. 19719 * @return {Boolean} true/false state if the class exists or not. 19720 */ 19721 hasClass: function(cls) { 19722 return this[0] ? this[0].hasClass(cls) : false;
19723 }, 19724 19725 /** 19726 * Sets/gets the specific property on the items in the collection. The same as executing control.<property>(<value>); 19727 * 19728 * @method prop 19729 * @param {String} name Property name to get/set. 19730 * @param {Object} value Optional object value to set. 19731 * @return {tinymce.ui.Collection} Current collection instance or value of the first item on a get operation. 19732 */ 19733 prop: function(name, value) { 19734 var self = this, undef, item; 19735 19736 if (value !== undef) { 19737 self.each(function(item) { 19738 if (item[name]) { 19739 item[name](value); 19740 } 19741 }); 19742 19743 return self; 19744 } 19745 19746 item = self[0]; 19747 19748 if (item && item[name]) { 19749 return item[name](); 19750 } 19751 }, 19752 19753 /** 19754 * Executes the specific function name with optional arguments an all items in collection if it exists. 19755 * 19756 * @example collection.exec("myMethod", arg1, arg2, arg3); 19757 * @method exec 19758 * @param {String} name Name of the function to execute. 19759 * @param {Object} ... Multiple arguments to pass to each function. 19760 * @return {tinymce.ui.Collection} Current collection. 19761 */ 19762 exec: function(name) { 19763 var self = this, args = Tools.toArray(arguments).slice(1); 19764 19765 self.each(function(item) { 19766 if (item[name]) { 19767 item[name].apply(item, args); 19768 } 19769 }); 19770 19771 return self; 19772 }, 19773 19774 /** 19775 * Remove all items from collection and DOM. 19776 * 19777 * @method remove 19778 * @return {tinymce.ui.Collection} Current collection. 19779 */ 19780 remove: function() { 19781 var i = this.length; 19782 19783 while (i--) { 19784 this[i].remove(); 19785 } 19786 19787 return this; 19788 } 19789 19790 /** 19791 * Fires the specified event by name and arguments on the control. This will execute all 19792 * bound event handlers. 19793 * 19794 * @method fire 19795 * @param {String} name Name of the event to fire. 19796 * @param {Object} args Optional arguments to pass to the event. 19797 * @return {tinymce.ui.Collection} Current collection instance. 19798 */ 19799 // fire: function(event, args) {}, -- Generated by code below 19800 19801 /** 19802 * Binds a callback to the specified event. This event can both be 19803 * native browser events like "click" or custom ones like PostRender. 19804 * 19805 * The callback function will have two parameters the first one being the control that received the event 19806 * the second one will be the event object either the browsers native event object or a custom JS object. 19807 * 19808 * @method on 19809 * @param {String} name Name of the event to bind. For example "click". 19810 * @param {String/function} callback Callback function to execute ones the event occurs. 19811 * @return {tinymce.ui.Collection} Current collection instance. 19812 */ 19813 // on: function(name, callback) {}, -- Generated by code below 19814 19815 /** 19816 * Unbinds the specified event and optionally a specific callback. If you omit the name 19817 * parameter all event handlers will be removed. If you omit the callback all event handles 19818 * by the specified name will be removed. 19819 * 19820 * @method off 19821 * @param {String} name Optional name for the event to unbind. 19822 * @param {function} callback Optional callback function to unbind. 19823 * @return {tinymce.ui.Collection} Current collection instance. 19824 */ 19825 // off: function(name, callback) {}, -- Generated by code below 19826 19827 /** 19828 * Shows the items in the current collection. 19829 * 19830 * @method show 19831 * @return {tinymce.ui.Collection} Current collection instance. 19832 */ 19833 // show: function() {}, -- Generated by code below 19834 19835 /** 19836 * Hides the items in the current collection. 19837 * 19838 * @method hide 19839 * @return {tinymce.ui.Collection} Current collection instance. 19840 */ 19841 // hide: function() {}, -- Generated by code below 19842 19843 /** 19844 * Sets/gets the text contents of the items in the current collection. 19845 * 19846 * @method text 19847 * @return {tinymce.ui.Collection} Current collection instance or text value of the first item on a get operation. 19848 */ 19849 // text: function(value) {}, -- Generated by code below 19850 19851 /** 19852 * Sets/gets the name contents of the items in the current collection. 19853 * 19854 * @method name 19855 * @return {tinymce.ui.Collection} Current collection instance or name value of the first item on a get operation. 19856 */ 19857 // name: function(value) {}, -- Generated by code below 19858 19859 /** 19860 * Sets/gets the disabled state on the items in the current collection. 19861 * 19862 * @method disabled 19863 * @return {tinymce.ui.Collection} Current collection instance or disabled state of the first item on a get operation. 19864 */ 19865 // disabled: function(state) {}, -- Generated by code below 19866 19867 /** 19868 * Sets/gets the active state on the items in the current collection. 19869 * 19870 * @method active 19871 * @return {tinymce.ui.Collection} Current collection instance or active state of the first item on a get operation. 19872 */ 19873 // active: function(state) {}, -- Generated by code below 19874 19875 /** 19876 * Sets/gets the selected state on the items in the current collection. 19877 * 19878 * @method selected 19879 * @return {tinymce.ui.Collection} Current collection instance or selected state of the first item on a get operation. 19880 */ 19881 // selected: function(state) {}, -- Generated by code below 19882 19883 /** 19884 * Sets/gets the selected state on the items in the current collection. 19885 * 19886 * @method visible 19887 * @return {tinymce.ui.Collection} Current collection instance or visible state of the first item on a get operation. 19888 */ 19889 // visible: function(state) {}, -- Generated by code below 19890 19891 /** 19892 * Adds a class to all items in the collection. 19893 * 19894 * @method addClass 19895 * @param {String} cls Class to add to each item. 19896 * @return {tinymce.ui.Collection} Current collection instance. 19897 */ 19898 // addClass: function(cls) {}, -- Generated by code below 19899 19900 /** 19901 * Removes the specified class from all items in collection. 19902 * 19903 * @method removeClass 19904 * @param {String} cls Class to remove from each item. 19905 * @return {tinymce.ui.Collection} Current collection instance. 19906 */ 19907 // removeClass: function(cls) {}, -- Generated by code below 19908 }; 19909 19910 // Extend tinymce.ui.Collection prototype with some generated control specific methods 19911 Tools.each('fire on off show hide addClass removeClass append prepend before after reflow'.split(' '), function(name) { 19912 proto[name] = function() { 19913 var args = Tools.toArray(arguments); 19914 19915 this.each(function(ctrl) { 19916 if (name in ctrl) { 19917 ctrl[name].apply(ctrl, args); 19918 } 19919 }); 19920 19921 return this; 19922 }; 19923 }); 19924 19925 // Extend tinymce.ui.Collection prototype with some property methods 19926 Tools.each('text name disabled active selected checked visible parent value data'.split(' '), function(name) { 19927 proto[name] = function(value) { 19928 return this.prop(name, value); 19929 }; 19930 }); 19931 19932 // Create class based on the new prototype 19933 Collection = Class.extend(proto); 19934 19935 // Stick Collection into Selector to prevent circual references 19936 Selector.Collection = Collection; 19937 19938 return Collection; 19939}); 19940 19941// Included from: js/tinymce/classes/ui/DomUtils.js 19942 19943/** 19944 * DOMUtils.js 19945 * 19946 * Copyright, Moxiecode Systems AB 19947 * Released under LGPL License. 19948 * 19949 * License: http://www.tinymce.com/license 19950 * Contributing: http://www.tinymce.com/contributing 19951 */ 19952
19953define("tinymce/ui/DomUtils", [ 19954 "tinymce/util/Tools", 19955 "tinymce/dom/DOMUtils" 19956], function(Tools, DOMUtils) { 19957 "use strict"; 19958 19959 var count = 0; 19960 19961 return { 19962 id: function() { 19963 return 'mceu_' + (count++); 19964 }, 19965 19966 createFragment: function(html) { 19967 return DOMUtils.DOM.createFragment(html); 19968 }, 19969 19970 getWindowSize: function() { 19971 return DOMUtils.DOM.getViewPort(); 19972 }, 19973 19974 getSize: function(elm) { 19975 var width, height; 19976 19977 if (elm.getBoundingClientRect) { 19978 var rect = elm.getBoundingClientRect(); 19979 19980 width = Math.max(rect.width || (rect.right - rect.left), elm.offsetWidth); 19981 height = Math.max(rect.height || (rect.bottom - rect.bottom), elm.offsetHeight); 19982 } else { 19983 width = elm.offsetWidth; 19984 height = elm.offsetHeight; 19985 } 19986 19987 return {width: width, height: height}; 19988 }, 19989 19990 getPos: function(elm, root) { 19991 return DOMUtils.DOM.getPos(elm, root); 19992 }, 19993 19994 getViewPort: function(win) { 19995 return DOMUtils.DOM.getViewPort(win); 19996 }, 19997 19998 get: function(id) { 19999 return document.getElementById(id); 20000 }, 20001 20002 addClass : function(elm, cls) { 20003 return DOMUtils.DOM.addClass(elm, cls); 20004 }, 20005 20006 removeClass : function(elm, cls) { 20007 return DOMUtils.DOM.removeClass(elm, cls); 20008 }, 20009 20010 hasClass : function(elm, cls) { 20011 return DOMUtils.DOM.hasClass(elm, cls); 20012 }, 20013 20014 toggleClass: function(elm, cls, state) { 20015 return DOMUtils.DOM.toggleClass(elm, cls, state); 20016 }, 20017 20018 css: function(elm, name, value) { 20019 return DOMUtils.DOM.setStyle(elm, name, value); 20020 }, 20021 20022 getRuntimeStyle: function(elm, name) { 20023 return DOMUtils.DOM.getStyle(elm, name, true); 20024 }, 20025 20026 on: function(target, name, callback, scope) { 20027 return DOMUtils.DOM.bind(target, name, callback, scope); 20028 }, 20029 20030 off: function(target, name, callback) { 20031 return DOMUtils.DOM.unbind(target, name, callback); 20032 }, 20033 20034 fire: function(target, name, args) { 20035 return DOMUtils.DOM.fire(target, name, args); 20036 }, 20037 20038 innerHtml: function(elm, html) { 20039 // Workaround for <div> in <p> bug on IE 8 #6178 20040 DOMUtils.DOM.setHTML(elm, html); 20041 } 20042 }; 20043}); 20044 20045// Included from: js/tinymce/classes/ui/Control.js 20046 20047/** 20048 * Control.js 20049 * 20050 * Copyright, Moxiecode Systems AB 20051 * Released under LGPL License. 20052 * 20053 * License: http://www.tinymce.com/license 20054 * Contributing: http://www.tinymce.com/contributing 20055 */ 20056 20057/*eslint consistent-this:0 */ 20058 20059/** 20060 * This is the base class for all controls and containers. All UI control instances inherit 20061 * from this one as it has the base logic needed by all of them. 20062 * 20063 * @class tinymce.ui.Control 20064 */ 20065define("tinymce/ui/Control", [ 20066 "tinymce/util/Class", 20067 "tinymce/util/Tools", 20068 "tinymce/util/EventDispatcher", 20069 "tinymce/ui/Collection", 20070 "tinymce/ui/DomUtils" 20071], function(Class, Tools, EventDispatcher, Collection, DomUtils) { 20072 "use strict"; 20073 20074 var hasMouseWheelEventSupport = "onmousewheel" in document; 20075 var hasWheelEventSupport = false; 20076 var classPrefix = "mce-"; 20077 20078 function getEventDispatcher(obj) { 20079 if (!obj._eventDispatcher) { 20080 obj._eventDispatcher = new EventDispatcher({ 20081 scope: obj, 20082 toggleEvent: function(name, state) { 20083 if (state && EventDispatcher.isNative(name)) { 20084 if (!obj._nativeEvents) { 20085 obj._nativeEvents = {}; 20086 } 20087 20088 obj._nativeEvents[name] = true; 20089 20090 if (obj._rendered) { 20091 obj.bindPendingEvents(); 20092 } 20093 } 20094 } 20095 }); 20096 } 20097 20098 return obj._eventDispatcher; 20099 } 20100 20101 var Control = Class.extend({ 20102 Statics: { 20103 classPrefix: classPrefix 20104 }, 20105 20106 isRtl: function() { 20107 return Control.rtl; 20108 }, 20109 20110 /** 20111 * Class/id prefix to use for all controls. 20112 * 20113 * @final 20114 * @field {String} classPrefix 20115 */ 20116 classPrefix: classPrefix, 20117 20118 /** 20119 * Constructs a new control instance with the specified settings. 20120 * 20121 * @constructor 20122 * @param {Object} settings Name/value object with settings. 20123 * @setting {String} style Style CSS properties to add. 20124 * @setting {String} border Border box values example: 1 1 1 1 20125 * @setting {String} padding Padding box values example: 1 1 1 1 20126 * @setting {String} margin Margin box values example: 1 1 1 1 20127 * @setting {Number} minWidth Minimal width for the control. 20128 * @setting {Number} minHeight Minimal height for the control.
20129 * @setting {String} classes Space separated list of classes to add. 20130 * @setting {String} role WAI-ARIA role to use for control. 20131 * @setting {Boolean} hidden Is the control hidden by default. 20132 * @setting {Boolean} disabled Is the control disabled by default. 20133 * @setting {String} name Name of the control instance. 20134 */ 20135 init: function(settings) { 20136 var self = this, classes, i; 20137 20138 self.settings = settings = Tools.extend({}, self.Defaults, settings); 20139 20140 // Initial states 20141 self._id = settings.id || DomUtils.id(); 20142 self._text = self._name = ''; 20143 self._width = self._height = 0; 20144 self._aria = {role: settings.role}; 20145 this._elmCache = {}; 20146 20147 // Setup classes 20148 classes = settings.classes; 20149 if (classes) { 20150 classes = classes.split(' '); 20151 classes.map = {}; 20152 i = classes.length; 20153 while (i--) { 20154 classes.map[classes[i]] = true; 20155 } 20156 } 20157 20158 self._classes = classes || []; 20159 self.visible(true); 20160 20161 // Set some properties 20162 Tools.each('title text width height name classes visible disabled active value'.split(' '), function(name) { 20163 var value = settings[name], undef; 20164 20165 if (value !== undef) { 20166 self[name](value); 20167 } else if (self['_' + name] === undef) { 20168 self['_' + name] = false; 20169 } 20170 }); 20171 20172 self.on('click', function() { 20173 if (self.disabled()) { 20174 return false; 20175 } 20176 }); 20177 20178 // TODO: Is this needed duplicate code see above? 20179 if (settings.classes) { 20180 Tools.each(settings.classes.split(' '), function(cls) { 20181 self.addClass(cls); 20182 }); 20183 } 20184 20185 /** 20186 * Name/value object with settings for the current control. 20187 * 20188 * @field {Object} settings 20189 */ 20190 self.settings = settings; 20191 20192 self._borderBox = self.parseBox(settings.border); 20193 self._paddingBox = self.parseBox(settings.padding); 20194 self._marginBox = self.parseBox(settings.margin); 20195 20196 if (settings.hidden) { 20197 self.hide(); 20198 } 20199 }, 20200 20201 // Will generate getter/setter methods for these properties 20202 Properties: 'parent,title,text,width,height,disabled,active,name,value', 20203 20204 // Will generate empty dummy functions for these 20205 Methods: 'renderHtml', 20206 20207 /** 20208 * Returns the root element to render controls into. 20209 * 20210 * @method getContainerElm 20211 * @return {Element} HTML DOM element to render into. 20212 */ 20213 getContainerElm: function() { 20214 return document.body; 20215 }, 20216 20217 /** 20218 * Returns a control instance for the current DOM element. 20219 * 20220 * @method getParentCtrl 20221 * @param {Element} elm HTML dom element to get parent control from. 20222 * @return {tinymce.ui.Control} Control instance or undefined. 20223 */ 20224 getParentCtrl: function(elm) { 20225 var ctrl, lookup = this.getRoot().controlIdLookup; 20226 20227 while (elm && lookup) { 20228 ctrl = lookup[elm.id]; 20229 if (ctrl) { 20230 break; 20231 } 20232 20233 elm = elm.parentNode; 20234 } 20235 20236 return ctrl; 20237 }, 20238 20239 /** 20240 * Parses the specified box value. A box value contains 1-4 properties in clockwise order. 20241 * 20242 * @method parseBox 20243 * @param {String/Number} value Box value "0 1 2 3" or "0" etc. 20244 * @return {Object} Object with top/right/bottom/left properties. 20245 * @private 20246 */ 20247 parseBox: function(value) { 20248 var len, radix = 10; 20249 20250 if (!value) { 20251 return; 20252 } 20253 20254 if (typeof(value) === "number") { 20255 value = value || 0; 20256 20257 return { 20258 top: value, 20259 left: value, 20260 bottom: value, 20261 right: value 20262 }; 20263 } 20264 20265 value = value.split(' '); 20266 len = value.length; 20267 20268 if (len === 1) { 20269 value[1] = value[2] = value[3] = value[0]; 20270 } else if (len === 2) { 20271 value[2] = value[0]; 20272 value[3] = value[1]; 20273 } else if (len === 3) { 20274 value[3] = value[1]; 20275 } 20276 20277 return { 20278 top: parseInt(value[0], radix) || 0, 20279 right: parseInt(value[1], radix) || 0, 20280 bottom: parseInt(value[2], radix) || 0, 20281 left: parseInt(value[3], radix) || 0 20282 }; 20283 }, 20284 20285 borderBox: function() { 20286 return this._borderBox; 20287 }, 20288 20289 paddingBox: function() { 20290 return this._paddingBox; 20291 }, 20292 20293 marginBox: function() { 20294 return this._marginBox; 20295 }, 20296 20297 measureBox: function(elm, prefix) { 20298 function getStyle(name) { 20299 var defaultView = document.defaultView; 20300 20301 if (defaultView) { 20302 // Remove camelcase 20303 name = name.replace(/[A-Z]/g, function(a) { 20304 return '-' + a; 20305 }); 20306 20307 return defaultView.getComputedStyle(elm, null).getPropertyValue(name); 20308 } 20309 20310 return elm.currentStyle[name]; 20311 } 20312 20313 function getSide(name) { 20314 var val = parseFloat(getStyle(name), 10); 20315 20316 return isNaN(val) ? 0 : val; 20317 } 20318 20319 return { 20320 top: getSide(prefix + "TopWidth"), 20321 right: getSide(prefix + "RightWidth"), 20322 bottom: getSide(prefix + "BottomWidth"), 20323 left: getSide(prefix + "LeftWidth") 20324 }; 20325 }, 20326 20327 /** 20328 * Initializes the current controls layout rect. 20329 * This will be executed by the layout managers to determine the 20330 * default minWidth/minHeight etc. 20331 * 20332 * @method initLayoutRect 20333 * @return {Object} Layout rect instance. 20334 */ 20335 initLayoutRect: function() { 20336 var self = this, settings = self.settings, borderBox, layoutRect; 20337 var elm = self.getEl(), width, height, minWidth, minHeight, autoResize; 20338 var startMinWidth, startMinHeight, initialSize; 20339 20340 // Measure the current element 20341 borderBox = self._borderBox = self._borderBox || self.measureBox(elm, 'border'); 20342 self._paddingBox = self._paddingBox || self.measureBox(elm, 'padding'); 20343 self._marginBox = self._marginBox || self.measureBox(elm, 'margin'); 20344 initialSize = DomUtils.getSize(elm); 20345 20346 // Setup minWidth/minHeight and width/height 20347 startMinWidth = settings.minWidth; 20348 startMinHeight = settings.minHeight; 20349 minWidth = startMinWidth || initialSize.width; 20350 minHeight = startMinHeight || initialSize.height; 20351 width = settings.width; 20352 height = settings.height; 20353 autoResize = settings.autoResize; 20354 autoResize = typeof(autoResize) != "undefined" ? autoResize : !width && !height; 20355 20356 width = width || minWidth; 20357 height = height || minHeight; 20358 20359 var deltaW = borderBox.left + borderBox.right; 20360 var deltaH = borderBox.top + borderBox.bottom; 20361 20362 var maxW = settings.maxWidth || 0xFFFF; 20363 var maxH = settings.maxHeight || 0xFFFF; 20364 20365 // Setup initial layout rect
20366 self._layoutRect = layoutRect = { 20367 x: settings.x || 0, 20368 y: settings.y || 0, 20369 w: width, 20370 h: height, 20371 deltaW: deltaW, 20372 deltaH: deltaH, 20373 contentW: width - deltaW, 20374 contentH: height - deltaH, 20375 innerW: width - deltaW, 20376 innerH: height - deltaH, 20377 startMinWidth: startMinWidth || 0, 20378 startMinHeight: startMinHeight || 0, 20379 minW: Math.min(minWidth, maxW), 20380 minH: Math.min(minHeight, maxH), 20381 maxW: maxW, 20382 maxH: maxH, 20383 autoResize: autoResize, 20384 scrollW: 0 20385 }; 20386 20387 self._lastLayoutRect = {}; 20388 20389 return layoutRect; 20390 }, 20391 20392 /** 20393 * Getter/setter for the current layout rect. 20394 * 20395 * @method layoutRect 20396 * @param {Object} [newRect] Optional new layout rect. 20397 * @return {tinymce.ui.Control/Object} Current control or rect object. 20398 */ 20399 layoutRect: function(newRect) { 20400 var self = this, curRect = self._layoutRect, lastLayoutRect, size, deltaWidth, deltaHeight, undef, repaintControls; 20401 20402 // Initialize default layout rect 20403 if (!curRect) { 20404 curRect = self.initLayoutRect(); 20405 } 20406 20407 // Set new rect values 20408 if (newRect) { 20409 // Calc deltas between inner and outer sizes 20410 deltaWidth = curRect.deltaW; 20411 deltaHeight = curRect.deltaH; 20412 20413 // Set x position 20414 if (newRect.x !== undef) { 20415 curRect.x = newRect.x; 20416 } 20417 20418 // Set y position 20419 if (newRect.y !== undef) { 20420 curRect.y = newRect.y; 20421 } 20422 20423 // Set minW 20424 if (newRect.minW !== undef) { 20425 curRect.minW = newRect.minW; 20426 } 20427 20428 // Set minH 20429 if (newRect.minH !== undef) { 20430 curRect.minH = newRect.minH; 20431 } 20432 20433 // Set new width and calculate inner width 20434 size = newRect.w; 20435 if (size !== undef) { 20436 size = size < curRect.minW ? curRect.minW : size; 20437 size = size > curRect.maxW ? curRect.maxW : size; 20438 curRect.w = size; 20439 curRect.innerW = size - deltaWidth; 20440 } 20441 20442 // Set new height and calculate inner height 20443 size = newRect.h; 20444 if (size !== undef) { 20445 size = size < curRect.minH ? curRect.minH : size; 20446 size = size > curRect.maxH ? curRect.maxH : size; 20447 curRect.h = size; 20448 curRect.innerH = size - deltaHeight; 20449 } 20450 20451 // Set new inner width and calculate width 20452 size = newRect.innerW; 20453 if (size !== undef) { 20454 size = size < curRect.minW - deltaWidth ? curRect.minW - deltaWidth : size; 20455 size = size > curRect.maxW - deltaWidth ? curRect.maxW - deltaWidth : size; 20456 curRect.innerW = size; 20457 curRect.w = size + deltaWidth; 20458 } 20459 20460 // Set new height and calculate inner height 20461 size = newRect.innerH; 20462 if (size !== undef) { 20463 size = size < curRect.minH - deltaHeight ? curRect.minH - deltaHeight : size; 20464 size = size > curRect.maxH - deltaHeight ? curRect.maxH - deltaHeight : size; 20465 curRect.innerH = size; 20466 curRect.h = size + deltaHeight; 20467 } 20468 20469 // Set new contentW 20470 if (newRect.contentW !== undef) { 20471 curRect.contentW = newRect.contentW; 20472 } 20473 20474 // Set new contentH 20475 if (newRect.contentH !== undef) { 20476 curRect.contentH = newRect.contentH; 20477 } 20478 20479 // Compare last layout rect with the current one to see if we need to repaint or not 20480 lastLayoutRect = self._lastLayoutRect; 20481 if (lastLayoutRect.x !== curRect.x || lastLayoutRect.y !== curRect.y || 20482 lastLayoutRect.w !== curRect.w || lastLayoutRect.h !== curRect.h) { 20483 repaintControls = Control.repaintControls; 20484 20485 if (repaintControls) { 20486 if (repaintControls.map && !repaintControls.map[self._id]) { 20487 repaintControls.push(self); 20488 repaintControls.map[self._id] = true; 20489 } 20490 } 20491 20492 lastLayoutRect.x = curRect.x; 20493 lastLayoutRect.y = curRect.y; 20494 lastLayoutRect.w = curRect.w; 20495 lastLayoutRect.h = curRect.h; 20496 } 20497 20498 return self; 20499 } 20500 20501 return curRect; 20502 }, 20503 20504 /** 20505 * Repaints the control after a layout operation. 20506 * 20507 * @method repaint 20508 */ 20509 repaint: function() { 20510 var self = this, style, bodyStyle, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect, round; 20511 20512 // Use Math.round on all values on IE < 9 20513 round = !document.createRange ? Math.round : function(value) { 20514 return value; 20515 }; 20516 20517 style = self.getEl().style; 20518 rect = self._layoutRect; 20519 lastRepaintRect = self._lastRepaintRect || {}; 20520 20521 borderBox = self._borderBox; 20522 borderW = borderBox.left + borderBox.right; 20523 borderH = borderBox.top + borderBox.bottom; 20524 20525 if (rect.x !== lastRepaintRect.x) { 20526 style.left = round(rect.x) + 'px'; 20527 lastRepaintRect.x = rect.x; 20528 } 20529 20530 if (rect.y !== lastRepaintRect.y) { 20531 style.top = round(rect.y) + 'px'; 20532 lastRepaintRect.y = rect.y; 20533 } 20534 20535 if (rect.w !== lastRepaintRect.w) { 20536 style.width = round(rect.w - borderW) + 'px'; 20537 lastRepaintRect.w = rect.w; 20538 } 20539 20540 if (rect.h !== lastRepaintRect.h) { 20541 style.height = round(rect.h - borderH) + 'px'; 20542 lastRepaintRect.h = rect.h; 20543 } 20544 20545 // Update body if needed 20546 if (self._hasBody && rect.innerW !== lastRepaintRect.innerW) { 20547 bodyStyle = self.getEl('body').style; 20548 bodyStyle.width = round(rect.innerW) + 'px'; 20549 lastRepaintRect.innerW = rect.innerW; 20550 } 20551 20552 if (self._hasBody && rect.innerH !== lastRepaintRect.innerH) { 20553 bodyStyle = bodyStyle || self.getEl('body').style; 20554 bodyStyle.height = round(rect.innerH) + 'px'; 20555 lastRepaintRect.innerH = rect.innerH; 20556 } 20557 20558 self._lastRepaintRect = lastRepaintRect; 20559 self.fire('repaint', {}, false); 20560 }, 20561 20562 /** 20563 * Binds a callback to the specified event. This event can both be 20564 * native browser events like "click" or custom ones like PostRender. 20565 * 20566 * The callback function will be passed a DOM event like object that enables yout do stop propagation. 20567 * 20568 * @method on 20569 * @param {String} name Name of the event to bind. For example "click". 20570 * @param {String/function} callback Callback function to execute ones the event occurs. 20571 * @return {tinymce.ui.Control} Current control object. 20572 */ 20573 on: function(name, callback) { 20574 var self = this; 20575
20576 function resolveCallbackName(name) { 20577 var callback, scope; 20578 20579 if (typeof(name) != 'string') { 20580 return name; 20581 } 20582 20583 return function(e) { 20584 if (!callback) { 20585 self.parentsAndSelf().each(function(ctrl) { 20586 var callbacks = ctrl.settings.callbacks; 20587 20588 if (callbacks && (callback = callbacks[name])) { 20589 scope = ctrl; 20590 return false; 20591 } 20592 }); 20593 } 20594 20595 return callback.call(scope, e); 20596 }; 20597 } 20598 20599 getEventDispatcher(self).on(name, resolveCallbackName(callback)); 20600 20601 return self; 20602 }, 20603 20604 /** 20605 * Unbinds the specified event and optionally a specific callback. If you omit the name 20606 * parameter all event handlers will be removed. If you omit the callback all event handles 20607 * by the specified name will be removed. 20608 * 20609 * @method off 20610 * @param {String} [name] Name for the event to unbind. 20611 * @param {function} [callback] Callback function to unbind. 20612 * @return {mxex.ui.Control} Current control object. 20613 */ 20614 off: function(name, callback) { 20615 getEventDispatcher(this).off(name, callback); 20616 return this; 20617 }, 20618 20619 /** 20620 * Fires the specified event by name and arguments on the control. This will execute all 20621 * bound event handlers. 20622 * 20623 * @method fire 20624 * @param {String} name Name of the event to fire. 20625 * @param {Object} [args] Arguments to pass to the event. 20626 * @param {Boolean} [bubble] Value to control bubbeling. Defaults to true. 20627 * @return {Object} Current arguments object. 20628 */ 20629 fire: function(name, args, bubble) { 20630 var self = this; 20631 20632 args = args || {}; 20633 20634 if (!args.control) { 20635 args.control = self; 20636 } 20637 20638 args = getEventDispatcher(self).fire(name, args); 20639 20640 // Bubble event up to parents 20641 if (bubble !== false && self.parent) { 20642 var parent = self.parent(); 20643 while (parent && !args.isPropagationStopped()) { 20644 parent.fire(name, args, false); 20645 parent = parent.parent(); 20646 } 20647 } 20648 20649 return args; 20650 }, 20651 20652 /** 20653 * Returns true/false if the specified event has any listeners. 20654 * 20655 * @method hasEventListeners 20656 * @param {String} name Name of the event to check for. 20657 * @return {Boolean} True/false state if the event has listeners. 20658 */ 20659 hasEventListeners: function(name) { 20660 return getEventDispatcher(this).has(name); 20661 }, 20662 20663 /** 20664 * Returns a control collection with all parent controls. 20665 * 20666 * @method parents 20667 * @param {String} selector Optional selector expression to find parents. 20668 * @return {tinymce.ui.Collection} Collection with all parent controls. 20669 */ 20670 parents: function(selector) { 20671 var self = this, ctrl, parents = new Collection(); 20672 20673 // Add each parent to collection 20674 for (ctrl = self.parent(); ctrl; ctrl = ctrl.parent()) { 20675 parents.add(ctrl); 20676 } 20677 20678 // Filter away everything that doesn't match the selector 20679 if (selector) { 20680 parents = parents.filter(selector); 20681 } 20682 20683 return parents; 20684 }, 20685 20686 /** 20687 * Returns the current control and it's parents. 20688 * 20689 * @method parentsAndSelf 20690 * @param {String} selector Optional selector expression to find parents. 20691 * @return {tinymce.ui.Collection} Collection with all parent controls. 20692 */ 20693 parentsAndSelf: function(selector) { 20694 return new Collection(this).add(this.parents(selector)); 20695 }, 20696 20697 /** 20698 * Returns the control next to the current control. 20699 * 20700 * @method next 20701 * @return {tinymce.ui.Control} Next control instance. 20702 */ 20703 next: function() { 20704 var parentControls = this.parent().items(); 20705 20706 return parentControls[parentControls.indexOf(this) + 1]; 20707 }, 20708 20709 /** 20710 * Returns the control previous to the current control. 20711 * 20712 * @method prev 20713 * @return {tinymce.ui.Control} Previous control instance. 20714 */ 20715 prev: function() { 20716 var parentControls = this.parent().items(); 20717 20718 return parentControls[parentControls.indexOf(this) - 1]; 20719 }, 20720 20721 /** 20722 * Find the common ancestor for two control instances. 20723 * 20724 * @method findCommonAncestor 20725 * @param {tinymce.ui.Control} ctrl1 First control. 20726 * @param {tinymce.ui.Control} ctrl2 Second control. 20727 * @return {tinymce.ui.Control} Ancestor control instance. 20728 */ 20729 findCommonAncestor: function(ctrl1, ctrl2) { 20730 var parentCtrl; 20731 20732 while (ctrl1) { 20733 parentCtrl = ctrl2; 20734 20735 while (parentCtrl && ctrl1 != parentCtrl) { 20736 parentCtrl = parentCtrl.parent(); 20737 } 20738 20739 if (ctrl1 == parentCtrl) { 20740 break; 20741 } 20742 20743 ctrl1 = ctrl1.parent(); 20744 } 20745 20746 return ctrl1; 20747 }, 20748 20749 /** 20750 * Returns true/false if the specific control has the specific class. 20751 * 20752 * @method hasClass 20753 * @param {String} cls Class to check for. 20754 * @param {String} [group] Sub element group name. 20755 * @return {Boolean} True/false if the control has the specified class. 20756 */ 20757 hasClass: function(cls, group) { 20758 var classes = this._classes[group || 'control']; 20759 20760 cls = this.classPrefix + cls; 20761 20762 return classes && !!classes.map[cls]; 20763 }, 20764 20765 /** 20766 * Adds the specified class to the control 20767 * 20768 * @method addClass 20769 * @param {String} cls Class to check for. 20770 * @param {String} [group] Sub element group name. 20771 * @return {tinymce.ui.Control} Current control object. 20772 */ 20773 addClass: function(cls, group) { 20774 var self = this, classes, elm; 20775 20776 cls = this.classPrefix + cls; 20777 classes = self._classes[group || 'control']; 20778 20779 if (!classes) { 20780 classes = []; 20781 classes.map = {}; 20782 self._classes[group || 'control'] = classes; 20783 } 20784 20785 if (!classes.map[cls]) { 20786 classes.map[cls] = cls; 20787 classes.push(cls); 20788 20789 if (self._rendered) { 20790 elm = self.getEl(group); 20791 20792 if (elm) { 20793 elm.className = classes.join(' '); 20794 } 20795 } 20796 } 20797 20798 return self; 20799 }, 20800 20801 /** 20802 * Removes the specified class from the control. 20803 * 20804 * @method removeClass 20805 * @param {String} cls Class to remove. 20806 * @param {String} [group] Sub element group name. 20807 * @return {tinymce.ui.Control} Current control object. 20808 */ 20809 removeClass: function(cls, group) { 20810 var self = this, classes, i, elm; 20811 20812 cls = this.classPrefix + cls; 20813 classes = self._classes[group || 'control']; 20814 if (classes && classes.map[cls]) { 20815 delete classes.map[cls]; 20816 20817 i = classes.length; 20818 while (i--) { 20819 if (classes[i] === cls) { 20820 classes.splice(i, 1); 20821 } 20822 } 20823 } 20824 20825 if (self._rendered) { 20826 elm = self.getEl(group); 20827 20828 if (elm) { 20829 elm.className = classes.join(' '); 20830 } 20831 } 20832 20833 return self; 20834 }, 20835 20836 /** 20837 * Toggles the specified class on the control. 20838 * 20839 * @method toggleClass 20840 * @param {String} cls Class to remove. 20841 * @param {Boolean} state True/false state to add/remove class. 20842 * @param {String} [group] Sub element group name. 20843 * @return {tinymce.ui.Control} Current control object. 20844 */ 20845 toggleClass: function(cls, state, group) { 20846 var self = this; 20847 20848 if (state) { 20849 self.addClass(cls, group); 20850 } else { 20851 self.removeClass(cls, group); 20852 } 20853 20854 return self; 20855 }, 20856 20857 /** 20858 * Returns the class string for the specified group name. 20859 * 20860 * @method classes 20861 * @param {String} [group] Group to get clases by. 20862 * @return {String} Classes for the specified group. 20863 */ 20864 classes: function(group) { 20865 var classes = this._classes[group || 'control']; 20866 20867 return classes ? classes.join(' ') : ''; 20868 }, 20869 20870 /** 20871 * Sets the inner HTML of the control element. 20872 * 20873 * @method innerHtml 20874 * @param {String} html Html string to set as inner html. 20875 * @return {tinymce.ui.Control} Current control object. 20876 */ 20877 innerHtml: function(html) { 20878 DomUtils.innerHtml(this.getEl(), html); 20879 return this; 20880 }, 20881 20882 /** 20883 * Returns the control DOM element or sub element. 20884 * 20885 * @method getEl 20886 * @param {String} [suffix] Suffix to get element by. 20887 * @return {Element} HTML DOM element for the current control or it's children. 20888 */ 20889 getEl: function(suffix) { 20890 var id = suffix ? this._id + '-' + suffix : this._id; 20891 20892 if (!this._elmCache[id]) { 20893 this._elmCache[id] = DomUtils.get(id); 20894 } 20895 20896 return this._elmCache[id]; 20897 }, 20898 20899 /** 20900 * Sets/gets the visible for the control. 20901 * 20902 * @method visible 20903 * @param {Boolean} state Value to set to control. 20904 * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get. 20905 */ 20906 visible: function(state) { 20907 var self = this, parentCtrl; 20908 20909 if (typeof(state) !== "undefined") { 20910 if (self._visible !== state) { 20911 if (self._rendered) { 20912 self.getEl().style.display = state ? '' : 'none'; 20913 } 20914 20915 self._visible = state; 20916 20917 // Parent container needs to reflow 20918 parentCtrl = self.parent(); 20919 if (parentCtrl) { 20920 parentCtrl._lastRect = null; 20921 } 20922 20923 self.fire(state ? 'show' : 'hide'); 20924 } 20925 20926 return self; 20927 } 20928 20929 return self._visible; 20930 }, 20931 20932 /** 20933 * Sets the visible state to true. 20934 * 20935 * @method show 20936 * @return {tinymce.ui.Control} Current control instance. 20937 */ 20938 show: function() { 20939 return this.visible(true); 20940 }, 20941 20942 /** 20943 * Sets the visible state to false. 20944 * 20945 * @method hide 20946 * @return {tinymce.ui.Control} Current control instance. 20947 */ 20948 hide: function() { 20949 return this.visible(false); 20950 }, 20951 20952 /** 20953 * Focuses the current control. 20954 *
20955 * @method focus 20956 * @return {tinymce.ui.Control} Current control instance. 20957 */ 20958 focus: function() { 20959 try { 20960 this.getEl().focus(); 20961 } catch (ex) { 20962 // Ignore IE error 20963 } 20964 20965 return this; 20966 }, 20967 20968 /** 20969 * Blurs the current control. 20970 * 20971 * @method blur 20972 * @return {tinymce.ui.Control} Current control instance. 20973 */ 20974 blur: function() { 20975 this.getEl().blur(); 20976 20977 return this; 20978 }, 20979 20980 /** 20981 * Sets the specified aria property. 20982 * 20983 * @method aria 20984 * @param {String} name Name of the aria property to set. 20985 * @param {String} value Value of the aria property. 20986 * @return {tinymce.ui.Control} Current control instance. 20987 */ 20988 aria: function(name, value) { 20989 var self = this, elm = self.getEl(self.ariaTarget); 20990 20991 if (typeof(value) === "undefined") { 20992 return self._aria[name]; 20993 } else { 20994 self._aria[name] = value; 20995 } 20996 20997 if (self._rendered) { 20998 elm.setAttribute(name == 'role' ? name : 'aria-' + name, value); 20999 } 21000 21001 return self; 21002 }, 21003 21004 /** 21005 * Encodes the specified string with HTML entities. It will also 21006 * translate the string to different languages. 21007 * 21008 * @method encode 21009 * @param {String/Object/Array} text Text to entity encode. 21010 * @param {Boolean} [translate=true] False if the contents shouldn't be translated. 21011 * @return {String} Encoded and possible traslated string. 21012 */ 21013 encode: function(text, translate) { 21014 if (translate !== false) { 21015 text = this.translate(text); 21016 } 21017 21018 return (text || '').replace(/[&<>"]/g, function(match) { 21019 return '&#' + match.charCodeAt(0) + ';'; 21020 }); 21021 }, 21022 21023 /** 21024 * Returns the translated string. 21025 * 21026 * @method translate 21027 * @param {String} text Text to translate. 21028 * @return {String} Translated string or the same as the input. 21029 */ 21030 translate: function(text) { 21031 return Control.translate ? Control.translate(text) : text; 21032 }, 21033 21034 /** 21035 * Adds items before the current control. 21036 * 21037 * @method before 21038 * @param {Array/tinymce.ui.Collection} items Array of items to prepend before this control. 21039 * @return {tinymce.ui.Control} Current control instance. 21040 */ 21041 before: function(items) { 21042 var self = this, parent = self.parent(); 21043 21044 if (parent) { 21045 parent.insert(items, parent.items().indexOf(self), true); 21046 } 21047 21048 return self; 21049 }, 21050 21051 /** 21052 * Adds items after the current control. 21053 * 21054 * @method after 21055 * @param {Array/tinymce.ui.Collection} items Array of items to append after this control. 21056 * @return {tinymce.ui.Control} Current control instance. 21057 */ 21058 after: function(items) { 21059 var self = this, parent = self.parent(); 21060 21061 if (parent) { 21062 parent.insert(items, parent.items().indexOf(self)); 21063 } 21064 21065 return self; 21066 }, 21067 21068 /** 21069 * Removes the current control from DOM and from UI collections. 21070 * 21071 * @method remove 21072 * @return {tinymce.ui.Control} Current control instance. 21073 */ 21074 remove: function() { 21075 var self = this, elm = self.getEl(), parent = self.parent(), newItems, i; 21076 21077 if (self.items) { 21078 var controls = self.items().toArray(); 21079 i = controls.length; 21080 while (i--) { 21081 controls[i].remove(); 21082 } 21083 } 21084 21085 if (parent && parent.items) { 21086 newItems = []; 21087 21088 parent.items().each(function(item) { 21089 if (item !== self) { 21090 newItems.push(item); 21091 } 21092 }); 21093 21094 parent.items().set(newItems); 21095 parent._lastRect = null; 21096 } 21097 21098 if (self._eventsRoot && self._eventsRoot == self) { 21099 DomUtils.off(elm); 21100 } 21101 21102 var lookup = self.getRoot().controlIdLookup; 21103 if (lookup) { 21104 delete lookup[self._id]; 21105 } 21106 21107 if (elm && elm.parentNode) { 21108 elm.parentNode.removeChild(elm); 21109 } 21110 21111 self._rendered = false; 21112 21113 return self; 21114 }, 21115 21116 /** 21117 * Renders the control before the specified element. 21118 * 21119 * @method renderBefore 21120 * @param {Element} elm Element to render before. 21121 * @return {tinymce.ui.Control} Current control instance. 21122 */ 21123 renderBefore: function(elm) { 21124 var self = this; 21125 21126 elm.parentNode.insertBefore(DomUtils.createFragment(self.renderHtml()), elm); 21127 self.postRender(); 21128 21129 return self; 21130 }, 21131 21132 /** 21133 * Renders the control to the specified element. 21134 * 21135 * @method renderBefore 21136 * @param {Element} elm Element to render to. 21137 * @return {tinymce.ui.Control} Current control instance. 21138 */ 21139 renderTo: function(elm) { 21140 var self = this; 21141 21142 elm = elm || self.getContainerElm(); 21143 elm.appendChild(DomUtils.createFragment(self.renderHtml())); 21144 self.postRender(); 21145 21146 return self; 21147 }, 21148 21149 /** 21150 * Post render method. Called after the control has been rendered to the target. 21151 * 21152 * @method postRender 21153 * @return {tinymce.ui.Control} Current control instance. 21154 */ 21155 postRender: function() { 21156 var self = this, settings = self.settings, elm, box, parent, name, parentEventsRoot; 21157 21158 // Bind on<event> settings 21159 for (name in settings) { 21160 if (name.indexOf("on") === 0) { 21161 self.on(name.substr(2), settings[name]); 21162 } 21163 } 21164 21165 if (self._eventsRoot) { 21166 for (parent = self.parent(); !parentEventsRoot && parent; parent = parent.parent()) { 21167 parentEventsRoot = parent._eventsRoot; 21168 } 21169 21170 if (parentEventsRoot) { 21171 for (name in parentEventsRoot._nativeEvents) { 21172 self._nativeEvents[name] = true; 21173 } 21174 } 21175 } 21176 21177 self.bindPendingEvents(); 21178 21179 if (settings.style) { 21180 elm = self.getEl(); 21181 if (elm) { 21182 elm.setAttribute('style', settings.style); 21183 elm.style.cssText = settings.style; 21184 } 21185 } 21186 21187 if (!self._visible) { 21188 DomUtils.css(self.getEl(), 'display', 'none'); 21189 } 21190 21191 if (self.settings.border) { 21192 box = self.borderBox(); 21193 DomUtils.css(self.getEl(), { 21194 'border-top-width': box.top, 21195 'border-right-width': box.right, 21196 'border-bottom-width': box.bottom, 21197 'border-left-width': box.left 21198 }); 21199 } 21200 21201 // Add instance to lookup 21202 var root = self.getRoot(); 21203 if (!root.controlIdLookup) { 21204 root.controlIdLookup = {}; 21205 } 21206 21207 root.controlIdLookup[self._id] = self; 21208 21209 for (var key in self._aria) { 21210 self.aria(key, self._aria[key]); 21211 } 21212 21213 self.fire('postrender', {}, false); 21214 }, 21215 21216 /** 21217 * Scrolls the current control into view. 21218 * 21219 * @method scrollIntoView 21220 * @param {String} align Alignment in view top|center|bottom. 21221 * @return {tinymce.ui.Control} Current control instance. 21222 */ 21223 scrollIntoView: function(align) { 21224 function getOffset(elm, rootElm) { 21225 var x, y, parent = elm; 21226 21227 x = y = 0; 21228 while (parent && parent != rootElm && parent.nodeType) { 21229 x += parent.offsetLeft || 0; 21230 y += parent.offsetTop || 0; 21231 parent = parent.offsetParent; 21232 } 21233 21234 return {x: x, y: y}; 21235 } 21236 21237 var elm = this.getEl(), parentElm = elm.parentNode; 21238 var x, y, width, height, parentWidth, parentHeight; 21239 var pos = getOffset(elm, parentElm); 21240 21241 x = pos.x; 21242 y = pos.y; 21243 width = elm.offsetWidth; 21244 height = elm.offsetHeight; 21245 parentWidth = parentElm.clientWidth; 21246 parentHeight = parentElm.clientHeight; 21247
21248 if (align == "end") { 21249 x -= parentWidth - width; 21250 y -= parentHeight - height; 21251 } else if (align == "center") { 21252 x -= (parentWidth / 2) - (width / 2); 21253 y -= (parentHeight / 2) - (height / 2); 21254 } 21255 21256 parentElm.scrollLeft = x; 21257 parentElm.scrollTop = y; 21258 21259 return this; 21260 }, 21261 21262 /** 21263 * Binds pending DOM events. 21264 * 21265 * @private 21266 */ 21267 bindPendingEvents: function() { 21268 var self = this, i, l, parents, eventRootCtrl, nativeEvents, name; 21269 21270 function delegate(e) { 21271 var control = self.getParentCtrl(e.target); 21272 21273 if (control) { 21274 control.fire(e.type, e); 21275 } 21276 } 21277 21278 function mouseLeaveHandler() { 21279 var ctrl = eventRootCtrl._lastHoverCtrl; 21280 21281 if (ctrl) { 21282 ctrl.fire("mouseleave", {target: ctrl.getEl()}); 21283 21284 ctrl.parents().each(function(ctrl) { 21285 ctrl.fire("mouseleave", {target: ctrl.getEl()}); 21286 }); 21287 21288 eventRootCtrl._lastHoverCtrl = null; 21289 } 21290 } 21291 21292 function mouseEnterHandler(e) { 21293 var ctrl = self.getParentCtrl(e.target), lastCtrl = eventRootCtrl._lastHoverCtrl, idx = 0, i, parents, lastParents; 21294 21295 // Over on a new control 21296 if (ctrl !== lastCtrl) { 21297 eventRootCtrl._lastHoverCtrl = ctrl; 21298 21299 parents = ctrl.parents().toArray().reverse(); 21300 parents.push(ctrl); 21301 21302 if (lastCtrl) { 21303 lastParents = lastCtrl.parents().toArray().reverse(); 21304 lastParents.push(lastCtrl); 21305 21306 for (idx = 0; idx < lastParents.length; idx++) { 21307 if (parents[idx] !== lastParents[idx]) { 21308 break; 21309 } 21310 } 21311 21312 for (i = lastParents.length - 1; i >= idx; i--) { 21313 lastCtrl = lastParents[i]; 21314 lastCtrl.fire("mouseleave", { 21315 target : lastCtrl.getEl() 21316 }); 21317 } 21318 } 21319 21320 for (i = idx; i < parents.length; i++) { 21321 ctrl = parents[i]; 21322 ctrl.fire("mouseenter", { 21323 target : ctrl.getEl() 21324 }); 21325 } 21326 } 21327 } 21328 21329 function fixWheelEvent(e) { 21330 e.preventDefault(); 21331 21332 if (e.type == "mousewheel") { 21333 e.deltaY = -1 / 40 * e.wheelDelta; 21334 21335 if (e.wheelDeltaX) { 21336 e.deltaX = -1 / 40 * e.wheelDeltaX; 21337 } 21338 } else { 21339 e.deltaX = 0; 21340 e.deltaY = e.detail; 21341 } 21342 21343 e = self.fire("wheel", e); 21344 } 21345 21346 self._rendered = true; 21347 21348 nativeEvents = self._nativeEvents; 21349 if (nativeEvents) { 21350 // Find event root element if it exists 21351 parents = self.parents().toArray(); 21352 parents.unshift(self); 21353 for (i = 0, l = parents.length; !eventRootCtrl && i < l; i++) { 21354 eventRootCtrl = parents[i]._eventsRoot; 21355 } 21356 21357 // Event root wasn't found the use the root control 21358 if (!eventRootCtrl) { 21359 eventRootCtrl = parents[parents.length - 1] || self; 21360 } 21361 21362 // Set the eventsRoot property on children that didn't have it 21363 self._eventsRoot = eventRootCtrl; 21364 for (l = i, i = 0; i < l; i++) { 21365 parents[i]._eventsRoot = eventRootCtrl; 21366 } 21367 21368 var eventRootDelegates = eventRootCtrl._delegates; 21369 if (!eventRootDelegates) { 21370 eventRootDelegates = eventRootCtrl._delegates = {}; 21371 } 21372 21373 // Bind native event delegates 21374 for (name in nativeEvents) { 21375 if (!nativeEvents) { 21376 return false; 21377 } 21378 21379 if (name === "wheel" && !hasWheelEventSupport) { 21380 if (hasMouseWheelEventSupport) { 21381 DomUtils.on(self.getEl(), "mousewheel", fixWheelEvent); 21382 } else { 21383 DomUtils.on(self.getEl(), "DOMMouseScroll", fixWheelEvent); 21384 } 21385 21386 continue; 21387 } 21388 21389 // Special treatment for mousenter/mouseleave since these doesn't bubble 21390 if (name === "mouseenter" || name === "mouseleave") { 21391 // Fake mousenter/mouseleave 21392 if (!eventRootCtrl._hasMouseEnter) { 21393 DomUtils.on(eventRootCtrl.getEl(), "mouseleave", mouseLeaveHandler); 21394 DomUtils.on(eventRootCtrl.getEl(), "mouseover", mouseEnterHandler); 21395 eventRootCtrl._hasMouseEnter = 1; 21396 } 21397 } else if (!eventRootDelegates[name]) { 21398 DomUtils.on(eventRootCtrl.getEl(), name, delegate); 21399 eventRootDelegates[name] = true; 21400 } 21401 21402 // Remove the event once it's bound 21403 nativeEvents[name] = false; 21404 } 21405 } 21406 }, 21407 21408 getRoot: function() { 21409 var ctrl = this, rootControl, parents = []; 21410 21411 while (ctrl) { 21412 if (ctrl.rootControl) { 21413 rootControl = ctrl.rootControl; 21414 break; 21415 } 21416 21417 parents.push(ctrl); 21418 rootControl = ctrl; 21419 ctrl = ctrl.parent(); 21420 } 21421 21422 if (!rootControl) { 21423 rootControl = this; 21424 } 21425 21426 var i = parents.length; 21427 while (i--) { 21428 parents[i].rootControl = rootControl; 21429 } 21430 21431 return rootControl; 21432 }, 21433 21434 /** 21435 * Reflows the current control and it's parents. 21436 * This should be used after you for example append children to the current control so 21437 * that the layout managers know that they need to reposition everything. 21438 * 21439 * @example 21440 * container.append({type: 'button', text: 'My button'}).reflow(); 21441 * 21442 * @method reflow 21443 * @return {tinymce.ui.Control} Current control instance. 21444 */ 21445 reflow: function() { 21446 this.repaint(); 21447 21448 return this; 21449 } 21450 21451 /** 21452 * Sets/gets the parent container for the control. 21453 * 21454 * @method parent 21455 * @param {tinymce.ui.Container} parent Optional parent to set. 21456 * @return {tinymce.ui.Control} Parent control or the current control on a set action. 21457 */ 21458 // parent: function(parent) {} -- Generated 21459 21460 /** 21461 * Sets/gets the text for the control. 21462 * 21463 * @method text 21464 * @param {String} value Value to set to control. 21465 * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get. 21466 */ 21467 // text: function(value) {} -- Generated 21468 21469 /** 21470 * Sets/gets the width for the control. 21471 * 21472 * @method width 21473 * @param {Number} value Value to set to control. 21474 * @return {Number/tinymce.ui.Control} Current control on a set operation or current value on a get. 21475 */ 21476 // width: function(value) {} -- Generated 21477 21478 /** 21479 * Sets/gets the height for the control. 21480 * 21481 * @method height 21482 * @param {Number} value Value to set to control. 21483 * @return {Number/tinymce.ui.Control} Current control on a set operation or current value on a get. 21484 */ 21485 // height: function(value) {} -- Generated 21486 21487 /** 21488 * Sets/gets the disabled state on the control. 21489 * 21490 * @method disabled 21491 * @param {Boolean} state Value to set to control. 21492 * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get. 21493 */ 21494 // disabled: function(state) {} -- Generated 21495 21496 /** 21497 * Sets/gets the active for the control. 21498 * 21499 * @method active 21500 * @param {Boolean} state Value to set to control. 21501 * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get. 21502 */ 21503 // active: function(state) {} -- Generated 21504 21505 /** 21506 * Sets/gets the name for the control. 21507 *
21508 * @method name 21509 * @param {String} value Value to set to control. 21510 * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get. 21511 */ 21512 // name: function(value) {} -- Generated 21513 21514 /** 21515 * Sets/gets the title for the control. 21516 * 21517 * @method title 21518 * @param {String} value Value to set to control. 21519 * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get. 21520 */ 21521 // title: function(value) {} -- Generated 21522 }); 21523 21524 return Control; 21525}); 21526 21527// Included from: js/tinymce/classes/ui/Factory.js 21528 21529/** 21530 * Factory.js 21531 * 21532 * Copyright, Moxiecode Systems AB 21533 * Released under LGPL License. 21534 * 21535 * License: http://www.tinymce.com/license 21536 * Contributing: http://www.tinymce.com/contributing 21537 */ 21538 21539/*global tinymce:true */ 21540 21541/** 21542 * This class is a factory for control instances. This enables you 21543 * to create instances of controls without having to require the UI controls directly. 21544 * 21545 * It also allow you to override or add new control types. 21546 * 21547 * @class tinymce.ui.Factory 21548 */ 21549define("tinymce/ui/Factory", [], function() { 21550 "use strict"; 21551 21552 var types = {}, namespaceInit; 21553 21554 return { 21555 /** 21556 * Adds a new control instance type to the factory. 21557 * 21558 * @method add 21559 * @param {String} type Type name for example "button". 21560 * @param {function} typeClass Class type function. 21561 */ 21562 add: function(type, typeClass) { 21563 types[type.toLowerCase()] = typeClass; 21564 }, 21565 21566 /** 21567 * Returns true/false if the specified type exists or not. 21568 * 21569 * @method has 21570 * @param {String} type Type to look for. 21571 * @return {Boolean} true/false if the control by name exists. 21572 */ 21573 has: function(type) { 21574 return !!types[type.toLowerCase()]; 21575 }, 21576 21577 /** 21578 * Creates a new control instance based on the settings provided. The instance created will be 21579 * based on the specified type property it can also create whole structures of components out of 21580 * the specified JSON object. 21581 * 21582 * @example 21583 * tinymce.ui.Factory.create({ 21584 * type: 'button', 21585 * text: 'Hello world!' 21586 * }); 21587 * 21588 * @method create 21589 * @param {Object/String} settings Name/Value object with items used to create the type. 21590 * @return {tinymce.ui.Control} Control instance based on the specified type. 21591 */ 21592 create: function(type, settings) { 21593 var ControlType, name, namespace; 21594 21595 // Build type lookup 21596 if (!namespaceInit) { 21597 namespace = tinymce.ui; 21598 21599 for (name in namespace) { 21600 types[name.toLowerCase()] = namespace[name]; 21601 } 21602 21603 namespaceInit = true; 21604 } 21605 21606 // If string is specified then use it as the type 21607 if (typeof(type) == 'string') { 21608 settings = settings || {}; 21609 settings.type = type; 21610 } else { 21611 settings = type; 21612 type = settings.type; 21613 } 21614 21615 // Find control type 21616 type = type.toLowerCase(); 21617 ControlType = types[type]; 21618 21619 // #if debug 21620 21621 if (!ControlType) { 21622 throw new Error("Could not find control by type: " + type); 21623 } 21624 21625 // #endif 21626 21627 ControlType = new ControlType(settings); 21628 ControlType.type = type; // Set the type on the instance, this will be used by the Selector engine 21629 21630 return ControlType; 21631 } 21632 }; 21633}); 21634 21635// Included from: js/tinymce/classes/ui/KeyboardNavigation.js 21636 21637/** 21638 * KeyboardNavigation.js 21639 * 21640 * Copyright, Moxiecode Systems AB 21641 * Released under LGPL License. 21642 * 21643 * License: http://www.tinymce.com/license 21644 * Contributing: http://www.tinymce.com/contributing 21645 */ 21646 21647/** 21648 * This class handles keyboard navigation of controls and elements. 21649 * 21650 * @class tinymce.ui.KeyboardNavigation 21651 */ 21652define("tinymce/ui/KeyboardNavigation", [ 21653], function() { 21654 "use strict"; 21655 21656 /** 21657 * This class handles all keyboard navigation for WAI-ARIA support. Each root container 21658 * gets an instance of this class. 21659 * 21660 * @constructor 21661 */ 21662 return function(settings) { 21663 var root = settings.root, focusedElement, focusedControl; 21664 21665 try { 21666 focusedElement = document.activeElement; 21667 } catch (ex) { 21668 // IE sometimes fails to return a proper element 21669 focusedElement = document.body; 21670 } 21671 21672 focusedControl = root.getParentCtrl(focusedElement); 21673 21674 /** 21675 * Returns the currently focused elements wai aria role of the currently 21676 * focused element or specified element. 21677 * 21678 * @private 21679 * @param {Element} elm Optional element to get role from. 21680 * @return {String} Role of specified element. 21681 */ 21682 function getRole(elm) { 21683 elm = elm || focusedElement; 21684 21685 return elm && elm.getAttribute('role'); 21686 } 21687 21688 /** 21689 * Returns the wai role of the parent element of the currently 21690 * focused element or specified element. 21691 * 21692 * @private 21693 * @param {Element} elm Optional element to get parent role from. 21694 * @return {String} Role of the first parent that has a role. 21695 */ 21696 function getParentRole(elm) { 21697 var role, parent = elm || focusedElement; 21698 21699 while ((parent = parent.parentNode)) { 21700 if ((role = getRole(parent))) { 21701 return role; 21702 } 21703 } 21704 } 21705 21706 /** 21707 * Returns a wai aria property by name for example aria-selected. 21708 * 21709 * @private 21710 * @param {String} name Name of the aria property to get for example "disabled". 21711 * @return {String} Aria property value. 21712 */ 21713 function getAriaProp(name) { 21714 var elm = focusedElement; 21715 21716 if (elm) { 21717 return elm.getAttribute('aria-' + name); 21718 } 21719 } 21720 21721 /** 21722 * Is the element a text input element or not. 21723 * 21724 * @private 21725 * @param {Element} elm Element to check if it's an text input element or not. 21726 * @return {Boolean} True/false if the element is a text element or not. 21727 */ 21728 function isTextInputElement(elm) { 21729 var tagName = elm.tagName.toUpperCase(); 21730 21731 // Notice: since type can be "email" etc we don't check the type 21732 // So all input elements gets treated as text input elements 21733 return tagName == "INPUT" || tagName == "TEXTAREA"; 21734 } 21735 21736 /** 21737 * Returns true/false if the specified element can be focused or not. 21738 * 21739 * @private 21740 * @param {Element} elm DOM element to check if it can be focused or not. 21741 * @return {Boolean} True/false if the element can have focus. 21742 */ 21743 function canFocus(elm) { 21744 if (isTextInputElement(elm) && !elm.hidden) { 21745 return true; 21746 } 21747 21748 if (/^(button|menuitem|checkbox|tab|menuitemcheckbox|option|gridcell)$/.test(getRole(elm))) { 21749 return true; 21750 } 21751 21752 return false;
21753 } 21754 21755 /** 21756 * Returns an array of focusable visible elements within the specified container element. 21757 * 21758 * @private 21759 * @param {Element} elm DOM element to find focusable elements within. 21760 * @return {Array} Array of focusable elements. 21761 */ 21762 function getFocusElements(elm) { 21763 var elements = []; 21764 21765 function collect(elm) { 21766 if (elm.nodeType != 1 || elm.style.display == 'none') { 21767 return; 21768 } 21769 21770 if (canFocus(elm)) { 21771 elements.push(elm); 21772 } 21773 21774 for (var i = 0; i < elm.childNodes.length; i++) { 21775 collect(elm.childNodes[i]); 21776 } 21777 } 21778 21779 collect(elm || root.getEl()); 21780 21781 return elements; 21782 } 21783 21784 /** 21785 * Returns the navigation root control for the specified control. The navigation root 21786 * is the control that the keyboard navigation gets scoped to for example a menubar or toolbar group. 21787 * It will look for parents of the specified target control or the currently focused control if this option is omitted. 21788 * 21789 * @private 21790 * @param {tinymce.ui.Control} targetControl Optional target control to find root of. 21791 * @return {tinymce.ui.Control} Navigation root control. 21792 */ 21793 function getNavigationRoot(targetControl) { 21794 var navigationRoot, controls; 21795 21796 targetControl = targetControl || focusedControl;
21797 controls = targetControl.parents().toArray(); 21798 controls.unshift(targetControl); 21799 21800 for (var i = 0; i < controls.length; i++) { 21801 navigationRoot = controls[i]; 21802 21803 if (navigationRoot.settings.ariaRoot) { 21804 break; 21805 } 21806 } 21807 21808 return navigationRoot; 21809 } 21810 21811 /** 21812 * Focuses the first item in the specified targetControl element or the last aria index if the 21813 * navigation root has the ariaRemember option enabled. 21814 * 21815 * @private 21816 * @param {tinymce.ui.Control} targetControl Target control to focus the first item in. 21817 */ 21818 function focusFirst(targetControl) { 21819 var navigationRoot = getNavigationRoot(targetControl); 21820 var focusElements = getFocusElements(navigationRoot.getEl()); 21821 21822 if (navigationRoot.settings.ariaRemember && "lastAriaIndex" in navigationRoot) { 21823 moveFocusToIndex(navigationRoot.lastAriaIndex, focusElements); 21824 } else { 21825 moveFocusToIndex(0, focusElements); 21826 } 21827 } 21828 21829 /** 21830 * Moves the focus to the specified index within the elements list. 21831 * This will scope the index to the size of the element list if it changed. 21832 * 21833 * @private 21834 * @param {Number} idx Specified index to move to. 21835 * @param {Array} elements Array with dom elements to move focus within. 21836 * @return {Number} Input index or a changed index if it was out of range. 21837 */ 21838 function moveFocusToIndex(idx, elements) { 21839 if (idx < 0) { 21840 idx = elements.length - 1; 21841 } else if (idx >= elements.length) { 21842 idx = 0; 21843 } 21844 21845 if (elements[idx]) { 21846 elements[idx].focus(); 21847 } 21848 21849 return idx; 21850 } 21851 21852 /** 21853 * Moves the focus forwards or backwards. 21854 * 21855 * @private 21856 * @param {Number} dir Direction to move in positive means forward, negative means backwards. 21857 * @param {Array} elements Optional array of elements to move within defaults to the current navigation roots elements. 21858 */ 21859 function moveFocus(dir, elements) { 21860 var idx = -1, navigationRoot = getNavigationRoot(); 21861 21862 elements = elements || getFocusElements(navigationRoot.getEl()); 21863 21864 for (var i = 0; i < elements.length; i++) { 21865 if (elements[i] === focusedElement) { 21866 idx = i; 21867 } 21868 } 21869 21870 idx += dir; 21871 navigationRoot.lastAriaIndex = moveFocusToIndex(idx, elements); 21872 } 21873 21874 /** 21875 * Moves the focus to the left this is called by the left key. 21876 * 21877 * @private 21878 */ 21879 function left() { 21880 var parentRole = getParentRole(); 21881 21882 if (parentRole == "tablist") { 21883 moveFocus(-1, getFocusElements(focusedElement.parentNode)); 21884 } else if (focusedControl.parent().submenu) { 21885 cancel(); 21886 } else { 21887 moveFocus(-1); 21888 } 21889 } 21890 21891 /** 21892 * Moves the focus to the right this is called by the right key. 21893 * 21894 * @private 21895 */ 21896 function right() { 21897 var role = getRole(), parentRole = getParentRole(); 21898 21899 if (parentRole == "tablist") { 21900 moveFocus(1, getFocusElements(focusedElement.parentNode)); 21901 } else if (role == "menuitem" && parentRole == "menu" && getAriaProp('haspopup')) { 21902 enter(); 21903 } else { 21904 moveFocus(1); 21905 } 21906 } 21907 21908 /** 21909 * Moves the focus to the up this is called by the up key. 21910 * 21911 * @private 21912 */ 21913 function up() { 21914 moveFocus(-1); 21915 } 21916 21917 /** 21918 * Moves the focus to the up this is called by the down key. 21919 * 21920 * @private 21921 */ 21922 function down() { 21923 var role = getRole(), parentRole = getParentRole(); 21924 21925 if (role == "menuitem" && parentRole == "menubar") { 21926 enter(); 21927 } else if (role == "button" && getAriaProp('haspopup')) { 21928 enter({key: 'down'}); 21929 } else { 21930 moveFocus(1); 21931 } 21932 } 21933 21934 /** 21935 * Moves the focus to the next item or previous item depending on shift key. 21936 * 21937 * @private 21938 * @param {DOMEvent} e DOM event object. 21939 */ 21940 function tab(e) { 21941 var parentRole = getParentRole(); 21942 21943 if (parentRole == "tablist") { 21944 var elm = getFocusElements(focusedControl.getEl('body'))[0]; 21945 21946 if (elm) { 21947 elm.focus(); 21948 } 21949 } else { 21950 moveFocus(e.shiftKey ? -1 : 1); 21951 } 21952 } 21953 21954 /** 21955 * Calls the cancel event on the currently focused control. This is normally done using the Esc key. 21956 * 21957 * @private 21958 */ 21959 function cancel() { 21960 focusedControl.fire('cancel'); 21961 } 21962 21963 /** 21964 * Calls the click event on the currently focused control. This is normally done using the Enter/Space keys. 21965 * 21966 * @private 21967 * @param {Object} aria Optional aria data to pass along with the enter event. 21968 */ 21969 function enter(aria) { 21970 aria = aria || {};
21971 focusedControl.fire('click', {target: focusedElement, aria: aria}); 21972 } 21973 21974 root.on('keydown', function(e) { 21975 function handleNonTabOrEscEvent(e, handler) { 21976 // Ignore non tab keys for text elements 21977 if (isTextInputElement(focusedElement)) { 21978 return; 21979 } 21980 21981 if (handler(e) !== false) { 21982 e.preventDefault(); 21983 } 21984 } 21985 21986 if (e.isDefaultPrevented()) { 21987 return; 21988 } 21989 21990 switch (e.keyCode) { 21991 case 37: // DOM_VK_LEFT 21992 handleNonTabOrEscEvent(e, left); 21993 break; 21994 21995 case 39: // DOM_VK_RIGHT 21996 handleNonTabOrEscEvent(e, right); 21997 break; 21998 21999 case 38: // DOM_VK_UP 22000 handleNonTabOrEscEvent(e, up); 22001 break; 22002 22003 case 40: // DOM_VK_DOWN 22004 handleNonTabOrEscEvent(e, down); 22005 break; 22006 22007 case 27: // DOM_VK_ESCAPE 22008 cancel(); 22009 break; 22010 22011 case 14: // DOM_VK_ENTER 22012 case 13: // DOM_VK_RETURN 22013 case 32: // DOM_VK_SPACE 22014 handleNonTabOrEscEvent(e, enter); 22015 break; 22016 22017 case 9: // DOM_VK_TAB 22018 if (tab(e) !== false) { 22019 e.preventDefault(); 22020 } 22021 break; 22022 } 22023 }); 22024 22025 root.on('focusin', function(e) { 22026 focusedElement = e.target; 22027 focusedControl = e.control; 22028 }); 22029 22030 return { 22031 focusFirst: focusFirst 22032 }; 22033 }; 22034}); 22035 22036// Included from: js/tinymce/classes/ui/Container.js 22037 22038/** 22039 * Container.js 22040 * 22041 * Copyright, Moxiecode Systems AB 22042 * Released under LGPL License. 22043 * 22044 * License: http://www.tinymce.com/license 22045 * Contributing: http://www.tinymce.com/contributing 22046 */ 22047 22048/** 22049 * Container control. This is extended by all controls that can have 22050 * children such as panels etc. You can also use this class directly as an 22051 * generic container instance. The container doesn't have any specific role or style. 22052 * 22053 * @-x-less Container.less 22054 * @class tinymce.ui.Container 22055 * @extends tinymce.ui.Control 22056 */ 22057define("tinymce/ui/Container", [ 22058 "tinymce/ui/Control", 22059 "tinymce/ui/Collection", 22060 "tinymce/ui/Selector", 22061 "tinymce/ui/Factory", 22062 "tinymce/ui/KeyboardNavigation", 22063 "tinymce/util/Tools", 22064 "tinymce/ui/DomUtils" 22065], function(Control, Collection, Selector, Factory, KeyboardNavigation, Tools, DomUtils) { 22066 "use strict"; 22067 22068 var selectorCache = {}; 22069 22070 return Control.extend({ 22071 layout: '', 22072 innerClass: 'container-inner', 22073 22074 /** 22075 * Constructs a new control instance with the specified settings. 22076 * 22077 * @constructor 22078 * @param {Object} settings Name/value object with settings. 22079 * @setting {Array} items Items to add to container in JSON format or control instances. 22080 * @setting {String} layout Layout manager by name to use. 22081 * @setting {Object} defaults Default settings to apply to all items. 22082 */ 22083 init: function(settings) { 22084 var self = this; 22085 22086 self._super(settings); 22087 settings = self.settings; 22088 self._fixed = settings.fixed; 22089 self._items = new Collection(); 22090 22091 if (self.isRtl()) { 22092 self.addClass('rtl'); 22093 } 22094 22095 self.addClass('container'); 22096 self.addClass('container-body', 'body'); 22097 22098 if (settings.containerCls) { 22099 self.addClass(settings.containerCls); 22100 } 22101 22102 self._layout = Factory.create((settings.layout || self.layout) + 'layout'); 22103 22104 if (self.settings.items) { 22105 self.add(self.settings.items); 22106 } 22107 22108 // TODO: Fix this! 22109 self._hasBody = true; 22110 }, 22111 22112 /** 22113 * Returns a collection of child items that the container currently have. 22114 * 22115 * @method items 22116 * @return {tinymce.ui.Collection} Control collection direct child controls. 22117 */ 22118 items: function() { 22119 return this._items; 22120 }, 22121 22122 /** 22123 * Find child controls by selector. 22124 * 22125 * @method find 22126 * @param {String} selector Selector CSS pattern to find children by. 22127 * @return {tinymce.ui.Collection} Control collection with child controls. 22128 */ 22129 find: function(selector) { 22130 selector = selectorCache[selector] = selectorCache[selector] || new Selector(selector); 22131 22132 return selector.find(this); 22133 }, 22134 22135 /** 22136 * Adds one or many items to the current container. This will create instances of 22137 * the object representations if needed. 22138 * 22139 * @method add 22140 * @param {Array/Object/tinymce.ui.Control} items Array or item that will be added to the container. 22141 * @return {tinymce.ui.Collection} Current collection control. 22142 */ 22143 add: function(items) { 22144 var self = this; 22145 22146 self.items().add(self.create(items)).parent(self); 22147 22148 return self; 22149 }, 22150 22151 /** 22152 * Focuses the current container instance. This will look 22153 * for the first control in the container and focus that. 22154 *
22155 * @method focus 22156 * @param {Boolean} keyboard Optional true/false if the focus was a keyboard focus or not. 22157 * @return {tinymce.ui.Collection} Current instance. 22158 */ 22159 focus: function(keyboard) { 22160 var self = this, focusCtrl, keyboardNav, items; 22161 22162 if (keyboard) { 22163 keyboardNav = self.keyboardNav || self.parents().eq(-1)[0].keyboardNav; 22164 22165 if (keyboardNav) { 22166 keyboardNav.focusFirst(self); 22167 return; 22168 } 22169 } 22170 22171 items = self.find('*'); 22172 22173 // TODO: Figure out a better way to auto focus alert dialog buttons 22174 if (self.statusbar) { 22175 items.add(self.statusbar.items()); 22176 } 22177 22178 items.each(function(ctrl) { 22179 if (ctrl.settings.autofocus) { 22180 focusCtrl = null; 22181 return false; 22182 } 22183 22184 if (ctrl.canFocus) { 22185 focusCtrl = focusCtrl || ctrl; 22186 } 22187 }); 22188 22189 if (focusCtrl) { 22190 focusCtrl.focus(); 22191 } 22192 22193 return self; 22194 }, 22195 22196 /** 22197 * Replaces the specified child control with a new control. 22198 * 22199 * @method replace 22200 * @param {tinymce.ui.Control} oldItem Old item to be replaced. 22201 * @param {tinymce.ui.Control} newItem New item to be inserted. 22202 */ 22203 replace: function(oldItem, newItem) { 22204 var ctrlElm, items = this.items(), i = items.length; 22205 22206 // Replace the item in collection 22207 while (i--) { 22208 if (items[i] === oldItem) { 22209 items[i] = newItem; 22210 break; 22211 } 22212 } 22213 22214 if (i >= 0) { 22215 // Remove new item from DOM 22216 ctrlElm = newItem.getEl(); 22217 if (ctrlElm) { 22218 ctrlElm.parentNode.removeChild(ctrlElm); 22219 } 22220 22221 // Remove old item from DOM 22222 ctrlElm = oldItem.getEl(); 22223 if (ctrlElm) { 22224 ctrlElm.parentNode.removeChild(ctrlElm); 22225 } 22226 } 22227 22228 // Adopt the item 22229 newItem.parent(this); 22230 }, 22231 22232 /** 22233 * Creates the specified items. If any of the items is plain JSON style objects 22234 * it will convert these into real tinymce.ui.Control instances. 22235 * 22236 * @method create 22237 * @param {Array} items Array of items to convert into control instances. 22238 * @return {Array} Array with control instances. 22239 */ 22240 create: function(items) { 22241 var self = this, settings, ctrlItems = []; 22242 22243 // Non array structure, then force it into an array 22244 if (!Tools.isArray(items)) { 22245 items = [items]; 22246 } 22247 22248 // Add default type to each child control 22249 Tools.each(items, function(item) { 22250 if (item) { 22251 // Construct item if needed 22252 if (!(item instanceof Control)) { 22253 // Name only then convert it to an object 22254 if (typeof(item) == "string") { 22255 item = {type: item}; 22256 } 22257 22258 // Create control instance based on input settings and default settings 22259 settings = Tools.extend({}, self.settings.defaults, item); 22260 item.type = settings.type = settings.type || item.type || self.settings.defaultType || 22261 (settings.defaults ? settings.defaults.type : null); 22262 item = Factory.create(settings); 22263 } 22264 22265 ctrlItems.push(item); 22266 } 22267 }); 22268 22269 return ctrlItems; 22270 }, 22271 22272 /** 22273 * Renders new control instances. 22274 * 22275 * @private 22276 */ 22277 renderNew: function() { 22278 var self = this; 22279 22280 // Render any new items 22281 self.items().each(function(ctrl, index) { 22282 var containerElm, fragment; 22283 22284 ctrl.parent(self); 22285 22286 if (!ctrl._rendered) { 22287 containerElm = self.getEl('body'); 22288 fragment = DomUtils.createFragment(ctrl.renderHtml()); 22289 22290 // Insert or append the item 22291 if (containerElm.hasChildNodes() && index <= containerElm.childNodes.length - 1) { 22292 containerElm.insertBefore(fragment, containerElm.childNodes[index]); 22293 } else { 22294 containerElm.appendChild(fragment); 22295 } 22296 22297 ctrl.postRender(); 22298 } 22299 }); 22300 22301 self._layout.applyClasses(self); 22302 self._lastRect = null; 22303 22304 return self; 22305 }, 22306 22307 /** 22308 * Appends new instances to the current container. 22309 * 22310 * @method append 22311 * @param {Array/tinymce.ui.Collection} items Array if controls to append. 22312 * @return {tinymce.ui.Container} Current container instance. 22313 */ 22314 append: function(items) { 22315 return this.add(items).renderNew(); 22316 }, 22317 22318 /** 22319 * Prepends new instances to the current container. 22320 * 22321 * @method prepend 22322 * @param {Array/tinymce.ui.Collection}
22322 items Array if controls to prepend. 22323 * @return {tinymce.ui.Container} Current container instance. 22324 */ 22325 prepend: function(items) { 22326 var self = this; 22327 22328 self.items().set(self.create(items).concat(self.items().toArray())); 22329 22330 return self.renderNew(); 22331 }, 22332 22333 /** 22334 * Inserts an control at a specific index. 22335 * 22336 * @method insert 22337 * @param {Array/tinymce.ui.Collection} items Array if controls to insert. 22338 * @param {Number} index Index to insert controls at. 22339 * @param {Boolean} [before=false] Inserts controls before the index. 22340 */ 22341 insert: function(items, index, before) { 22342 var self = this, curItems, beforeItems, afterItems; 22343 22344 items = self.create(items); 22345 curItems = self.items(); 22346 22347 if (!before && index < curItems.length - 1) { 22348 index += 1; 22349 } 22350 22351 if (index >= 0 && index < curItems.length) { 22352 beforeItems = curItems.slice(0, index).toArray(); 22353 afterItems = curItems.slice(index).toArray(); 22354 curItems.set(beforeItems.concat(items, afterItems)); 22355 } 22356 22357 return self.renderNew(); 22358 }, 22359 22360 /** 22361 * Populates the form fields from the specified JSON data object. 22362 * 22363 * Control items in the form that matches the data will have it's value set. 22364 * 22365 * @method fromJSON 22366 * @param {Object} data JSON data object to set control values by. 22367 * @return {tinymce.ui.Container} Current form instance. 22368 */ 22369 fromJSON: function(data) { 22370 var self = this; 22371 22372 for (var name in data) { 22373 self.find('#' + name).value(data[name]); 22374 } 22375 22376 return self; 22377 }, 22378 22379 /** 22380 * Serializes the form into a JSON object by getting all items 22381 * that has a name and a value. 22382 * 22383 * @method toJSON 22384 * @return {Object} JSON object with form data. 22385 */ 22386 toJSON: function() { 22387 var self = this, data = {}; 22388 22389 self.find('*').each(function(ctrl) { 22390 var name = ctrl.name(), value = ctrl.value(); 22391 22392 if (name && typeof(value) != "undefined") { 22393 data[name] = value; 22394 } 22395 }); 22396 22397 return data; 22398 }, 22399 22400 preRender: function() { 22401 }, 22402 22403 /** 22404 * Renders the control as a HTML string. 22405 * 22406 * @method renderHtml 22407 * @return {String} HTML representing the control. 22408 */ 22409 renderHtml: function() { 22410 var self = this, layout = self._layout, role = this.settings.role; 22411 22412 self.preRender(); 22413 layout.preRender(self); 22414 22415 return ( 22416 '<div id="' + self._id + '" class="' + self.classes() + '"' + (role ? ' role="' + this.settings.role + '"' : '') + '>' + 22417 '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' + 22418 (self.settings.html || '') + layout.renderHtml(self) + 22419 '</div>' + 22420 '</div>' 22421 ); 22422 }, 22423 22424 /** 22425 * Post render method. Called after the control has been rendered to the target. 22426 * 22427 * @method postRender 22428 * @return {tinymce.ui.Container} Current combobox instance. 22429 */ 22430 postRender: function() { 22431 var self = this, box; 22432 22433 self.items().exec('postRender'); 22434 self._super(); 22435 22436 self._layout.postRender(self); 22437 self._rendered = true; 22438 22439 if (self.settings.style) { 22440 DomUtils.css(self.getEl(), self.settings.style); 22441 } 22442 22443 if (self.settings.border) { 22444 box = self.borderBox(); 22445 DomUtils.css(self.getEl(), { 22446 'border-top-width': box.top, 22447 'border-right-width': box.right, 22448 'border-bottom-width': box.bottom, 22449 'border-left-width': box.left 22450 }); 22451 } 22452 22453 if (!self.parent()) { 22454 self.keyboardNav = new KeyboardNavigation({ 22455 root: self 22456 }); 22457 } 22458 22459 return self; 22460 }, 22461 22462 /** 22463 * Initializes the current controls layout rect. 22464 * This will be executed by the layout managers to determine the 22465 * default minWidth/minHeight etc. 22466 * 22467 * @method initLayoutRect 22468 * @return {Object} Layout rect instance. 22469 */ 22470 initLayoutRect: function() { 22471 var self = this, layoutRect = self._super(); 22472 22473 // Recalc container size by asking layout manager 22474 self._layout.recalc(self); 22475 22476 return layoutRect; 22477 }, 22478 22479 /** 22480 * Recalculates the positions of the controls in the current container. 22481 * This is invoked by the reflow method and shouldn't be called directly. 22482 * 22483 * @method recalc 22484 */ 22485 recalc: function() { 22486 var self = this, rect = self._layoutRect, lastRect = self._lastRect; 22487 22488 if (!lastRect || lastRect.w != rect.w || lastRect.h != rect.h) { 22489 self._layout.recalc(self); 22490 rect = self.layoutRect(); 22491 self._lastRect = {x: rect.x, y: rect.y, w: rect.w, h: rect.h}; 22492 return true; 22493 } 22494 }, 22495 22496 /** 22497 * Reflows the current container and it's children and possible parents. 22498 * This should be used after you for example append children to the current control so 22499 * that the layout managers know that they need to reposition everything. 22500 * 22501 * @example 22502 * container.append({type: 'button', text: 'My button'}).reflow(); 22503 * 22504 * @method reflow 22505 * @return {tinymce.ui.Container} Current container instance. 22506 */ 22507 reflow: function() { 22508 var i; 22509 22510 if (this.visible()) { 22511 Control.repaintControls = []; 22512 Control.repaintControls.map = {}; 22513 22514 this.recalc(); 22515 i = Control.repaintControls.length; 22516 22517 while (i--) { 22518 Control.repaintControls[i].repaint(); 22519 } 22520 22521 // TODO: Fix me! 22522 if (this.settings.layout !== "flow" && this.settings.layout !== "stack") { 22523 this.repaint(); 22524 } 22525 22526 Control.repaintControls = []; 22527 } 22528 22529 return this; 22530 } 22531 }); 22532}); 22533 22534// Included from: js/tinymce/classes/ui/DragHelper.js 22535 22536/** 22537 * DragHelper.js 22538 * 22539 * Copyright, Moxiecode Systems AB 22540 * Released under LGPL License. 22541 * 22542 * License: http://www.tinymce.com/license 22543 * Contributing: http://www.tinymce.com/contributing 22544 */ 22545 22546/** 22547 * Drag/drop helper class. 22548 * 22549 * @example 22550 * var dragHelper = new tinymce.ui.DragHelper('mydiv', { 22551 * start: function(evt) { 22552 * }, 22553 * 22554 * drag: function(evt) { 22555 * }, 22556 * 22557 * end: function(evt) { 22558 * } 22559 * }); 22560 * 22561 * @class tinymce.ui.DragHelper 22562 */
22563define("tinymce/ui/DragHelper", [ 22564 "tinymce/ui/DomUtils" 22565], function(DomUtils) { 22566 "use strict"; 22567 22568 function getDocumentSize() { 22569 var doc = document, documentElement, body, scrollWidth, clientWidth; 22570 var offsetWidth, scrollHeight, clientHeight, offsetHeight, max = Math.max; 22571 22572 documentElement = doc.documentElement; 22573 body = doc.body; 22574 22575 scrollWidth = max(documentElement.scrollWidth, body.scrollWidth); 22576 clientWidth = max(documentElement.clientWidth, body.clientWidth); 22577 offsetWidth = max(documentElement.offsetWidth, body.offsetWidth); 22578 22579 scrollHeight = max(documentElement.scrollHeight, body.scrollHeight); 22580 clientHeight = max(documentElement.clientHeight, body.clientHeight); 22581 offsetHeight = max(documentElement.offsetHeight, body.offsetHeight); 22582 22583 return { 22584 width: scrollWidth < offsetWidth ? clientWidth : scrollWidth, 22585 height: scrollHeight < offsetHeight ? clientHeight : scrollHeight 22586 }; 22587 } 22588 22589 return function(id, settings) { 22590 var eventOverlayElm, doc = document, downButton, start, stop, drag, startX, startY; 22591 22592 settings = settings || {}; 22593 22594 function getHandleElm() { 22595 return doc.getElementById(settings.handle || id); 22596 } 22597 22598 start = function(e) { 22599 var docSize = getDocumentSize(), handleElm, cursor; 22600 22601 e.preventDefault(); 22602 downButton = e.button; 22603 handleElm = getHandleElm(); 22604 startX = e.screenX; 22605 startY = e.screenY; 22606 22607 // Grab cursor from handle 22608 if (window.getComputedStyle) { 22609 cursor = window.getComputedStyle(handleElm, null).getPropertyValue("cursor"); 22610 } else { 22611 cursor = handleElm.runtimeStyle.cursor; 22612 } 22613 22614 // Create event overlay and add it to document 22615 eventOverlayElm = doc.createElement('div'); 22616 DomUtils.css(eventOverlayElm, { 22617 position: "absolute", 22618 top: 0, left: 0, 22619 width: docSize.width, 22620 height: docSize.height, 22621 zIndex: 0x7FFFFFFF, 22622 opacity: 0.0001, 22623 cursor: cursor 22624 }); 22625 22626 doc.body.appendChild(eventOverlayElm); 22627 22628 // Bind mouse events 22629 DomUtils.on(doc, 'mousemove', drag); 22630 DomUtils.on(doc, 'mouseup', stop); 22631 22632 // Begin drag 22633 settings.start(e); 22634 }; 22635 22636 drag = function(e) { 22637 if (e.button !== downButton) { 22638 return stop(e); 22639 } 22640 22641 e.deltaX = e.screenX - startX; 22642 e.deltaY = e.screenY - startY; 22643 22644 e.preventDefault(); 22645 settings.drag(e); 22646 }; 22647 22648 stop = function(e) { 22649 DomUtils.off(doc, 'mousemove', drag); 22650 DomUtils.off(doc, 'mouseup', stop); 22651 22652 eventOverlayElm.parentNode.removeChild(eventOverlayElm); 22653 22654 if (settings.stop) { 22655 settings.stop(e); 22656 } 22657 }; 22658 22659 /** 22660 * Destroys the drag/drop helper instance. 22661 * 22662 * @method destroy 22663 */ 22664 this.destroy = function() { 22665 DomUtils.off(getHandleElm()); 22666 }; 22667 22668 DomUtils.on(getHandleElm(), 'mousedown', start); 22669 }; 22670}); 22671 22672// Included from: js/tinymce/classes/ui/Scrollable.js 22673 22674/** 22675 * Scrollable.js 22676 * 22677 * Copyright, Moxiecode Systems AB 22678 * Released under LGPL License. 22679 * 22680 * License: http://www.tinymce.com/license 22681 * Contributing: http://www.tinymce.com/contributing 22682 */ 22683 22684/** 22685 * This mixin makes controls scrollable using custom scrollbars. 22686 * 22687 * @-x-less Scrollable.less 22688 * @mixin tinymce.ui.Scrollable 22689 */ 22690define("tinymce/ui/Scrollable", [ 22691 "tinymce/ui/DomUtils", 22692 "tinymce/ui/DragHelper" 22693], function(DomUtils, DragHelper) { 22694 "use strict"; 22695 22696 return { 22697 init: function() { 22698 var self = this; 22699 self.on('repaint', self.renderScroll); 22700 }, 22701 22702 renderScroll: function() { 22703 var self = this, margin = 2; 22704 22705 function repaintScroll() { 22706 var hasScrollH, hasScrollV, bodyElm; 22707 22708 function repaintAxis(axisName, posName, sizeName, contentSizeName, hasScroll, ax) { 22709 var containerElm, scrollBarElm, scrollThumbElm; 22710 var containerSize, scrollSize, ratio, rect; 22711 var posNameLower, sizeNameLower; 22712 22713 scrollBarElm = self.getEl('scroll' + axisName); 22714 if (scrollBarElm) { 22715 posNameLower = posName.toLowerCase(); 22716 sizeNameLower = sizeName.toLowerCase(); 22717 22718 if (self.getEl('absend')) { 22719 DomUtils.css(self.getEl('absend'), posNameLower, self.layoutRect()[contentSizeName] - 1); 22720 } 22721 22722 if (!hasScroll) { 22723 DomUtils.css(scrollBarElm, 'display', 'none'); 22724 return; 22725 } 22726 22727 DomUtils.css(scrollBarElm, 'display', 'block'); 22728 containerElm = self.getEl('body'); 22729 scrollThumbElm = self.getEl('scroll' + axisName + "t"); 22730 containerSize = containerElm["client" + sizeName] - (margin * 2); 22731 containerSize -= hasScrollH && hasScrollV ? scrollBarElm["client" + ax] : 0; 22732 scrollSize = containerElm["scroll" + sizeName]; 22733 ratio = containerSize / scrollSize; 22734 22735 rect = {}; 22736 rect[posNameLower] = containerElm["offset" + posName] + margin; 22737 rect[sizeNameLower] = containerSize; 22738 DomUtils.css(scrollBarElm, rect); 22739 22740 rect = {}; 22741 rect[posNameLower] = containerElm["scroll" + posName] * ratio; 22742 rect[sizeNameLower] = containerSize * ratio; 22743 DomUtils.css(scrollThumbElm, rect); 22744 } 22745 } 22746 22747 bodyElm = self.getEl('body'); 22748 hasScrollH = bodyElm.scrollWidth > bodyElm.clientWidth; 22749 hasScrollV = bodyElm.scrollHeight > bodyElm.clientHeight; 22750 22751 repaintAxis("h", "Left", "Width", "contentW", hasScrollH, "Height"); 22752 repaintAxis("v", "Top", "Height", "contentH", hasScrollV, "Width"); 22753 } 22754 22755 function addScroll() { 22756 function addScrollAxis(axisName, posName, sizeName, deltaPosName, ax) { 22757 var scrollStart, axisId = self._id + '-scroll' + axisName, prefix = self.classPrefix; 22758 22759 self.getEl().appendChild(DomUtils.createFragment( 22760 '<div id="' + axisId + '" class="' + prefix + 'scrollbar ' + prefix + 'scrollbar-' + axisName + '">' + 22761 '<div id="' + axisId + 't" class="' + prefix + 'scrollbar-thumb"></div>' + 22762 '</div>' 22763 )); 22764 22765 self.draghelper = new DragHelper(axisId + 't', { 22766 start: function() { 22767 scrollStart = self.getEl('body')["scroll" + posName]; 22768 DomUtils.addClass(DomUtils.get(axisId), prefix + 'active'); 22769 }, 22770 22771 drag: function(e) { 22772 var ratio, hasScrollH, hasScrollV, containerSize, layoutRect = self.layoutRect(); 22773 22774 hasScrollH = layoutRect.contentW > layoutRect.innerW; 22775 hasScrollV = layoutRect.contentH > layoutRect.innerH; 22776 containerSize = self.getEl('body')["client" + sizeName] - (margin * 2); 22777 containerSize -= hasScrollH && hasScrollV ? self.getEl('scroll' + axisName)["client" + ax] : 0; 22778 22779 ratio = containerSize / self.getEl('body')["scroll" + sizeName]; 22780 self.getEl('body')["scroll" + posName] = scrollStart + (e["delta" + deltaPosName] / ratio); 22781 }, 22782 22783 stop: function() { 22784 DomUtils.removeClass(DomUtils.get(axisId), prefix + 'active'); 22785 } 22786 }); 22787/* 22788 self.on('click', function(e) { 22789 if (e.target.id == self._id + '-scrollv') { 22790 22791 } 22792 });*/ 22793 } 22794 22795 self.addClass('scroll'); 22796 22797 addScrollAxis("v", "Top", "Height", "Y", "Width"); 22798 addScrollAxis("h", "Left", "Width", "X", "Height"); 22799 } 22800 22801 if (self.settings.autoScroll) { 22802 if (!self._hasScroll) { 22803 self._hasScroll = true; 22804 addScroll(); 22805 22806 self.on('wheel', function(e) { 22807 var bodyEl = self.getEl('body'); 22808 22809 bodyEl.scrollLeft += (e.deltaX || 0) * 10; 22810 bodyEl.scrollTop += e.deltaY * 10; 22811 22812 repaintScroll(); 22813 }); 22814 22815 DomUtils.on(self.getEl('body'), "scroll", repaintScroll); 22816 } 22817 22818 repaintScroll(); 22819 } 22820 } 22821 }; 22822}); 22823 22824// Included from: js/tinymce/classes/ui/Panel.js 22825 22826/**
22827 * Panel.js 22828 * 22829 * Copyright, Moxiecode Systems AB 22830 * Released under LGPL License. 22831 * 22832 * License: http://www.tinymce.com/license 22833 * Contributing: http://www.tinymce.com/contributing 22834 */ 22835 22836/** 22837 * Creates a new panel. 22838 * 22839 * @-x-less Panel.less 22840 * @class tinymce.ui.Panel 22841 * @extends tinymce.ui.Container 22842 * @mixes tinymce.ui.Scrollable 22843 */ 22844define("tinymce/ui/Panel", [ 22845 "tinymce/ui/Container", 22846 "tinymce/ui/Scrollable" 22847], function(Container, Scrollable) { 22848 "use strict"; 22849 22850 return Container.extend({ 22851 Defaults: { 22852 layout: 'fit', 22853 containerCls: 'panel' 22854 }, 22855 22856 Mixins: [Scrollable], 22857 22858 /** 22859 * Renders the control as a HTML string. 22860 * 22861 * @method renderHtml 22862 * @return {String} HTML representing the control. 22863 */ 22864 renderHtml: function() { 22865 var self = this, layout = self._layout, innerHtml = self.settings.html; 22866 22867 self.preRender(); 22868 layout.preRender(self); 22869 22870 if (typeof(innerHtml) == "undefined") { 22871 innerHtml = ( 22872 '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' + 22873 layout.renderHtml(self) + 22874 '</div>' 22875 ); 22876 } else { 22877 if (typeof(innerHtml) == 'function') { 22878 innerHtml = innerHtml.call(self); 22879 } 22880 22881 self._hasBody = false; 22882 } 22883 22884 return ( 22885 '<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1" role="group">' + 22886 (self._preBodyHtml || '') + 22887 innerHtml + 22888 '</div>' 22889 ); 22890 } 22891 }); 22892}); 22893 22894// Included from: js/tinymce/classes/ui/Movable.js 22895 22896/** 22897 * Movable.js 22898 * 22899 * Copyright, Moxiecode Systems AB 22900 * Released under LGPL License. 22901 * 22902 * License: http://www.tinymce.com/license 22903 * Contributing: http://www.tinymce.com/contributing 22904 */ 22905 22906/** 22907 * Movable mixin. Makes controls movable absolute and relative to other elements. 22908 * 22909 * @mixin tinymce.ui.Movable 22910 */ 22911define("tinymce/ui/Movable", [ 22912 "tinymce/ui/DomUtils" 22913], function(DomUtils) { 22914 "use strict"; 22915 22916 function calculateRelativePosition(ctrl, targetElm, rel) { 22917 var ctrlElm, pos, x, y, selfW, selfH, targetW, targetH, viewport, size; 22918 22919 viewport = DomUtils.getViewPort(); 22920 22921 // Get pos of target 22922 pos = DomUtils.getPos(targetElm); 22923 x = pos.x; 22924 y = pos.y; 22925 22926 if (ctrl._fixed && DomUtils.getRuntimeStyle(document.body, 'position') == 'static') { 22927 x -= viewport.x; 22928 y -= viewport.y; 22929 } 22930 22931 // Get size of self 22932 ctrlElm = ctrl.getEl(); 22933 size = DomUtils.getSize(ctrlElm); 22934 selfW = size.width; 22935 selfH = size.height; 22936 22937 // Get size of target 22938 size = DomUtils.getSize(targetElm); 22939 targetW = size.width; 22940 targetH = size.height; 22941 22942 // Parse align string 22943 rel = (rel || '').split(''); 22944 22945 // Target corners 22946 if (rel[0] === 'b') { 22947 y += targetH; 22948 } 22949 22950 if (rel[1] === 'r') { 22951 x += targetW; 22952 } 22953 22954 if (rel[0] === 'c') { 22955 y += Math.round(targetH / 2); 22956 } 22957 22958 if (rel[1] === 'c') { 22959 x += Math.round(targetW / 2); 22960 } 22961 22962 // Self corners 22963 if (rel[3] === 'b') { 22964 y -= selfH; 22965 } 22966 22967 if (rel[4] === 'r') { 22968 x -= selfW; 22969 } 22970 22971 if (rel[3] === 'c') { 22972 y -= Math.round(selfH / 2); 22973 } 22974 22975 if (rel[4] === 'c') { 22976 x -= Math.round(selfW / 2); 22977 } 22978 22979 return { 22980 x: x, 22981 y: y, 22982 w: selfW, 22983 h: selfH 22984 }; 22985 } 22986 22987 return { 22988 /** 22989 * Tests various positions to get the most suitable one. 22990 * 22991 * @method testMoveRel 22992 * @param {DOMElement} elm Element to position against. 22993 * @param {Array} rels Array with relative positions. 22994 * @return {String} Best suitable relative position. 22995 */ 22996 testMoveRel: function(elm, rels) { 22997 var viewPortRect = DomUtils.getViewPort(); 22998 22999 for (var i = 0; i < rels.length; i++) { 23000 var pos = calculateRelativePosition(this, elm, rels[i]); 23001 23002 if (this._fixed) { 23003 if (pos.x > 0 && pos.x + pos.w < viewPortRect.w && pos.y > 0 && pos.y + pos.h < viewPortRect.h) { 23004 return rels[i]; 23005 } 23006 } else { 23007 if (pos.x > viewPortRect.x && pos.x + pos.w < viewPortRect.w + viewPortRect.x && 23008 pos.y > viewPortRect.y && pos.y + pos.h < viewPortRect.h + viewPortRect.y) { 23009 return rels[i]; 23010 } 23011 } 23012 } 23013 23014 return rels[0]; 23015 }, 23016 23017 /** 23018 * Move relative to the specified element. 23019 * 23020 * @method moveRel 23021 * @param {Element} elm Element to move relative to. 23022 * @param {String} rel Relative mode. For example: br-tl. 23023 * @return {tinymce.ui.Control} Current control instance. 23024 */ 23025 moveRel: function(elm, rel) { 23026 if (typeof(rel) != 'string') { 23027 rel = this.testMoveRel(elm, rel); 23028 } 23029 23030 var pos = calculateRelativePosition(this, elm, rel); 23031 return this.moveTo(pos.x, pos.y); 23032 }, 23033 23034 /** 23035 * Move by a relative x, y values. 23036 * 23037 * @method moveBy 23038 * @param {Number} dx Relative x position. 23039 * @param {Number} dy Relative y position. 23040 * @return {tinymce.ui.Control} Current control instance. 23041 */ 23042 moveBy: function(dx, dy) { 23043 var self = this, rect = self.layoutRect(); 23044 23045 self.moveTo(rect.x + dx, rect.y + dy); 23046 23047 return self; 23048 }, 23049 23050 /** 23051 * Move to absolute position. 23052 * 23053 * @method moveTo 23054 * @param {Number} x Absolute x position. 23055 * @param {Number} y Absolute y position. 23056 * @return {tinymce.ui.Control} Current control instance. 23057 */ 23058 moveTo: function(x, y) { 23059 var self = this; 23060 23061 // TODO: Move this to some global class 23062 function contrain(value, max, size) { 23063 if (value < 0) { 23064 return 0; 23065 } 23066 23067 if (value + size > max) { 23068 value = max - size; 23069 return value < 0 ? 0 : value; 23070 } 23071 23072 return value; 23073 } 23074 23075 if (self.settings.constrainToViewport) { 23076 var viewPortRect = DomUtils.getViewPort(window); 23077 var layoutRect = self.layoutRect(); 23078 23079 x = contrain(x, viewPortRect.w + viewPortRect.x, layoutRect.w); 23080 y = contrain(y, viewPortRect.h + viewPortRect.y, layoutRect.h); 23081 } 23082 23083 if (self._rendered) { 23084 self.layoutRect({x: x, y: y}).repaint(); 23085 } else { 23086 self.settings.x = x; 23087 self.settings.y = y; 23088 } 23089 23090 self.fire('move', {x: x, y: y}); 23091 23092 return self; 23093 } 23094 }; 23095}); 23096 23097// Included from: js/tinymce/classes/ui/Resizable.js 23098 23099/** 23100 * Resizable.js 23101 * 23102 * Copyright, Moxiecode Systems AB 23103 * Released under LGPL License. 23104 * 23105 * License: http://www.tinymce.com/license 23106 * Contributing: http://www.tinymce.com/contributing 23107 */ 23108 23109/** 23110 * Resizable mixin. Enables controls to be resized. 23111 * 23112 * @mixin tinymce.ui.Resizable 23113 */
23114define("tinymce/ui/Resizable", [ 23115 "tinymce/ui/DomUtils" 23116], function(DomUtils) { 23117 "use strict"; 23118 23119 return { 23120 /** 23121 * Resizes the control to contents. 23122 * 23123 * @method resizeToContent 23124 */ 23125 resizeToContent: function() { 23126 this._layoutRect.autoResize = true; 23127 this._lastRect = null; 23128 this.reflow(); 23129 }, 23130 23131 /** 23132 * Resizes the control to a specific width/height. 23133 * 23134 * @method resizeTo 23135 * @param {Number} w Control width. 23136 * @param {Number} h Control height. 23137 * @return {tinymce.ui.Control} Current control instance. 23138 */ 23139 resizeTo: function(w, h) { 23140 // TODO: Fix hack 23141 if (w <= 1 || h <= 1) { 23142 var rect = DomUtils.getWindowSize(); 23143 23144 w = w <= 1 ? w * rect.w : w; 23145 h = h <= 1 ? h * rect.h : h; 23146 } 23147 23148 this._layoutRect.autoResize = false; 23149 return this.layoutRect({minW: w, minH: h, w: w, h: h}).reflow(); 23150 }, 23151 23152 /** 23153 * Resizes the control to a specific relative width/height. 23154 * 23155 * @method resizeBy 23156 * @param {Number} dw Relative control width. 23157 * @param {Number} dh Relative control height. 23158 * @return {tinymce.ui.Control} Current control instance. 23159 */ 23160 resizeBy: function(dw, dh) { 23161 var self = this, rect = self.layoutRect(); 23162 23163 return self.resizeTo(rect.w + dw, rect.h + dh); 23164 } 23165 }; 23166}); 23167 23168// Included from: js/tinymce/classes/ui/FloatPanel.js 23169 23170/** 23171 * FloatPanel.js 23172 * 23173 * Copyright, Moxiecode Systems AB 23174 * Released under LGPL License. 23175 * 23176 * License: http://www.tinymce.com/license 23177 * Contributing: http://www.tinymce.com/contributing 23178 */ 23179 23180/** 23181 * This class creates a floating panel. 23182 * 23183 * @-x-less FloatPanel.less 23184 * @class tinymce.ui.FloatPanel 23185 * @extends tinymce.ui.Panel 23186 * @mixes tinymce.ui.Movable 23187 * @mixes tinymce.ui.Resizable 23188 */ 23189define("tinymce/ui/FloatPanel", [ 23190 "tinymce/ui/Panel", 23191 "tinymce/ui/Movable", 23192 "tinymce/ui/Resizable", 23193 "tinymce/ui/DomUtils" 23194], function(Panel, Movable, Resizable, DomUtils) { 23195 "use strict"; 23196 23197 var documentClickHandler, documentScrollHandler, windowResizeHandler, visiblePanels = []; 23198 var zOrder = [], hasModal; 23199 23200 function bindDocumentClickHandler() { 23201 function isChildOf(ctrl, parent) { 23202 while (ctrl) { 23203 if (ctrl == parent) { 23204 return true; 23205 } 23206 23207 ctrl = ctrl.parent(); 23208 } 23209 } 23210 23211 if (!documentClickHandler) { 23212 documentClickHandler = function(e) { 23213 // Gecko fires click event and in the wrong order on Mac so lets normalize 23214 if (e.button == 2) { 23215 return; 23216 } 23217 23218 // Hide any float panel when a click is out side that float panel and the 23219 // float panels direct parent for example a click on a menu button 23220 var i = visiblePanels.length; 23221 while (i--) { 23222 var panel = visiblePanels[i], clickCtrl = panel.getParentCtrl(e.target); 23223 23224 if (panel.settings.autohide) { 23225 if (clickCtrl) { 23226 if (isChildOf(clickCtrl, panel) || panel.parent() === clickCtrl) { 23227 continue; 23228 } 23229 } 23230 23231 e = panel.fire('autohide', {target: e.target}); 23232 if (!e.isDefaultPrevented()) { 23233 panel.hide(); 23234 } 23235 } 23236 } 23237 }; 23238 23239 DomUtils.on(document, 'click', documentClickHandler); 23240 } 23241 } 23242 23243 function bindDocumentScrollHandler() { 23244 if (!documentScrollHandler) { 23245 documentScrollHandler = function() { 23246 var i; 23247 23248 i = visiblePanels.length; 23249 while (i--) { 23250 repositionPanel(visiblePanels[i]); 23251 } 23252 }; 23253 23254 DomUtils.on(window, 'scroll', documentScrollHandler); 23255 } 23256 } 23257 23258 function bindWindowResizeHandler() { 23259 if (!windowResizeHandler) { 23260 var docElm = document.documentElement, clientWidth = docElm.clientWidth, clientHeight = docElm.clientHeight; 23261 23262 windowResizeHandler = function() { 23263 // Workaround for #7065 IE 7 fires resize events event though the window wasn't resized 23264 if (!document.all || clientWidth != docElm.clientWidth || clientHeight != docElm.clientHeight) { 23265 clientWidth = docElm.clientWidth; 23266 clientHeight = docElm.clientHeight; 23267 FloatPanel.hideAll(); 23268 } 23269 }; 23270 23271 DomUtils.on(window, 'resize', windowResizeHandler); 23272 } 23273 } 23274 23275 /** 23276 * Repositions the panel to the top of page if the panel is outside of the visual viewport. It will 23277 * also reposition all child panels of the current panel. 23278 */ 23279 function repositionPanel(panel) { 23280 var scrollY = DomUtils.getViewPort().y; 23281 23282 function toggleFixedChildPanels(fixed, deltaY) { 23283 var parent; 23284 23285 for (var i = 0; i < visiblePanels.length; i++) { 23286 if (visiblePanels[i] != panel) { 23287 parent = visiblePanels[i].parent(); 23288 23289 while (parent && (parent = parent.parent())) { 23290 if (parent == panel) {
23291 visiblePanels[i].fixed(fixed).moveBy(0, deltaY).repaint(); 23292 } 23293 } 23294 } 23295 } 23296 } 23297 23298 if (panel.settings.autofix) { 23299 if (!panel._fixed) { 23300 panel._autoFixY = panel.layoutRect().y; 23301 23302 if (panel._autoFixY < scrollY) { 23303 panel.fixed(true).layoutRect({y: 0}).repaint(); 23304 toggleFixedChildPanels(true, scrollY - panel._autoFixY); 23305 } 23306 } else { 23307 if (panel._autoFixY > scrollY) { 23308 panel.fixed(false).layoutRect({y: panel._autoFixY}).repaint(); 23309 toggleFixedChildPanels(false, panel._autoFixY - scrollY); 23310 } 23311 } 23312 } 23313 } 23314 23315 function addRemove(add, ctrl) { 23316 var i, zIndex = FloatPanel.zIndex || 0xFFFF, topModal; 23317 23318 if (add) { 23319 zOrder.push(ctrl); 23320 } else { 23321 i = zOrder.length; 23322 23323 while (i--) { 23324 if (zOrder[i] === ctrl) { 23325 zOrder.splice(i, 1); 23326 } 23327 } 23328 } 23329 23330 if (zOrder.length) { 23331 for (i = 0; i < zOrder.length; i++) { 23332 if (zOrder[i].modal) { 23333 zIndex++; 23334 topModal = zOrder[i]; 23335 } 23336 23337 zOrder[i].getEl().style.zIndex = zIndex; 23338 zOrder[i].zIndex = zIndex; 23339 zIndex++; 23340 } 23341 } 23342 23343 var modalBlockEl = document.getElementById(ctrl.classPrefix + 'modal-block'); 23344 23345 if (topModal) { 23346 DomUtils.css(modalBlockEl, 'z-index', topModal.zIndex - 1); 23347 } else if (modalBlockEl) { 23348 modalBlockEl.parentNode.removeChild(modalBlockEl); 23349 hasModal = false; 23350 } 23351 23352 FloatPanel.currentZIndex = zIndex; 23353 } 23354 23355 var FloatPanel = Panel.extend({ 23356 Mixins: [Movable, Resizable], 23357 23358 /** 23359 * Constructs a new control instance with the specified settings. 23360 * 23361 * @constructor 23362 * @param {Object} settings Name/value object with settings. 23363 * @setting {Boolean} autohide Automatically hide the panel. 23364 */ 23365 init: function(settings) { 23366 var self = this; 23367 23368 self._super(settings); 23369 self._eventsRoot = self; 23370 23371 self.addClass('floatpanel'); 23372 23373 // Hide floatpanes on click out side the root button 23374 if (settings.autohide) { 23375 bindDocumentClickHandler(); 23376 bindWindowResizeHandler(); 23377 visiblePanels.push(self); 23378 } 23379 23380 if (settings.autofix) { 23381 bindDocumentScrollHandler(); 23382 23383 self.on('move', function() { 23384 repositionPanel(this); 23385 }); 23386 } 23387 23388 self.on('postrender show', function(e) { 23389 if (e.control == self) { 23390 var modalBlockEl, prefix = self.classPrefix; 23391 23392 if (self.modal && !hasModal) { 23393 modalBlockEl = DomUtils.createFragment('<div id="' + prefix + 'modal-block" class="' + 23394 prefix + 'reset ' + prefix + 'fade"></div>'); 23395 modalBlockEl = modalBlockEl.firstChild; 23396 23397 self.getContainerElm().appendChild(modalBlockEl); 23398 23399 setTimeout(function() { 23400 DomUtils.addClass(modalBlockEl, prefix + 'in'); 23401 DomUtils.addClass(self.getEl(), prefix + 'in'); 23402 }, 0); 23403 23404 hasModal = true; 23405 } 23406 23407 addRemove(true, self); 23408 } 23409 }); 23410 23411 self.on('show', function() { 23412 self.parents().each(function(ctrl) { 23413 if (ctrl._fixed) { 23414 self.fixed(true); 23415 return false; 23416 } 23417 }); 23418 }); 23419 23420 if (settings.popover) { 23421 self._preBodyHtml = '<div class="' + self.classPrefix + 'arrow"></div>'; 23422 self.addClass('popover').addClass('bottom').addClass(self.isRtl() ? 'end' : 'start'); 23423 } 23424 }, 23425 23426 fixed: function(state) { 23427 var self = this; 23428 23429 if (self._fixed != state) { 23430 if (self._rendered) { 23431 var viewport = DomUtils.getViewPort(); 23432 23433 if (state) { 23434 self.layoutRect().y -= viewport.y; 23435 } else { 23436 self.layoutRect().y += viewport.y; 23437 } 23438 } 23439 23440 self.toggleClass('fixed', state); 23441 self._fixed = state; 23442 } 23443 23444 return self; 23445 }, 23446 23447 /** 23448 * Shows the current float panel. 23449 * 23450 * @method show 23451 * @return {tinymce.ui.FloatPanel} Current floatpanel instance. 23452 */ 23453 show: function() { 23454 var self = this, i, state = self._super(); 23455 23456 i = visiblePanels.length; 23457 while (i--) { 23458 if (visiblePanels[i] === self) { 23459 break; 23460 } 23461 } 23462 23463 if (i === -1) { 23464 visiblePanels.push(self); 23465 } 23466 23467 return state; 23468 }, 23469 23470 /** 23471 * Hides the current float panel. 23472 * 23473 * @method hide 23474 * @return {tinymce.ui.FloatPanel} Current floatpanel instance. 23475 */ 23476 hide: function() { 23477 removeVisiblePanel(this); 23478 addRemove(false, this); 23479 23480 return this._super(); 23481 }, 23482 23483 /** 23484 * Hide all visible float panels with he autohide setting enabled. This is for 23485 * manually hiding floating menus or panels. 23486 * 23487 * @method hideAll 23488 */ 23489 hideAll: function() { 23490 FloatPanel.hideAll(); 23491 }, 23492 23493 /** 23494 * Closes the float panel. This will remove the float panel from page and fire the close event. 23495 * 23496 * @method close 23497 */ 23498 close: function() { 23499 var self = this; 23500 23501 if (!self.fire('close').isDefaultPrevented()) { 23502 self.remove(); 23503 addRemove(false, self); 23504 } 23505 23506 return self; 23507 }, 23508 23509 /** 23510 * Removes the float panel from page. 23511 * 23512 * @method remove 23513 */ 23514 remove: function() { 23515 removeVisiblePanel(this); 23516 this._super(); 23517 }, 23518 23519 postRender: function() { 23520 var self = this; 23521 23522 if (self.settings.bodyRole) { 23523 this.getEl('body').setAttribute('role', self.settings.bodyRole); 23524 } 23525 23526 return self._super(); 23527 } 23528 }); 23529 23530 /** 23531 * Hide all visible float panels with he autohide setting enabled. This is for 23532 * manually hiding floating menus or panels. 23533 * 23534 * @static 23535 * @method hideAll 23536 */ 23537 FloatPanel.hideAll = function() { 23538 var i = visiblePanels.length; 23539 23540 while (i--) { 23541 var panel = visiblePanels[i]; 23542 23543 if (panel && panel.settings.autohide) { 23544 panel.hide(); 23545 visiblePanels.splice(i, 1); 23546 } 23547 } 23548 }; 23549 23550 function removeVisiblePanel(panel) { 23551 var i; 23552 23553 i = visiblePanels.length; 23554 while (i--) { 23555 if (visiblePanels[i] === panel) { 23556 visiblePanels.splice(i, 1); 23557 } 23558 } 23559 23560 i = zOrder.length; 23561 while (i--) { 23562 if (zOrder[i] === panel) { 23563 zOrder.splice(i, 1); 23564 } 23565 } 23566 } 23567 23568 return FloatPanel; 23569}); 23570 23571// Included from: js/tinymce/classes/ui/Window.js 23572 23573/** 23574 * Window.js 23575 * 23576 * Copyright, Moxiecode Systems AB 23577 * Released under LGPL License. 23578 * 23579 * License: http://www.tinymce.com/license 23580 * Contributing: http://www.tinymce.com/contributing 23581 */ 23582 23583/** 23584 * Creates a new window. 23585 * 23586 * @-x-less Window.less 23587 * @class tinymce.ui.Window 23588 * @extends tinymce.ui.FloatPanel 23589 */
23590define("tinymce/ui/Window", [ 23591 "tinymce/ui/FloatPanel", 23592 "tinymce/ui/Panel", 23593 "tinymce/ui/DomUtils", 23594 "tinymce/ui/DragHelper" 23595], function(FloatPanel, Panel, DomUtils, DragHelper) { 23596 "use strict"; 23597 23598 var Window = FloatPanel.extend({ 23599 modal: true, 23600 23601 Defaults: { 23602 border: 1, 23603 layout: 'flex', 23604 containerCls: 'panel', 23605 role: 'dialog', 23606 callbacks: { 23607 submit: function() { 23608 this.fire('submit', {data: this.toJSON()}); 23609 }, 23610 23611 close: function() { 23612 this.close(); 23613 } 23614 } 23615 }, 23616 23617 /** 23618 * Constructs a instance with the specified settings. 23619 * 23620 * @constructor 23621 * @param {Object} settings Name/value object with settings. 23622 */ 23623 init: function(settings) { 23624 var self = this; 23625 23626 self._super(settings); 23627 23628 if (self.isRtl()) { 23629 self.addClass('rtl'); 23630 } 23631 23632 self.addClass('window'); 23633 self._fixed = true; 23634 23635 // Create statusbar 23636 if (settings.buttons) { 23637 self.statusbar = new Panel({ 23638 layout: 'flex', 23639 border: '1 0 0 0', 23640 spacing: 3, 23641 padding: 10, 23642 align: 'center', 23643 pack: self.isRtl() ? 'start' : 'end', 23644 defaults: { 23645 type: 'button' 23646 }, 23647 items: settings.buttons 23648 }); 23649 23650 self.statusbar.addClass('foot'); 23651 self.statusbar.parent(self); 23652 } 23653 23654 self.on('click', function(e) { 23655 if (e.target.className.indexOf(self.classPrefix + 'close') != -1) { 23656 self.close(); 23657 } 23658 }); 23659 23660 self.on('cancel', function() { 23661 self.close(); 23662 }); 23663 23664 self.aria('describedby', self.describedBy || self._id + '-none'); 23665 self.aria('label', settings.title); 23666 self._fullscreen = false; 23667 }, 23668 23669 /** 23670 * Recalculates the positions of the controls in the current container. 23671 * This is invoked by the reflow method and shouldn't be called directly. 23672 * 23673 * @method recalc 23674 */ 23675 recalc: function() { 23676 var self = this, statusbar = self.statusbar, layoutRect, width, x, needsRecalc; 23677 23678 if (self._fullscreen) { 23679 self.layoutRect(DomUtils.getWindowSize()); 23680 self.layoutRect().contentH = self.layoutRect().innerH; 23681 } 23682 23683 self._super(); 23684 23685 layoutRect = self.layoutRect(); 23686 23687 // Resize window based on title width 23688 if (self.settings.title && !self._fullscreen) { 23689 width = layoutRect.headerW; 23690 if (width > layoutRect.w) { 23691 x = layoutRect.x - Math.max(0, width / 2); 23692 self.layoutRect({w: width, x: x}); 23693 needsRecalc = true; 23694 } 23695 } 23696 23697 // Resize window based on statusbar width 23698 if (statusbar) { 23699 statusbar.layoutRect({w: self.layoutRect().innerW}).recalc(); 23700 23701 width = statusbar.layoutRect().minW + layoutRect.deltaW; 23702 if (width > layoutRect.w) { 23703 x = layoutRect.x - Math.max(0, width - layoutRect.w); 23704 self.layoutRect({w: width, x: x}); 23705 needsRecalc = true; 23706 } 23707 } 23708 23709 // Recalc body and disable auto resize 23710 if (needsRecalc) { 23711 self.recalc(); 23712 } 23713 }, 23714 23715 /** 23716 * Initializes the current controls layout rect. 23717 * This will be executed by the layout managers to determine the 23718 * default minWidth/minHeight etc. 23719 * 23720 * @method initLayoutRect 23721 * @return {Object} Layout rect instance. 23722 */ 23723 initLayoutRect: function() { 23724 var self = this, layoutRect = self._super(), deltaH = 0, headEl; 23725 23726 // Reserve vertical space for title 23727 if (self.settings.title && !self._fullscreen) { 23728 headEl = self.getEl('head'); 23729 23730 var size = DomUtils.getSize(headEl); 23731 23732 layoutRect.headerW = size.width; 23733 layoutRect.headerH = size.height; 23734 23735 deltaH += layoutRect.headerH; 23736 } 23737 23738 // Reserve vertical space for statusbar 23739 if (self.statusbar) { 23740 deltaH += self.statusbar.layoutRect().h; 23741 } 23742 23743 layoutRect.deltaH += deltaH; 23744 layoutRect.minH += deltaH; 23745 //layoutRect.innerH -= deltaH; 23746 layoutRect.h += deltaH; 23747 23748 var rect = DomUtils.getWindowSize(); 23749 23750 layoutRect.x = Math.max(0, rect.w / 2 - layoutRect.w / 2); 23751 layoutRect.y = Math.max(0, rect.h / 2 - layoutRect.h / 2); 23752 23753 return layoutRect; 23754 }, 23755 23756 /** 23757 * Renders the control as a HTML string. 23758 * 23759 * @method renderHtml 23760 * @return {String} HTML representing the control. 23761 */ 23762 renderHtml: function() { 23763 var self = this, layout = self._layout, id = self._id, prefix = self.classPrefix; 23764 var settings = self.settings, headerHtml = '', footerHtml = '', html = settings.html; 23765 23766 self.preRender(); 23767 layout.preRender(self); 23768 23769 if (settings.title) { 23770 headerHtml = ( 23771 '<div id="' + id + '-head" class="' + prefix + 'window-head">' + 23772 '<div id="' + id + '-title" class="' + prefix + 'title">' + self.encode(settings.title) + '</div>' + 23773 '<button type="button" class="' + prefix + 'close" aria-hidden="true">\u00d7</button>' + 23774 '<div id="' + id + '-dragh" class="' + prefix + 'dragh"></div>' + 23775 '</div>' 23776 ); 23777 } 23778 23779 if (settings.url) { 23780 html = '<iframe src="' + settings.url + '" tabindex="-1"></iframe>'; 23781 } 23782 23783 if (typeof(html) == "undefined") { 23784 html = layout.renderHtml(self); 23785 } 23786 23787 if (self.statusbar) { 23788 footerHtml = self.statusbar.renderHtml(); 23789 } 23790 23791 return ( 23792 '<div id="' + id + '" class="' + self.classes() + '" hidefocus="1">' + 23793 '<div class="' + self.classPrefix + 'reset" role="application">' + 23794 headerHtml + 23795 '<div id="' + id + '-body" class="' + self.classes('body') + '">' + 23796 html + 23797 '</div>' + 23798 footerHtml + 23799 '</div>' + 23800 '</div>' 23801 ); 23802 }, 23803 23804 /** 23805 * Switches the window fullscreen mode. 23806 * 23807 * @method fullscreen 23808 * @param {Boolean} state True/false state. 23809 * @return {tinymce.ui.Window} Current window instance. 23810 */ 23811 fullscreen: function(state) { 23812 var self = this, documentElement = document.documentElement, slowRendering, prefix = self.classPrefix, layoutRect; 23813 23814 if (state != self._fullscreen) { 23815 DomUtils.on(window, 'resize', function() { 23816 var time; 23817 23818 if (self._fullscreen) { 23819 // Time the layout time if it's to slow use a timeout to not hog the CPU 23820 if (!slowRendering) { 23821 time = new Date().getTime(); 23822 23823 var rect = DomUtils.getWindowSize(); 23824 self.moveTo(0, 0).resizeTo(rect.w, rect.h); 23825 23826 if ((new Date().getTime()) - time > 50) { 23827 slowRendering = true; 23828 } 23829 } else { 23830 if (!self._timer) { 23831 self._timer = setTimeout(function() { 23832 var rect = DomUtils.getWindowSize(); 23833 self.moveTo(0, 0).resizeTo(rect.w, rect.h); 23834 23835 self._timer = 0; 23836 }, 50); 23837 } 23838 } 23839 } 23840 }); 23841 23842 layoutRect = self.layoutRect(); 23843 self._fullscreen = state; 23844 23845 if (!state) { 23846 self._borderBox = self.parseBox(self.settings.border); 23847 self.getEl('head').style.display = ''; 23848 layoutRect.deltaH += layoutRect.headerH; 23849 DomUtils.removeClass(documentElement, prefix + 'fullscreen'); 23850 DomUtils.removeClass(document.body, prefix + 'fullscreen'); 23851 self.removeClass('fullscreen'); 23852 self.moveTo(self._initial.x, self._initial.y).resizeTo(self._initial.w, self._initial.h); 23853 } else { 23854 self._initial = {x: layoutRect.x, y: layoutRect.y, w: layoutRect.w, h: layoutRect.h}; 23855 23856 self._borderBox = self.parseBox('0'); 23857 self.getEl('head').style.display = 'none'; 23858 layoutRect.deltaH -= layoutRect.headerH + 2; 23859 DomUtils.addClass(documentElement, prefix + 'fullscreen'); 23860 DomUtils.addClass(document.body, prefix + 'fullscreen'); 23861 self.addClass('fullscreen'); 23862 23863 var rect = DomUtils.getWindowSize(); 23864 self.moveTo(0, 0).resizeTo(rect.w, rect.h); 23865 } 23866 } 23867 23868 return self.reflow(); 23869 }, 23870 23871 /** 23872 * Called after the control has been rendered. 23873 * 23874 * @method postRender 23875 */ 23876 postRender: function() { 23877 var self = this, startPos; 23878 23879 setTimeout(function() { 23880 self.addClass('in'); 23881 }, 0); 23882 23883 self._super(); 23884 23885 if (self.statusbar) { 23886 self.statusbar.postRender(); 23887 } 23888 23889 self.focus(); 23890 23891 this.dragHelper = new DragHelper(self._id + '-dragh', { 23892 start: function() { 23893 startPos = { 23894 x: self.layoutRect().x, 23895 y: self.layoutRect().y 23896 }; 23897 }, 23898 23899 drag: function(e) { 23900 self.moveTo(startPos.x + e.deltaX, startPos.y + e.deltaY); 23901 } 23902 }); 23903 23904 self.on('submit', function(e) { 23905 if (!e.isDefaultPrevented()) { 23906 self.close(); 23907 } 23908 }); 23909 }, 23910 23911 /** 23912 * Fires a submit event with the serialized form. 23913 * 23914 * @method submit 23915 * @return {Object} Event arguments object. 23916 */ 23917 submit: function() { 23918 return this.fire('submit', {data: this.toJSON()}); 23919 }, 23920 23921 /** 23922 * Removes the current control from DOM and from UI collections. 23923 * 23924 * @method remove 23925 * @return {tinymce.ui.Control} Current control instance. 23926 */ 23927 remove: function() { 23928 var self = this, prefix = self.classPrefix; 23929 23930 self.dragHelper.destroy(); 23931 self._super(); 23932 23933 if (self.statusbar) { 23934 this.statusbar.remove(); 23935 } 23936 23937 if (self._fullscreen) { 23938 DomUtils.removeClass(document.documentElement, prefix + 'fullscreen'); 23939 DomUtils.removeClass(document.body, prefix + 'fullscreen'); 23940 } 23941 }, 23942 23943 /** 23944 * Returns the contentWindow object of the iframe if it exists. 23945 * 23946 * @method getContentWindow 23947 * @return {Window} window object or null. 23948 */ 23949 getContentWindow: function() { 23950 var ifr = this.getEl().getElementsByTagName('iframe')[0]; 23951 return ifr ? ifr.contentWindow : null; 23952 } 23953 }); 23954 23955 return Window; 23956}); 23957 23958// Included from: js/tinymce/classes/ui/MessageBox.js 23959 23960/** 23961 * MessageBox.js 23962 * 23963 * Copyright, Moxiecode Systems AB 23964 * Released under LGPL License. 23965 * 23966 * License: http://www.tinymce.com/license 23967 * Contributing: http://www.tinymce.com/contributing 23968 */ 23969 23970/** 23971 * This class is used to create MessageBoxes like alerts/confirms etc. 23972 * 23973 * @class tinymce.ui.MessageBox 23974 * @extends tinymce.ui.Window 23975 */
23976define("tinymce/ui/MessageBox", [ 23977 "tinymce/ui/Window" 23978], function(Window) { 23979 "use strict"; 23980 23981 var MessageBox = Window.extend({ 23982 /** 23983 * Constructs a instance with the specified settings. 23984 * 23985 * @constructor 23986 * @param {Object} settings Name/value object with settings. 23987 */ 23988 init: function(settings) { 23989 settings = { 23990 border: 1, 23991 padding: 20, 23992 layout: 'flex', 23993 pack: "center", 23994 align: "center", 23995 containerCls: 'panel', 23996 autoScroll: true, 23997 buttons: {type: "button", text: "Ok", action: "ok"}, 23998 items: { 23999 type: "label", 24000 multiline: true, 24001 maxWidth: 500, 24002 maxHeight: 200 24003 } 24004 }; 24005 24006 this._super(settings); 24007 }, 24008 24009 Statics: { 24010 /** 24011 * Ok buttons constant. 24012 * 24013 * @static 24014 * @final 24015 * @field {Number} OK 24016 */ 24017 OK: 1, 24018 24019 /** 24020 * Ok/cancel buttons constant. 24021 * 24022 * @static 24023 * @final 24024 * @field {Number} OK_CANCEL 24025 */ 24026 OK_CANCEL: 2, 24027 24028 /** 24029 * yes/no buttons constant. 24030 * 24031 * @static 24032 * @final 24033 * @field {Number} YES_NO 24034 */ 24035 YES_NO: 3, 24036 24037 /** 24038 * yes/no/cancel buttons constant. 24039 * 24040 * @static 24041 * @final 24042 * @field {Number} YES_NO_CANCEL 24043 */ 24044 YES_NO_CANCEL: 4, 24045 24046 /** 24047 * Constructs a new message box and renders it to the body element. 24048 * 24049 * @static 24050 * @method msgBox 24051 * @param {Object} settings Name/value object with settings. 24052 */ 24053 msgBox: function(settings) { 24054 var buttons, callback = settings.callback || function() {}; 24055 24056 function createButton(text, status, primary) { 24057 return { 24058 type: "button", 24059 text: text, 24060 subtype: primary ? 'primary' : '', 24061 onClick: function(e) { 24062 e.control.parents()[1].close(); 24063 callback(status); 24064 } 24065 }; 24066 } 24067 24068 switch (settings.buttons) { 24069 case MessageBox.OK_CANCEL: 24070 buttons = [ 24071 createButton('Ok', true, true), 24072 createButton('Cancel', false) 24073 ]; 24074 break; 24075 24076 case MessageBox.YES_NO: 24077 case MessageBox.YES_NO_CANCEL: 24078 buttons = [ 24079 createButton('Yes', 1, true), 24080 createButton('No', 0) 24081 ]; 24082 24083 if (settings.buttons == MessageBox.YES_NO_CANCEL) { 24084 buttons.push(createButton('Cancel', -1)); 24085 } 24086 break; 24087 24088 default: 24089 buttons = [ 24090 createButton('Ok', true, true) 24091 ]; 24092 break; 24093 } 24094 24095 return new Window({ 24096 padding: 20, 24097 x: settings.x, 24098 y: settings.y, 24099 minWidth: 300, 24100 minHeight: 100, 24101 layout: "flex", 24102 pack: "center", 24103 align: "center", 24104 buttons: buttons, 24105 title: settings.title, 24106 role: 'alertdialog', 24107 items: { 24108 type: "label", 24109 multiline: true, 24110 maxWidth: 500, 24111 maxHeight: 200, 24112 text: settings.text 24113 }, 24114 onPostRender: function() { 24115 this.aria('describedby', this.items()[0]._id); 24116 }, 24117 onClose: settings.onClose, 24118 onCancel: function() { 24119 callback(false); 24120 } 24121 }).renderTo(document.body).reflow(); 24122 }, 24123 24124 /** 24125 * Creates a new alert dialog. 24126 * 24127 * @method alert 24128 * @param {Object} settings Settings for the alert dialog. 24129 * @param {function} [callback] Callback to execute when the user makes a choice. 24130 */ 24131 alert: function(settings, callback) {
24132 if (typeof(settings) == "string") { 24133 settings = {text: settings}; 24134 } 24135 24136 settings.callback = callback; 24137 return MessageBox.msgBox(settings); 24138 }, 24139 24140 /** 24141 * Creates a new confirm dialog. 24142 * 24143 * @method confirm 24144 * @param {Object} settings Settings for the confirm dialog. 24145 * @param {function} [callback] Callback to execute when the user makes a choice. 24146 */ 24147 confirm: function(settings, callback) { 24148 if (typeof(settings) == "string") { 24149 settings = {text: settings}; 24150 } 24151 24152 settings.callback = callback; 24153 settings.buttons = MessageBox.OK_CANCEL; 24154 24155 return MessageBox.msgBox(settings); 24156 } 24157 } 24158 }); 24159 24160 return MessageBox; 24161}); 24162 24163// Included from: js/tinymce/classes/WindowManager.js 24164 24165/** 24166 * WindowManager.js 24167 * 24168 * Copyright, Moxiecode Systems AB 24169 * Released under LGPL License. 24170 * 24171 * License: http://www.tinymce.com/license 24172 * Contributing: http://www.tinymce.com/contributing 24173 */ 24174 24175/** 24176 * This class handles the creation of native windows and dialogs. This class can be extended to provide for example inline dialogs. 24177 * 24178 * @class tinymce.WindowManager 24179 * @example 24180 * // Opens a new dialog with the file.htm file and the size 320x240 24181 * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog. 24182 * tinymce.activeEditor.windowManager.open({ 24183 * url: 'file.htm', 24184 * width: 320, 24185 * height: 240 24186 * }, { 24187 * custom_param: 1 24188 * }); 24189 * 24190 * // Displays an alert box using the active editors window manager instance 24191 * tinymce.activeEditor.windowManager.alert('Hello world!'); 24192 * 24193 * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm 24194 * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) { 24195 * if (s) 24196 * tinymce.activeEditor.windowManager.alert("Ok"); 24197 * else 24198 * tinymce.activeEditor.windowManager.alert("Cancel"); 24199 * }); 24200 */ 24201define("tinymce/WindowManager", [ 24202 "tinymce/ui/Window", 24203 "tinymce/ui/MessageBox" 24204], function(Window, MessageBox) { 24205 return function(editor) { 24206 var self = this, windows = []; 24207 24208 function getTopMostWindow() { 24209 if (windows.length) { 24210 return windows[windows.length - 1]; 24211 } 24212 } 24213 24214 self.windows = windows; 24215 24216 editor.on('remove', function() { 24217 var i = windows.length; 24218 24219 while (i--) { 24220 windows[i].close(); 24221 } 24222 }); 24223 24224 /** 24225 * Opens a new window. 24226 * 24227 * @method open 24228 * @param {Object} args Optional name/value settings collection contains things like width/height/url etc. 24229 * @option {String} title Window title. 24230 * @option {String} file URL of the file to open in the window. 24231 * @option {Number} width Width in pixels. 24232 * @option {Number} height Height in pixels. 24233 * @option {Boolean} resizable Specifies whether the popup window is resizable or not. 24234 * @option {Boolean} maximizable Specifies whether the popup window has a "maximize" button and can get maximized or not. 24235 * @option {String/Boolean} scrollbars Specifies whether the popup window can have scrollbars if required (i.e. content 24236 * larger than the popup size specified). 24237 */ 24238 self.open = function(args, params) { 24239 var win; 24240 24241 editor.editorManager.setActive(editor); 24242 24243 args.title = args.title || ' '; 24244 24245 // Handle URL 24246 args.url = args.url || args.file; // Legacy 24247 if (args.url) { 24248 args.width = parseInt(args.width || 320, 10); 24249 args.height = parseInt(args.height || 240, 10); 24250 } 24251 24252 // Handle body 24253 if (args.body) { 24254 args.items = { 24255 defaults: args.defaults, 24256 type: args.bodyType || 'form', 24257 items: args.body 24258 }; 24259 } 24260 24261 if (!args.url && !args.buttons) { 24262 args.buttons = [ 24263 {text: 'Ok', subtype: 'primary', onclick: function() { 24264 win.find('form')[0].submit(); 24265 }}, 24266 24267 {text: 'Cancel', onclick: function() { 24268 win.close(); 24269 }} 24270 ]; 24271 } 24272 24273 win = new Window(args); 24274 windows.push(win); 24275 24276 win.on('close', function() { 24277 var i = windows.length; 24278 24279 while (i--) { 24280 if (windows[i] === win) { 24281 windows.splice(i, 1); 24282 } 24283 } 24284 24285 if (!windows.length) { 24286 editor.focus(); 24287 } 24288 }); 24289 24290 // Handle data
24291 if (args.data) { 24292 win.on('postRender', function() { 24293 this.find('*').each(function(ctrl) { 24294 var name = ctrl.name(); 24295 24296 if (name in args.data) { 24297 ctrl.value(args.data[name]); 24298 } 24299 }); 24300 }); 24301 } 24302 24303 // store args and parameters 24304 win.features = args || {}; 24305 win.params = params || {}; 24306 24307 // Takes a snapshot in the FocusManager of the selection before focus is lost to dialog 24308 if (windows.length === 1) { 24309 editor.nodeChanged(); 24310 } 24311 24312 return win.renderTo().reflow(); 24313 }; 24314 24315 /** 24316 * Creates a alert dialog. Please don't use the blocking behavior of this 24317 * native version use the callback method instead then it can be extended. 24318 * 24319 * @method alert 24320 * @param {String} message Text to display in the new alert dialog. 24321 * @param {function} callback Callback function to be executed after the user has selected ok. 24322 * @param {Object} scope Optional scope to execute the callback in. 24323 * @example 24324 * // Displays an alert box using the active editors window manager instance 24325 * tinymce.activeEditor.windowManager.alert('Hello world!'); 24326 */ 24327 self.alert = function(message, callback, scope) { 24328 MessageBox.alert(message, function() { 24329 if (callback) { 24330 callback.call(scope || this); 24331 } else { 24332 editor.focus(); 24333 } 24334 }); 24335 }; 24336 24337 /** 24338 * Creates a confirm dialog. Please don't use the blocking behavior of this 24339 * native version use the callback method instead then it can be extended. 24340 * 24341 * @method confirm 24342 * @param {String} messageText to display in the new confirm dialog. 24343 * @param {function} callback Callback function to be executed after the user has selected ok or cancel. 24344 * @param {Object} scope Optional scope to execute the callback in. 24345 * @example 24346 * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm 24347 * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) { 24348 * if (s) 24349 * tinymce.activeEditor.windowManager.alert("Ok"); 24350 * else 24351 * tinymce.activeEditor.windowManager.alert("Cancel"); 24352 * }); 24353 */ 24354 self.confirm = function(message, callback, scope) { 24355 MessageBox.confirm(message, function(state) { 24356 callback.call(scope || this, state); 24357 }); 24358 }; 24359 24360 /** 24361 * Closes the top most window. 24362 * 24363 * @method close 24364 */ 24365 self.close = function() { 24366 if (getTopMostWindow()) { 24367 getTopMostWindow().close(); 24368 } 24369 }; 24370 24371 /** 24372 * Returns the params of the last window open call. This can be used in iframe based 24373 * dialog to get params passed from the tinymce plugin. 24374 * 24375 * @example 24376 * var dialogArguments = top.tinymce.activeEditor.windowManager.getParams(); 24377 * 24378 * @method getParams 24379 * @return {Object} Name/value object with parameters passed from windowManager.open call. 24380 */ 24381 self.getParams = function() { 24382 return getTopMostWindow() ? getTopMostWindow().params : null; 24383 }; 24384 24385 /** 24386 * Sets the params of the last opened window. 24387 * 24388 * @method setParams 24389 * @param {Object} params Params object to set for the last opened window. 24390 */ 24391 self.setParams = function(params) { 24392 if (getTopMostWindow()) { 24393 getTopMostWindow().params = params; 24394 } 24395 }; 24396 24397 /** 24398 * Returns the currently opened window objects. 24399 * 24400 * @method getWindows 24401 * @return {Array} Array of the currently opened windows. 24402 */ 24403 self.getWindows = function() { 24404 return windows; 24405 }; 24406 }; 24407}); 24408 24409// Included from: js/tinymce/classes/util/Quirks.js 24410 24411/** 24412 * Quirks.js 24413 * 24414 * Copyright, Moxiecode Systems AB 24415 * Released under LGPL License. 24416 * 24417 * License: http://www.tinymce.com/license 24418 * Contributing: http://www.tinymce.com/contributing 24419 * 24420 * @ignore-file 24421 */ 24422 24423/** 24424 * This file includes fixes for various browser quirks it's made to make it easy to add/remove browser specific fixes. 24425 * 24426 * @class tinymce.util.Quirks 24427 */ 24428define("tinymce/util/Quirks", [ 24429 "tinymce/util/VK", 24430 "tinymce/dom/RangeUtils", 24431 "tinymce/html/Node", 24432 "tinymce/html/Entities", 24433 "tinymce/Env", 24434 "tinymce/util/Tools" 24435], function(VK, RangeUtils, Node, Entities, Env, Tools) { 24436 return function(editor) { 24437 var each = Tools.each; 24438 var BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, 24439 settings = editor.settings, parser = editor.parser, serializer = editor.serializer; 24440 var isGecko = Env.gecko, isIE = Env.ie, isWebKit = Env.webkit; 24441 24442 /** 24443 * Executes a command with a specific state this can be to enable/disable browser editing features. 24444 */ 24445 function setEditorCommandState(cmd, state) { 24446 try { 24447 editor.getDoc().execCommand(cmd, false, state); 24448 } catch (ex) { 24449 // Ignore 24450 } 24451 } 24452 24453 /** 24454 * Returns current IE document mode. 24455 */ 24456 function getDocumentMode() { 24457 var documentMode = editor.getDoc().documentMode; 24458 24459 return documentMode ? documentMode : 6; 24460 } 24461 24462 /** 24463 * Returns true/false if the event is prevented or not. 24464 * 24465 * @private 24466 * @param {Event} e Event object. 24467 * @return {Boolean} true/false if the event is prevented or not. 24468 */ 24469 function isDefaultPrevented(e) { 24470 return e.isDefaultPrevented(); 24471 } 24472 24473 /** 24474 * Fixes a WebKit bug when deleting contents using backspace or delete key. 24475 * WebKit will produce a span element if you delete across two block elements. 24476 * 24477 * Example: 24478 * <h1>a</h1><p>|b</p> 24479 * 24480 * Will produce this on backspace: 24481 * <h1>a<span style="<all runtime styles>">b</span></p> 24482 * 24483 * This fixes the backspace to produce: 24484 * <h1>a|b</p> 24485 * 24486 * See bug: https://bugs.webkit.org/show_bug.cgi?id=45784 24487 * 24488 * This fixes the following delete scenarios: 24489 * 1. Delete by pressing backspace key. 24490 * 2. Delete by pressing delete key. 24491 * 3. Delete by pressing backspace key with ctrl/cmd (Word delete). 24492 * 4. Delete by pressing delete key with ctrl/cmd (Word delete). 24493 * 5. Delete by drag/dropping contents inside the editor. 24494 * 6. Delete by using Cut Ctrl+X/Cmd+X. 24495 * 7. Delete by selecting contents and writing a character.' 24496 * 24497 * This code is a ugly hack since writing full custom delete logic for just this bug 24498 * fix seemed like a huge task. I hope we can remove this before the year 2030. 24499 */ 24500 function cleanupStylesWhenDeleting() { 24501 var doc = editor.getDoc(), urlPrefix = 'data:text/mce-internal,'; 24502 var MutationObserver = window.MutationObserver, olderWebKit, dragStartRng; 24503 24504 // Add mini polyfill for older WebKits 24505 // TODO: Remove this when old Safari versions gets updated 24506 if (!MutationObserver) { 24507 olderWebKit = true; 24508 24509 MutationObserver = function() { 24510 var records = [], target; 24511 24512 function nodeInsert(e) { 24513 var target = e.relatedNode || e.target; 24514 records.push({target: target, addedNodes: [target]}); 24515 } 24516 24517 function attrModified(e) { 24518 var target = e.relatedNode || e.target; 24519 records.push({target: target, attributeName: e.attrName}); 24520 } 24521 24522 this.observe = function(node) { 24523 target = node; 24524 target.addEventListener('DOMSubtreeModified', nodeInsert, false); 24525 target.addEventListener('DOMNodeInsertedIntoDocument', nodeInsert, false); 24526 target.addEventListener('DOMNodeInserted', nodeInsert, false); 24527 target.addEventListener('DOMAttrModified', attrModified, false); 24528 }; 24529 24530 this.disconnect = function() { 24531 target.removeEventListener('DOMSubtreeModified', nodeInsert, false); 24532 target.removeEventListener('DOMNodeInsertedIntoDocument', nodeInsert, false); 24533 target.removeEventListener('DOMNodeInserted', nodeInsert, false); 24534 target.removeEventListener('DOMAttrModified', attrModified, false); 24535 }; 24536 24537 this.takeRecords = function() { 24538 return records; 24539 }; 24540 }; 24541 } 24542 24543 function customDelete(isForward) { 24544 var mutationObserver = new MutationObserver(function() {}); 24545 24546 Tools.each(editor.getBody().getElementsByTagName('*'), function(elm) { 24547 // Mark existing spans 24548 if (elm.tagName == 'SPAN') { 24549 elm.setAttribute('mce-data-marked', 1); 24550 } 24551 24552 // Make sure all elements has a data-mce-style attribute 24553 if (!elm.hasAttribute('data-mce-style') && elm.hasAttribute('style')) { 24554 editor.dom.setAttrib(elm, 'style', editor.dom.getAttrib(elm, 'style')); 24555 } 24556 }); 24557
24558 // Observe added nodes and style attribute changes 24559 mutationObserver.observe(editor.getDoc(), { 24560 childList: true, 24561 attributes: true, 24562 subtree: true, 24563 attributeFilter: ['style'] 24564 }); 24565 24566 editor.getDoc().execCommand(isForward ? 'ForwardDelete' : 'Delete', false, null); 24567 24568 var rng = editor.selection.getRng(); 24569 var caretElement = rng.startContainer.parentNode; 24570 24571 Tools.each(mutationObserver.takeRecords(), function(record) { 24572 if (!dom.isChildOf(record.target, editor.getBody())) { 24573 return; 24574 } 24575 24576 // Restore style attribute to previous value 24577 if (record.attributeName == "style") { 24578 var oldValue = record.target.getAttribute('data-mce-style'); 24579 24580 if (oldValue) { 24581 record.target.setAttribute("style", oldValue); 24582 } else { 24583 record.target.removeAttribute("style"); 24584 } 24585 } 24586 24587 // Remove all spans that isn't maked and retain selection 24588 Tools.each(record.addedNodes, function(node) { 24589 if (node.nodeName == "SPAN" && !node.getAttribute('mce-data-marked')) { 24590 var offset, container; 24591 24592 if (node == caretElement) { 24593 offset = rng.startOffset; 24594 container = node.firstChild; 24595 } 24596 24597 dom.remove(node, true); 24598 24599 if (container) { 24600 rng.setStart(container, offset); 24601 rng.setEnd(container, offset); 24602 editor.selection.setRng(rng); 24603 } 24604 } 24605 }); 24606 }); 24607 24608 mutationObserver.disconnect(); 24609 24610 // Remove any left over marks 24611 Tools.each(editor.dom.select('span[mce-data-marked]'), function(span) { 24612 span.removeAttribute('mce-data-marked'); 24613 }); 24614 } 24615 24616 editor.on('keydown', function(e) { 24617 var isForward = e.keyCode == DELETE, isMeta = VK.metaKeyPressed(e); 24618 24619 if (!isDefaultPrevented(e) && (isForward || e.keyCode == BACKSPACE)) { 24620 var rng = editor.selection.getRng(), container = rng.startContainer, offset = rng.startOffset; 24621 24622 // Ignore non meta delete in the where there is text before/after the caret 24623 if (!isMeta && rng.collapsed && container.nodeType == 3) { 24624 if (isForward ? offset < container.data.length : offset > 0) { 24625 return; 24626 } 24627 } 24628 24629 e.preventDefault(); 24630 24631 if (isMeta) { 24632 editor.selection.getSel().modify("extend", isForward ? "forward" : "backward", "word"); 24633 } 24634 24635 customDelete(isForward); 24636 } 24637 }); 24638 24639 editor.on('keypress', function(e) { 24640 if (!isDefaultPrevented(e) && !selection.isCollapsed() && e.charCode && !VK.metaKeyPressed(e)) { 24641 e.preventDefault(); 24642 customDelete(true); 24643 editor.selection.setContent(String.fromCharCode(e.charCode)); 24644 } 24645 }); 24646 24647 editor.addCommand('Delete', function() { 24648 customDelete(); 24649 }); 24650 24651 editor.addCommand('ForwardDelete', function() { 24652 customDelete(true); 24653 }); 24654
24655 // Older WebKits doesn't properly handle the clipboard so we can't add the rest 24656 if (olderWebKit) { 24657 return; 24658 } 24659 24660 editor.on('dragstart', function(e) { 24661 var selectionHtml; 24662 24663 if (editor.selection.isCollapsed() && e.target.tagName == 'IMG') { 24664 selection.select(e.target); 24665 } 24666 24667 dragStartRng = selection.getRng(); 24668 selectionHtml = editor.selection.getContent(); 24669 24670 // Safari doesn't support custom dataTransfer items so we can only use URL and Text 24671 if (selectionHtml.length > 0) { 24672 e.dataTransfer.setData('URL', 'data:text/mce-internal,' + escape(selectionHtml)); 24673 } 24674 }); 24675 24676 editor.on('drop', function(e) { 24677 if (!isDefaultPrevented(e)) { 24678 var internalContent = e.dataTransfer.getData('URL'); 24679 24680 if (!internalContent || internalContent.indexOf(urlPrefix) == -1 || !doc.caretRangeFromPoint) { 24681 return; 24682 } 24683 24684 internalContent = unescape(internalContent.substr(urlPrefix.length)); 24685 if (doc.caretRangeFromPoint) { 24686 e.preventDefault(); 24687 24688 // Safari has a weird issue where drag/dropping images sometimes 24689 // produces a green plus icon. When this happens the caretRangeFromPoint 24690 // will return "null" even though the x, y coordinate is correct. 24691 // But if we detach the insert from the drop event we will get a proper range 24692 window.setTimeout(function() { 24693 var pointRng = doc.caretRangeFromPoint(e.x, e.y); 24694 24695 if (dragStartRng) { 24696 selection.setRng(dragStartRng); 24697 dragStartRng = null; 24698 } 24699 24700 customDelete(); 24701 24702 selection.setRng(pointRng); 24703 editor.insertContent(internalContent); 24704 }, 0); 24705 } 24706 24707 } 24708 }); 24709 24710 editor.on('cut', function(e) { 24711 if (!isDefaultPrevented(e) && e.clipboardData) { 24712 e.preventDefault(); 24713 e.clipboardData.clearData(); 24714 e.clipboardData.setData('text/html', editor.selection.getContent()); 24715 e.clipboardData.setData('text/plain', editor.selection.getContent({format: 'text'})); 24716 customDelete(true); 24717 } 24718 }); 24719 } 24720 24721 /** 24722 * Makes sure that the editor body becomes empty when backspace or delete is pressed in empty editors. 24723 * 24724 * For example: 24725 * <p><b>|</b></p> 24726 * 24727 * Or: 24728 * <h1>|</h1> 24729 * 24730 * Or: 24731 * [<h1></h1>] 24732 */ 24733 function emptyEditorWhenDeleting() { 24734 function serializeRng(rng) { 24735 var body = dom.create("body"); 24736 var contents = rng.cloneContents(); 24737 body.appendChild(contents); 24738 return selection.serializer.serialize(body, {format: 'html'}); 24739 } 24740 24741 function allContentsSelected(rng) { 24742 if (!rng.setStart) { 24743 if (rng.item) { 24744 return false; 24745 } 24746 24747 var bodyRng = rng.duplicate(); 24748 bodyRng.moveToElementText(editor.getBody()); 24749 return RangeUtils.compareRanges(rng, bodyRng); 24750 } 24751 24752 var selection = serializeRng(rng); 24753 24754 var allRng = dom.createRng(); 24755 allRng.selectNode(editor.getBody()); 24756 24757 var allSelection = serializeRng(allRng); 24758 return selection === allSelection; 24759 } 24760 24761 editor.on('keydown', function(e) { 24762 var keyCode = e.keyCode, isCollapsed, body; 24763 24764 // Empty the editor if it's needed for example backspace at <p><b>|</b></p> 24765 if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) { 24766 isCollapsed = editor.selection.isCollapsed(); 24767 body = editor.getBody(); 24768 24769 // Selection is collapsed but the editor isn't empty 24770 if (isCollapsed && !dom.isEmpty(body)) { 24771 return; 24772 } 24773 24774 // Selection isn't collapsed but not all the contents is selected 24775 if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) { 24776 return; 24777 } 24778 24779 // Manually empty the editor 24780 e.preventDefault(); 24781 editor.setContent(''); 24782 24783 if (body.firstChild && dom.isBlock(body.firstChild)) { 24784 editor.selection.setCursorLocation(body.firstChild, 0); 24785 } else { 24786 editor.selection.setCursorLocation(body, 0); 24787 } 24788 24789 editor.nodeChanged(); 24790 } 24791 }); 24792 } 24793 24794 /** 24795 * WebKit doesn't select all the nodes in the body when you press Ctrl+A. 24796 * IE selects more than the contents <body>[<p>a</p>]</body> instead of <body><p>[a]</p]</body> see bug #6438 24797 * This selects the whole body so that backspace/delete logic will delete everything 24798 */ 24799 function selectAll() { 24800 editor.shortcuts.add('ctrl+a', null, 'SelectAll'); 24801 } 24802 24803 /** 24804 * WebKit has a weird issue where it some times fails to properly convert keypresses to input method keystrokes. 24805 * The IME on Mac doesn't initialize when it doesn't fire a proper focus event. 24806 * 24807 * This seems to happen when the user manages to click the documentElement element then the window doesn't get proper focus until 24808 * you enter a character into the editor. 24809 * 24810 * It also happens when the first focus in made to the body. 24811 * 24812 * See: https://bugs.webkit.org/show_bug.cgi?id=83566 24813 */ 24814 function inputMethodFocus() { 24815 if (!editor.settings.content_editable) { 24816 // Case 1 IME doesn't initialize if you focus the document
24817 dom.bind(editor.getDoc(), 'focusin', function() { 24818 selection.setRng(selection.getRng()); 24819 }); 24820 24821 // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event 24822 // Needs to be both down/up due to weird rendering bug on Chrome Windows 24823 dom.bind(editor.getDoc(), 'mousedown mouseup', function(e) { 24824 if (e.target == editor.getDoc().documentElement) { 24825 editor.getBody().focus(); 24826 24827 if (e.type == 'mousedown') { 24828 // Edge case for mousedown, drag select and mousedown again within selection on Chrome Windows to render caret 24829 selection.placeCaretAt(e.clientX, e.clientY); 24830 } else { 24831 selection.setRng(selection.getRng()); 24832 } 24833 } 24834 }); 24835 } 24836 } 24837 24838 /** 24839 * Backspacing in FireFox/IE from a paragraph into a horizontal rule results in a floating text node because the 24840 * browser just deletes the paragraph - the browser fails to merge the text node with a horizontal rule so it is 24841 * left there. TinyMCE sees a floating text node and wraps it in a paragraph on the key up event (ForceBlocks.js 24842 * addRootBlocks), meaning the action does nothing. With this code, FireFox/IE matche the behaviour of other 24843 * browsers. 24844 * 24845 * It also fixes a bug on Firefox where it's impossible to delete HR elements. 24846 */ 24847 function removeHrOnBackspace() { 24848 editor.on('keydown', function(e) { 24849 if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) { 24850 // Check if there is any HR elements this is faster since getRng on IE 7 & 8 is slow 24851 if (!editor.getBody().getElementsByTagName('hr').length) { 24852 return; 24853 } 24854 24855 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { 24856 var node = selection.getNode(); 24857 var previousSibling = node.previousSibling; 24858 24859 if (node.nodeName == 'HR') { 24860 dom.remove(node); 24861 e.preventDefault(); 24862 return; 24863 } 24864 24865 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") { 24866 dom.remove(previousSibling); 24867 e.preventDefault(); 24868 } 24869 } 24870 } 24871 }); 24872 } 24873 24874 /** 24875 * Firefox 3.x has an issue where the body element won't get proper focus if you click out 24876 * side it's rectangle. 24877 */ 24878 function focusBody() { 24879 // Fix for a focus bug in FF 3.x where the body element 24880 // wouldn't get proper focus if the user clicked on the HTML element 24881 if (!window.Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4 24882 editor.on('mousedown', function(e) { 24883 if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") { 24884 var body = editor.getBody(); 24885 24886 // Blur the body it's focused but not correctly focused 24887 body.blur(); 24888 24889 // Refocus the body after a little while 24890 setTimeout(function() { 24891 body.focus(); 24892 }, 0); 24893 } 24894 }); 24895 } 24896 } 24897 24898 /** 24899 * WebKit has a bug where it isn't possible to select image, hr or anchor elements 24900 * by clicking on them so we need to fake that. 24901 */ 24902 function selectControlElements() { 24903 editor.on('click', function(e) { 24904 var target = e.target; 24905 24906 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250 24907 // WebKit can't even do simple things like selecting an image 24908 // Needs to be the setBaseAndExtend or it will fail to select floated images 24909 if (/^(IMG|HR)$/.test(target.nodeName)) { 24910 e.preventDefault(); 24911 selection.getSel().setBaseAndExtent(target, 0, target, 1); 24912 editor.nodeChanged(); 24913 } 24914 24915 if (target.nodeName == 'A' && dom.hasClass(target, 'mce-item-anchor')) { 24916 e.preventDefault(); 24917 selection.select(target); 24918 } 24919 }); 24920 } 24921 24922 /** 24923 * Fixes a Gecko bug where the style attribute gets added to the wrong element when deleting between two block elements. 24924 * 24925 * Fixes do backspace/delete on this: 24926 * <p>bla[ck</p><p style="color:red">r]ed</p> 24927 * 24928 * Would become: 24929 * <p>bla|ed</p> 24930 * 24931 * Instead of: 24932 * <p style="color:red">bla|ed</p> 24933 */ 24934 function removeStylesWhenDeletingAcrossBlockElements() { 24935 function getAttributeApplyFunction() { 24936 var template = dom.getAttribs(selection.getStart().cloneNode(false)); 24937 24938 return function() { 24939 var target = selection.getStart(); 24940 24941 if (target !== editor.getBody()) { 24942 dom.setAttrib(target, "style", null); 24943 24944 each(template, function(attr) { 24945 target.setAttributeNode(attr.cloneNode(true)); 24946 }); 24947 } 24948 }; 24949 } 24950 24951 function isSelectionAcrossElements() { 24952 return !selection.isCollapsed() && 24953 dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock); 24954 } 24955
24956 editor.on('keypress', function(e) { 24957 var applyAttributes; 24958 24959 if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) { 24960 applyAttributes = getAttributeApplyFunction(); 24961 editor.getDoc().execCommand('delete', false, null); 24962 applyAttributes(); 24963 e.preventDefault(); 24964 return false; 24965 } 24966 }); 24967 24968 dom.bind(editor.getDoc(), 'cut', function(e) { 24969 var applyAttributes; 24970 24971 if (!isDefaultPrevented(e) && isSelectionAcrossElements()) { 24972 applyAttributes = getAttributeApplyFunction(); 24973 24974 setTimeout(function() { 24975 applyAttributes(); 24976 }, 0); 24977 } 24978 }); 24979 } 24980 24981 /** 24982 * Screen readers on IE needs to have the role application set on the body. 24983 */ 24984 function ensureBodyHasRoleApplication() { 24985 document.body.setAttribute("role", "application"); 24986 } 24987 24988 /** 24989 * Backspacing into a table behaves differently depending upon browser type. 24990 * Therefore, disable Backspace when cursor immediately follows a table. 24991 */ 24992 function disableBackspaceIntoATable() { 24993 editor.on('keydown', function(e) { 24994 if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) { 24995 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { 24996 var previousSibling = selection.getNode().previousSibling; 24997 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") { 24998 e.preventDefault(); 24999 return false; 25000 } 25001 } 25002 } 25003 }); 25004 } 25005 25006 /** 25007 * Old IE versions can't properly render BR elements in PRE tags white in contentEditable mode. So this 25008 * logic adds a \n before the BR so that it will get rendered. 25009 */ 25010 function addNewLinesBeforeBrInPre() { 25011 // IE8+ rendering mode does the right thing with BR in PRE 25012 if (getDocumentMode() > 7) { 25013 return; 25014 } 25015 25016 // Enable display: none in area and add a specific class that hides all BR elements in PRE to 25017 // avoid the caret from getting stuck at the BR elements while pressing the right arrow key 25018 setEditorCommandState('RespectVisibilityInDesign', true); 25019 editor.contentStyles.push('.mceHideBrInPre pre br {display: none}'); 25020 dom.addClass(editor.getBody(), 'mceHideBrInPre'); 25021 25022 // Adds a \n before all BR elements in PRE to get them visual 25023 parser.addNodeFilter('pre', function(nodes) { 25024 var i = nodes.length, brNodes, j, brElm, sibling; 25025 25026 while (i--) { 25027 brNodes = nodes[i].getAll('br'); 25028 j = brNodes.length; 25029 while (j--) { 25030 brElm = brNodes[j]; 25031 25032 // Add \n before BR in PRE elements on older IE:s so the new lines get rendered 25033 sibling = brElm.prev; 25034 if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') { 25035 sibling.value += '\n'; 25036 } else { 25037 brElm.parent.insert(new Node('#text', 3), brElm, true).value = '\n'; 25038 } 25039 } 25040 } 25041 }); 25042 25043 // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible 25044 serializer.addNodeFilter('pre', function(nodes) { 25045 var i = nodes.length, brNodes, j, brElm, sibling; 25046 25047 while (i--) { 25048 brNodes = nodes[i].getAll('br'); 25049 j = brNodes.length; 25050 while (j--) { 25051 brElm = brNodes[j]; 25052 sibling = brElm.prev; 25053 if (sibling && sibling.type == 3) { 25054 sibling.value = sibling.value.replace(/\r?\n$/, ''); 25055 } 25056 } 25057 } 25058 }); 25059 } 25060 25061 /** 25062 * Moves style width/height to attribute width/height when the user resizes an image on IE. 25063 */ 25064 function removePreSerializedStylesWhenSelectingControls() { 25065 dom.bind(editor.getBody(), 'mouseup', function() { 25066 var value, node = selection.getNode(); 25067 25068 // Moved styles to attributes on IMG eements 25069 if (node.nodeName == 'IMG') { 25070 // Convert style width to width attribute 25071 if ((value = dom.getStyle(node, 'width'))) { 25072 dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, '')); 25073 dom.setStyle(node, 'width', ''); 25074 } 25075 25076 // Convert style height to height attribute 25077 if ((value = dom.getStyle(node, 'height'))) { 25078 dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, '')); 25079 dom.setStyle(node, 'height', ''); 25080 } 25081 } 25082 }); 25083 } 25084 25085 /** 25086 * Removes a blockquote when backspace is pressed at the beginning of it. 25087 * 25088 * For example: 25089 * <blockquote><p>|x</p></blockquote> 25090 * 25091 * Becomes: 25092 * <p>|x</p> 25093 */
25094 function removeBlockQuoteOnBackSpace() { 25095 // Add block quote deletion handler 25096 editor.on('keydown', function(e) { 25097 var rng, container, offset, root, parent; 25098 25099 if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) { 25100 return; 25101 } 25102 25103 rng = selection.getRng(); 25104 container = rng.startContainer; 25105 offset = rng.startOffset; 25106 root = dom.getRoot(); 25107 parent = container; 25108 25109 if (!rng.collapsed || offset !== 0) { 25110 return; 25111 } 25112 25113 while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) { 25114 parent = parent.parentNode; 25115 } 25116 25117 // Is the cursor at the beginning of a blockquote? 25118 if (parent.tagName === 'BLOCKQUOTE') { 25119 // Remove the blockquote 25120 editor.formatter.toggle('blockquote', null, parent); 25121 25122 // Move the caret to the beginning of container 25123 rng = dom.createRng(); 25124 rng.setStart(container, 0); 25125 rng.setEnd(container, 0); 25126 selection.setRng(rng); 25127 } 25128 }); 25129 } 25130 25131 /** 25132 * Sets various Gecko editing options on mouse down and before a execCommand to disable inline table editing that is broken etc. 25133 */ 25134 function setGeckoEditingOptions() { 25135 function setOpts() { 25136 editor._refreshContentEditable(); 25137 25138 setEditorCommandState("StyleWithCSS", false); 25139 setEditorCommandState("enableInlineTableEditing", false); 25140 25141 if (!settings.object_resizing) { 25142 setEditorCommandState("enableObjectResizing", false); 25143 } 25144 } 25145 25146 if (!settings.readonly) { 25147 editor.on('BeforeExecCommand MouseDown', setOpts); 25148 } 25149 } 25150 25151 /** 25152 * Fixes a gecko link bug, when a link is placed at the end of block elements there is 25153 * no way to move the caret behind the link. This fix adds a bogus br element after the link. 25154 * 25155 * For example this: 25156 * <p><b><a href="#">x</a></b></p> 25157 * 25158 * Becomes this: 25159 * <p><b><a href="#">x</a></b><br></p> 25160 */ 25161 function addBrAfterLastLinks() { 25162 function fixLinks() { 25163 each(dom.select('a'), function(node) { 25164 var parentNode = node.parentNode, root = dom.getRoot(); 25165 25166 if (parentNode.lastChild === node) { 25167 while (parentNode && !dom.isBlock(parentNode)) { 25168 if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) { 25169 return; 25170 } 25171 25172 parentNode = parentNode.parentNode; 25173 } 25174 25175 dom.add(parentNode, 'br', {'data-mce-bogus': 1}); 25176 } 25177 }); 25178 } 25179 25180 editor.on('SetContent ExecCommand', function(e) { 25181 if (e.type == "setcontent" || e.command === 'mceInsertLink') { 25182 fixLinks(); 25183 } 25184 }); 25185 } 25186 25187 /** 25188 * WebKit will produce DIV elements here and there by default. But since TinyMCE uses paragraphs by 25189 * default we want to change that behavior. 25190 */ 25191 function setDefaultBlockType() { 25192 if (settings.forced_root_block) { 25193 editor.on('init', function() { 25194 setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block); 25195 }); 25196 } 25197 } 25198 25199 /** 25200 * Removes ghost selections from images/tables on Gecko. 25201 */ 25202 function removeGhostSelection() { 25203 editor.on('Undo Redo SetContent', function(e) { 25204 if (!e.initial) { 25205 editor.execCommand('mceRepaint'); 25206 } 25207 }); 25208 } 25209 25210 /** 25211 * Deletes the selected image on IE instead of navigating to previous page. 25212 */ 25213 function deleteControlItemOnBackSpace() { 25214 editor.on('keydown', function(e) { 25215 var rng; 25216 25217 if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) { 25218 rng = editor.getDoc().selection.createRange(); 25219 if (rng && rng.item) { 25220 e.preventDefault(); 25221 editor.undoManager.beforeChange(); 25222 dom.remove(rng.item(0)); 25223 editor.undoManager.add(); 25224 } 25225 } 25226 }); 25227 } 25228 25229 /** 25230 * IE10 doesn't properly render block elements with the right height until you add contents to them. 25231 * This fixes that by adding a padding-right to all empty text block elements. 25232 * See: https://connect.microsoft.com/IE/feedback/details/743881 25233 */ 25234 function renderEmptyBlocksFix() { 25235 var emptyBlocksCSS; 25236 25237 // IE10+ 25238 if (getDocumentMode() >= 10) { 25239 emptyBlocksCSS = ''; 25240 each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) { 25241 emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty'; 25242 }); 25243 25244 editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}'); 25245 } 25246 } 25247 25248 /** 25249 * Old IE versions can't retain contents within noscript elements so this logic will store the contents 25250 * as a attribute and the insert that value as it's raw text when the DOM is serialized. 25251 */ 25252 function keepNoScriptContents() { 25253 if (getDocumentMode() < 9) { 25254 parser.addNodeFilter('noscript', function(nodes) { 25255 var i = nodes.length, node, textNode; 25256 25257 while (i--) { 25258 node = nodes[i]; 25259 textNode = node.firstChild; 25260 25261 if (textNode) { 25262 node.attr('data-mce-innertext', textNode.value); 25263 } 25264 } 25265 }); 25266 25267 serializer.addNodeFilter('noscript', function(nodes) { 25268 var i = nodes.length, node, textNode, value; 25269 25270 while (i--) { 25271 node = nodes[i]; 25272 textNode = nodes[i].firstChild; 25273 25274 if (textNode) { 25275 textNode.value = Entities.decode(textNode.value); 25276 } else { 25277 // Old IE can't retain noscript value so an attribute is used to store it 25278 value = node.attributes.map['data-mce-innertext']; 25279 if (value) { 25280 node.attr('data-mce-innertext', null); 25281 textNode = new Node('#text', 3); 25282 textNode.value = value; 25283 textNode.raw = true; 25284 node.append(textNode); 25285 } 25286 } 25287 } 25288 }); 25289 } 25290 } 25291 25292 /** 25293 * IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode. 25294 */ 25295 function fixCaretSelectionOfDocumentElementOnIe() { 25296 var doc = dom.doc, body = doc.body, started, startRng, htmlElm; 25297 25298 // Return range from point or null if it failed 25299 function rngFromPoint(x, y) { 25300 var rng = body.createTextRange(); 25301 25302 try { 25303 rng.moveToPoint(x, y); 25304 } catch (ex) { 25305 // IE sometimes throws and exception, so lets just ignore it 25306 rng = null; 25307 } 25308 25309 return rng; 25310 } 25311 25312 // Fires while the selection is changing 25313 function selectionChange(e) { 25314 var pointRng; 25315 25316 // Check if the button is down or not 25317 if (e.button) { 25318 // Create range from mouse position 25319 pointRng = rngFromPoint(e.x, e.y); 25320 25321 if (pointRng) { 25322 // Check if pointRange is before/after selection then change the endPoint 25323 if (pointRng.compareEndPoints('StartToStart', startRng) > 0) { 25324 pointRng.setEndPoint('StartToStart', startRng); 25325 } else { 25326 pointRng.setEndPoint('EndToEnd', startRng); 25327 } 25328 25329 pointRng.select(); 25330 } 25331 } else { 25332 endSelection(); 25333 } 25334 } 25335 25336 // Removes listeners 25337 function endSelection() { 25338 var rng = doc.selection.createRange(); 25339 25340 // If the range is collapsed then use the last start range 25341 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) { 25342 startRng.select(); 25343 } 25344 25345 dom.unbind(doc, 'mouseup', endSelection); 25346 dom.unbind(doc, 'mousemove', selectionChange); 25347 startRng = started = 0; 25348 } 25349 25350 // Make HTML element unselectable since we are going to handle selection by hand 25351 doc.documentElement.unselectable = true; 25352 25353 // Detect when user selects outside BODY 25354 dom.bind(doc, 'mousedown contextmenu', function(e) { 25355 if (e.target.nodeName === 'HTML') { 25356 if (started) { 25357 endSelection(); 25358 } 25359 25360 // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML 25361 htmlElm = doc.documentElement; 25362 if (htmlElm.scrollHeight > htmlElm.clientHeight) { 25363 return; 25364 } 25365 25366 started = 1; 25367 // Setup start position 25368 startRng = rngFromPoint(e.x, e.y); 25369 if (startRng) { 25370 // Listen for selection change events 25371 dom.bind(doc, 'mouseup', endSelection); 25372 dom.bind(doc, 'mousemove', selectionChange); 25373 25374 dom.getRoot().focus(); 25375 startRng.select(); 25376 } 25377 } 25378 }); 25379 } 25380 25381 /** 25382 * Fixes selection issues where the caret can be placed between two inline elements like <b>a</b>|<b>b</b> 25383 * this fix will lean the caret right into the closest inline element. 25384 */ 25385 function normalizeSelection() { 25386 // Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything
25387 editor.on('keyup focusin mouseup', function(e) { 25388 if (e.keyCode != 65 || !VK.metaKeyPressed(e)) { 25389 selection.normalize(); 25390 } 25391 }, true); 25392 } 25393 25394 /** 25395 * Forces Gecko to render a broken image icon if it fails to load an image. 25396 */ 25397 function showBrokenImageIcon() { 25398 editor.contentStyles.push( 25399 'img:-moz-broken {' + 25400 '-moz-force-broken-image-icon:1;' + 25401 'min-width:24px;' + 25402 'min-height:24px' + 25403 '}' 25404 ); 25405 } 25406 25407 /** 25408 * iOS has a bug where it's impossible to type if the document has a touchstart event 25409 * bound and the user touches the document while having the on screen keyboard visible. 25410 * 25411 * The touch event moves the focus to the parent document while having the caret inside the iframe 25412 * this fix moves the focus back into the iframe document. 25413 */ 25414 function restoreFocusOnKeyDown() { 25415 if (!editor.inline) { 25416 editor.on('keydown', function() { 25417 if (document.activeElement == document.body) { 25418 editor.getWin().focus(); 25419 } 25420 }); 25421 } 25422 } 25423 25424 /** 25425 * IE 11 has an annoying issue where you can't move focus into the editor 25426 * by clicking on the white area HTML element. We used to be able to to fix this with 25427 * the fixCaretSelectionOfDocumentElementOnIe fix. But since M$ removed the selection 25428 * object it's not possible anymore. So we need to hack in a ungly CSS to force the 25429 * body to be at least 150px. If the user clicks the HTML element out side this 150px region 25430 * we simply move the focus into the first paragraph. Not ideal since you loose the 25431 * positioning of the caret but goot enough for most cases. 25432 */ 25433 function bodyHeight() { 25434 if (!editor.inline) { 25435 editor.contentStyles.push('body {min-height: 150px}'); 25436 editor.on('click', function(e) { 25437 if (e.target.nodeName == 'HTML') { 25438 editor.getBody().focus(); 25439 editor.selection.normalize(); 25440 editor.nodeChanged(); 25441 } 25442 }); 25443 } 25444 } 25445 25446 /** 25447 * Firefox on Mac OS will move the browser back to the previous page if you press CMD+Left arrow. 25448 * You might then loose all your work so we need to block that behavior and replace it with our own. 25449 */ 25450 function blockCmdArrowNavigation() { 25451 if (Env.mac) { 25452 editor.on('keydown', function(e) { 25453 if (VK.metaKeyPressed(e) && (e.keyCode == 37 || e.keyCode == 39)) { 25454 e.preventDefault(); 25455 editor.selection.getSel().modify('move', e.keyCode == 37 ? 'backward' : 'forward', 'word'); 25456 } 25457 }); 25458 } 25459 } 25460 25461 /** 25462 * Disables the autolinking in IE 9+ this is then re-enabled by the autolink plugin. 25463 */ 25464 function disableAutoUrlDetect() { 25465 setEditorCommandState("AutoUrlDetect", false); 25466 } 25467 25468 /** 25469 * IE 11 has a fantastic bug where it will produce two trailing BR elements to iframe bodies when 25470 * the iframe is hidden by display: none on a parent container. The DOM is actually out of sync 25471 * with innerHTML in this case. It's like IE adds shadow DOM BR elements that appears on innerHTML 25472 * but not as the lastChild of the body. However is we add a BR element to the body then remove it 25473 * it doesn't seem to add these BR elements makes sence right?! 25474 * 25475 * Example of what happens: <body>text</body> becomes <body>text<br><br></body> 25476 */ 25477 function doubleTrailingBrElements() { 25478 if (!editor.inline) { 25479 editor.on('focus blur beforegetcontent', function() { 25480 var br = editor.dom.create('br'); 25481 editor.getBody().appendChild(br); 25482 br.parentNode.removeChild(br); 25483 }, true); 25484 } 25485 } 25486 25487 /** 25488 * iOS 7.1 introduced two new bugs: 25489 * 1) It's possible to open links within a contentEditable area by clicking on them. 25490 * 2) If you hold down the finger it will display the link/image touch callout menu. 25491 */ 25492 function tapLinksAndImages() { 25493 editor.on('click', function(e) { 25494 var elm = e.target; 25495 25496 do { 25497 if (elm.tagName === 'A') { 25498 e.preventDefault(); 25499 return; 25500 } 25501 } while ((elm = elm.parentNode)); 25502 }); 25503 25504 editor.contentStyles.push('.mce-content-body {-webkit-touch-callout: none}'); 25505 } 25506 25507 /** 25508 * iOS Safari and possible other browsers have a bug where it won't fire 25509 * a click event when a contentEditable is focused. This function fakes click events 25510 * by using touchstart/touchend and measuring the time and distance travelled. 25511 */ 25512 function touchClickEvent() { 25513 editor.on('touchstart', function(e) { 25514 var elm, time, startTouch, changedTouches; 25515 25516 elm = e.target; 25517 time = new Date().getTime(); 25518 changedTouches = e.changedTouches; 25519 25520 if (!changedTouches || changedTouches.length > 1) { 25521 return; 25522 } 25523 25524 startTouch = changedTouches[0]; 25525 25526 editor.once('touchend', function(e) { 25527 var endTouch = e.changedTouches[0], args; 25528 25529 if (new Date().getTime() - time > 500) { 25530 return; 25531 } 25532 25533 if (Math.abs(startTouch.clientX - endTouch.clientX) > 5) { 25534 return; 25535 } 25536 25537 if (Math.abs(startTouch.clientY - endTouch.clientY) > 5) { 25538 return; 25539 } 25540 25541 args = { 25542 target: elm 25543 }; 25544 25545 each('pageX pageY clientX clientY screenX screenY'.split(' '), function(key) { 25546 args[key] = endTouch[key]; 25547 }); 25548
25549 args = editor.fire('click', args); 25550 25551 if (!args.isDefaultPrevented()) { 25552 // iOS WebKit can't place the caret properly once 25553 // you bind touch events so we need to do this manually 25554 // TODO: Expand to the closest word? Touble tap still works. 25555 editor.selection.placeCaretAt(endTouch.clientX, endTouch.clientY); 25556 editor.nodeChanged(); 25557 } 25558 }); 25559 }); 25560 } 25561 25562 /** 25563 * WebKit has a bug where it will allow forms to be submitted if they are inside a contentEditable element. 25564 * For example this: <form><button></form> 25565 */ 25566 function blockFormSubmitInsideEditor() { 25567 editor.on('init', function() { 25568 editor.dom.bind(editor.getBody(), 'submit', function(e) { 25569 e.preventDefault(); 25570 }); 25571 }); 25572 } 25573 25574 /** 25575 * Sometimes WebKit/Blink generates BR elements with the Apple-interchange-newline class. 25576 * 25577 * Scenario: 25578 * 1) Create a table 2x2. 25579 * 2) Select and copy cells A2-B2. 25580 * 3) Paste and it will add BR element to table cell. 25581 */ 25582 function removeAppleInterchangeBrs() { 25583 parser.addNodeFilter('br', function(nodes) { 25584 var i = nodes.length; 25585 25586 while (i--) { 25587 if (nodes[i].attr('class') == 'Apple-interchange-newline') { 25588 nodes[i].remove(); 25589 } 25590 } 25591 }); 25592 } 25593 25594 // All browsers 25595 removeBlockQuoteOnBackSpace(); 25596 emptyEditorWhenDeleting(); 25597 normalizeSelection(); 25598 25599 // WebKit 25600 if (isWebKit) { 25601 cleanupStylesWhenDeleting(); 25602 inputMethodFocus(); 25603 selectControlElements(); 25604 setDefaultBlockType(); 25605 blockFormSubmitInsideEditor(); 25606 disableBackspaceIntoATable(); 25607 removeAppleInterchangeBrs(); 25608 touchClickEvent(); 25609 25610 // iOS 25611 if (Env.iOS) { 25612 restoreFocusOnKeyDown(); 25613 bodyHeight(); 25614 tapLinksAndImages(); 25615 } else { 25616 selectAll(); 25617 } 25618 } 25619 25620 // IE 25621 if (isIE && Env.ie < 11) { 25622 removeHrOnBackspace(); 25623 ensureBodyHasRoleApplication(); 25624 addNewLinesBeforeBrInPre(); 25625 removePreSerializedStylesWhenSelectingControls(); 25626 deleteControlItemOnBackSpace(); 25627 renderEmptyBlocksFix(); 25628 keepNoScriptContents(); 25629 fixCaretSelectionOfDocumentElementOnIe(); 25630 } 25631 25632 if (Env.ie >= 11) { 25633 bodyHeight(); 25634 doubleTrailingBrElements(); 25635 disableBackspaceIntoATable(); 25636 } 25637 25638 if (Env.ie) { 25639 selectAll(); 25640 disableAutoUrlDetect(); 25641 } 25642 25643 // Gecko 25644 if (isGecko) { 25645 removeHrOnBackspace(); 25646 focusBody(); 25647 removeStylesWhenDeletingAcrossBlockElements(); 25648 setGeckoEditingOptions(); 25649 addBrAfterLastLinks(); 25650 removeGhostSelection(); 25651 showBrokenImageIcon(); 25652 blockCmdArrowNavigation(); 25653 disableBackspaceIntoATable(); 25654 } 25655 }; 25656}); 25657 25658// Included from: js/tinymce/classes/util/Observable.js 25659 25660/** 25661 * Observable.js 25662 * 25663 * Copyright, Moxiecode Systems AB 25664 * Released under LGPL License. 25665 * 25666 * License: http://www.tinymce.com/license 25667 * Contributing: http://www.tinymce.com/contributing 25668 */ 25669 25670/** 25671 * This mixin will add event binding logic to classes. 25672 * 25673 * @mixin tinymce.util.Observable 25674 */ 25675define("tinymce/util/Observable", [ 25676 "tinymce/util/EventDispatcher" 25677], function(EventDispatcher) { 25678 function getEventDispatcher(obj) { 25679 if (!obj._eventDispatcher) { 25680 obj._eventDispatcher = new EventDispatcher({ 25681 scope: obj, 25682 toggleEvent: function(name, state) { 25683 if (EventDispatcher.isNative(name) && obj.toggleNativeEvent) { 25684 obj.toggleNativeEvent(name, state); 25685 } 25686 } 25687 }); 25688 } 25689 25690 return obj._eventDispatcher; 25691 } 25692 25693 return { 25694 /** 25695 * Fires the specified event by name. 25696 * 25697 * @method fire 25698 * @param {String} name Name of the event to fire. 25699 * @param {Object?} args Event arguments. 25700 * @param {Boolean?} bubble True/false if the event is to be bubbled. 25701 * @return {Object} Event args instance passed in. 25702 * @example 25703 * instance.fire('event', {...}); 25704 */ 25705 fire: function(name, args, bubble) { 25706 var self = this; 25707 25708 // Prevent all events except the remove event after the instance has been removed 25709 if (self.removed && name !== "remove") { 25710 return args; 25711 } 25712 25713 args = getEventDispatcher(self).fire(name, args, bubble); 25714 25715 // Bubble event up to parents 25716 if (bubble !== false && self.parent) { 25717 var parent = self.parent(); 25718 while (parent && !args.isPropagationStopped()) { 25719 parent.fire(name, args, false); 25720 parent = parent.parent(); 25721 } 25722 } 25723 25724 return args; 25725 }, 25726 25727 /** 25728 * Binds an event listener to a specific event by name. 25729 * 25730 * @method on 25731 * @param {String} name Event name or space separated list of events to bind. 25732 * @param {callback} callback Callback to be executed when the event occurs. 25733 * @param {Boolean} first Optional flag if the event should be prepended. Use this with care. 25734 * @return {Object} Current class instance. 25735 * @example 25736 * instance.on('event', function(e) { 25737 * // Callback logic 25738 * }); 25739 */ 25740 on: function(name, callback, prepend) { 25741 return getEventDispatcher(this).on(name, callback, prepend); 25742 }, 25743 25744 /** 25745 * Unbinds an event listener to a specific event by name. 25746 * 25747 * @method off 25748 * @param {String?} name Name of the event to unbind. 25749 * @param {callback?} callback Callback to unbind. 25750 * @return {Object} Current class instance. 25751 * @example 25752 * // Unbind specific callback 25753 * instance.off('event', handler); 25754 * 25755 * // Unbind all listeners by name 25756 * instance.off('event'); 25757 * 25758 * // Unbind all events 25759 * instance.off(); 25760 */ 25761 off: function(name, callback) { 25762 return getEventDispatcher(this).off(name, callback); 25763 }, 25764 25765 /** 25766 * Bind the event callback and once it fires the callback is removed. 25767 * 25768 * @method once 25769 * @param {String} name Name of the event to bind. 25770 * @param {callback} callback Callback to bind only once. 25771 * @return {Object} Current class instance. 25772 */ 25773 once: function(name, callback) { 25774 return getEventDispatcher(this).once(name, callback); 25775 }, 25776 25777 /** 25778 * Returns true/false if the object has a event of the specified name. 25779 * 25780 * @method hasEventListeners 25781 * @param {String} name Name of the event to check for. 25782 * @return {Boolean} true/false if the event exists or not. 25783 */ 25784 hasEventListeners: function(name) { 25785 return getEventDispatcher(this).has(name); 25786 } 25787 }; 25788}); 25789 25790// Included from: js/tinymce/classes/EditorObservable.js 25791 25792/** 25793 * EditorObservable.js 25794 * 25795 * Copyright, Moxiecode Systems AB 25796 * Released under LGPL License. 25797 * 25798 * License: http://www.tinymce.com/license 25799 * Contributing: http://www.tinymce.com/contributing 25800 */ 25801 25802/** 25803 * This mixin contains the event logic for the tinymce.Editor class. 25804 * 25805 * @mixin tinymce.EditorObservable 25806 * @extends tinymce.util.Observable 25807 */ 25808define("tinymce/EditorObservable", [ 25809 "tinymce/util/Observable", 25810 "tinymce/dom/DOMUtils", 25811 "tinymce/util/Tools" 25812], function(Observable, DOMUtils, Tools) { 25813 var DOM = DOMUtils.DOM, customEventRootDelegates; 25814 25815 /** 25816 * Returns the event target so for the specified event. Some events fire 25817 * only on document, some fire on documentElement etc. This also handles the 25818 * custom event root setting where it returns that element instead of the body. 25819 * 25820 * @private 25821 * @param {tinymce.Editor} editor Editor instance to get event target from. 25822 * @param {String} eventName Name of the event for example "click". 25823 * @return {Element/Document} HTML Element or document target to bind on. 25824 */ 25825 function getEventTarget(editor, eventName) { 25826 if (eventName == 'selectionchange') { 25827 return editor.getDoc(); 25828 } 25829 25830 // Need to bind mousedown/mouseup etc to document not body in iframe mode 25831 // Since the user might click on the HTML element not the BODY 25832 if (!editor.inline && /^mouse|click|contextmenu|drop|dragover|dragend/.test(eventName)) { 25833 return editor.getDoc().documentElement; 25834 } 25835 25836 // Bind to event root instead of body if it's defined 25837 if (editor.settings.event_root) { 25838 if (!editor.eventRoot) { 25839 editor.eventRoot = DOM.select(editor.settings.event_root)[0]; 25840 } 25841 25842 return editor.eventRoot; 25843 } 25844 25845 return editor.getBody(); 25846 } 25847 25848 /** 25849 * Binds a event delegate for the specified name this delegate will fire 25850 * the event to the editor dispatcher. 25851 * 25852 * @private 25853 * @param {tinymce.Editor} editor Editor instance to get event target from. 25854 * @param {String} eventName Name of the event for example "click". 25855 */ 25856 function bindEventDelegate(editor, eventName) { 25857 var eventRootElm = getEventTarget(editor, eventName), delegate; 25858 25859 if (!editor.delegates) { 25860 editor.delegates = {}; 25861 } 25862 25863 if (editor.delegates[eventName]) { 25864 return; 25865 } 25866 25867 if (editor.settings.event_root) { 25868 if (!customEventRootDelegates) { 25869 customEventRootDelegates = {}; 25870 editor.editorManager.on('removeEditor', function() { 25871 var name; 25872 25873 if (!editor.editorManager.activeEditor) { 25874 if (customEventRootDelegates) { 25875 for (name in customEventRootDelegates) { 25876 editor.dom.unbind(getEventTarget(editor, name)); 25877 } 25878 25879 customEventRootDelegates = null; 25880 } 25881 } 25882 }); 25883 } 25884 25885 if (customEventRootDelegates[eventName]) { 25886 return; 25887 } 25888 25889 delegate = function(e) { 25890 var target = e.target, editors = editor.editorManager.editors, i = editors.length; 25891 25892 while (i--) { 25893 var body = editors[i].getBody(); 25894 25895 if (body === target || DOM.isChildOf(target, body)) { 25896 if (!editors[i].hidden) { 25897 editors[i].fire(eventName, e); 25898 } 25899 } 25900 } 25901 }; 25902 25903 customEventRootDelegates[eventName] = delegate; 25904 DOM.bind(eventRootElm, eventName, delegate); 25905 } else { 25906 delegate = function(e) { 25907 if (!editor.hidden) { 25908 editor.fire(eventName, e); 25909 } 25910 }; 25911 25912 DOM.bind(eventRootElm, eventName, delegate); 25913 editor.delegates[eventName] = delegate; 25914 } 25915 } 25916 25917 var EditorObservable = { 25918 /** 25919 * Bind any pending event delegates. This gets executed after the target body/document is created. 25920 * 25921 * @private 25922 */ 25923 bindPendingEventDelegates: function() { 25924 var self = this; 25925 25926 Tools.each(self._pendingNativeEvents, function(name) { 25927 bindEventDelegate(self, name); 25928 }); 25929 }, 25930 25931 /** 25932 * Toggles a native event on/off this is called by the EventDispatcher when 25933 * the first native event handler is added and when the last native event handler is removed. 25934 * 25935 * @private 25936 */ 25937 toggleNativeEvent: function(name, state) { 25938 var self = this; 25939 25940 if (self.settings.readonly) { 25941 return; 25942 } 25943 25944 // Never bind focus/blur since the FocusManager fakes those 25945 if (name == "focus" || name == "blur") { 25946 return; 25947 } 25948 25949 if (state) { 25950 if (self.initialized) { 25951 bindEventDelegate(self, name); 25952 } else { 25953 if (!self._pendingNativeEvents) { 25954 self._pendingNativeEvents = [name]; 25955 } else { 25956 self._pendingNativeEvents.push(name); 25957 } 25958 } 25959 } else if (self.initialized) { 25960 self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]); 25961 delete self.delegates[name]; 25962 } 25963 }, 25964 25965 /** 25966 * Unbinds all native event handlers that means delegates, custom events bound using the Events API etc. 25967 * 25968 * @private 25969 */ 25970 unbindAllNativeEvents: function() { 25971 var self = this, name; 25972 25973 if (self.delegates) { 25974 for (name in self.delegates) { 25975 self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]); 25976 } 25977 25978 delete self.delegates; 25979 } 25980 25981 if (!self.inline) { 25982 self.getBody().onload = null; 25983 self.dom.unbind(self.getWin()); 25984 self.dom.unbind(self.getDoc()); 25985 } 25986 25987 self.dom.unbind(self.getBody()); 25988 self.dom.unbind(self.getContainer()); 25989 } 25990 }; 25991 25992 EditorObservable = Tools.extend({}, Observable, EditorObservable); 25993 25994 return EditorObservable; 25995}); 25996 25997// Included from: js/tinymce/classes/Shortcuts.js 25998 25999/** 26000 * Shortcuts.js 26001 * 26002 * Copyright, Moxiecode Systems AB 26003 * Released under LGPL License. 26004 * 26005 * License: http://www.tinymce.com/license 26006 * Contributing: http://www.tinymce.com/contributing 26007 */ 26008 26009/** 26010 * Contains all logic for handling of keyboard shortcuts. 26011 */ 26012define("tinymce/Shortcuts", [ 26013 "tinymce/util/Tools", 26014 "tinymce/Env" 26015], function(Tools, Env) { 26016 var each = Tools.each, explode = Tools.explode; 26017 26018 var keyCodeLookup = { 26019 "f9": 120, 26020 "f10": 121, 26021 "f11": 122 26022 }; 26023 26024 return function(editor) { 26025 var self = this, shortcuts = {}; 26026
26027 editor.on('keyup keypress keydown', function(e) { 26028 if ((e.altKey || e.ctrlKey || e.metaKey) && !e.isDefaultPrevented()) { 26029 each(shortcuts, function(shortcut) { 26030 var ctrlKey = Env.mac ? e.metaKey : e.ctrlKey; 26031 26032 if (shortcut.ctrl != ctrlKey || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) { 26033 return; 26034 } 26035 26036 if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) { 26037 e.preventDefault(); 26038 26039 if (e.type == "keydown") { 26040 shortcut.func.call(shortcut.scope); 26041 } 26042 26043 return true; 26044 } 26045 }); 26046 } 26047 }); 26048 26049 /** 26050 * Adds a keyboard shortcut for some command or function. 26051 * 26052 * @method addShortcut 26053 * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o. 26054 * @param {String} desc Text description for the command. 26055 * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed. 26056 * @param {Object} sc Optional scope to execute the function in. 26057 * @return {Boolean} true/false state if the shortcut was added or not. 26058 */ 26059 self.add = function(pattern, desc, cmdFunc, scope) { 26060 var cmd; 26061 26062 cmd = cmdFunc; 26063 26064 if (typeof(cmdFunc) === 'string') { 26065 cmdFunc = function() { 26066 editor.execCommand(cmd, false, null); 26067 }; 26068 } else if (Tools.isArray(cmd)) { 26069 cmdFunc = function() { 26070 editor.execCommand(cmd[0], cmd[1], cmd[2]); 26071 }; 26072 } 26073 26074 each(explode(pattern.toLowerCase()), function(pattern) { 26075 var shortcut = { 26076 func: cmdFunc, 26077 scope: scope || editor, 26078 desc: editor.translate(desc), 26079 alt: false, 26080 ctrl: false, 26081 shift: false 26082 }; 26083 26084 each(explode(pattern, '+'), function(value) { 26085 switch (value) { 26086 case 'alt': 26087 case 'ctrl': 26088 case 'shift': 26089 shortcut[value] = true; 26090 break; 26091 26092 default: 26093 // Allow numeric keycodes like ctrl+219 for ctrl+[ 26094 if (/^[0-9]{2,}$/.test(value)) { 26095 shortcut.keyCode = parseInt(value, 10); 26096 } else { 26097 shortcut.charCode = value.charCodeAt(0); 26098 shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0); 26099 } 26100 } 26101 }); 26102 26103 shortcuts[ 26104 (shortcut.ctrl ? 'ctrl' : '') + ',' + 26105 (shortcut.alt ? 'alt' : '') + ',' + 26106 (shortcut.shift ? 'shift' : '') + ',' + 26107 shortcut.keyCode 26108 ] = shortcut; 26109 }); 26110 26111 return true; 26112 }; 26113 }; 26114}); 26115 26116// Included from: js/tinymce/classes/Editor.js 26117 26118/** 26119 * Editor.js 26120 * 26121 * Copyright, Moxiecode Systems AB 26122 * Released under LGPL License. 26123 * 26124 * License: http://www.tinymce.com/license 26125 * Contributing: http://www.tinymce.com/contributing 26126 */ 26127 26128/*jshint scripturl:true */ 26129 26130/** 26131 * Include the base event class documentation. 26132 * 26133 * @include ../../../tools/docs/tinymce.Event.js 26134 */ 26135 26136/** 26137 * This class contains the core logic for a TinyMCE editor. 26138 * 26139 * @class tinymce.Editor 26140 * @mixes tinymce.util.Observable 26141 * @example 26142 * // Add a class to all paragraphs in the editor. 26143 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass'); 26144 * 26145 * // Gets the current editors selection as text 26146 * tinymce.activeEditor.selection.getContent({format: 'text'}); 26147 * 26148 * // Creates a new editor instance 26149 * var ed = new tinymce.Editor('textareaid', { 26150 * some_setting: 1 26151 * }, tinymce.EditorManager); 26152 * 26153 * // Select each item the user clicks on 26154 * ed.on('click', function(e) { 26155 * ed.selection.select(e.target); 26156 * }); 26157 * 26158 * ed.render(); 26159 */ 26160define("tinymce/Editor", [ 26161 "tinymce/dom/DOMUtils", 26162 "tinymce/dom/DomQuery", 26163 "tinymce/AddOnManager", 26164 "tinymce/NodeChange", 26165 "tinymce/html/Node", 26166 "tinymce/dom/Serializer", 26167 "tinymce/html/Serializer", 26168 "tinymce/dom/Selection", 26169 "tinymce/Formatter", 26170 "tinymce/UndoManager", 26171 "tinymce/EnterKey", 26172 "tinymce/ForceBlocks", 26173 "tinymce/EditorCommands", 26174 "tinymce/util/URI", 26175 "tinymce/dom/ScriptLoader", 26176 "tinymce/dom/EventUtils", 26177 "tinymce/WindowManager", 26178 "tinymce/html/Schema", 26179 "tinymce/html/DomParser", 26180 "tinymce/util/Quirks", 26181 "tinymce/Env", 26182 "tinymce/util/Tools", 26183 "tinymce/EditorObservable", 26184 "tinymce/Shortcuts" 26185], function( 26186 DOMUtils, DomQuery, AddOnManager, NodeChange, Node, DomSerializer, Serializer, 26187 Selection, Formatter, UndoManager, EnterKey, ForceBlocks, EditorCommands, 26188 URI, ScriptLoader, EventUtils, WindowManager, 26189 Schema, DomParser, Quirks, Env, Tools, EditorObservable, Shortcuts 26190) { 26191 // Shorten these names 26192 var DOM = DOMUtils.DOM, ThemeManager = AddOnManager.ThemeManager, PluginManager = AddOnManager.PluginManager; 26193 var extend = Tools.extend, each = Tools.each, explode = Tools.explode; 26194 var inArray = Tools.inArray, trim = Tools.trim, resolve = Tools.resolve; 26195 var Event = EventUtils.Event; 26196 var isGecko = Env.gecko, ie = Env.ie; 26197 26198 /** 26199 * Include documentation for all the events. 26200 * 26201 * @include ../../../tools/docs/tinymce.Editor.js 26202 */ 26203 26204 /** 26205 * Constructs a editor instance by id. 26206 * 26207 * @constructor 26208 * @method Editor 26209 * @param {String} id Unique id for the editor. 26210 * @param {Object} settings Settings for the editor. 26211 * @param {tinymce.EditorManager} editorManager EditorManager instance. 26212 * @author Moxiecode 26213 */ 26214 function Editor(id, settings, editorManager) { 26215 var self = this, documentBaseUrl, baseUri; 26216 26217 documentBaseUrl = self.documentBaseUrl = editorManager.documentBaseURL; 26218 baseUri = editorManager.baseURI; 26219 26220 /** 26221 * Name/value collection with editor settings. 26222 * 26223 * @property settings 26224 * @type Object 26225 * @example 26226 * // Get the value of the theme setting 26227 * tinymce.activeEditor.windowManager.alert("You are using the " + tinymce.activeEditor.settings.theme + " theme"); 26228 */ 26229 self.settings = settings = extend({ 26230 id: id, 26231 theme: 'modern', 26232 delta_width: 0, 26233 delta_height: 0, 26234 popup_css: '', 26235 plugins: '', 26236 document_base_url: documentBaseUrl, 26237 add_form_submit_trigger: true, 26238 submit_patch: true, 26239 add_unload_trigger: true, 26240 convert_urls: true, 26241 relative_urls: true, 26242 remove_script_host: true, 26243 object_resizing: true, 26244 doctype: '<!DOCTYPE html>', 26245 visual: true, 26246 font_size_style_values: 'xx-small,x-small,small,medium,large,x-large,xx-large', 26247 26248 // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size 26249 font_size_legacy_values: 'xx-small,small,medium,large,x-large,xx-large,300%', 26250 forced_root_block: 'p', 26251 hidden_input: true, 26252 padd_empty_editor: true, 26253 render_ui: true, 26254 indentation: '30px', 26255 inline_styles: true, 26256 convert_fonts_to_spans: true, 26257 indent: 'simple', 26258 indent_before: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,ol,li,dl,dt,dd,area,table,thead,' +
26259 'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist', 26260 indent_after: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,ol,li,dl,dt,dd,area,table,thead,' + 26261 'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist', 26262 validate: true, 26263 entity_encoding: 'named', 26264 url_converter: self.convertURL, 26265 url_converter_scope: self, 26266 ie7_compat: true 26267 }, settings); 26268 26269 AddOnManager.language = settings.language || 'en'; 26270 AddOnManager.languageLoad = settings.language_load; 26271 26272 AddOnManager.baseURL = editorManager.baseURL; 26273 26274 /** 26275 * Editor instance id, normally the same as the div/textarea that was replaced. 26276 * 26277 * @property id 26278 * @type String 26279 */ 26280 self.id = settings.id = id; 26281 26282 /** 26283 * State to force the editor to return false on a isDirty call. 26284 * 26285 * @property isNotDirty 26286 * @type Boolean 26287 * @example 26288 * function ajaxSave() { 26289 * var ed = tinymce.get('elm1'); 26290 * 26291 * // Save contents using some XHR call 26292 * alert(ed.getContent()); 26293 * 26294 * ed.isNotDirty = true; // Force not dirty state 26295 * } 26296 */ 26297 self.isNotDirty = true; 26298 26299 /** 26300 * Name/Value object containting plugin instances. 26301 * 26302 * @property plugins 26303 * @type Object 26304 * @example 26305 * // Execute a method inside a plugin directly 26306 * tinymce.activeEditor.plugins.someplugin.someMethod(); 26307 */ 26308 self.plugins = {}; 26309 26310 /** 26311 * URI object to document configured for the TinyMCE instance. 26312 * 26313 * @property documentBaseURI 26314 * @type tinymce.util.URI 26315 * @example 26316 * // Get relative URL from the location of document_base_url 26317 * tinymce.activeEditor.documentBaseURI.toRelative('/somedir/somefile.htm'); 26318 * 26319 * // Get absolute URL from the location of document_base_url 26320 * tinymce.activeEditor.documentBaseURI.toAbsolute('somefile.htm'); 26321 */ 26322 self.documentBaseURI = new URI(settings.document_base_url || documentBaseUrl, { 26323 base_uri: baseUri 26324 }); 26325 26326 /** 26327 * URI object to current document that holds the TinyMCE editor instance. 26328 * 26329 * @property baseURI 26330 * @type tinymce.util.URI 26331 * @example 26332 * // Get relative URL from the location of the API 26333 * tinymce.activeEditor.baseURI.toRelative('/somedir/somefile.htm'); 26334 * 26335 * // Get absolute URL from the location of the API 26336 * tinymce.activeEditor.baseURI.toAbsolute('somefile.htm'); 26337 */ 26338 self.baseURI = baseUri; 26339 26340 /** 26341 * Array with CSS files to load into the iframe. 26342 * 26343 * @property contentCSS 26344 * @type Array 26345 */ 26346 self.contentCSS = []; 26347 26348 /** 26349 * Array of CSS styles to add to head of document when the editor loads. 26350 * 26351 * @property contentStyles 26352 * @type Array 26353 */ 26354 self.contentStyles = []; 26355 26356 // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic 26357 self.shortcuts = new Shortcuts(self); 26358 26359 // Internal command handler objects 26360 self.execCommands = {}; 26361 self.queryStateCommands = {}; 26362 self.queryValueCommands = {}; 26363 self.loadedCSS = {}; 26364 26365 if (settings.target) { 26366 self.targetElm = settings.target; 26367 } 26368 26369 self.suffix = editorManager.suffix; 26370 self.editorManager = editorManager; 26371 self.inline = settings.inline; 26372 26373 if (settings.cache_suffix) { 26374 Env.cacheSuffix = settings.cache_suffix.replace(/^[\?\&]+/, ''); 26375 } 26376 26377 // Call setup 26378 editorManager.fire('SetupEditor', self); 26379 self.execCallback('setup', self); 26380 26381 /** 26382 * Dom query instance with default scope to the editor document and default element is the body of the editor. 26383 * 26384 * @property $ 26385 * @type tinymce.dom.DomQuery 26386 * @example 26387 * tinymce.activeEditor.$('p').css('color', 'red'); 26388 * tinymce.activeEditor.$().append('<p>new</p>'); 26389 */ 26390 self.$ = DomQuery.overrideDefaults(function() { 26391 return { 26392 context: self.inline ? self.getBody() : self.getDoc(), 26393 element: self.getBody() 26394 }; 26395 }); 26396 } 26397 26398 Editor.prototype = { 26399 /** 26400 * Renderes the editor/adds it to the page. 26401 * 26402 * @method render 26403 */ 26404 render: function() { 26405 var self = this, settings = self.settings, id = self.id, suffix = self.suffix; 26406 26407 function readyHandler() { 26408 DOM.unbind(window, 'ready', readyHandler); 26409 self.render(); 26410 } 26411 26412 // Page is not loaded yet, wait for it 26413 if (!Event.domLoaded) { 26414 DOM.bind(window, 'ready', readyHandler); 26415 return; 26416 } 26417 26418 // Element not found, then skip initialization 26419 if (!self.getElement()) { 26420 return; 26421 } 26422 26423 // No editable support old iOS versions etc 26424 if (!Env.contentEditable) { 26425 return; 26426 } 26427 26428 // Hide target element early to prevent content flashing 26429 if (!settings.inline) { 26430 self.orgVisibility = self.getElement().style.visibility; 26431 self.getElement().style.visibility = 'hidden'; 26432 } else { 26433 self.inline = true; 26434 } 26435 26436 var form = self.getElement().form || DOM.getParent(id, 'form'); 26437 if (form) { 26438 self.formElement = form; 26439 26440 // Add hidden input for non input elements inside form elements 26441 if (settings.hidden_input && !/TEXTAREA|INPUT/i.test(self.getElement().nodeName)) { 26442 DOM.insertAfter(DOM.create('input', {type: 'hidden', name: id}), id); 26443 self.hasHiddenInput = true; 26444 } 26445 26446 // Pass submit/reset from form to editor instance 26447 self.formEventDelegate = function(e) { 26448 self.fire(e.type, e); 26449 }; 26450 26451 DOM.bind(form, 'submit reset', self.formEventDelegate); 26452 26453 // Reset contents in editor when the form is reset 26454 self.on('reset', function() { 26455 self.setContent(self.startContent, {format: 'raw'}); 26456 }); 26457 26458 // Check page uses id="submit" or name="submit" for it's submit button 26459 if (settings.submit_patch && !form.submit.nodeType && !form.submit.length && !form._mceOldSubmit) { 26460 form._mceOldSubmit = form.submit; 26461 form.submit = function() { 26462 self.editorManager.triggerSave(); 26463 self.isNotDirty = true; 26464 26465 return form._mceOldSubmit(form); 26466 }; 26467 } 26468 } 26469 26470 /** 26471 * Window manager reference, use this to open new windows and dialogs. 26472 * 26473 * @property windowManager 26474 * @type tinymce.WindowManager 26475 * @example 26476 * // Shows an alert message 26477 * tinymce.activeEditor.windowManager.alert('Hello world!'); 26478 * 26479 * // Opens a new dialog with the file.htm file and the size 320x240 26480 * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog. 26481 * tinymce.activeEditor.windowManager.open({ 26482 * url: 'file.htm', 26483 * width: 320, 26484 * height: 240 26485 * }, { 26486 * custom_param: 1 26487 * }); 26488 */ 26489 self.windowManager = new WindowManager(self); 26490 26491 if (settings.encoding == 'xml') { 26492 self.on('GetContent', function(e) { 26493 if (e.save) { 26494 e.content = DOM.encode(e.content); 26495 } 26496 }); 26497 } 26498 26499 if (settings.add_form_submit_trigger) { 26500 self.on('submit', function() { 26501 if (self.initialized) { 26502 self.save(); 26503 } 26504 }); 26505 } 26506 26507 if (settings.add_unload_trigger) { 26508 self._beforeUnload = function() { 26509 if (self.initialized && !self.destroyed && !self.isHidden()) { 26510 self.save({format: 'raw', no_events: true, set_dirty: false}); 26511 } 26512 }; 26513 26514 self.editorManager.on('BeforeUnload', self._beforeUnload); 26515 } 26516 26517 // Load scripts 26518 function loadScripts() { 26519 var scriptLoader = ScriptLoader.ScriptLoader; 26520
26521 if (settings.language && settings.language != 'en' && !settings.language_url) { 26522 settings.language_url = self.editorManager.baseURL + '/langs/' + settings.language + '.js'; 26523 } 26524 26525 if (settings.language_url) { 26526 scriptLoader.add(settings.language_url); 26527 } 26528 26529 if (settings.theme && typeof settings.theme != "function" && 26530 settings.theme.charAt(0) != '-' && !ThemeManager.urls[settings.theme]) { 26531 var themeUrl = settings.theme_url; 26532 26533 if (themeUrl) { 26534 themeUrl = self.documentBaseURI.toAbsolute(themeUrl); 26535 } else { 26536 themeUrl = 'themes/' + settings.theme + '/theme' + suffix + '.js'; 26537 } 26538 26539 ThemeManager.load(settings.theme, themeUrl); 26540 } 26541 26542 if (Tools.isArray(settings.plugins)) { 26543 settings.plugins = settings.plugins.join(' '); 26544 } 26545 26546 each(settings.external_plugins, function(url, name) { 26547 PluginManager.load(name, url); 26548 settings.plugins += ' ' + name; 26549 }); 26550 26551 each(settings.plugins.split(/[ ,]/), function(plugin) { 26552 plugin = trim(plugin); 26553 26554 if (plugin && !PluginManager.urls[plugin]) { 26555 if (plugin.charAt(0) == '-') { 26556 plugin = plugin.substr(1, plugin.length); 26557 26558 var dependencies = PluginManager.dependencies(plugin); 26559 26560 each(dependencies, function(dep) { 26561 var defaultSettings = { 26562 prefix: 'plugins/', 26563 resource: dep, 26564 suffix: '/plugin' + suffix + '.js' 26565 }; 26566 26567 dep = PluginManager.createUrl(defaultSettings, dep); 26568 PluginManager.load(dep.resource, dep); 26569 }); 26570 } else { 26571 PluginManager.load(plugin, { 26572 prefix: 'plugins/', 26573 resource: plugin, 26574 suffix: '/plugin' + suffix + '.js' 26575 }); 26576 } 26577 } 26578 }); 26579 26580 scriptLoader.loadQueue(function() { 26581 if (!self.removed) { 26582 self.init(); 26583 } 26584 }); 26585 } 26586 26587 loadScripts(); 26588 }, 26589 26590 /** 26591 * Initializes the editor this will be called automatically when 26592 * all plugins/themes and language packs are loaded by the rendered method. 26593 * This method will setup the iframe and create the theme and plugin instances. 26594 * 26595 * @method init 26596 */ 26597 init: function() { 26598 var self = this, settings = self.settings, elm = self.getElement(); 26599 var w, h, minHeight, n, o, Theme, url, bodyId, bodyClass, re, i, initializedPlugins = []; 26600 26601 self.rtl = this.editorManager.i18n.rtl; 26602 self.editorManager.add(self); 26603 26604 settings.aria_label = settings.aria_label || DOM.getAttrib(elm, 'aria-label', self.getLang('aria.rich_text_area')); 26605 26606 /** 26607 * Reference to the theme instance that was used to generate the UI. 26608 * 26609 * @property theme 26610 * @type tinymce.Theme 26611 * @example 26612 * // Executes a method on the theme directly 26613 * tinymce.activeEditor.theme.someMethod(); 26614 */ 26615 if (settings.theme) { 26616 if (typeof settings.theme != "function") { 26617 settings.theme = settings.theme.replace(/-/, ''); 26618 Theme = ThemeManager.get(settings.theme); 26619 self.theme = new Theme(self, ThemeManager.urls[settings.theme]); 26620 26621 if (self.theme.init) { 26622 self.theme.init(self, ThemeManager.urls[settings.theme] || self.documentBaseUrl.replace(/\/$/, ''), self.$); 26623 } 26624 } else { 26625 self.theme = settings.theme; 26626 } 26627 } 26628 26629 function initPlugin(plugin) { 26630 var Plugin = PluginManager.get(plugin), pluginUrl, pluginInstance; 26631 26632 pluginUrl = PluginManager.urls[plugin] || self.documentBaseUrl.replace(/\/$/, ''); 26633 plugin = trim(plugin); 26634 if (Plugin && inArray(initializedPlugins, plugin) === -1) { 26635 each(PluginManager.dependencies(plugin), function(dep) { 26636 initPlugin(dep); 26637 }); 26638 26639 pluginInstance = new Plugin(self, pluginUrl, self.$); 26640 26641 self.plugins[plugin] = pluginInstance; 26642 26643 if (pluginInstance.init) { 26644 pluginInstance.init(self, pluginUrl); 26645 initializedPlugins.push(plugin); 26646 } 26647 } 26648 } 26649 26650 // Create all plugins 26651 each(settings.plugins.replace(/\-/g, '').split(/[ ,]/), initPlugin); 26652 26653 // Measure box 26654 if (settings.render_ui && self.theme) { 26655 self.orgDisplay = elm.style.display; 26656 26657 if (typeof settings.theme != "function") { 26658 w = settings.width || elm.style.width || elm.offsetWidth; 26659 h = settings.height || elm.style.height || elm.offsetHeight; 26660 minHeight = settings.min_height || 100; 26661 re = /^[0-9\.]+(|px)$/i; 26662 26663 if (re.test('' + w)) { 26664 w = Math.max(parseInt(w, 10), 100); 26665 } 26666 26667 if (re.test('' + h)) { 26668 h = Math.max(parseInt(h, 10), minHeight); 26669 } 26670 26671 // Render UI 26672 o = self.theme.renderUI({ 26673 targetNode: elm, 26674 width: w, 26675 height: h, 26676 deltaWidth: settings.delta_width, 26677 deltaHeight: settings.delta_height 26678 }); 26679 26680 // Resize editor 26681 if (!settings.content_editable) { 26682 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0
26682) : ''); 26683 if (h < minHeight) { 26684 h = minHeight; 26685 } 26686 } 26687 } else { 26688 o = settings.theme(self, elm); 26689 26690 // Convert element type to id:s 26691 if (o.editorContainer.nodeType) { 26692 o.editorContainer = o.editorContainer.id = o.editorContainer.id || self.id + "_parent"; 26693 } 26694 26695 // Convert element type to id:s 26696 if (o.iframeContainer.nodeType) { 26697 o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || self.id + "_iframecontainer"; 26698 } 26699 26700 // Use specified iframe height or the targets offsetHeight 26701 h = o.iframeHeight || elm.offsetHeight; 26702 } 26703 26704 self.editorContainer = o.editorContainer; 26705 } 26706 26707 // Load specified content CSS last 26708 if (settings.content_css) { 26709 each(explode(settings.content_css), function(u) { 26710 self.contentCSS.push(self.documentBaseURI.toAbsolute(u)); 26711 }); 26712 } 26713 26714 // Load specified content CSS last 26715 if (settings.content_style) { 26716 self.contentStyles.push(settings.content_style); 26717 } 26718 26719 // Content editable mode ends here 26720 if (settings.content_editable) { 26721 elm = n = o = null; // Fix IE leak 26722 return self.initContentBody(); 26723 } 26724 26725 self.iframeHTML = settings.doctype + '<html><head>'; 26726 26727 // We only need to override paths if we have to 26728 // IE has a bug where it remove site absolute urls to relative ones if this is specified 26729 if (settings.document_base_url != self.documentBaseUrl) { 26730 self.iframeHTML += '<base href="' + self.documentBaseURI.getURI() + '" />'; 26731 } 26732 26733 // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode. 26734 if (!Env.caretAfter && settings.ie7_compat) { 26735 self.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />'; 26736 } 26737 26738 self.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />'; 26739 26740 // Load the CSS by injecting them into the HTML this will reduce "flicker" 26741 for (i = 0; i < self.contentCSS.length; i++) { 26742 var cssUrl = self.contentCSS[i]; 26743 self.iframeHTML += ( 26744 '<link type="text/css" ' + 26745 'rel="stylesheet" ' + 26746 'href="' + Tools._addCacheSuffix(cssUrl) + '" />' 26747 ); 26748 self.loadedCSS[cssUrl] = true; 26749 } 26750 26751 bodyId = settings.body_id || 'tinymce'; 26752 if (bodyId.indexOf('=') != -1) { 26753 bodyId = self.getParam('body_id', '', 'hash'); 26754 bodyId = bodyId[self.id] || bodyId; 26755 } 26756 26757 bodyClass = settings.body_class || ''; 26758 if (bodyClass.indexOf('=') != -1) { 26759 bodyClass = self.getParam('body_class', '', 'hash'); 26760 bodyClass = bodyClass[self.id] || ''; 26761 } 26762 26763 if (settings.content_security_policy) { 26764 self.iframeHTML += '<meta http-equiv="Content-Security-Policy" content="' + settings.content_security_policy + '" />'; 26765 } 26766 26767 self.iframeHTML += '</head><body id="' + bodyId + 26768 '" class="mce-content-body ' + bodyClass + 26769 '" data-id="' + self.id + '"><br></body></html>'; 26770 26771 /*eslint no-script-url:0 */ 26772 var domainRelaxUrl = 'javascript:(function(){' + 26773 'document.open();document.domain="' + document.domain + '";' + 26774 'var ed = window.parent.tinymce.get("' + self.id + '");document.write(ed.iframeHTML);' + 26775 'document.close();ed.initContentBody(true);})()'; 26776 26777 // Domain relaxing is required since the user has messed around with document.domain 26778 if (document.domain != location.hostname) { 26779 url = domainRelaxUrl; 26780 } 26781 26782 // Create iframe 26783 // TODO: ACC add the appropriate description on this. 26784 var ifr = DOM.create('iframe', { 26785 id: self.id + "_ifr", 26786 //src: url || 'javascript:""', // Workaround for HTTPS warning in IE6/7 26787 frameBorder: '0', 26788 allowTransparency: "true", 26789 title: self.editorManager.translate( 26790 "Rich Text Area. Press ALT-F9 for menu. " + 26791 "Press ALT-F10 for toolbar. Press ALT-0 for help" 26792 ), 26793 style: { 26794 width: '100%', 26795 height: h, 26796 display: 'block' // Important for Gecko to render the iframe correctly 26797 } 26798 }); 26799 26800 ifr.onload = function() { 26801 ifr.onload = null; 26802 self.fire("load"); 26803 }; 26804 26805 DOM.setAttrib(ifr, "src", url || 'javascript:""'); 26806 26807 self.contentAreaContainer = o.iframeContainer; 26808 self.iframeElement = ifr; 26809
26810 n = DOM.add(o.iframeContainer, ifr); 26811 26812 // Try accessing the document this will fail on IE when document.domain is set to the same as location.hostname 26813 // Then we have to force domain relaxing using the domainRelaxUrl approach very ugly!! 26814 if (ie) { 26815 try { 26816 self.getDoc(); 26817 } catch (e) { 26818 n.src = url = domainRelaxUrl; 26819 } 26820 } 26821 26822 if (o.editorContainer) { 26823 DOM.get(o.editorContainer).style.display = self.orgDisplay; 26824 self.hidden = DOM.isHidden(o.editorContainer); 26825 } 26826 26827 self.getElement().style.display = 'none'; 26828 DOM.setAttrib(self.id, 'aria-hidden', true); 26829 26830 if (!url) { 26831 self.initContentBody(); 26832 } 26833 26834 elm = n = o = null; // Cleanup 26835 }, 26836 26837 /** 26838 * This method get called by the init method ones the iframe is loaded. 26839 * It will fill the iframe with contents, setups DOM and selection objects for the iframe. 26840 * 26841 * @method initContentBody 26842 * @private 26843 */ 26844 initContentBody: function(skipWrite) { 26845 var self = this, settings = self.settings, targetElm = self.getElement(), doc = self.getDoc(), body, contentCssText; 26846 26847 // Restore visibility on target element 26848 if (!settings.inline) { 26849 self.getElement().style.visibility = self.orgVisibility; 26850 } 26851 26852 // Setup iframe body 26853 if (!skipWrite && !settings.content_editable) { 26854 doc.open(); 26855 doc.write(self.iframeHTML); 26856 doc.close(); 26857 } 26858 26859 if (settings.content_editable) { 26860 self.on('remove', function() { 26861 var bodyEl = this.getBody(); 26862 26863 DOM.removeClass(bodyEl, 'mce-content-body'); 26864 DOM.removeClass(bodyEl, 'mce-edit-focus'); 26865 DOM.setAttrib(bodyEl, 'contentEditable', null); 26866 }); 26867 26868 DOM.addClass(targetElm, 'mce-content-body'); 26869 self.contentDocument = doc = settings.content_document || document; 26870 self.contentWindow = settings.content_window || window; 26871 self.bodyElement = targetElm; 26872 26873 // Prevent leak in IE 26874 settings.content_document = settings.content_window = null; 26875 26876 // TODO: Fix this 26877 settings.root_name = targetElm.nodeName.toLowerCase(); 26878 } 26879 26880 // It will not steal focus while setting contentEditable 26881 body = self.getBody(); 26882 body.disabled = true; 26883 26884 if (!settings.readonly) { 26885 if (self.inline && DOM.getStyle(body, 'position', true) == 'static') { 26886 body.style.position = 'relative'; 26887 } 26888 26889 body.contentEditable = self.getParam('content_editable_state', true); 26890 } 26891 26892 body.disabled = false; 26893 26894 /** 26895 * Schema instance, enables you to validate elements and it's children. 26896 * 26897 * @property schema 26898 * @type tinymce.html.Schema 26899 */ 26900 self.schema = new Schema(settings); 26901 26902 /** 26903 * DOM instance for the editor. 26904 * 26905 * @property dom 26906 * @type tinymce.dom.DOMUtils 26907 * @example 26908 * // Adds a class to all paragraphs within the editor 26909 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass'); 26910 */ 26911 self.dom = new DOMUtils(doc, { 26912 keep_values: true, 26913 url_converter: self.convertURL, 26914 url_converter_scope: self, 26915 hex_colors: settings.force_hex_style_colors, 26916 class_filter: settings.class_filter, 26917 update_styles: true, 26918 root_element: self.inline ? self.getBody() : null, 26919 collect: settings.content_editable, 26920 schema: self.schema, 26921 onSetAttrib: function(e) { 26922 self.fire('SetAttrib', e); 26923 } 26924 }); 26925 26926 /** 26927 * HTML parser will be used when contents is inserted into the editor. 26928 * 26929 * @property parser 26930 * @type tinymce.html.DomParser 26931 */ 26932 self.parser = new DomParser(settings, self.schema); 26933 26934 // Convert src and href into data-mce-src, data-mce-href and data-mce-style 26935 self.parser.addAttributeFilter('src,href,style,tabindex', function(nodes, name) { 26936 var i = nodes.length, node, dom = self.dom, value, internalName; 26937 26938 while (i--) { 26939 node = nodes[i]; 26940 value = node.attr(name); 26941 internalName = 'data-mce-' + name; 26942 26943 // Add internal attribute if we need to we don't on a refresh of the document 26944 if (!node.attributes.map[internalName]) { 26945 if (name === "style") { 26946 value = dom.serializeStyle(dom.parseStyle(value), node.name); 26947 26948 if (!value.length) { 26949 value = null; 26950 } 26951 26952 node.attr(internalName, value); 26953 node.attr(name, value); 26954 } else if (name === "tabindex") { 26955 node.attr(internalName, value); 26956 node.attr(name, null); 26957 } else { 26958 node.attr(internalName, self.convertURL(value, name, node.name)); 26959 } 26960 } 26961 } 26962 }); 26963 26964 // Keep scripts from executing 26965 self.parser.addNodeFilter('script', function(nodes) { 26966 var i = nodes.length, node; 26967 26968 while (i--) { 26969 node = nodes[i]; 26970 node.attr('type', 'mce-' + (node.attr('type') || 'no/type')); 26971 } 26972 }); 26973 26974 self.parser.addNodeFilter('#cdata', function(nodes) { 26975 var i = nodes.length, node; 26976 26977 while (i--) { 26978 node = nodes[i]; 26979 node.type = 8; 26980 node.name = '#comment'; 26981 node.value = '[CDATA[' + node.value + ']]'; 26982 } 26983 }); 26984 26985 self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes) { 26986 var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements(); 26987 26988 while (i--) { 26989 node = nodes[i]; 26990 26991 if (node.isEmpty(nonEmptyElements)) { 26992 node.append(new Node('br', 1)).shortEnded = true; 26993 } 26994 } 26995 }); 26996 26997 /** 26998 * DOM serializer for the editor. Will be used when contents is extracted from the editor. 26999 * 27000 * @property serializer 27001 * @type tinymce.dom.Serializer 27002 * @example 27003 * // Serializes the first paragraph in the editor into a string 27004 * tinymce.activeEditor.serializer.serialize(tinymce.activeEditor.dom.select('p')[0]); 27005 */ 27006 self.serializer = new DomSerializer(settings, self); 27007 27008 /** 27009 * Selection instance for the editor. 27010 * 27011 * @property selection 27012 * @type tinymce.dom.Selection 27013 * @example 27014 * // Sets some contents to the current selection in the editor
27015 * tinymce.activeEditor.selection.setContent('Some contents'); 27016 * 27017 * // Gets the current selection 27018 * alert(tinymce.activeEditor.selection.getContent()); 27019 * 27020 * // Selects the first paragraph found 27021 * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]); 27022 */ 27023 self.selection = new Selection(self.dom, self.getWin(), self.serializer, self); 27024 27025 /** 27026 * Formatter instance. 27027 * 27028 * @property formatter 27029 * @type tinymce.Formatter 27030 */ 27031 self.formatter = new Formatter(self); 27032 27033 /** 27034 * Undo manager instance, responsible for handling undo levels. 27035 * 27036 * @property undoManager 27037 * @type tinymce.UndoManager 27038 * @example 27039 * // Undoes the last modification to the editor 27040 * tinymce.activeEditor.undoManager.undo(); 27041 */ 27042 self.undoManager = new UndoManager(self); 27043 27044 self.forceBlocks = new ForceBlocks(self); 27045 self.enterKey = new EnterKey(self); 27046 self.editorCommands = new EditorCommands(self); 27047 self._nodeChangeDispatcher = new NodeChange(self); 27048 27049 self.fire('PreInit'); 27050 27051 if (!settings.browser_spellcheck && !settings.gecko_spellcheck) { 27052 doc.body.spellcheck = false; // Gecko 27053 DOM.setAttrib(body, "spellcheck", "false"); 27054 } 27055 27056 self.fire('PostRender'); 27057 27058 self.quirks = new Quirks(self); 27059 27060 if (settings.directionality) { 27061 body.dir = settings.directionality; 27062 } 27063 27064 if (settings.nowrap) { 27065 body.style.whiteSpace = "nowrap"; 27066 } 27067 27068 if (settings.protect) { 27069 self.on('BeforeSetContent', function(e) { 27070 each(settings.protect, function(pattern) { 27071 e.content = e.content.replace(pattern, function(str) { 27072 return '<!--mce:protected ' + escape(str) + '-->'; 27073 }); 27074 }); 27075 }); 27076 } 27077 27078 self.on('SetContent', function() { 27079 self.addVisual(self.getBody()); 27080 }); 27081 27082 // Remove empty contents 27083 if (settings.padd_empty_editor) { 27084 self.on('PostProcess', function(e) { 27085 e.content = e.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, ''); 27086 }); 27087 } 27088 27089 self.load({initial: true, format: 'html'}); 27090 self.startContent = self.getContent({format: 'raw'}); 27091 27092 /** 27093 * Is set to true after the editor instance has been initialized 27094 * 27095 * @property initialized 27096 * @type Boolean 27097 * @example 27098 * function isEditorInitialized(editor) { 27099 * return editor && editor.initialized; 27100 * } 27101 */ 27102 self.initialized = true; 27103 self.bindPendingEventDelegates(); 27104 27105 self.fire('init'); 27106 self.focus(true); 27107 self.nodeChanged({initial: true}); 27108 self.execCallback('init_instance_callback', self); 27109 27110 // Add editor specific CSS styles 27111 if (self.contentStyles.length > 0) { 27112 contentCssText = ''; 27113 27114 each(self.contentStyles, function(style) { 27115 contentCssText += style + "\r\n"; 27116 }); 27117 27118 self.dom.addStyle(contentCssText); 27119 } 27120 27121 // Load specified content CSS last 27122 each(self.contentCSS, function(cssUrl) { 27123 if (!self.loadedCSS[cssUrl]) { 27124 self.dom.loadCSS(cssUrl); 27125 self.loadedCSS[cssUrl] = true; 27126 } 27127 }); 27128 27129 // Handle auto focus 27130 if (settings.auto_focus) { 27131 setTimeout(function() { 27132 var editor; 27133 27134 if (settings.auto_focus === true) { 27135 editor = self; 27136 } else { 27137 editor = self.editorManager.get(settings.auto_focus); 27138 } 27139 27140 editor.focus(); 27141 }, 100); 27142 } 27143 27144 // Clean up references for IE 27145 targetElm = doc = body = null; 27146 }, 27147 27148 /** 27149 * Focuses/activates the editor. This will set this editor as the activeEditor in the tinymce collection 27150 * it will also place DOM focus inside the editor. 27151 * 27152 * @method focus 27153 * @param {Boolean} skipFocus Skip DOM focus. Just set is as the active editor. 27154 */ 27155 focus: function(skipFocus) { 27156 var self = this, selection = self.selection, contentEditable = self.settings.content_editable, rng; 27157 var controlElm, doc = self.getDoc(), body; 27158 27159 if (!skipFocus) { 27160 // Get selected control element 27161 rng = selection.getRng(); 27162 if (rng.item) { 27163 controlElm = rng.item(0); 27164 } 27165 27166 self._refreshContentEditable(); 27167 27168 // Focus the window iframe 27169 if (!contentEditable) { 27170 // WebKit needs this call to fire focusin event properly see #5948 27171 // But Opera pre Blink engine will produce an empty selection so skip Opera 27172 if (!Env.opera) { 27173 self.getBody().focus(); 27174 } 27175 27176 self.getWin().focus(); 27177 } 27178 27179 // Focus the body as well since it's contentEditable 27180 if (isGecko || contentEditable) { 27181 body = self.getBody(); 27182 27183 // Check for setActive since it doesn't scroll to the element 27184 if (body.setActive) { 27185 // IE 11 sometimes throws "Invalid function" then fallback to focus 27186 try { 27187 body.setActive(); 27188 } catch (ex) { 27189 body.focus(); 27190 } 27191 } else { 27192 body.focus(); 27193 } 27194 27195 if (contentEditable) { 27196 selection.normalize(); 27197 } 27198 } 27199 27200 // Restore selected control element 27201 // This is needed when for example an image is selected within a 27202 // layer a call to focus will then remove the control selection 27203 if (controlElm && controlElm.ownerDocument == doc) { 27204 rng = doc.body.createControlRange(); 27205 rng.addElement(controlElm); 27206 rng.select(); 27207 } 27208 } 27209 27210 self.editorManager.setActive(self); 27211 }, 27212 27213 /** 27214 * Executes a legacy callback. This method is useful to call old 2.x option callbacks. 27215 * There new event model is a better way to add callback so this method might be removed in the future. 27216 * 27217 * @method execCallback 27218 * @param {String} name Name of the callback to execute. 27219 * @return {Object} Return value passed from callback function. 27220 */ 27221 execCallback: function(name) { 27222 var self = this, callback = self.settings[name], scope; 27223 27224 if (!callback) { 27225 return; 27226 } 27227 27228 // Look through lookup 27229 if (self.callbackLookup && (scope = self.callbackLookup[name])) { 27230 callback = scope.func; 27231 scope = scope.scope; 27232 } 27233 27234 if (typeof(callback) === 'string') { 27235 scope = callback.replace(/\.\w+$/, ''); 27236 scope = scope ? resolve(scope) : 0; 27237 callback = resolve(callback); 27238 self.callbackLookup = self.callbackLookup || {}; 27239 self.callbackLookup[name] = {func: callback, scope: scope}; 27240 } 27241 27242 return callback.apply(scope || self, Array.prototype.slice.call(arguments, 1)); 27243 }, 27244 27245 /** 27246 * Translates the specified string by replacing variables with language pack items it will also check if there is 27247 * a key mathcin the input. 27248 * 27249 * @method translate 27250 * @param {String} text String to translate by the language pack data. 27251 * @return {String} Translated string. 27252 */ 27253 translate: function(text) { 27254 var lang = this.settings.language || 'en', i18n = this.editorManager.i18n; 27255 27256 if (!text) { 27257 return ''; 27258 } 27259 27260 return i18n.data[lang + '.' + text] || text.replace(/\{\#([^\}]+)\}/g, function(a, b) { 27261 return i18n.data[lang + '.' + b] || '{#' + b + '}'; 27262 }); 27263 }, 27264 27265 /** 27266 * Returns a language pack item by name/key. 27267 * 27268 * @method getLang 27269 * @param {String} name Name/key to get from the language pack. 27270 * @param {String} defaultVal Optional default value to retrive. 27271 */ 27272 getLang: function(name, defaultVal) { 27273 return ( 27274 this.editorManager.i18n.data[(this.settings.language || 'en') + '.' + name] || 27275 (defaultVal !== undefined ? defaultVal : '{#' + name + '}') 27276 ); 27277 }, 27278 27279 /** 27280 * Returns a configuration parameter by name. 27281 * 27282 * @method getParam 27283 * @param {String} name Configruation parameter to retrive. 27284 * @param {String} defaultVal Optional default value to return. 27285 * @param {String} type Optional type parameter. 27286 * @return {String} Configuration parameter value or default value. 27287 * @example 27288 * // Returns a specific config value from the currently active editor 27289 * var someval = tinymce.activeEditor.getParam('myvalue'); 27290 * 27291 * // Returns a specific config value from a specific editor instance by id 27292 * var someval2 = tinymce.get('my_editor').getParam('myvalue'); 27293 */ 27294 getParam: function(name, defaultVal, type) { 27295 var value = name in this.settings ? this.settings[name] : defaultVal, output; 27296 27297 if (type === 'hash') { 27298 output = {}; 27299 27300 if (typeof(value) === 'string') { 27301 each(value.indexOf('=') > 0 ? value.split(/[;,](?![^=;,]*(?:[;,]|$))/) : value.split(','), function(value) { 27302 value = value.split('='); 27303 27304 if (value.length > 1) { 27305 output[trim(value[0])] = trim(value[1]); 27306 } else { 27307 output[trim(value[0])] = trim(value); 27308 } 27309 }); 27310 } else { 27311 output = value; 27312 } 27313 27314 return output; 27315 } 27316 27317 return value; 27318 }, 27319 27320 /** 27321 * Distpaches out a onNodeChange event to all observers. This method should be called when you 27322 * need to update the UI states or element path etc. 27323 * 27324 * @method nodeChanged 27325 * @param {Object} args Optional args to pass to NodeChange event handlers. 27326 */ 27327 nodeChanged: function(args) { 27328 this._nodeChangeDispatcher.nodeChanged(args); 27329 }, 27330 27331 /** 27332 * Adds a button that later gets created by the theme in the editors toolbars. 27333 * 27334 * @method addButton 27335 * @param {String} name Button name to add. 27336 * @param {Object} settings Settings object with title, cmd etc. 27337 * @example 27338 * // Adds a custom button to the editor that inserts contents when clicked 27339 * tinymce.init({ 27340 * ... 27341 * 27342 * toolbar: 'example' 27343 * 27344 * setup: function(ed) { 27345 * ed.addButton('example', { 27346 * title: 'My title', 27347 * image: '../js/tinymce/plugins/example/img/example.gif', 27348 * onclick: function() { 27349 * ed.insertContent('Hello world!!'); 27350 * } 27351 * }); 27352 * } 27353 * }); 27354 */ 27355 addButton: function(name, settings) { 27356 var self = this; 27357 27358 if (settings.cmd) { 27359 settings.onclick = function() { 27360 self.execCommand(settings.cmd); 27361 }; 27362 } 27363
27364 if (!settings.text && !settings.icon) { 27365 settings.icon = name; 27366 } 27367 27368 self.buttons = self.buttons || {}; 27369 settings.tooltip = settings.tooltip || settings.title; 27370 self.buttons[name] = settings; 27371 }, 27372 27373 /** 27374 * Adds a menu item to be used in the menus of the theme. There might be multiple instances 27375 * of this menu item for example it might be used in the main menus of the theme but also in 27376 * the context menu so make sure that it's self contained and supports multiple instances. 27377 * 27378 * @method addMenuItem 27379 * @param {String} name Menu item name to add. 27380 * @param {Object} settings Settings object with title, cmd etc. 27381 * @example 27382 * // Adds a custom menu item to the editor that inserts contents when clicked 27383 * // The context option allows you to add the menu item to an existing default menu 27384 * tinymce.init({ 27385 * ... 27386 * 27387 * setup: function(ed) { 27388 * ed.addMenuItem('example', { 27389 * text: 'My menu item', 27390 * context: 'tools', 27391 * onclick: function() { 27392 * ed.insertContent('Hello world!!'); 27393 * } 27394 * }); 27395 * } 27396 * }); 27397 */ 27398 addMenuItem: function(name, settings) { 27399 var self = this; 27400 27401 if (settings.cmd) { 27402 settings.onclick = function() { 27403 self.execCommand(settings.cmd); 27404 }; 27405 } 27406 27407 self.menuItems = self.menuItems || {}; 27408 self.menuItems[name] = settings; 27409 }, 27410 27411 /** 27412 * Adds a custom command to the editor, you can also override existing commands with this method. 27413 * The command that you add can be executed with execCommand. 27414 * 27415 * @method addCommand 27416 * @param {String} name Command name to add/override. 27417 * @param {addCommandCallback} callback Function to execute when the command occurs. 27418 * @param {Object} scope Optional scope to execute the function in. 27419 * @example 27420 * // Adds a custom command that later can be executed using execCommand 27421 * tinymce.init({ 27422 * ... 27423 * 27424 * setup: function(ed) { 27425 * // Register example command 27426 * ed.addCommand('mycommand', function(ui, v) { 27427 * ed.windowManager.alert('Hello world!! Selection: ' + ed.selection.getContent({format: 'text'})); 27428 * }); 27429 * } 27430 * }); 27431 */ 27432 addCommand: function(name, callback, scope) { 27433 /** 27434 * Callback function that gets called when a command is executed. 27435 * 27436 * @callback addCommandCallback 27437 * @param {Boolean} ui Display UI state true/false. 27438 * @param {Object} value Optional value for command. 27439 * @return {Boolean} True/false state if the command was handled or not. 27440 */ 27441 this.execCommands[name] = {func: callback, scope: scope || this}; 27442 }, 27443 27444 /** 27445 * Adds a custom query state command to the editor, you can also override existing commands with this method. 27446 * The command that you add can be executed with queryCommandState function. 27447 * 27448 * @method addQueryStateHandler 27449 * @param {String} name Command name to add/override. 27450 * @param {addQueryStateHandlerCallback} callback Function to execute when the command state retrival occurs. 27451 * @param {Object} scope Optional scope to execute the function in. 27452 */ 27453 addQueryStateHandler: function(name, callback, scope) { 27454 /** 27455 * Callback function that gets called when a queryCommandState is executed. 27456 * 27457 * @callback addQueryStateHandlerCallback 27458 * @return {Boolean} True/false state if the command is enabled or not like is it bold. 27459 */ 27460 this.queryStateCommands[name] = {func: callback, scope: scope || this}; 27461 }, 27462 27463 /** 27464 * Adds a custom query value command to the editor, you can also override existing commands with this method. 27465 * The command that you add can be executed with queryCommandValue function. 27466 * 27467 * @method addQueryValueHandler 27468 * @param {String} name Command name to add/override. 27469 * @param {addQueryValueHandlerCallback} callback Function to execute when the command value retrival occurs. 27470 * @param {Object} scope Optional scope to execute the function in. 27471 */ 27472 addQueryValueHandler: function(name, callback, scope) { 27473 /** 27474 * Callback function that gets called when a queryCommandValue is executed. 27475 * 27476 * @callback addQueryValueHandlerCallback 27477 * @return {Object} Value of the command or undefined. 27478 */ 27479 this.queryValueCommands[name] = {func: callback, scope: scope || this}; 27480 }, 27481 27482 /** 27483 * Adds a keyboard shortcut for some command or function. 27484 * 27485 * @method addShortcut 27486 * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o. 27487 * @param {String} desc Text description for the command. 27488 * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed. 27489 * @param {Object} sc Optional scope to execute the function in. 27490 * @return {Boolean} true/false state if the shortcut was added or not. 27491 */ 27492 addShortcut: function(pattern, desc, cmdFunc, scope) { 27493 this.shortcuts.add(pattern, desc, cmdFunc, scope); 27494 }, 27495 27496 /**
27497 * Executes a command on the current instance. These commands can be TinyMCE internal commands prefixed with "mce" or 27498 * they can be build in browser commands such as "Bold". A compleate list of browser commands is available on MSDN or Mozilla.org. 27499 * This function will dispatch the execCommand function on each plugin, theme or the execcommand_callback option if none of these 27500 * return true it will handle the command as a internal browser command. 27501 * 27502 * @method execCommand 27503 * @param {String} cmd Command name to execute, for example mceLink or Bold. 27504 * @param {Boolean} ui True/false state if a UI (dialog) should be presented or not. 27505 * @param {mixed} value Optional command value, this can be anything. 27506 * @param {Object} a Optional arguments object. 27507 */ 27508 execCommand: function(cmd, ui, value, args) { 27509 var self = this, state = 0, cmdItem; 27510 27511 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(cmd) && (!args || !args.skip_focus)) { 27512 self.focus(); 27513 } 27514 27515 args = extend({}, args); 27516 args = self.fire('BeforeExecCommand', {command: cmd, ui: ui, value: value}); 27517 if (args.isDefaultPrevented()) { 27518 return false; 27519 } 27520 27521 // Registred commands 27522 if ((cmdItem = self.execCommands[cmd])) { 27523 // Fall through on true 27524 if (cmdItem.func.call(cmdItem.scope, ui, value) !== true) { 27525 self.fire('ExecCommand', {command: cmd, ui: ui, value: value}); 27526 return true; 27527 } 27528 } 27529 27530 // Plugin commands 27531 each(self.plugins, function(p) { 27532 if (p.execCommand && p.execCommand(cmd, ui, value)) { 27533 self.fire('ExecCommand', {command: cmd, ui: ui, value: value}); 27534 state = true; 27535 return false; 27536 } 27537 }); 27538 27539 if (state) { 27540 return state; 27541 } 27542 27543 // Theme commands 27544 if (self.theme && self.theme.execCommand && self.theme.execCommand(cmd, ui, value)) { 27545 self.fire('ExecCommand', {command: cmd, ui: ui, value: value}); 27546 return true; 27547 } 27548 27549 // Editor commands 27550 if (self.editorCommands.execCommand(cmd, ui, value)) { 27551 self.fire('ExecCommand', {command: cmd, ui: ui, value: value}); 27552 return true; 27553 } 27554 27555 // Browser commands 27556 try { 27557 state = self.getDoc().execCommand(cmd, ui, value); 27558 } catch (ex) { 27559 // Ignore old IE errors 27560 } 27561 27562 if (state) { 27563 self.fire('ExecCommand', {command: cmd, ui: ui, value: value}); 27564 return true; 27565 } 27566 27567 return false; 27568 }, 27569 27570 /** 27571 * Returns a command specific state, for example if bold is enabled or not. 27572 * 27573 * @method queryCommandState 27574 * @param {string} cmd Command to query state from. 27575 * @return {Boolean} Command specific state, for example if bold is enabled or not. 27576 */ 27577 queryCommandState: function(cmd) { 27578 var self = this, queryItem, returnVal; 27579 27580 // Is hidden then return undefined 27581 if (self._isHidden()) { 27582 return; 27583 } 27584 27585 // Registred commands 27586 if ((queryItem = self.queryStateCommands[cmd])) { 27587 returnVal = queryItem.func.call(queryItem.scope); 27588 27589 // Fall though on non boolean returns 27590 if (returnVal === true || returnVal === false) { 27591 return returnVal; 27592 } 27593 } 27594 27595 // Editor commands 27596 returnVal = self.editorCommands.queryCommandState(cmd); 27597 if (returnVal !== -1) { 27598 return returnVal; 27599 } 27600 27601 // Browser commands 27602 try { 27603 return self.getDoc().queryCommandState(cmd); 27604 } catch (ex) { 27605 // Fails sometimes see bug: 1896577 27606 } 27607 }, 27608 27609 /** 27610 * Returns a command specific value, for example the current font size. 27611 * 27612 * @method queryCommandValue 27613 * @param {string} cmd Command to query value from. 27614 * @return {Object} Command specific value, for example the current font size. 27615 */ 27616 queryCommandValue: function(cmd) { 27617 var self = this, queryItem, returnVal; 27618 27619 // Is hidden then return undefined 27620 if (self._isHidden()) { 27621 return; 27622 } 27623 27624 // Registred commands 27625 if ((queryItem = self.queryValueCommands[cmd])) { 27626 returnVal = queryItem.func.call(queryItem.scope); 27627 27628 // Fall though on true 27629 if (returnVal !== true) { 27630 return returnVal; 27631 } 27632 } 27633 27634 // Editor commands 27635 returnVal = self.editorCommands.queryCommandValue(cmd); 27636 if (returnVal !== undefined) { 27637 return returnVal; 27638 } 27639 27640 // Browser commands 27641 try { 27642 return self.getDoc().queryCommandValue(cmd); 27643 } catch (ex) { 27644 // Fails sometimes see bug: 1896577 27645 } 27646 }, 27647 27648 /** 27649 * Shows the editor and hides any textarea/div that the editor is supposed to replace. 27650 * 27651 * @method show 27652 */ 27653 show: function() { 27654 var self = this; 27655 27656 if (self.hidden) { 27657 self.hidden = false;
27658 27659 if (self.inline) { 27660 self.getBody().contentEditable = true; 27661 } else { 27662 DOM.show(self.getContainer()); 27663 DOM.hide(self.id); 27664 } 27665 27666 self.load(); 27667 self.fire('show'); 27668 } 27669 }, 27670 27671 /** 27672 * Hides the editor and shows any textarea/div that the editor is supposed to replace. 27673 * 27674 * @method hide 27675 */ 27676 hide: function() { 27677 var self = this, doc = self.getDoc(); 27678 27679 if (!self.hidden) { 27680 // Fixed bug where IE has a blinking cursor left from the editor 27681 if (ie && doc && !self.inline) { 27682 doc.execCommand('SelectAll'); 27683 } 27684 27685 // We must save before we hide so Safari doesn't crash 27686 self.save(); 27687 27688 if (self.inline) { 27689 self.getBody().contentEditable = false; 27690 27691 // Make sure the editor gets blurred 27692 if (self == self.editorManager.focusedEditor) { 27693 self.editorManager.focusedEditor = null; 27694 } 27695 } else { 27696 DOM.hide(self.getContainer()); 27697 DOM.setStyle(self.id, 'display', self.orgDisplay); 27698 } 27699 27700 self.hidden = true; 27701 self.fire('hide'); 27702 } 27703 }, 27704 27705 /** 27706 * Returns true/false if the editor is hidden or not. 27707 * 27708 * @method isHidden 27709 * @return {Boolean} True/false if the editor is hidden or not. 27710 */ 27711 isHidden: function() { 27712 return !!this.hidden; 27713 }, 27714 27715 /** 27716 * Sets the progress state, this will display a throbber/progess for the editor. 27717 * This is ideal for asycronous operations like an AJAX save call. 27718 * 27719 * @method setProgressState 27720 * @param {Boolean} state Boolean state if the progress should be shown or hidden. 27721 * @param {Number} time Optional time to wait before the progress gets shown. 27722 * @return {Boolean} Same as the input state. 27723 * @example 27724 * // Show progress for the active editor 27725 * tinymce.activeEditor.setProgressState(true); 27726 * 27727 * // Hide progress for the active editor 27728 * tinymce.activeEditor.setProgressState(false); 27729 *
27730 * // Show progress after 3 seconds 27731 * tinymce.activeEditor.setProgressState(true, 3000); 27732 */ 27733 setProgressState: function(state, time) { 27734 this.fire('ProgressState', {state: state, time: time}); 27735 }, 27736 27737 /** 27738 * Loads contents from the textarea or div element that got converted into an editor instance. 27739 * This method will move the contents from that textarea or div into the editor by using setContent 27740 * so all events etc that method has will get dispatched as well. 27741 * 27742 * @method load 27743 * @param {Object} args Optional content object, this gets passed around through the whole load process. 27744 * @return {String} HTML string that got set into the editor. 27745 */ 27746 load: function(args) { 27747 var self = this, elm = self.getElement(), html; 27748 27749 if (elm) { 27750 args = args || {}; 27751 args.load = true; 27752 27753 html = self.setContent(elm.value !== undefined ? elm.value : elm.innerHTML, args); 27754 args.element = elm; 27755 27756 if (!args.no_events) { 27757 self.fire('LoadContent', args); 27758 } 27759 27760 args.element = elm = null; 27761 27762 return html; 27763 } 27764 }, 27765 27766 /** 27767 * Saves the contents from a editor out to the textarea or div element that got converted into an editor instance. 27768 * This method will move the HTML contents from the editor into that textarea or div by getContent 27769 * so all events etc that method has will get dispatched as well. 27770 * 27771 * @method save 27772 * @param {Object} args Optional content object, this gets passed around through the whole save process. 27773 * @return {String} HTML string that got set into the textarea/div. 27774 */ 27775 save: function(args) { 27776 var self = this, elm = self.getElement(), html, form; 27777 27778 if (!elm || !self.initialized) { 27779 return; 27780 } 27781 27782 args = args || {}; 27783 args.save = true; 27784 27785 args.element = elm; 27786 html = args.content = self.getContent(args); 27787 27788 if (!args.no_events) { 27789 self.fire('SaveContent', args); 27790 } 27791 27792 html = args.content; 27793 27794 if (!/TEXTAREA|INPUT/i.test(elm.nodeName)) { 27795 // Update DIV element when not in inline mode 27796 if (!self.inline) { 27797 elm.innerHTML = html; 27798 } 27799 27800 // Update hidden form element 27801 if ((form = DOM.getParent(self.id, 'form'))) { 27802 each(form.elements, function(elm) { 27803 if (elm.name == self.id) { 27804 elm.value = html; 27805 return false; 27806 } 27807 }); 27808 } 27809 } else { 27810 elm.value = html; 27811 } 27812 27813 args.element = elm = null; 27814 27815 if (args.set_dirty !== false) { 27816 self.isNotDirty = true; 27817 } 27818 27819 return html; 27820 }, 27821 27822 /** 27823 * Sets the specified content to the editor instance, this will cleanup the content before it gets set using 27824 * the different cleanup rules options. 27825 * 27826 * @method setContent 27827 * @param {String} content Content to set to editor, normally HTML contents but can be other formats as well. 27828 * @param {Object} args Optional content object, this gets passed around through the whole set process. 27829 * @return {String} HTML string that got set into the editor. 27830 * @example 27831 * // Sets the HTML contents of the activeEditor editor 27832 * tinymce.activeEditor.setContent('<span>some</span> html'); 27833 *
27834 * // Sets the raw contents of the activeEditor editor 27835 * tinymce.activeEditor.setContent('<span>some</span> html', {format: 'raw'}); 27836 * 27837 * // Sets the content of a specific editor (my_editor in this example) 27838 * tinymce.get('my_editor').setContent(data); 27839 * 27840 * // Sets the bbcode contents of the activeEditor editor if the bbcode plugin was added 27841 * tinymce.activeEditor.setContent('[b]some[/b] html', {format: 'bbcode'}); 27842 */ 27843 setContent: function(content, args) { 27844 var self = this, body = self.getBody(), forcedRootBlockName; 27845 27846 // Setup args object 27847 args = args || {}; 27848 args.format = args.format || 'html'; 27849 args.set = true; 27850 args.content = content; 27851 27852 // Do preprocessing 27853 if (!args.no_events) { 27854 self.fire('BeforeSetContent', args); 27855 } 27856 27857 content = args.content; 27858 27859 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content 27860 // It will also be impossible to place the caret in the editor unless there is a BR element present 27861 if (content.length === 0 || /^\s+$/.test(content)) { 27862 forcedRootBlockName = self.settings.forced_root_block; 27863 27864 // Check if forcedRootBlock is configured and that the block is a valid child of the body 27865 if (forcedRootBlockName && self.schema.isValidChild(body.nodeName.toLowerCase(), forcedRootBlockName.toLowerCase())) { 27866 // Padd with bogus BR elements on modern browsers and IE 7 and 8 since they don't render empty P tags properly 27867 content = ie && ie < 11 ? '' : '<br data-mce-bogus="1">'; 27868 content = self.dom.createHTML(forcedRootBlockName, self.settings.forced_root_block_attrs, content); 27869 } else if (!ie) { 27870 // We need to add a BR when forced_root_block is disabled on non IE browsers to place the caret 27871 content = '<br data-mce-bogus="1">'; 27872 } 27873 27874 self.dom.setHTML(body, content); 27875 27876 self.fire('SetContent', args); 27877 } else { 27878 // Parse and serialize the html 27879 if (args.format !== 'raw') { 27880 content = new Serializer({}, self.schema).serialize( 27881 self.parser.parse(content, {isRootContent: true}) 27882 ); 27883 } 27884 27885 // Set the new cleaned contents to the editor 27886 args.content = trim(content); 27887 self.dom.setHTML(body, args.content); 27888 27889 // Do post processing 27890 if (!args.no_events) { 27891 self.fire('SetContent', args); 27892 } 27893 27894 // Don't normalize selection if the focused element isn't the body in 27895 // content editable mode since it will steal focus otherwise 27896 /*if (!self.settings.content_editable || document.activeElement === self.getBody()) { 27897 self.selection.normalize(); 27898 }*/ 27899 } 27900 27901 return args.content; 27902 }, 27903 27904 /** 27905 * Gets the content from the editor instance, this will cleanup the content before it gets returned using 27906 * the different cleanup rules options. 27907 * 27908 * @method getContent 27909 * @param {Object} args Optional content object, this gets passed around through the whole get process. 27910 * @return {String} Cleaned content string, normally HTML contents. 27911 * @example 27912 * // Get the HTML contents of the currently active editor 27913 * console.debug(tinymce.activeEditor.getContent()); 27914 * 27915 * // Get the raw contents of the currently active editor 27916 * tinymce.activeEditor.getContent({format: 'raw'}); 27917 * 27918 * // Get content of a specific editor: 27919 * tinymce.get('content id').getContent() 27920 */ 27921 getContent: function(args) { 27922 var self = this, content, body = self.getBody(); 27923 27924 // Setup args object 27925 args = args || {}; 27926 args.format = args.format || 'html'; 27927 args.get = true; 27928 args.getInner = true; 27929 27930 // Do preprocessing 27931 if (!args.no_events) { 27932 self.fire('BeforeGetContent', args); 27933 } 27934 27935 // Get raw contents or by default the cleaned contents 27936 if (args.format == 'raw') { 27937 content = body.innerHTML; 27938 } else if (args.format == 'text') { 27939 content = body.innerText || body.textContent; 27940 } else { 27941 content = self.serializer.serialize(body, args); 27942 } 27943 27944 // Trim whitespace in beginning/end of HTML 27945 if (args.format != 'text') { 27946 args.content = trim(content); 27947 } else { 27948 args.content = content; 27949 } 27950 27951 // Do post processing 27952 if (!args.no_events) { 27953 self.fire('GetContent', args); 27954 } 27955 27956 return args.content; 27957 }, 27958 27959 /** 27960 * Inserts content at caret position. 27961 * 27962 * @method insertContent 27963 * @param {String} content Content to insert. 27964 * @param {Object} args Optional args to pass to insert call. 27965 */ 27966 insertContent: function(content, args) { 27967 if (args) { 27968 content = extend({content: content}, args); 27969 } 27970 27971 this.execCommand('mceInsertContent', false, content); 27972 }, 27973 27974 /** 27975 * Returns true/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents. 27976 * 27977 * @method isDirty 27978 * @return {Boolean} True/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents. 27979 * @example 27980 * if (tinymce.activeEditor.isDirty()) 27981 * alert("You must save your contents."); 27982 */ 27983 isDirty: function() { 27984 return !this.isNotDirty; 27985 }, 27986 27987 /** 27988 * Returns the editors container element. The container element wrappes in 27989 * all the elements added to the page for the editor. Such as UI, iframe etc. 27990 * 27991 * @method getContainer 27992 * @return {Element} HTML DOM element for the editor container. 27993 */ 27994 getContainer: function() { 27995 var self = this; 27996 27997 if (!self.container) { 27998 self.container = DOM.get(self.editorContainer || self.id + '_parent'); 27999 } 28000 28001 return self.container; 28002 }, 28003 28004 /** 28005 * Returns the editors content area container element. The this element is the one who 28006 * holds the iframe or the editable element. 28007 * 28008 * @method getContentAreaContainer 28009 * @return {Element} HTML DOM element for the editor area container. 28010 */ 28011 getContentAreaContainer: function() { 28012 return this.contentAreaContainer; 28013 }, 28014 28015 /** 28016 * Returns the target element/textarea that got replaced with a TinyMCE editor instance. 28017 * 28018 * @method getElement 28019 * @return {Element} HTML DOM element for the replaced element. 28020 */ 28021 getElement: function() { 28022 if (!this.targetElm) {
28023 this.targetElm = DOM.get(this.id); 28024 } 28025 28026 return this.targetElm; 28027 }, 28028 28029 /** 28030 * Returns the iframes window object. 28031 * 28032 * @method getWin 28033 * @return {Window} Iframe DOM window object. 28034 */ 28035 getWin: function() { 28036 var self = this, elm; 28037 28038 if (!self.contentWindow) { 28039 elm = self.iframeElement; 28040 28041 if (elm) { 28042 self.contentWindow = elm.contentWindow; 28043 } 28044 } 28045 28046 return self.contentWindow; 28047 }, 28048 28049 /** 28050 * Returns the iframes document object. 28051 * 28052 * @method getDoc 28053 * @return {Document} Iframe DOM document object. 28054 */ 28055 getDoc: function() { 28056 var self = this, win; 28057 28058 if (!self.contentDocument) { 28059 win = self.getWin(); 28060 28061 if (win) { 28062 self.contentDocument = win.document; 28063 } 28064 } 28065 28066 return self.contentDocument; 28067 }, 28068 28069 /** 28070 * Returns the iframes body element. 28071 * 28072 * @method getBody 28073 * @return {Element} Iframe body element. 28074 */ 28075 getBody: function() { 28076 return this.bodyElement || this.getDoc().body; 28077 }, 28078 28079 /** 28080 * URL converter function this gets executed each time a user adds an img, a or 28081 * any other element that has a URL in it. This will be called both by the DOM and HTML 28082 * manipulation functions. 28083 * 28084 * @method convertURL 28085 * @param {string} url URL to convert. 28086 * @param {string} name Attribute name src, href etc. 28087 * @param {string/HTMLElement} elm Tag name or HTML DOM element depending on HTML or DOM insert. 28088 * @return {string} Converted URL string. 28089 */ 28090 convertURL: function(url, name, elm) { 28091 var self = this, settings = self.settings; 28092 28093 // Use callback instead 28094 if (settings.urlconverter_callback) { 28095 return self.execCallback('urlconverter_callback', url, elm, true, name); 28096 } 28097 28098 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs 28099 if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0 || url.length === 0) { 28100 return url; 28101 } 28102 28103 // Convert to relative 28104 if (settings.relative_urls) { 28105 return self.documentBaseURI.toRelative(url); 28106 } 28107 28108 // Convert to absolute 28109 url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host); 28110 28111 return url; 28112 }, 28113 28114 /** 28115 * Adds visual aid for tables, anchors etc so they can be more easily edited inside the editor. 28116 * 28117 * @method addVisual 28118 * @param {Element} elm Optional root element to loop though to find tables etc that needs the visual aid. 28119 */ 28120 addVisual: function(elm) { 28121 var self = this, settings = self.settings, dom = self.dom, cls; 28122 28123 elm = elm || self.getBody(); 28124 28125 if (self.hasVisual === undefined) { 28126 self.hasVisual = settings.visual; 28127 } 28128 28129 each(dom.select('table,a', elm), function(elm) { 28130 var value; 28131 28132 switch (elm.nodeName) { 28133 case 'TABLE': 28134 cls = settings.visual_table_class || 'mce-item-table'; 28135 value = dom.getAttrib(elm, 'border'); 28136 28137 if ((!value || value == '0') && self.hasVisual) { 28138 dom.addClass(elm, cls); 28139 } else { 28140 dom.removeClass(elm, cls); 28141 } 28142 28143 return; 28144 28145 case 'A': 28146 if (!dom.getAttrib(elm, 'href', false)) { 28147 value = dom.getAttrib(elm, 'name') || elm.id; 28148 cls = settings.visual_anchor_class || 'mce-item-anchor'; 28149 28150 if (value && self.hasVisual) { 28151 dom.addClass(elm, cls); 28152 } else { 28153 dom.removeClass(elm, cls); 28154 } 28155 } 28156 28157 return; 28158 } 28159 }); 28160 28161 self.fire('VisualAid', {element: elm, hasVisual: self.hasVisual}); 28162 }, 28163 28164 /** 28165 * Removes the editor from the dom and tinymce collection. 28166 * 28167 * @method remove 28168 */ 28169 remove: function() { 28170 var self = this; 28171 28172 if (!self.removed) { 28173 self.save(); 28174 self.removed = 1; 28175 self.unbindAllNativeEvents(); 28176 28177 // Remove any hidden input 28178 if (self.hasHiddenInput) { 28179 DOM.remove(self.getElement().nextSibling); 28180 } 28181 28182 if (!self.inline) { 28183 // IE 9 has a bug where the selection stops working if you place the 28184 // caret inside the editor then remove the iframe 28185 if (ie && ie < 10) { 28186 self.getDoc().execCommand('SelectAll', false, null); 28187 } 28188 28189 DOM.setStyle(self.id, 'display', self.orgDisplay); 28190 self.getBody().onload = null; // Prevent #6816 28191 } 28192 28193 self.fire('remove'); 28194 28195 self.editorManager.remove(self); 28196 DOM.remove(self.getContainer()); 28197 self.destroy(); 28198 } 28199 }, 28200 28201 /** 28202 * Destroys the editor instance by removing all events, element references or other resources 28203 * that could leak memory. This method will be called automatically when the page is unloaded
28204 * but you can also call it directly if you know what you are doing. 28205 * 28206 * @method destroy 28207 * @param {Boolean} automatic Optional state if the destroy is an automatic destroy or user called one. 28208 */ 28209 destroy: function(automatic) { 28210 var self = this, form; 28211 28212 // One time is enough 28213 if (self.destroyed) { 28214 return; 28215 } 28216 28217 // If user manually calls destroy and not remove 28218 // Users seems to have logic that calls destroy instead of remove 28219 if (!automatic && !self.removed) { 28220 self.remove(); 28221 return; 28222 } 28223 28224 if (!automatic) { 28225 self.editorManager.off('beforeunload', self._beforeUnload); 28226 28227 // Manual destroy 28228 if (self.theme && self.theme.destroy) { 28229 self.theme.destroy(); 28230 } 28231 28232 // Destroy controls, selection and dom 28233 self.selection.destroy(); 28234 self.dom.destroy(); 28235 } 28236 28237 form = self.formElement; 28238 if (form) { 28239 if (form._mceOldSubmit) { 28240 form.submit = form._mceOldSubmit; 28241 form._mceOldSubmit = null; 28242 } 28243 28244 DOM.unbind(form, 'submit reset', self.formEventDelegate); 28245 } 28246 28247 self.contentAreaContainer = self.formElement = self.container = self.editorContainer = null; 28248 self.bodyElement = self.contentDocument = self.contentWindow = null; 28249 self.iframeElement = self.targetElm = null; 28250 28251 if (self.selection) { 28252 self.selection = self.selection.win = self.selection.dom = self.selection.dom.doc = null; 28253 } 28254 28255 self.destroyed = 1; 28256 }, 28257 28258 // Internal functions 28259 28260 _refreshContentEditable: function() { 28261 var self = this, body, parent; 28262 28263 // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again 28264 if (self._isHidden()) { 28265 body = self.getBody(); 28266 parent = body.parentNode; 28267 28268 parent.removeChild(body); 28269 parent.appendChild(body); 28270 28271 body.focus(); 28272 } 28273 }, 28274 28275 _isHidden: function() { 28276 var sel; 28277 28278 if (!isGecko) { 28279 return 0; 28280 } 28281 28282 // Weird, wheres that cursor selection? 28283 sel = this.selection.getSel(); 28284 return (!sel || !sel.rangeCount || sel.rangeCount === 0); 28285 } 28286 }; 28287 28288 extend(Editor.prototype, EditorObservable); 28289 28290 return Editor; 28291}); 28292 28293// Included from: js/tinymce/classes/util/I18n.js 28294 28295/** 28296 * I18n.js 28297 * 28298 * Copyright, Moxiecode Systems AB 28299 * Released under LGPL License. 28300 * 28301 * License: http://www.tinymce.com/license 28302 * Contributing: http://www.tinymce.com/contributing 28303 */ 28304 28305/** 28306 * I18n class that handles translation of TinyMCE UI. 28307 * Uses po style with csharp style parameters. 28308 * 28309 * @class tinymce.util.I18n 28310 */ 28311define("tinymce/util/I18n", [], function() { 28312 "use strict"; 28313 28314 var data = {}; 28315 28316 return { 28317 /** 28318 * Property gets set to true if a RTL language pack was loaded. 28319 * 28320 * @property rtl 28321 * @type Boolean 28322 */ 28323 rtl: false, 28324 28325 /** 28326 * Adds translations for a specific language code. 28327 * 28328 * @method add 28329 * @param {String} code Language code like sv_SE. 28330 * @param {Array} items Name/value array with English en_US to sv_SE. 28331 */ 28332 add: function(code, items) { 28333 for (var name in items) { 28334 data[name] = items[name]; 28335 } 28336 28337 this.rtl = this.rtl || data._dir === 'rtl'; 28338 }, 28339 28340 /** 28341 * Translates the specified text. 28342 * 28343 * It has a few formats: 28344 * I18n.translate("Text"); 28345 * I18n.translate(["Text {0}/{1}", 0, 1]); 28346 * I18n.translate({raw: "Raw string"}); 28347 * 28348 * @method translate 28349 * @param {String/Object/Array} text Text to translate. 28350 * @return {String} String that got translated. 28351 */ 28352 translate: function(text) { 28353 if (typeof(text) == "undefined") { 28354 return text; 28355 } 28356 28357 if (typeof(text) != "string" && text.raw) { 28358 return text.raw; 28359 } 28360 28361 if (text.push) { 28362 var values = text.slice(1); 28363 28364 text = (data[text[0]] || text[0]).replace(/\{([^\}]+)\}/g, function(match1, match2) { 28365 return values[match2]; 28366 }); 28367 } 28368 28369 return data[text] || text; 28370 }, 28371 28372 data: data 28373 }; 28374}); 28375 28376// Included from: js/tinymce/classes/FocusManager.js 28377 28378/** 28379 * FocusManager.js 28380 * 28381 * Copyright, Moxiecode Systems AB 28382 * Released under LGPL License. 28383 * 28384 * License: http://www.tinymce.com/license 28385 * Contributing: http://www.tinymce.com/contributing 28386 */ 28387 28388/** 28389 * This class manages the focus/blur state of the editor. This class is needed since some
28390 * browsers fire false focus/blur states when the selection is moved to a UI dialog or similar. 28391 * 28392 * This class will fire two events focus and blur on the editor instances that got affected. 28393 * It will also handle the restore of selection when the focus is lost and returned. 28394 * 28395 * @class tinymce.FocusManager 28396 */ 28397define("tinymce/FocusManager", [ 28398 "tinymce/dom/DOMUtils", 28399 "tinymce/Env" 28400], function(DOMUtils, Env) { 28401 var selectionChangeHandler, documentFocusInHandler, documentMouseUpHandler, DOM = DOMUtils.DOM; 28402 28403 /** 28404 * Constructs a new focus manager instance. 28405 * 28406 * @constructor FocusManager 28407 * @param {tinymce.EditorManager} editorManager Editor manager instance to handle focus for. 28408 */ 28409 function FocusManager(editorManager) { 28410 function getActiveElement() { 28411 try { 28412 return document.activeElement; 28413 } catch (ex) { 28414 // IE sometimes fails to get the activeElement when resizing table 28415 // TODO: Investigate this 28416 return document.body; 28417 } 28418 } 28419 28420 // We can't store a real range on IE 11 since it gets mutated so we need to use a bookmark object 28421 // TODO: Move this to a separate range utils class since it's it's logic is present in Selection as well. 28422 function createBookmark(dom, rng) { 28423 if (rng && rng.startContainer) { 28424 // Verify that the range is within the root of the editor 28425 if (!dom.isChildOf(rng.startContainer, dom.getRoot()) || !dom.isChildOf(rng.endContainer, dom.getRoot())) { 28426 return; 28427 } 28428 28429 return { 28430 startContainer: rng.startContainer, 28431 startOffset: rng.startOffset, 28432 endContainer: rng.endContainer, 28433 endOffset: rng.endOffset 28434 }; 28435 } 28436 28437 return rng; 28438 } 28439 28440 function bookmarkToRng(editor, bookmark) { 28441 var rng; 28442 28443 if (bookmark.startContainer) { 28444 rng = editor.getDoc().createRange(); 28445 rng.setStart(bookmark.startContainer, bookmark.startOffset); 28446 rng.setEnd(bookmark.endContainer, bookmark.endOffset); 28447 } else { 28448 rng = bookmark; 28449 } 28450 28451 return rng; 28452 } 28453 28454 function isUIElement(elm) { 28455 return !!DOM.getParent(elm, FocusManager.isEditorUIElement); 28456 } 28457 28458 function registerEvents(e) { 28459 var editor = e.editor; 28460 28461 editor.on('init', function() { 28462 // Gecko/WebKit has ghost selections in iframes and IE only has one selection per browser tab 28463 if (editor.inline || Env.ie) { 28464 // Use the onbeforedeactivate event when available since it works better see #7023 28465 if ("onbeforedeactivate" in document && Env.ie < 9) { 28466 editor.dom.bind(editor.getBody(), 'beforedeactivate', function(e) { 28467 if (e.target != editor.getBody()) { 28468 return; 28469 } 28470 28471 try { 28472 editor.lastRng = editor.selection.getRng(); 28473 } catch (ex) { 28474 // IE throws "Unexcpected call to method or property access" some times so lets ignore it 28475 } 28476 }); 28477 } else { 28478 // On other browsers take snapshot on nodechange in inline mode since they have Ghost selections for iframes 28479 editor.on('nodechange mouseup keyup', function(e) { 28480 var node = getActiveElement(); 28481 28482 // Only act on manual nodechanges 28483 if (e.type == 'nodechange' && e.selectionChange) { 28484 return; 28485 } 28486 28487 // IE 11 reports active element as iframe not body of iframe 28488 if (node && node.id == editor.id + '_ifr') { 28489 node = editor.getBody(); 28490 } 28491 28492 if (editor.dom.isChildOf(node, editor.getBody())) { 28493 editor.lastRng = editor.selection.getRng(); 28494 } 28495 }); 28496 } 28497 28498 // Handles the issue with WebKit not retaining selection within inline document 28499 // If the user releases the mouse out side the body since a mouse up event wont occur on the body 28500 if (Env.webkit && !selectionChangeHandler) { 28501 selectionChangeHandler = function() { 28502 var activeEditor = editorManager.activeEditor; 28503 28504 if (activeEditor && activeEditor.selection) { 28505 var rng = activeEditor.selection.getRng(); 28506 28507 // Store when it's non collapsed 28508 if (rng && !rng.collapsed) { 28509 editor.lastRng = rng; 28510 } 28511 } 28512 }; 28513 28514 DOM.bind(document, 'selectionchange', selectionChangeHandler); 28515 } 28516 } 28517 }); 28518 28519 editor.on('setcontent', function() { 28520 editor.lastRng = null; 28521 }); 28522 28523 // Remove last selection bookmark on mousedown see #6305 28524 editor.on('mousedown', function() { 28525 editor.selection.lastFocusBookmark = null; 28526 }); 28527 28528 editor.on('focusin', function() { 28529 var focusedEditor = editorManager.focusedEditor; 28530 28531 if (editor.selection.lastFocusBookmark) { 28532 editor.selection.setRng(bookmarkToRng(editor, editor.selection.lastFocusBookmark)); 28533 editor.selection.lastFocusBookmark = null; 28534 } 28535 28536 if (focusedEditor != editor) { 28537 if (focusedEditor) { 28538 focusedEditor.fire('blur', {focusedEditor: editor}); 28539 } 28540 28541 editorManager.setActive(editor); 28542 editorManager.focusedEditor = editor; 28543 editor.fire('focus', {blurredEditor: focusedEditor}); 28544 editor.focus(true); 28545 } 28546 28547 editor.lastRng = null; 28548 }); 28549 28550 editor.on('focusout', function() { 28551 window.setTimeout(function() { 28552 var focusedEditor = editorManager.focusedEditor; 28553 28554 // Still the same editor the the blur was outside any editor UI 28555 if (!isUIElement(getActiveElement()) && focusedEditor == editor) { 28556 editor.fire('blur', {focusedEditor: null}); 28557 editorManager.focusedEditor = null; 28558 28559 // Make sure selection is valid could be invalid if the editor is blured and removed before the timeout occurs 28560 if (editor.selection) { 28561 editor.selection.lastFocusBookmark = null; 28562 } 28563 } 28564 }, 0); 28565 }); 28566 28567 // Check if focus is moved to an element outside the active editor by checking if the target node 28568 // isn't within the body of the activeEditor nor a UI element such as a dialog child control 28569 if (!documentFocusInHandler) { 28570 documentFocusInHandler = function(e) { 28571 var activeEditor = editorManager.activeEditor; 28572 28573 if (activeEditor && e.target.ownerDocument == document) { 28574 // Check to make sure we have a valid selection don't update the bookmark if it's 28575 // a focusin to the body of the editor see #7025 28576 if (activeEditor.selection && e.target != activeEditor.getBody()) { 28577 activeEditor.selection.lastFocusBookmark = createBookmark(activeEditor.dom, activeEditor.lastRng); 28578 } 28579 28580 // Fire a blur event if the element isn't a UI element 28581 if (e.target != document.body && !isUIElement(e.target) && editorManager.focusedEditor == activeEditor) { 28582 activeEditor.fire('blur', {focusedEditor: null}); 28583 editorManager.focusedEditor = null; 28584 } 28585 } 28586 }; 28587 28588 DOM.bind(document, 'focusin', documentFocusInHandler); 28589 } 28590 28591 // Handle edge case when user starts the selection inside the editor and releases 28592 // the mouse outside the editor producing a new selection. This weird workaround is needed since 28593 // Gecko doesn't have the "selectionchange" event we need to do this. Fixes: #6843 28594 if (editor.inline && !documentMouseUpHandler) { 28595 documentMouseUpHandler = function(e) { 28596 var activeEditor = editorManager.activeEditor; 28597 28598 if (activeEditor.inline && !activeEditor.dom.isChildOf(e.target, activeEditor.getBody())) { 28599 var rng = activeEditor.selection.getRng(); 28600 28601 if (!rng.collapsed) { 28602 activeEditor.lastRng = rng; 28603 } 28604 } 28605 }; 28606 28607 DOM.bind(document, 'mouseup', documentMouseUpHandler); 28608 } 28609 } 28610 28611 function unregisterDocumentEvents(e) { 28612 if (editorManager.focusedEditor == e.editor) { 28613 editorManager.focusedEditor = null; 28614 } 28615 28616 if (!editorManager.activeEditor) { 28617 DOM.unbind(document, 'selectionchange', selectionChangeHandler); 28618 DOM.unbind(document, 'focusin', documentFocusInHandler); 28619 DOM.unbind(document, 'mouseup', documentMouseUpHandler); 28620 selectionChangeHandler = documentFocusInHandler = documentMouseUpHandler = null; 28621 } 28622 } 28623 28624 editorManager.on('AddEditor', registerEvents); 28625 editorManager.on('RemoveEditor', unregisterDocumentEvents); 28626 } 28627 28628 /** 28629 * Returns true if the specified element is part of the UI for example an button or text input. 28630 * 28631 * @method isEditorUIElement 28632 * @param {Element} elm Element to check if it's part of the UI or not. 28633 * @return {Boolean} True/false state if the element is part of the UI or not. 28634 */ 28635 FocusManager.isEditorUIElement = function(elm) { 28636 // Needs to be converted to string since svg can have focus: #6776 28637 return elm.className.toString().indexOf('mce-') !== -1; 28638 }; 28639 28640 return FocusManager; 28641}); 28642 28643// Included from: js/tinymce/classes/EditorManager.js 28644 28645/** 28646 * EditorManager.js 28647 * 28648 * Copyright, Moxiecode Systems AB 28649 * Released under LGPL License. 28650 * 28651 * License: http://www.tinymce.com/license 28652 * Contributing: http://www.tinymce.com/contributing 28653 */ 28654 28655/** 28656 * This class used as a factory for manager for tinymce.Editor instances. 28657 * 28658 * @example 28659 * tinymce.EditorManager.init({}); 28660 * 28661 * @class tinymce.EditorManager 28662 * @mixes tinymce.util.Observable
28663 * @static 28664 */ 28665define("tinymce/EditorManager", [ 28666 "tinymce/Editor", 28667 "tinymce/dom/DomQuery", 28668 "tinymce/dom/DOMUtils", 28669 "tinymce/util/URI", 28670 "tinymce/Env", 28671 "tinymce/util/Tools", 28672 "tinymce/util/Observable", 28673 "tinymce/util/I18n", 28674 "tinymce/FocusManager" 28675], function(Editor, DomQuery, DOMUtils, URI, Env, Tools, Observable, I18n, FocusManager) { 28676 var DOM = DOMUtils.DOM; 28677 var explode = Tools.explode, each = Tools.each, extend = Tools.extend; 28678 var instanceCounter = 0, beforeUnloadDelegate, EditorManager; 28679 28680 function removeEditorFromList(editor) { 28681 var editors = EditorManager.editors, removedFromList; 28682 28683 delete editors[editor.id]; 28684 28685 for (var i = 0; i < editors.length; i++) { 28686 if (editors[i] == editor) { 28687 editors.splice(i, 1); 28688 removedFromList = true; 28689 break; 28690 } 28691 } 28692 28693 // Select another editor since the active one was removed 28694 if (EditorManager.activeEditor == editor) { 28695 EditorManager.activeEditor = editors[0]; 28696 } 28697 28698 // Clear focusedEditor if necessary, so that we don't try to blur the destroyed editor 28699 if (EditorManager.focusedEditor == editor) { 28700 EditorManager.focusedEditor = null; 28701 } 28702 28703 return removedFromList; 28704 } 28705 28706 function purgeDestroyedEditor(editor) { 28707 // User has manually destroyed the editor lets clean up the mess 28708 if (editor && !(editor.getContainer() || editor.getBody()).parentNode) { 28709 removeEditorFromList(editor); 28710 editor.unbindAllNativeEvents(); 28711 editor.destroy(true); 28712 editor = null; 28713 } 28714 28715 return editor; 28716 } 28717 28718 EditorManager = { 28719 /** 28720 * Dom query instance. 28721 * 28722 * @property $ 28723 * @type tinymce.dom.DomQuery 28724 */ 28725 $: DomQuery, 28726 28727 /** 28728 * Major version of TinyMCE build. 28729 * 28730 * @property majorVersion
28731 * @type String 28732 */ 28733 majorVersion: '4', 28734 28735 /** 28736 * Minor version of TinyMCE build. 28737 * 28738 * @property minorVersion 28739 * @type String 28740 */ 28741 minorVersion: '1.7', 28742 28743 /** 28744 * Release date of TinyMCE build. 28745 * 28746 * @property releaseDate 28747 * @type String 28748 */ 28749 releaseDate: '2014-11-27', 28750 28751 /** 28752 * Collection of editor instances. 28753 * 28754 * @property editors 28755 * @type Object 28756 * @example 28757 * for (edId in tinymce.editors) 28758 * tinymce.editors[edId].save(); 28759 */ 28760 editors: [], 28761 28762 /** 28763 * Collection of language pack data. 28764 * 28765 * @property i18n 28766 * @type Object 28767 */ 28768 i18n: I18n, 28769 28770 /** 28771 * Currently active editor instance. 28772 * 28773 * @property activeEditor 28774 * @type tinymce.Editor 28775 * @example 28776 * tinyMCE.activeEditor.selection.getContent(); 28777 * tinymce.EditorManager.activeEditor.selection.getContent(); 28778 */ 28779 activeEditor: null, 28780 28781 setup: function() { 28782 var self = this, baseURL, documentBaseURL, suffix = "", preInit, src; 28783 28784 // Get base URL for the current document 28785 documentBaseURL = document.location.href; 28786 28787 // Check if the URL is a document based format like: http://site/dir/file and file:/// 28788 // leave other formats like applewebdata://... intact 28789 if (/^[^:]+:\/\/\/?[^\/]+\//.test(documentBaseURL)) { 28790 documentBaseURL = documentBaseURL.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); 28791 28792 if (!/[\/\\]$/.test(documentBaseURL)) { 28793 documentBaseURL += '/'; 28794 } 28795 } 28796 28797 // If tinymce is defined and has a base use that or use the old tinyMCEPreInit 28798 preInit = window.tinymce || window.tinyMCEPreInit; 28799 if (preInit) { 28800 baseURL = preInit.base || preInit.baseURL; 28801 suffix = preInit.suffix; 28802 } else { 28803 28804 // Get base where the tinymce script is located 28805 var scripts = document.getElementsByTagName('script'); 28806 for (var i = 0; i < scripts.length; i++) { 28807 src = scripts[i].src; 28808 var srcScript = src.substring(src.lastIndexOf('/')); 28809 28810 // Script types supported: 28811 // tinymce.js tinymce.min.js tinymce.dev.js 28812 // tinymce.jquery.js tinymce.jquery.min.js tinymce.jquery.dev.js 28813 // tinymce.full.js tinymce.full.min.js tinymce.full.dev.js 28814 if (/tinymce(\.full|\.jquery|)(\.min|\.dev|)\.js/.test(src)) { 28815 if (srcScript.indexOf('.min') != -1) { 28816 suffix = '.min'; 28817 } 28818 28819 baseURL = src.substring(0, src.lastIndexOf('/')); 28820 break; 28821 } 28822 } 28823 28824 // We didn't find any baseURL by looking at the script elements 28825 // Try to use the document.currentScript as a fallback 28826 if (!baseURL && document.currentScript) { 28827 src = document.currentScript.src; 28828 28829 if (src.indexOf('.min') != -1) { 28830 suffix = '.min'; 28831 } 28832 28833 baseURL = src.substring(0, src.lastIndexOf('/')); 28834 } 28835 } 28836 28837 /** 28838 * Base URL where the root directory if TinyMCE is located. 28839 * 28840 * @property baseURL
28841 * @type String 28842 */ 28843 self.baseURL = new URI(documentBaseURL).toAbsolute(baseURL); 28844 28845 /** 28846 * Document base URL where the current document is located. 28847 * 28848 * @property documentBaseURL 28849 * @type String 28850 */ 28851 self.documentBaseURL = documentBaseURL; 28852 28853 /** 28854 * Absolute baseURI for the installation path of TinyMCE. 28855 * 28856 * @property baseURI 28857 * @type tinymce.util.URI 28858 */ 28859 self.baseURI = new URI(self.baseURL); 28860 28861 /** 28862 * Current suffix to add to each plugin/theme that gets loaded for example ".min". 28863 * 28864 * @property suffix 28865 * @type String 28866 */ 28867 self.suffix = suffix; 28868 28869 self.focusManager = new FocusManager(self); 28870 }, 28871 28872 /** 28873 * Initializes a set of editors. This method will create editors based on various settings. 28874 * 28875 * @method init 28876 * @param {Object} settings Settings object to be passed to each editor instance. 28877 * @example 28878 * // Initializes a editor using the longer method 28879 * tinymce.EditorManager.init({ 28880 * some_settings : 'some value' 28881 * }); 28882 * 28883 * // Initializes a editor instance using the shorter version 28884 * tinyMCE.init({ 28885 * some_settings : 'some value' 28886 * }); 28887 */ 28888 init: function(settings) { 28889 var self = this, editors = []; 28890 28891 function createId(elm) { 28892 var id = elm.id; 28893 28894 // Use element id, or unique name or generate a unique id 28895 if (!id) { 28896 id = elm.name; 28897 28898 if (id && !DOM.get(id)) { 28899 id = elm.name; 28900 } else { 28901 // Generate unique name 28902 id = DOM.uniqueId(); 28903 } 28904 28905 elm.setAttribute('id', id); 28906 } 28907 28908 return id; 28909 } 28910 28911 function createEditor(id, settings, targetElm) { 28912 if (!purgeDestroyedEditor(self.get(id))) { 28913 var editor = new Editor(id, settings, self); 28914 28915 editor.targetElm = editor.targetElm || targetElm; 28916 editors.push(editor); 28917 editor.render(); 28918 } 28919 } 28920 28921 function execCallback(name) { 28922 var callback = settings[name]; 28923 28924 if (!callback) { 28925 return; 28926 } 28927 28928 return callback.apply(self, Array.prototype.slice.call(arguments, 2)); 28929 } 28930 28931 function hasClass(elm, className) { 28932 return className.constructor === RegExp ? className.test(elm.className) : DOM.hasClass(elm, className); 28933 } 28934 28935 function readyHandler() { 28936 var l, co; 28937 28938 DOM.unbind(window, 'ready', readyHandler); 28939 28940 execCallback('onpageload'); 28941 28942 if (settings.types) { 28943 // Process type specific selector 28944 each(settings.types, function(type) { 28945 each(DOM.select(type.selector), function(elm) { 28946 createEditor(createId(elm), extend({}, settings, type), elm); 28947 }); 28948 }); 28949 28950 return; 28951 } else if (settings.selector) { 28952 // Process global selector 28953 each(DOM.select(settings.selector), function(elm) { 28954 createEditor(createId(elm), settings, elm); 28955 }); 28956 28957 return; 28958 } else if (settings.target) { 28959 createEditor(createId(settings.target), settings); 28960 } 28961 28962 // Fallback to old setting 28963 switch (settings.mode) { 28964 case "exact": 28965 l = settings.elements || ''; 28966 28967 if (l.length > 0) { 28968 each(explode(l), function(id) { 28969 var elm; 28970 28971 if ((elm = DOM.get(id))) { 28972 createEditor(id, settings, elm); 28973 } else { 28974 each(document.forms, function(f) { 28975 each(f.elements, function(e) { 28976 if (e.name === id) { 28977 id = 'mce_editor_' + instanceCounter++; 28978 DOM.setAttrib(e, 'id', id); 28979 createEditor(id, settings, e); 28980 } 28981 }); 28982 }); 28983 } 28984 }); 28985 } 28986 break; 28987 28988 case "textareas": 28989 case "specific_textareas": 28990 each(DOM.select('textarea'), function(elm) { 28991 if (settings.editor_deselector && hasClass(elm, settings.editor_deselector)) { 28992 return; 28993 } 28994 28995 if (!settings.editor_selector || hasClass(elm, settings.editor_selector)) { 28996 createEditor(createId(elm), settings, elm); 28997 } 28998 }); 28999 break; 29000 } 29001 29002 // Call onInit when all editors are initialized 29003 if (settings.oninit) { 29004 l = co = 0; 29005 29006 each(editors, function(ed) { 29007 co++; 29008 29009 if (!ed.initialized) { 29010 // Wait for it 29011 ed.on('init', function() { 29012 l++; 29013 29014 // All done 29015 if (l == co) { 29016 execCallback('oninit'); 29017 } 29018 }); 29019 } else { 29020 l++; 29021 } 29022 29023 // All done 29024 if (l == co) { 29025 execCallback('oninit'); 29026 } 29027 }); 29028 } 29029 } 29030 29031 self.settings = settings; 29032 29033 DOM.bind(window, 'ready', readyHandler); 29034 }, 29035 29036 /** 29037 * Returns a editor instance by id. 29038 * 29039 * @method get 29040 * @param {String/Number} id Editor instance id or index to return. 29041 * @return {tinymce.Editor} Editor instance to return. 29042 * @example 29043 * // Adds an onclick event to an editor by id (shorter version) 29044 * tinymce.get('mytextbox').on('click', function(e) { 29045 * ed.windowManager.alert('Hello world!'); 29046 * }); 29047 * 29048 * // Adds an onclick event to an editor by id (longer version) 29049 * tinymce.EditorManager.get('mytextbox').on('click', function(e) { 29050 * ed.windowManager.alert('Hello world!'); 29051 * }); 29052 */ 29053 get: function(id) { 29054 if (!arguments.length) { 29055 return this.editors; 29056 } 29057 29058 return id in this.editors ? this.editors[id] : null; 29059 }, 29060 29061 /** 29062 * Adds an editor instance to the editor collection. This will also set it as the active editor. 29063 * 29064 * @method add 29065 * @param {tinymce.Editor} editor Editor instance to add to the collection. 29066 * @return {tinymce.Editor} The same instance that got passed in. 29067 */ 29068 add: function(editor) { 29069 var self = this, editors = self.editors; 29070 29071 // Add named and index editor instance 29072 editors[editor.id] = editor; 29073 editors.push(editor); 29074 29075 // Doesn't call setActive method since we don't want 29076 // to fire a bunch of activate/deactivate calls while initializing 29077 self.activeEditor = editor; 29078 29079 /** 29080 * Fires when an editor is added to the EditorManager collection. 29081 * 29082 * @event AddEditor 29083 * @param {Object} e Event arguments. 29084 */ 29085 self.fire('AddEditor', {editor: editor}); 29086 29087 if (!beforeUnloadDelegate) { 29088 beforeUnloadDelegate = function() { 29089 self.fire('BeforeUnload'); 29090 }; 29091 29092 DOM.bind(window, 'beforeunload', beforeUnloadDelegate); 29093 } 29094 29095 return editor; 29096 }, 29097 29098 /** 29099 * Creates an editor instance and adds it to the EditorManager collection. 29100 * 29101 * @method createEditor 29102 * @param {String} id Instance id to use for editor. 29103 * @param {Object} settings Editor instance settings. 29104 * @return {tinymce.Editor} Editor instance that got created. 29105 */ 29106 createEditor: function(id, settings) { 29107 return this.add(new Editor(id, settings, this)); 29108 }, 29109 29110 /** 29111 * Removes a editor or editors form page. 29112 * 29113 * @example 29114 * // Remove all editors bound to divs 29115 * tinymce.remove('div'); 29116 * 29117 * // Remove all editors bound to textareas 29118 * tinymce.remove('textarea'); 29119 * 29120 * // Remove all editors 29121 * tinymce.remove(); 29122 * 29123 * // Remove specific instance by id 29124 * tinymce.remove('#id'); 29125 * 29126 * @method remove 29127 * @param {tinymce.Editor/String/Object}
29127 [selector] CSS selector or editor instance to remove. 29128 * @return {tinymce.Editor} The editor that got passed in will be return if it was found otherwise null. 29129 */ 29130 remove: function(selector) { 29131 var self = this, i, editors = self.editors, editor; 29132 29133 // Remove all editors 29134 if (!selector) { 29135 for (i = editors.length - 1; i >= 0; i--) { 29136 self.remove(editors[i]); 29137 } 29138 29139 return; 29140 } 29141 29142 // Remove editors by selector 29143 if (typeof(selector) == "string") { 29144 selector = selector.selector || selector; 29145 29146 each(DOM.select(selector), function(elm) { 29147 editor = editors[elm.id]; 29148 29149 if (editor) { 29150 self.remove(editor); 29151 } 29152 }); 29153 29154 return; 29155 } 29156 29157 // Remove specific editor 29158 editor = selector; 29159 29160 // Not in the collection 29161 if (!editors[editor.id]) { 29162 return null; 29163 } 29164 29165 /** 29166 * Fires when an editor is removed from EditorManager collection. 29167 * 29168 * @event RemoveEditor 29169 * @param {Object} e Event arguments. 29170 */ 29171 if (removeEditorFromList(editor)) { 29172 self.fire('RemoveEditor', {editor: editor}); 29173 } 29174 29175 if (!editors.length) { 29176 DOM.unbind(window, 'beforeunload', beforeUnloadDelegate); 29177 } 29178 29179 editor.remove(); 29180 29181 return editor; 29182 }, 29183 29184 /** 29185 * Executes a specific command on the currently active editor. 29186 * 29187 * @method execCommand 29188 * @param {String} c Command to perform for example Bold. 29189 * @param {Boolean} u Optional boolean state if a UI should be presented for the command or not. 29190 * @param {String} v Optional value parameter like for example an URL to a link. 29191 * @return {Boolean} true/false if the command was executed or not. 29192 */ 29193 execCommand: function(cmd, ui, value) { 29194 var self = this, editor = self.get(value); 29195 29196 // Manager commands 29197 switch (cmd) { 29198 case "mceAddEditor": 29199 if (!self.get(value)) { 29200 new Editor(value, self.settings, self).render(); 29201 } 29202 29203 return true; 29204 29205 case "mceRemoveEditor": 29206 if (editor) { 29207 editor.remove(); 29208 } 29209 29210 return true; 29211 29212 case 'mceToggleEditor': 29213 if (!editor) { 29214 self.execCommand('mceAddEditor', 0, value); 29215 return true; 29216 } 29217 29218 if (editor.isHidden()) { 29219 editor.show(); 29220 } else { 29221 editor.hide(); 29222 } 29223 29224 return true; 29225 } 29226 29227 // Run command on active editor 29228 if (self.activeEditor) { 29229 return self.activeEditor.execCommand(cmd, ui, value); 29230 } 29231 29232 return false; 29233 }, 29234 29235 /** 29236 * Calls the save method on all editor instances in the collection. This can be useful when a form is to be submitted. 29237 * 29238 * @method triggerSave 29239 * @example 29240 * // Saves all contents 29241 * tinyMCE.triggerSave(); 29242 */ 29243 triggerSave: function() { 29244 each(this.editors, function(editor) { 29245 editor.save(); 29246 }); 29247 }, 29248 29249 /** 29250 * Adds a language pack, this gets called by the loaded language files like en.js. 29251 * 29252 * @method addI18n 29253 * @param {String} code Optional language code. 29254 * @param {Object} items Name/value object with translations. 29255 */ 29256 addI18n: function(code, items) { 29257 I18n.add(code, items); 29258 }, 29259 29260 /** 29261 * Translates the specified string using the language pack items. 29262 * 29263 * @method translate 29264 * @param {String/Array/Object} text String to translate 29265 * @return {String} Translated string. 29266 */ 29267 translate: function(text) { 29268 return I18n.translate(text); 29269 }, 29270 29271 /** 29272 * Sets the active editor instance and fires the deactivate/activate events. 29273 * 29274 * @method setActive 29275 * @param {tinymce.Editor} editor Editor instance to set as the active instance. 29276 */ 29277 setActive: function(editor) { 29278 var activeEditor = this.activeEditor; 29279 29280 if (this.activeEditor != editor) { 29281 if (activeEditor) { 29282 activeEditor.fire('deactivate', {relatedTarget: editor}); 29283 } 29284 29285 editor.fire('activate', {relatedTarget: activeEditor}); 29286 } 29287 29288 this.activeEditor = editor; 29289 } 29290 }; 29291 29292 extend(EditorManager, Observable); 29293 29294 EditorManager.setup(); 29295 29296 // Export EditorManager as tinymce/tinymce in global namespace 29297 window.tinymce = window.tinyMCE = EditorManager; 29298 29299 return EditorManager; 29300}); 29301 29302// Included from: js/tinymce/classes/LegacyInput.js 29303 29304/** 29305 * LegacyInput.js 29306 * 29307 * Copyright, Moxiecode Systems AB 29308 * Released under LGPL License. 29309 * 29310 * License: http://www.tinymce.com/license 29311 * Contributing: http://www.tinymce.com/contributing 29312 */ 29313 29314define("tinymce/LegacyInput", [ 29315 "tinymce/EditorManager", 29316 "tinymce/util/Tools" 29317], function(EditorManager, Tools) { 29318 var each = Tools.each, explode = Tools.explode; 29319 29320 EditorManager.on('AddEditor', function(e) { 29321 var editor = e.editor; 29322 29323 editor.on('preInit', function() { 29324 var filters, fontSizes, dom, settings = editor.settings; 29325 29326 function replaceWithSpan(node, styles) { 29327 each(styles, function(value, name) { 29328 if (value) { 29329 dom.setStyle(node, name, value); 29330 } 29331 }); 29332 29333 dom.rename(node, 'span'); 29334 } 29335 29336 function convert(e) { 29337 dom = editor.dom; 29338 29339 if (settings.convert_fonts_to_spans) { 29340 each(dom.select('font,u,strike', e.node), function(node) { 29341 filters[node.nodeName.toLowerCase()](dom, node); 29342 }); 29343 } 29344 } 29345 29346 if (settings.inline_styles) { 29347 fontSizes = explode(settings.font_size_legacy_values); 29348 29349 filters = { 29350 font: function(dom, node) { 29351 replaceWithSpan(node, { 29352 backgroundColor: node.style.backgroundColor, 29353 color: node.color, 29354 fontFamily: node.face, 29355 fontSize: fontSizes[parseInt(node.size, 10) - 1] 29356 }); 29357 }, 29358 29359 u: function(dom, node) { 29360 replaceWithSpan(node, { 29361 textDecoration: 'underline' 29362 }); 29363 }, 29364 29365 strike: function(dom, node) { 29366 replaceWithSpan(node, { 29367 textDecoration: 'line-through' 29368 }); 29369 } 29370 }; 29371 29372 editor.on('PreProcess SetContent', convert); 29373 } 29374 }); 29375 }); 29376}); 29377 29378// Included from: js/tinymce/classes/util/XHR.js 29379 29380/** 29381 * XHR.js 29382 * 29383 * Copyright, Moxiecode Systems AB 29384 * Released under LGPL License. 29385 * 29386 * License: http://www.tinymce.com/license 29387 * Contributing: http://www.tinymce.com/contributing 29388 */ 29389 29390/** 29391 * This class enables you to send XMLHTTPRequests cross browser. 29392 * @class tinymce.util.XHR 29393 * @mixes tinymce.util.Observable
29394 * @static 29395 * @example 29396 * // Sends a low level Ajax request 29397 * tinymce.util.XHR.send({ 29398 * url: 'someurl', 29399 * success: function(text) { 29400 * console.debug(text); 29401 * } 29402 * }); 29403 * 29404 * // Add custom header to XHR request 29405 * tinymce.util.XHR.on('beforeSend', function(e) { 29406 * e.xhr.setRequestHeader('X-Requested-With', 'Something'); 29407 * }); 29408 */ 29409define("tinymce/util/XHR", [ 29410 "tinymce/util/Observable", 29411 "tinymce/util/Tools" 29412], function(Observable, Tools) { 29413 var XHR = { 29414 /** 29415 * Sends a XMLHTTPRequest. 29416 * Consult the Wiki for details on what settings this method takes. 29417 * 29418 * @method send 29419 * @param {Object} settings Object will target URL, callbacks and other info needed to make the request. 29420 */ 29421 send: function(settings) { 29422 var xhr, count = 0; 29423 29424 function ready() { 29425 if (!settings.async || xhr.readyState == 4 || count++ > 10000) { 29426 if (settings.success && count < 10000 && xhr.status == 200) { 29427 settings.success.call(settings.success_scope, '' + xhr.responseText, xhr, settings); 29428 } else if (settings.error) { 29429 settings.error.call(settings.error_scope, count > 10000 ? 'TIMED_OUT' : 'GENERAL', xhr, settings); 29430 } 29431 29432 xhr = null; 29433 } else { 29434 setTimeout(ready, 10); 29435 } 29436 } 29437 29438 // Default settings 29439 settings.scope = settings.scope || this; 29440 settings.success_scope = settings.success_scope || settings.scope; 29441 settings.error_scope = settings.error_scope || settings.scope; 29442 settings.async = settings.async === false ? false : true; 29443 settings.data = settings.data || ''; 29444 29445 xhr = new XMLHttpRequest(); 29446 29447 if (xhr) { 29448 if (xhr.overrideMimeType) { 29449 xhr.overrideMimeType(settings.content_type); 29450 } 29451 29452 xhr.open(settings.type || (settings.data ? 'POST' : 'GET'), settings.url, settings.async); 29453 29454 if (settings.crossDomain) { 29455 xhr.withCredentials = true; 29456 } 29457 29458 if (settings.content_type) { 29459 xhr.setRequestHeader('Content-Type', settings.content_type); 29460 } 29461 29462 xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 29463 29464 xhr = XHR.fire('beforeSend', {xhr: xhr, settings: settings}).xhr; 29465 xhr.send(settings.data); 29466 29467 // Syncronous request 29468 if (!settings.async) { 29469 return ready(); 29470 } 29471 29472 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE 29473 setTimeout(ready, 10); 29474 } 29475 } 29476 }; 29477 29478 Tools.extend(XHR, Observable); 29479 29480 return XHR; 29481}); 29482 29483// Included from: js/tinymce/classes/util/JSON.js 29484 29485/** 29486 * JSON.js 29487 * 29488 * Copyright, Moxiecode Systems AB 29489 * Released under LGPL License. 29490 * 29491 * License: http://www.tinymce.com/license 29492 * Contributing: http://www.tinymce.com/contributing 29493 */ 29494 29495/** 29496 * JSON parser and serializer class. 29497 * 29498 * @class tinymce.util.JSON 29499 * @static 29500 * @example 29501 * // JSON parse a string into an object 29502 * var obj = tinymce.util.JSON.parse(somestring); 29503 * 29504 * // JSON serialize a object into an string 29505 * var str = tinymce.util.JSON.serialize(obj); 29506 */ 29507define("tinymce/util/JSON", [], function() { 29508 function serialize(o, quote) { 29509 var i, v, t, name; 29510 29511 quote = quote || '"'; 29512 29513 if (o === null) { 29514 return 'null'; 29515 } 29516 29517 t = typeof o; 29518 29519 if (t == 'string') { 29520 v = '\bb\tt\nn\ff\rr\""\'\'\\\\'; 29521 29522 return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) { 29523 // Make sure single quotes never get encoded inside double quotes for JSON compatibility 29524 if (quote === '"' && a === "'") { 29525 return a; 29526 } 29527 29528 i = v.indexOf(b); 29529 29530 if (i + 1) { 29531 return '\\' + v.charAt(i + 1); 29532 } 29533 29534 a = b.charCodeAt().toString(16); 29535 29536 return '\\u' + '0000'.substring(a.length) + a; 29537 }) + quote; 29538 } 29539 29540 if (t == 'object') { 29541 if (o.hasOwnProperty && Object.prototype.toString.call(o) === '[object Array]') { 29542 for (i = 0, v = '['; i < o.length; i++) { 29543 v += (i > 0 ? ',' : '') + serialize(o[i], quote); 29544 } 29545 29546 return v + ']'; 29547 } 29548 29549 v = '{'; 29550 29551 for (name in o) { 29552 if (o.hasOwnProperty(name)) { 29553 v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + 29554 quote + ':' + serialize(o[name], quote) : ''; 29555 } 29556 } 29557 29558 return v + '}'; 29559 } 29560 29561 return '' + o; 29562 } 29563 29564 return { 29565 /** 29566 * Serializes the specified object as a JSON string. 29567 * 29568 * @method serialize 29569 * @param {Object} obj Object to serialize as a JSON string. 29570 * @param {String}
29570 quote Optional quote string defaults to ". 29571 * @return {string} JSON string serialized from input. 29572 */ 29573 serialize: serialize, 29574 29575 /** 29576 * Unserializes/parses the specified JSON string into a object. 29577 * 29578 * @method parse 29579 * @param {string} s JSON String to parse into a JavaScript object. 29580 * @return {Object} Object from input JSON string or undefined if it failed. 29581 */ 29582 parse: function(text) { 29583 try { 29584 // Trick uglify JS 29585 return window[String.fromCharCode(101) + 'val']('(' + text + ')'); 29586 } catch (ex) { 29587 // Ignore 29588 } 29589 } 29590 29591 /**#@-*/ 29592 }; 29593}); 29594 29595// Included from: js/tinymce/classes/util/JSONRequest.js 29596 29597/** 29598 * JSONRequest.js 29599 * 29600 * Copyright, Moxiecode Systems AB 29601 * Released under LGPL License. 29602 * 29603 * License: http://www.tinymce.com/license 29604 * Contributing: http://www.tinymce.com/contributing 29605 */ 29606 29607/** 29608 * This class enables you to use JSON-RPC to call backend methods. 29609 * 29610 * @class tinymce.util.JSONRequest 29611 * @example 29612 * var json = new tinymce.util.JSONRequest({ 29613 * url: 'somebackend.php' 29614 * }); 29615 * 29616 * // Send RPC call 1 29617 * json.send({ 29618 * method: 'someMethod1', 29619 * params: ['a', 'b'], 29620 * success: function(result) { 29621 * console.dir(result); 29622 * } 29623 * }); 29624 * 29625 * // Send RPC call 2 29626 * json.send({ 29627 * method: 'someMethod2', 29628 * params: ['a', 'b'], 29629 * success: function(result) { 29630 * console.dir(result); 29631 * } 29632 * }); 29633 */ 29634define("tinymce/util/JSONRequest", [ 29635 "tinymce/util/JSON", 29636 "tinymce/util/XHR", 29637 "tinymce/util/Tools" 29638], function(JSON, XHR, Tools) { 29639 var extend = Tools.extend; 29640 29641 function JSONRequest(settings) { 29642 this.settings = extend({}, settings); 29643 this.count = 0; 29644 } 29645 29646 /** 29647 * Simple helper function to send a JSON-RPC request without the need to initialize an object. 29648 * Consult the Wiki API documentation for more details on what you can pass to this function. 29649 * 29650 * @method sendRPC 29651 * @static 29652 * @param {Object} o Call object where there are three field id, method and params this object should also contain callbacks etc. 29653 */ 29654 JSONRequest.sendRPC = function(o) { 29655 return new JSONRequest().send(o); 29656 }; 29657 29658 JSONRequest.prototype = { 29659 /** 29660 * Sends a JSON-RPC call. Consult the Wiki API documentation for more details on what you can pass to this function. 29661 * 29662 * @method send 29663 * @param {Object} args Call object where there are three field id, method and params this object should also contain callbacks etc. 29664 */ 29665 send: function(args) { 29666 var ecb = args.error, scb = args.success; 29667 29668 args = extend(this.settings, args); 29669 29670 args.success = function(c, x) { 29671 c = JSON.parse(c); 29672 29673 if (typeof(c) == 'undefined') { 29674 c = { 29675 error : 'JSON Parse error.' 29676 }; 29677 } 29678 29679 if (c.error) { 29680 ecb.call(args.error_scope || args.scope, c.error, x); 29681 } else { 29682 scb.call(args.success_scope || args.scope, c.result); 29683 } 29684 }; 29685 29686 args.error = function(ty, x) { 29687 if (ecb) { 29688 ecb.call(args.error_scope || args.scope, ty, x); 29689 } 29690 }; 29691 29692 args.data = JSON.serialize({ 29693 id: args.id || 'c' + (this.count++), 29694 method: args.method, 29695 params: args.params 29696 }); 29697 29698 // JSON content type for Ruby on rails. Bug: #1883287 29699 args.content_type = 'application/json'; 29700 29701 XHR.send(args); 29702 } 29703 }; 29704 29705 return JSONRequest; 29706}); 29707 29708// Included from: js/tinymce/classes/util/JSONP.js 29709 29710/** 29711 * JSONP.js 29712 * 29713 * Copyright, Moxiecode Systems AB 29714 * Released under LGPL License. 29715 * 29716 * License: http://www.tinymce.com/license 29717 * Contributing: http://www.tinymce.com/contributing 29718 */ 29719 29720define("tinymce/util/JSONP", [ 29721 "tinymce/dom/DOMUtils" 29722], function(DOMUtils) { 29723 return { 29724 callbacks: {}, 29725 count: 0, 29726 29727 send: function(settings) { 29728 var self = this, dom = DOMUtils.DOM, count = settings.count !== undefined ? settings.count : self.count; 29729 var id = 'tinymce_jsonp_' + count; 29730 29731 self.callbacks[count] = function(json) { 29732 dom.remove(id); 29733 delete self.callbacks[count]; 29734 29735 settings.callback(json); 29736 }; 29737 29738 dom.add(dom.doc.body, 'script', { 29739 id: id, 29740 src: settings.url, 29741 type: 'text/javascript' 29742 }); 29743 29744 self.count++; 29745 } 29746 }; 29747}); 29748 29749// Included from: js/tinymce/classes/util/LocalStorage.js 29750 29751/** 29752 * LocalStorage.js 29753 * 29754 * Copyright, Moxiecode Systems AB 29755 * Released under LGPL License. 29756 * 29757 * License: http://www.tinymce.com/license 29758 * Contributing: http://www.tinymce.com/contributing 29759 */ 29760 29761/** 29762 * This class will simulate LocalStorage on IE 7 and return the native version on modern browsers. 29763 * Storage is done using userData on IE 7 and a special serialization format. The format is designed 29764 * to be as small as possible by making sure that the keys and values doesn't need to be encoded. This 29765 * makes it possible to store for example HTML data. 29766 * 29767 * Storage format for userData: 29768 * <base 32 key length>,<key string>,<base 32 value length>,<value>,... 29769 * 29770 * For example this data key1=value1,key2=value2 would be: 29771 * 4,key1,6,value1,4,key2,6,value2 29772 * 29773 * @class tinymce.util.LocalStorage
29774 * @static 29775 * @version 4.0 29776 * @example 29777 * tinymce.util.LocalStorage.setItem('key', 'value'); 29778 * var value = tinymce.util.LocalStorage.getItem('key'); 29779 */ 29780define("tinymce/util/LocalStorage", [], function() { 29781 var LocalStorage, storageElm, items, keys, userDataKey, hasOldIEDataSupport; 29782 29783 // Check for native support 29784 try { 29785 if (window.localStorage) { 29786 return localStorage; 29787 } 29788 } catch (ex) { 29789 // Ignore 29790 } 29791 29792 userDataKey = "tinymce"; 29793 storageElm = document.documentElement; 29794 hasOldIEDataSupport = !!storageElm.addBehavior; 29795 29796 if (hasOldIEDataSupport) { 29797 storageElm.addBehavior('#default#userData'); 29798 } 29799 29800 /** 29801 * Gets the keys names and updates LocalStorage.length property. Since IE7 doesn't have any getters/setters. 29802 */ 29803 function updateKeys() { 29804 keys = []; 29805 29806 for (var key in items) { 29807 keys.push(key); 29808 } 29809 29810 LocalStorage.length = keys.length; 29811 } 29812 29813 /** 29814 * Loads the userData string and parses it into the items structure. 29815 */ 29816 function load() { 29817 var key, data, value, pos = 0; 29818 29819 items = {}; 29820 29821 // localStorage can be disabled on WebKit/Gecko s
29821o make a dummy storage 29822 if (!hasOldIEDataSupport) { 29823 return; 29824 } 29825 29826 function next(end) { 29827 var value, nextPos; 29828 29829 nextPos = end !== undefined ? pos + end : data.indexOf(',', pos); 29830 if (nextPos === -1 || nextPos > data.length) { 29831 return null; 29832 } 29833 29834 value = data.substring(pos, nextPos); 29835 pos = nextPos + 1; 29836 29837 return value; 29838 } 29839 29840 storageElm.load(userDataKey); 29841 data = storageElm.getAttribute(userDataKey) || ''; 29842 29843 do { 29844 var offset = next(); 29845 if (offset === null) { 29846 break; 29847 } 29848 29849 key = next(parseInt(offset, 32) || 0); 29850 if (key !== null) { 29851 offset = next(); 29852 if (offset === null) { 29853 break; 29854 } 29855 29856 value = next(parseInt(offset, 32) || 0); 29857 29858 if (key) { 29859 items[key] = value; 29860 } 29861 } 29862 } while (key !== null); 29863 29864 updateKeys(); 29865 } 29866 29867 /** 29868 * Saves the items structure into a the userData format. 29869 */ 29870 function save() { 29871 var value, data = ''; 29872 29873 // localStorage can be disabled on WebKit/Gecko so make a dummy storage 29874 if (!hasOldIEDataSupport) { 29875 return; 29876 } 29877 29878 for (var key in items) { 29879 value = items[key]; 29880 data += (data ? ',' : '') + key.length.toString(32) + ',' + key + ',' + value.length.toString(32) + ',' + value; 29881 } 29882 29883 storageElm.setAttribute(userDataKey, data); 29884 29885 try { 29886 storageElm.save(userDataKey); 29887 } catch (ex) { 29888 // Ignore disk full 29889 } 29890 29891 updateKeys(); 29892 } 29893 29894 LocalStorage = { 29895 /** 29896 * Length of the number of items in storage. 29897 * 29898 * @property length 29899 * @type Number 29900 * @return {Number} Number of items in storage. 29901 */ 29902 //length:0, 29903 29904 /** 29905 * Returns the key name by index. 29906 * 29907 * @method key 29908 * @param {Number} index Index of key to return. 29909 * @return {String} Key value or null if it wasn't found. 29910 */ 29911 key: function(index) { 29912 return keys[index]; 29913 }, 29914 29915 /** 29916 * Returns the value if the specified key or null if it wasn't found. 29917 * 29918 * @method getItem 29919 * @param {String} key Key of item to retrive. 29920 * @return {String} Value of the specified item or null if it wasn't found. 29921 */ 29922 getItem: function(key) { 29923 return key in items ? items[key] : null; 29924 }, 29925 29926 /** 29927 * Sets the value of the specified item by it's key. 29928 * 29929 * @method setItem 29930 * @param {String} key Key of the item to set. 29931 * @param {String} value Value of the item to set. 29932 */ 29933 setItem: function(key, value) { 29934 items[key] = "" + value; 29935 save(); 29936 }, 29937 29938 /** 29939 * Removes the specified item by key. 29940 * 29941 * @method removeItem 29942 * @param {String} key Key of item to remove. 29943 */ 29944 removeItem: function(key) { 29945 delete items[key]; 29946 save(); 29947 }, 29948 29949 /** 29950 * Removes all items. 29951 * 29952 * @method clear 29953 */ 29954 clear: function() { 29955 items = {}; 29956 save(); 29957 } 29958 }; 29959 29960 load(); 29961 29962 return LocalStorage; 29963}); 29964 29965// Included from: js/tinymce/classes/Compat.js 29966 29967/** 29968 * Compat.js 29969 * 29970 * Copyright, Moxiecode Systems AB 29971 * Released under LGPL License. 29972 * 29973 * License: http://www.tinymce.com/license 29974 * Contributing: http://www.tinymce.com/contributing 29975 */ 29976 29977/** 29978 * TinyMCE core class. 29979 * 29980 * @static 29981 * @class tinymce 29982 * @borrow-members tinymce.EditorManager 29983 * @borrow-members tinymce.util.Tools 29984 */ 29985define("tinymce/Compat", [ 29986 "tinymce/dom/DOMUtils", 29987 "tinymce/dom/EventUtils", 29988 "tinymce/dom/ScriptLoader", 29989 "tinymce/AddOnManager", 29990 "tinymce/util/Tools", 29991 "tinymce/Env" 29992], function(DOMUtils, EventUtils, ScriptLoader, AddOnManager, Tools, Env) { 29993 var tinymce = window.tinymce; 29994 29995 /** 29996 * @property {tinymce.dom.DOMUtils} DOM Global DOM instance. 29997 * @property {tinymce.dom.ScriptLoader} ScriptLoader Global ScriptLoader instance. 29998 * @property {tinymce.AddOnManager} PluginManager Global PluginManager instance. 29999 * @property {tinymce.AddOnManager} ThemeManager Global ThemeManager instance. 30000 */ 30001 tinymce.DOM = DOMUtils.DOM; 30002 tinymce.ScriptLoader = ScriptLoader.ScriptLoader; 30003 tinymce.PluginManager = AddOnManager.PluginManager; 30004 tinymce.ThemeManager = AddOnManager.ThemeManager; 30005 30006 tinymce.dom = tinymce.dom || {}; 30007 tinymce.dom.Event = EventUtils.Event; 30008 30009 Tools.each(Tools, function(func, key) { 30010 tinymce[key] = func; 30011 }); 30012 30013 Tools.each('isOpera isWebKit isIE isGecko isMac'.split(' '), function(name) { 30014 tinymce[name] = Env[name.substr(2).toLowerCase()]; 30015 }); 30016 30017 return {}; 30018}); 30019
30020// Describe the different namespaces 30021 30022/** 30023 * Root level namespace this contains classes directly releated to the TinyMCE editor. 30024 * 30025 * @namespace tinymce 30026 */ 30027 30028/** 30029 * Contains classes for handling the browsers DOM. 30030 * 30031 * @namespace tinymce.dom 30032 */ 30033 30034/** 30035 * Contains html parser and serializer logic. 30036 * 30037 * @namespace tinymce.html 30038 */ 30039 30040/** 30041 * Contains the different UI types such as buttons, listboxes etc. 30042 * 30043 * @namespace tinymce.ui 30044 */ 30045 30046/** 30047 * Contains various utility classes such as json parser, cookies etc. 30048 * 30049 * @namespace tinymce.util 30050 */ 30051 30052// Included from: js/tinymce/classes/ui/Layout.js 30053 30054/** 30055 * Layout.js 30056 * 30057 * Copyright, Moxiecode Systems AB 30058 * Released under LGPL License. 30059 * 30060 * License: http://www.tinymce.com/license 30061 * Contributing: http://www.tinymce.com/contributing 30062 */ 30063 30064/** 30065 * Base layout manager class. 30066 * 30067 * @class tinymce.ui.Layout 30068 */ 30069define("tinymce/ui/Layout", [ 30070 "tinymce/util/Class", 30071 "tinymce/util/Tools" 30072], function(Class, Tools) { 30073 "use strict"; 30074 30075 return Class.extend({ 30076 Defaults: { 30077 firstControlClass: 'first', 30078 lastControlClass: 'last' 30079 }, 30080 30081 /** 30082 * Constructs a layout instance with the specified settings. 30083 * 30084 * @constructor 30085 * @param {Object} settings Name/value object with settings. 30086 */ 30087 init: function(settings) { 30088 this.settings = Tools.extend({}, this.Defaults, settings); 30089 }, 30090 30091 /** 30092 * This method gets invoked before the layout renders the controls. 30093 * 30094 * @method preRender 30095 * @param {tinymce.ui.Container} container Container instance to preRender. 30096 */ 30097 preRender: function(container) { 30098 container.addClass(this.settings.containerClass, 'body'); 30099 }, 30100 30101 /** 30102 * Applies layout classes to the container. 30103 * 30104 * @private 30105 */ 30106 applyClasses: function(container) { 30107 var self = this, settings = self.settings, items, firstClass, lastClass; 30108 30109 items = container.items().filter(':visible'); 30110 firstClass = settings.firstControlClass; 30111 lastClass = settings.lastControlClass; 30112 30113 items.each(function(item) { 30114 item.removeClass(firstClass).removeClass(lastClass); 30115 30116 if (settings.controlClass) { 30117 item.addClass(settings.controlClass); 30118 } 30119 }); 30120 30121 items.eq(0).addClass(firstClass); 30122 items.eq(-1).addClass(lastClass); 30123 }, 30124 30125 /** 30126 * Renders the specified container and any layout specific HTML. 30127 * 30128 * @method renderHtml 30129 * @param {tinymce.ui.Container} container Container to render HTML for. 30130 */ 30131 renderHtml: function(container) { 30132 var self = this, settings = self.settings, items, html = ''; 30133 30134 items = container.items(); 30135 items.eq(0).addClass(settings.firstControlClass); 30136 items.eq(-1).addClass(settings.lastControlClass); 30137 30138 items.each(function(item) { 30139 if (settings.controlClass) { 30140 item.addClass(settings.controlClass); 30141 } 30142 30143 html += item.renderHtml(); 30144 }); 30145 30146 return html; 30147 }, 30148 30149 /** 30150 * Recalculates the positions of the controls in the specified container. 30151 * 30152 * @method recalc 30153 * @param {tinymce.ui.Container} container Container instance to recalc. 30154 */ 30155 recalc: function() { 30156 }, 30157 30158 /** 30159 * This method gets invoked after the layout renders the controls. 30160 * 30161 * @method postRender 30162 * @param {tinymce.ui.Container} container Container instance to postRender. 30163 */ 30164 postRender: function() { 30165 } 30166 }); 30167}); 30168 30169// Included from: js/tinymce/classes/ui/AbsoluteLayout.js 30170 30171/** 30172 * AbsoluteLayout.js 30173 * 30174 * Copyright, Moxiecode Systems AB 30175 * Released under LGPL License. 30176 * 30177 * License: http://www.tinymce.com/license 30178 * Contributing: http://www.tinymce.com/contributing 30179 */ 30180 30181/** 30182 * LayoutManager for absolute positioning. This layout manager is more of 30183 * a base class for other layouts but can be created and used directly. 30184 * 30185 * @-x-less AbsoluteLayout.less 30186 * @class tinymce.ui.AbsoluteLayout 30187 * @extends tinymce.ui.Layout 30188 */ 30189define("tinymce/ui/AbsoluteLayout", [ 30190 "tinymce/ui/Layout" 30191], function(Layout) { 30192 "use strict"; 30193 30194 return Layout.extend({ 30195 Defaults: { 30196 containerClass: 'abs-layout', 30197 controlClass: 'abs-layout-item' 30198 }, 30199 30200 /** 30201 * Recalculates the positions of the controls in the specified container. 30202 * 30203 * @method recalc 30204 * @param {tinymce.ui.Container} container Container instance to recalc. 30205 */ 30206 recalc: function(container) { 30207 container.items().filter(':visible').each(function(ctrl) { 30208 var settings = ctrl.settings; 30209 30210 ctrl.layoutRect({ 30211 x: settings.x, 30212 y: settings.y, 30213 w: settings.w, 30214 h: settings.h 30215 }); 30216 30217 if (ctrl.recalc) { 30218 ctrl.recalc(); 30219 } 30220 }); 30221 }, 30222 30223 /** 30224 * Renders the specified container and any layout specific HTML. 30225 * 30226 * @method renderHtml 30227 * @param {tinymce.ui.Container} container Container to render HTML for. 30228 */ 30229 renderHtml: function(container) { 30230 return '<div id="' + container._id + '-absend" class="' + container.classPrefix + 'abs-end"></div>' + this._super(container); 30231 } 30232 }); 30233}); 30234 30235// Included from: js/tinymce/classes/ui/Tooltip.js 30236 30237/** 30238 * Tooltip.js 30239 * 30240 * Copyright, Moxiecode Systems AB 30241 * Released under LGPL License. 30242 * 30243 * License: http://www.tinymce.com/license 30244 * Contributing: http://www.tinymce.com/contributing 30245 */ 30246 30247/** 30248 * Creates a tooltip instance. 30249 * 30250 * @-x-less ToolTip.less 30251 * @class tinymce.ui.ToolTip 30252 * @extends tinymce.ui.Control 30253 * @mixes tinymce.ui.Movable 30254 */
30255define("tinymce/ui/Tooltip", [ 30256 "tinymce/ui/Control", 30257 "tinymce/ui/Movable" 30258], function(Control, Movable) { 30259 return Control.extend({ 30260 Mixins: [Movable], 30261 30262 Defaults: { 30263 classes: 'widget tooltip tooltip-n' 30264 }, 30265 30266 /** 30267 * Sets/gets the current label text. 30268 * 30269 * @method text 30270 * @param {String} [text] New label text. 30271 * @return {String|tinymce.ui.Tooltip} Current text or current label instance. 30272 */ 30273 text: function(value) { 30274 var self = this; 30275 30276 if (typeof(value) != "undefined") { 30277 self._value = value; 30278 30279 if (self._rendered) { 30280 self.getEl().lastChild.innerHTML = self.encode(value); 30281 } 30282 30283 return self; 30284 } 30285 30286 return self._value; 30287 }, 30288 30289 /** 30290 * Renders the control as a HTML string. 30291 * 30292 * @method renderHtml 30293 * @return {String} HTML representing the control. 30294 */ 30295 renderHtml: function() { 30296 var self = this, prefix = self.classPrefix; 30297 30298 return ( 30299 '<div id="' + self._id + '" class="' + self.classes() + '" role="presentation">' + 30300 '<div class="' + prefix + 'tooltip-arrow"></div>' + 30301 '<div class="' + prefix + 'tooltip-inner">' + self.encode(self._text) + '</div>' + 30302 '</div>' 30303 ); 30304 }, 30305 30306 /** 30307 * Repaints the control after a layout operation. 30308 * 30309 * @method repaint 30310 */ 30311 repaint: function() { 30312 var self = this, style, rect; 30313 30314 style = self.getEl().style; 30315 rect = self._layoutRect; 30316 30317 style.left = rect.x + 'px'; 30318 style.top = rect.y + 'px'; 30319 style.zIndex = 0xFFFF + 0xFFFF; 30320 } 30321 }); 30322}); 30323 30324// Included from: js/tinymce/classes/ui/Widget.js 30325 30326/** 30327 * Widget.js 30328 * 30329 * Copyright, Moxiecode Systems AB 30330 * Released under LGPL License. 30331 * 30332 * License: http://www.tinymce.com/license 30333 * Contributing: http://www.tinymce.com/contributing 30334 */ 30335 30336/** 30337 * Widget base class a widget is a control that has a tooltip and s
30337ome basic states. 30338 * 30339 * @class tinymce.ui.Widget 30340 * @extends tinymce.ui.Control 30341 */ 30342define("tinymce/ui/Widget", [ 30343 "tinymce/ui/Control", 30344 "tinymce/ui/Tooltip" 30345], function(Control, Tooltip) { 30346 "use strict"; 30347 30348 var tooltip; 30349 30350 var Widget = Control.extend({ 30351 /** 30352 * Constructs a instance with the specified settings. 30353 * 30354 * @constructor 30355 * @param {Object} settings Name/value object with settings. 30356 * @setting {String} tooltip Tooltip text to display when hovering. 30357 * @setting {Boolean} autofocus True if the control should be focused when rendered. 30358 * @setting {String} text Text to display inside widget. 30359 */ 30360 init: function(settings) { 30361 var self = this; 30362 30363 self._super(settings); 30364 settings = self.settings; 30365 self.canFocus = true; 30366 30367 if (settings.tooltip && Widget.tooltips !== false) { 30368 self.on('mouseenter', function(e) { 30369 var tooltip = self.tooltip().moveTo(-0xFFFF); 30370 30371 if (e.control == self) { 30372 var rel = tooltip.text(settings.tooltip).show().testMoveRel(self.getEl(), ['bc-tc', 'bc-tl', 'bc-tr']); 30373 30374 tooltip.toggleClass('tooltip-n', rel == 'bc-tc'); 30375 tooltip.toggleClass('tooltip-nw', rel == 'bc-tl'); 30376 tooltip.toggleClass('tooltip-ne', rel == 'bc-tr'); 30377 30378 tooltip.moveRel(self.getEl(), rel); 30379 } else { 30380 tooltip.hide(); 30381 } 30382 }); 30383 30384 self.on('mouseleave mousedown click', function() { 30385 self.tooltip().hide(); 30386 }); 30387 } 30388 30389 self.aria('label', settings.ariaLabel || settings.tooltip); 30390 }, 30391 30392 /** 30393 * Returns the current tooltip instance. 30394 * 30395 * @method tooltip 30396 * @return {tinymce.ui.Tooltip} Tooltip instance. 30397 */ 30398 tooltip: function() { 30399 if (!tooltip) { 30400 tooltip = new Tooltip({type: 'tooltip'}); 30401 tooltip.renderTo(); 30402 } 30403 30404 return tooltip; 30405 }, 30406 30407 /** 30408 * Sets/gets the active state of the widget. 30409 * 30410 * @method active 30411 * @param {Boolean} [state] State if the control is active. 30412 * @return {Boolean|tinymce.ui.Widget} True/false or current widget instance. 30413 */ 30414 active: function(state) { 30415 var self = this, undef; 30416 30417 if (state !== undef) { 30418 self.aria('pressed', state); 30419 self.toggleClass('active', state); 30420 } 30421 30422 return self._super(state); 30423 }, 30424 30425 /** 30426 * Sets/gets the disabled state of the widget. 30427 * 30428 * @method disabled 30429 * @param {Boolean} [state] State if the control is disabled. 30430 * @return {Boolean|tinymce.ui.Widget} True/false or current widget instance. 30431 */ 30432 disabled: function(state) { 30433 var self = this, undef; 30434 30435 if (state !== undef) { 30436 self.aria('disabled', state); 30437 self.toggleClass('disabled', state); 30438 } 30439 30440 return self._super(state); 30441 }, 30442 30443 /** 30444 * Called after the control has been rendered. 30445 * 30446 * @method postRender 30447 */ 30448 postRender: function() { 30449 var self = this, settings = self.settings; 30450 30451 self._rendered = true; 30452 30453 self._super(); 30454 30455 if (!self.parent() && (settings.width || settings.height)) { 30456 self.initLayoutRect(); 30457 self.repaint(); 30458 } 30459 30460 if (settings.autofocus) { 30461 self.focus(); 30462 } 30463 }, 30464 30465 /** 30466 * Removes the current control from DOM and from UI collections. 30467 * 30468 * @method remove 30469 * @return {tinymce.ui.Control} Current control instance. 30470 */ 30471 remove: function() { 30472 this._super(); 30473 30474 if (tooltip) { 30475 tooltip.remove(); 30476 tooltip = null; 30477 } 30478 } 30479 }); 30480 30481 return Widget; 30482}); 30483 30484// Included from: js/tinymce/classes/ui/Button.js 30485 30486/** 30487 * Button.js 30488 * 30489 * Copyright, Moxiecode Systems AB 30490 * Released under LGPL License. 30491 * 30492 * License: http://www.tinymce.com/license 30493 * Contributing: http://www.tinymce.com/contributing 30494 */ 30495 30496/** 30497 * This class is used to create buttons. You can create them directly or through the Factory. 30498 * 30499 * @example 30500 * // Create and render a button to the body element 30501 * tinymce.ui.Factory.create({ 30502 * type: 'button', 30503 * text: 'My button' 30504 * }).renderTo(document.body); 30505 * 30506 * @-x-less Button.less 30507 * @class tinymce.ui.Button 30508 * @extends tinymce.ui.Widget 30509 */ 30510define("tinymce/ui/Button", [ 30511 "tinymce/ui/Widget" 30512], function(Widget) { 30513 "use strict"; 30514 30515 return Widget.extend({ 30516 Defaults: { 30517 classes: "widget btn", 30518 role: "button" 30519 }, 30520 30521 /** 30522 * Constructs a new button instance with the specified settings. 30523 * 30524 * @constructor 30525 * @param {Object} settings Name/value object with settings.
30526 * @setting {String} size Size of the button small|medium|large. 30527 * @setting {String} image Image to use for icon. 30528 * @setting {String} icon Icon to use for button. 30529 */ 30530 init: function(settings) { 30531 var self = this, size; 30532 30533 self.on('click mousedown', function(e) { 30534 e.preventDefault(); 30535 }); 30536 30537 self._super(settings); 30538 size = settings.size; 30539 30540 if (settings.subtype) { 30541 self.addClass(settings.subtype); 30542 } 30543 30544 if (size) { 30545 self.addClass('btn-' + size); 30546 } 30547 }, 30548 30549 /** 30550 * Sets/gets the current button icon. 30551 * 30552 * @method icon 30553 * @param {String} [icon] New icon identifier. 30554 * @return {String|tinymce.ui.MenuButton} Current icon or current MenuButton instance. 30555 */ 30556 icon: function(icon) { 30557 var self = this, prefix = self.classPrefix; 30558 30559 if (typeof(icon) == 'undefined') { 30560 return self.settings.icon; 30561 } 30562 30563 self.settings.icon = icon; 30564 icon = icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : ''; 30565 30566 if (self._rendered) { 30567 var btnElm = self.getEl().firstChild, iconElm = btnElm.getElementsByTagName('i')[0]; 30568 30569 if (icon) { 30570 if (!iconElm || iconElm != btnElm.firstChild) { 30571 iconElm = document.createElement('i'); 30572 btnElm.insertBefore(iconElm, btnElm.firstChild); 30573 } 30574 30575 iconElm.className = icon; 30576 } else if (iconElm) { 30577 btnElm.removeChild(iconElm); 30578 } 30579 30580 self.text(self._text); // Set text again to fix whitespace between icon + text 30581 } 30582 30583 return self; 30584 }, 30585 30586 /** 30587 * Repaints the button for example after it's been resizes by a layout engine. 30588 * 30589 * @method repaint 30590 */ 30591 repaint: function() { 30592 var btnStyle = this.getEl().firstChild.style; 30593 30594 btnStyle.width = btnStyle.height = "100%"; 30595 30596 this._super(); 30597 }, 30598 30599 /** 30600 * Sets/gets the current button text. 30601 * 30602 * @method text 30603 * @param {String} [text] New button text. 30604 * @return {String|tinymce.ui.Button} Current text or current Button instance. 30605 */ 30606 text: function(text) { 30607 var self = this; 30608 30609 if (self._rendered) { 30610 var textNode = self.getEl().lastChild.lastChild; 30611 if (textNode) { 30612 textNode.data = self.translate(text); 30613 } 30614 } 30615 30616 return self._super(text); 30617 }, 30618 30619 /** 30620 * Renders the control as a HTML string. 30621 * 30622 * @method renderHtml 30623 * @return {String} HTML representing the control. 30624 */ 30625 renderHtml: function() { 30626 var self = this, id = self._id, prefix = self.classPrefix; 30627 var icon = self.settings.icon, image; 30628 30629 image = self.settings.image; 30630 if (image) { 30631 icon = 'none'; 30632 30633 // Support for [high dpi, low dpi] image sources 30634 if (typeof image != "string") { 30635 image = window.getSelection ? image[0] : image[1]; 30636 } 30637 30638 image = ' style="background-image: url(\'' + image + '\')"'; 30639 } else { 30640 image = ''; 30641 } 30642 30643 icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : ''; 30644 30645 return ( 30646 '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1" aria-labelledby="' + id + '">' + 30647 '<button role="presentation" type="button" tabindex="-1">' + 30648 (icon ? '<i class="' + icon + '"' + image + '></i>' : '') + 30649 (self._text ? (icon ? '\u00a0' : '') + self.encode(self._text) : '') + 30650 '</button>' + 30651 '</div>' 30652 ); 30653 } 30654 }); 30655}); 30656 30657// Included from: js/tinymce/classes/ui/ButtonGroup.js 30658 30659/** 30660 * ButtonGroup.js 30661 * 30662 * Copyright, Moxiecode Systems AB 30663 * Released under LGPL License. 30664 * 30665 * License: http://www.tinymce.com/license 30666 * Contributing: http://www.tinymce.com/contributing 30667 */ 30668 30669/** 30670 * This control enables you to put multiple buttons into a group. This is 30671 * useful when you want to combine similar toolbar buttons into a group. 30672 * 30673 * @example 30674 * // Create and render a buttongroup with two buttons to the body element 30675 * tinymce.ui.Factory.create({ 30676 * type: 'buttongroup', 30677 * items: [ 30678 * {text: 'Button A'}, 30679 * {text: 'Button B'} 30680 * ] 30681 * }).renderTo(document.body); 30682 * 30683 * @-x-less ButtonGroup.less 30684 * @class tinymce.ui.ButtonGroup 30685 * @extends tinymce.ui.Container 30686 */ 30687define("tinymce/ui/ButtonGroup", [ 30688 "tinymce/ui/Container" 30689], function(Container) { 30690 "use strict"; 30691 30692 return Container.extend({ 30693 Defaults: { 30694 defaultType: 'button', 30695 role: 'group' 30696 }, 30697 30698 /** 30699 * Renders the control as a HTML string. 30700 * 30701 * @method renderHtml 30702 * @return {String} HTML representing the control. 30703 */ 30704 renderHtml: function() { 30705 var self = this, layout = self._layout; 30706 30707 self.addClass('btn-group'); 30708 self.preRender(); 30709 layout.preRender(self); 30710 30711 return ( 30712 '<div id="' + self._id + '" class="' + self.classes() + '">' + 30713 '<div id="' + self._id + '-body">' + 30714 (self.settings.html || '') + layout.renderHtml(self) + 30715 '</div>' + 30716 '</div>' 30717 ); 30718 } 30719 }); 30720}); 30721 30722// Included from: js/tinymce/classes/ui/Checkbox.js 30723 30724/** 30725 * Checkbox.js 30726 * 30727 * Copyright, Moxiecode Systems AB 30728 * Released under LGPL License. 30729 * 30730 * License: http://www.tinymce.com/license 30731 * Contributing: http://www.tinymce.com/contributing 30732 */ 30733 30734/** 30735 * This control creates a custom checkbox. 30736 * 30737 * @example 30738 * // Create and render a checkbox to the body element 30739 * tinymce.ui.Factory.create({ 30740 * type: 'checkbox', 30741 * checked: true, 30742 * text: 'My checkbox' 30743 * }).renderTo(document.body); 30744 * 30745 * @-x-less Checkbox.less 30746 * @class tinymce.ui.Checkbox 30747 * @extends tinymce.ui.Widget 30748 */
30749define("tinymce/ui/Checkbox", [ 30750 "tinymce/ui/Widget" 30751], function(Widget) { 30752 "use strict"; 30753 30754 return Widget.extend({ 30755 Defaults: { 30756 classes: "checkbox", 30757 role: "checkbox", 30758 checked: false 30759 }, 30760 30761 /** 30762 * Constructs a new Checkbox instance with the specified settings. 30763 * 30764 * @constructor 30765 * @param {Object} settings Name/value object with settings. 30766 * @setting {Boolean} checked True if the checkbox should be checked by default. 30767 */ 30768 init: function(settings) { 30769 var self = this; 30770 30771 self._super(settings); 30772 30773 self.on('click mousedown', function(e) { 30774 e.preventDefault(); 30775 }); 30776 30777 self.on('click', function(e) { 30778 e.preventDefault(); 30779 30780 if (!self.disabled()) { 30781 self.checked(!self.checked()); 30782 } 30783 }); 30784 30785 self.checked(self.settings.checked); 30786 }, 30787 30788 /** 30789 * Getter/setter function for the checked state. 30790 * 30791 * @method checked 30792 * @param {Boolean} [state] State to be set. 30793 * @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation. 30794 */ 30795 checked: function(state) { 30796 var self = this; 30797 30798 if (typeof state != "undefined") { 30799 if (state) { 30800 self.addClass('checked'); 30801 } else { 30802 self.removeClass('checked'); 30803 } 30804 30805 self._checked = state; 30806 self.aria('checked', state); 30807 30808 return self; 30809 } 30810 30811 return self._checked; 30812 }, 30813 30814 /** 30815 * Getter/setter function for the value state. 30816 * 30817 * @method value 30818 * @param {Boolean} [state] State to be set. 30819 * @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation. 30820 */ 30821 value: function(state) { 30822 return this.checked(state); 30823 }, 30824 30825 /** 30826 * Renders the control as a HTML string. 30827 * 30828 * @method renderHtml 30829 * @return {String} HTML representing the control. 30830 */ 30831 renderHtml: function() { 30832 var self = this, id = self._id, prefix = self.classPrefix; 30833 30834 return ( 30835 '<div id="' + id + '" class="' + self.classes() + '" unselectable="on" aria-labelledby="' + id + '-al" tabindex="-1">' + 30836 '<i class="' + prefix + 'ico ' + prefix + 'i-checkbox"></i>' + 30837 '<span id="' + id + '-al" class="' + prefix + 'label">' + self.encode(self._text) + '</span>' + 30838 '</div>' 30839 ); 30840 } 30841 }); 30842}); 30843 30844// Included from: js/tinymce/classes/ui/ComboBox.js 30845 30846/** 30847 * ComboBox.js 30848 * 30849 * Copyright, Moxiecode Systems AB 30850 * Released under LGPL License. 30851 * 30852 * License: http://www.tinymce.com/license 30853 * Contributing: http://www.tinymce.com/contributing 30854 */ 30855 30856/** 30857 * This class creates a combobox control. Select box that you select a value from or 30858 * type a value into. 30859 * 30860 * @-x-less ComboBox.less 30861 * @class tinymce.ui.ComboBox 30862 * @extends tinymce.ui.Widget 30863 */ 30864define("tinymce/ui/ComboBox", [ 30865 "tinymce/ui/Widget", 30866 "tinymce/ui/Factory", 30867 "tinymce/ui/DomUtils" 30868], function(Widget, Factory, DomUtils) { 30869 "use strict"; 30870 30871 return Widget.extend({ 30872 /** 30873 * Constructs a new control instance with the specified settings. 30874 * 30875 * @constructor 30876 * @param {Object} settings Name/value object with settings. 30877 * @setting {String} placeholder Placeholder text to display. 30878 */ 30879 init: function(settings) { 30880 var self = this; 30881 30882 self._super(settings); 30883 self.addClass('combobox'); 30884 self.subinput = true; 30885 self.ariaTarget = 'inp'; // TODO: Figure out a better way 30886 30887 settings = self.settings; 30888 settings.menu = settings.menu || settings.values; 30889 30890 if (settings.menu) { 30891 settings.icon = 'caret'; 30892 } 30893 30894 self.on('click', function(e) { 30895 var elm = e.target, root = self.getEl(); 30896 30897 while (elm && elm != root) { 30898 if (elm.id && elm.id.indexOf('-open') != -1) { 30899 self.fire('action'); 30900 30901 if (settings.menu) { 30902 self.showMenu(); 30903 30904 if (e.aria) { 30905 self.menu.items()[0].focus(); 30906 } 30907 } 30908 } 30909 30910 elm = elm.parentNode; 30911 } 30912 }); 30913 30914 // TODO: Rework this 30915 self.on('keydown', function(e) { 30916 if (e.target.nodeName == "INPUT" && e.keyCode == 13) { 30917 self.parents().reverse().each(function(ctrl) { 30918 e.preventDefault(); 30919 self.fire('change'); 30920 30921 if (ctrl.hasEventListeners('submit') && ctrl.toJSON) { 30922 ctrl.fire('submit', {data: ctrl.toJSON()}); 30923 return false; 30924 } 30925 }); 30926 } 30927 }); 30928 30929 if (settings.placeholder) { 30930 self.addClass('placeholder'); 30931 30932 self.on('focusin', function() { 30933 if (!self._hasOnChange) { 30934 DomUtils.on(self.getEl('inp'), 'change', function() { 30935 self.fire('change'); 30936 }); 30937 30938 self._hasOnChange = true; 30939 } 30940 30941 if (self.hasClass('placeholder')) { 30942 self.getEl('inp').value = ''; 30943 self.removeClass('placeholder'); 30944 } 30945 }); 30946 30947 self.on('focusout', function() { 30948 if (self.value().length === 0) { 30949 self.getEl('inp').value = settings.placeholder; 30950 self.addClass('placeholder'); 30951 } 30952 }); 30953 } 30954 }, 30955 30956 showMenu: function() { 30957 var self = this, settings = self.settings, menu; 30958 30959 if (!self.menu) { 30960 menu = settings.menu || []; 30961 30962 // Is menu array then auto constuct menu control 30963 if (menu.length) { 30964 menu = { 30965 type: 'menu', 30966 items: menu 30967 }; 30968 } else { 30969 menu.type = menu.type || 'menu'; 30970 } 30971 30972 self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm()); 30973 self.fire('createmenu'); 30974 self.menu.reflow(); 30975 self.menu.on('cancel', function(e) { 30976 if (e.control === self.menu) { 30977 self.focus(); 30978 } 30979 }); 30980 30981 self.menu.on('show hide', function(e) { 30982 e.control.items().each(function(ctrl) { 30983 ctrl.active(ctrl.value() == self.value()); 30984 }); 30985 }).fire('show'); 30986 30987 self.menu.on('select', function(e) { 30988 self.value(e.control.value()); 30989 }); 30990 30991 self.on('focusin', function(e) { 30992 if (e.target.tagName.toUpperCase() == 'INPUT') { 30993 self.menu.hide(); 30994 } 30995 }); 30996 30997 self.aria('expanded', true); 30998 } 30999 31000 self.menu.show(); 31001 self.menu.layoutRect({w: self.layoutRect().w}); 31002 self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']); 31003 }, 31004 31005 /** 31006 * Getter/setter function for the control value. 31007 * 31008 * @method value 31009 * @param {String} [value] Value to be set. 31010 * @return {String|tinymce.ui.ComboBox} Value or self if it's a set operation. 31011 */ 31012 value: function(value) { 31013 var self = this; 31014 31015 if (typeof(value) != "undefined") { 31016 self._value = value; 31017 self.removeClass('placeholder'); 31018 31019 if (self._rendered) { 31020 self.getEl('inp').value = value; 31021 } 31022 31023 return self; 31024 } 31025 31026 if (self._rendered) { 31027 value = self.getEl('inp').value; 31028 31029 if (value != self.settings.placeholder) { 31030 return value; 31031 } 31032 31033 return ''; 31034 } 31035 31036 return self._value; 31037 }, 31038 31039 /** 31040 * Getter/setter function for the disabled state. 31041 * 31042 * @method value 31043 * @param {Boolean} [state] State to be set.
31044 * @return {Boolean|tinymce.ui.ComboBox} True/false or self if it's a set operation. 31045 */ 31046 disabled: function(state) { 31047 var self = this; 31048 31049 if (self._rendered && typeof(state) != 'undefined') { 31050 self.getEl('inp').disabled = state; 31051 } 31052 31053 return self._super(state); 31054 }, 31055 31056 /** 31057 * Focuses the input area of the control. 31058 * 31059 * @method focus 31060 */ 31061 focus: function() { 31062 this.getEl('inp').focus(); 31063 }, 31064 31065 /** 31066 * Repaints the control after a layout operation. 31067 * 31068 * @method repaint 31069 */ 31070 repaint: function() { 31071 var self = this, elm = self.getEl(), openElm = self.getEl('open'), rect = self.layoutRect(); 31072 var width, lineHeight; 31073 31074 if (openElm) { 31075 width = rect.w - DomUtils.getSize(openElm).width - 10; 31076 } else { 31077 width = rect.w - 10; 31078 } 31079 31080 // Detect old IE 7+8 add lineHeight to align caret vertically in the middle 31081 var doc = document; 31082 if (doc.all && (!doc.documentMode || doc.documentMode <= 8)) { 31083 lineHeight = (self.layoutRect().h - 2) + 'px'; 31084 } 31085 31086 DomUtils.css(elm.firstChild, { 31087 width: width, 31088 lineHeight: lineHeight 31089 }); 31090 31091 self._super(); 31092 31093 return self; 31094 }, 31095 31096 /** 31097 * Post render method. Called after the control has been rendered to the target. 31098 * 31099 * @method postRender 31100 * @return {tinymce.ui.ComboBox} Current combobox instance. 31101 */ 31102 postRender: function() { 31103 var self = this; 31104 31105 DomUtils.on(this.getEl('inp'), 'change', function() { 31106 self.fire('change'); 31107 }); 31108 31109 return self._super(); 31110 }, 31111 31112 remove: function() { 31113 DomUtils.off(this.getEl('inp')); 31114 this._super(); 31115 }, 31116 31117 /** 31118 * Renders the control as a HTML string. 31119 * 31120 * @method renderHtml 31121 * @return {String} HTML representing the control. 31122 */ 31123 renderHtml: function() { 31124 var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix; 31125 var value = settings.value || settings.placeholder || ''; 31126 var icon, text, openBtnHtml = '', extraAttrs = ''; 31127 31128 if ("spellcheck" in settings) { 31129 extraAttrs += ' spellcheck="' + settings.spellcheck + '"'; 31130 } 31131 31132 if (settings.maxLength) { 31133 extraAttrs += ' maxlength="' + settings.maxLength + '"'; 31134 } 31135 31136 if (settings.size) { 31137 extraAttrs += ' size="' + settings.size + '"'; 31138 } 31139 31140 if (settings.subtype) { 31141 extraAttrs += ' type="' + settings.subtype + '"'; 31142 } 31143 31144 if (self.disabled()) { 31145 extraAttrs += ' disabled="disabled"'; 31146 } 31147 31148 icon = settings.icon; 31149 if (icon && icon != 'caret') { 31150 icon = prefix + 'ico ' + prefix + 'i-' + settings.icon; 31151 } 31152 31153 text = self._text; 31154 31155 if (icon || text) { 31156 openBtnHtml = ( 31157 '<div id="' + id + '-open" class="' + prefix + 'btn ' + prefix + 'open" tabIndex="-1" role="button">' + 31158 '<button id="' + id + '-action" type="button" hidefocus="1" tabindex="-1">' + 31159 (icon != 'caret' ? '<i class="' + icon + '"></i>' : '<i class="' + prefix + 'caret"></i>') + 31160 (text ? (icon ? ' ' : '') + text : '') + 31161 '</button>' + 31162 '</div>' 31163 ); 31164 31165 self.addClass('has-open'); 31166 } 31167 31168 return ( 31169 '<div id="' + id + '" class="' + self.classes() + '">' + 31170 '<input id="' + id + '-inp" class="' + prefix + 'textbox ' + prefix + 'placeholder" value="' + 31171 value + '" hidefocus="1"' + extraAttrs + ' />' + 31172 openBtnHtml + 31173 '</div>' 31174 ); 31175 } 31176 }); 31177}); 31178 31179// Included from: js/tinymce/classes/ui/ColorBox.js 31180 31181/** 31182 * ColorBox.js 31183 * 31184 * Copyright, Moxiecode Systems AB 31185 * Released under LGPL License. 31186 * 31187 * License: http://www.tinymce.com/license 31188 * Contributing: http://www.tinymce.com/contributing 31189 */ 31190 31191/** 31192 * This widget lets you enter colors and browse for colors by pressing the color button. It also displays 31193 * a preview of the current color. 31194 * 31195 * @-x-less ColorBox.less 31196 * @class tinymce.ui.ColorBox 31197 * @extends tinymce.ui.ComboBox 31198 */ 31199define("tinymce/ui/ColorBox", [ 31200 "tinymce/ui/ComboBox" 31201], function(ComboBox) { 31202 "use strict"; 31203 31204 return ComboBox.extend({ 31205 /** 31206 * Constructs a new control instance with the specified settings. 31207 * 31208 * @constructor 31209 * @param {Object} settings Name/value object with settings. 31210 */ 31211 init: function(settings) { 31212 var self = this; 31213 31214 settings.spellcheck = false;
31215 31216 if (settings.onaction) { 31217 settings.icon = 'none'; 31218 } 31219 31220 self._super(settings); 31221 31222 self.addClass('colorbox'); 31223 self.on('change keyup postrender', function() { 31224 self.repaintColor(self.value()); 31225 }); 31226 }, 31227 31228 repaintColor: function(value) { 31229 var elm = this.getEl().getElementsByTagName('i')[0]; 31230 31231 if (elm) { 31232 try { 31233 elm.style.background = value; 31234 } catch (ex) { 31235 // Ignore 31236 } 31237 } 31238 }, 31239 31240 value: function(value) { 31241 var self = this; 31242 31243 if (typeof value != "undefined") { 31244 if (self._rendered) { 31245 self.repaintColor(value); 31246 } 31247 } 31248 31249 return self._super(value); 31250 } 31251 }); 31252}); 31253 31254// Included from: js/tinymce/classes/ui/PanelButton.js 31255 31256/** 31257 * PanelButton.js 31258 * 31259 * Copyright, Moxiecode Systems AB 31260 * Released under LGPL License. 31261 * 31262 * License: http://www.tinymce.com/license 31263 * Contributing: http://www.tinymce.com/contributing 31264 */ 31265 31266/** 31267 * Creates a new panel button. 31268 * 31269 * @class tinymce.ui.PanelButton 31270 * @extends tinymce.ui.Button 31271 */ 31272define("tinymce/ui/PanelButton", [ 31273 "tinymce/ui/Button", 31274 "tinymce/ui/FloatPanel" 31275], function(Button, FloatPanel) { 31276 "use strict"; 31277 31278 return Button.extend({ 31279 /** 31280 * Shows the panel for the button. 31281 * 31282 * @method showPanel 31283 */ 31284 showPanel: function() { 31285 var self = this, settings = self.settings; 31286 31287 self.active(true); 31288 31289 if (!self.panel) { 31290 var panelSettings = settings.panel; 31291 31292 // Wrap panel in grid layout if type if specified 31293 // This makes it possible to add forms or other containers directly in the panel option 31294 if (panelSettings.type) { 31295 panelSettings = { 31296 layout: 'grid', 31297 items: panelSettings 31298 }; 31299 } 31300 31301 panelSettings.role = panelSettings.role || 'dialog'; 31302 panelSettings.popover = true; 31303 panelSettings.autohide = true; 31304 panelSettings.ariaRoot = true; 31305 31306 self.panel = new FloatPanel(panelSettings).on('hide', function() { 31307 self.active(false); 31308 }).on('cancel', function(e) { 31309 e.stopPropagation(); 31310 self.focus(); 31311 self.hidePanel(); 31312 }).parent(self).renderTo(self.getContainerElm()); 31313 31314 self.panel.fire('show'); 31315 self.panel.reflow(); 31316 } else { 31317 self.panel.show(); 31318 } 31319 31320 self.panel.moveRel(self.getEl(), settings.popoverAlign || (self.isRtl() ? ['bc-tr', 'bc-tc'] : ['bc-tl', 'bc-tc'])); 31321 }, 31322 31323 /** 31324 * Hides the panel for the button. 31325 * 31326 * @method hidePanel 31327 */ 31328 hidePanel: function() { 31329 var self = this; 31330 31331 if (self.panel) { 31332 self.panel.hide(); 31333 } 31334 }, 31335 31336 /** 31337 * Called after the control has been rendered. 31338 * 31339 * @method postRender 31340 */ 31341 postRender: function() { 31342 var self = this; 31343 31344 self.aria('haspopup', true); 31345 31346 self.on('click', function(e) { 31347 if (e.control === self) { 31348 if (self.panel && self.panel.visible()) { 31349 self.hidePanel(); 31350 } else { 31351 self.showPanel(); 31352 self.panel.focus(!!e.aria); 31353 } 31354 } 31355 }); 31356 31357 return self._super(); 31358 }, 31359 31360 remove: function() { 31361 if (this.panel) { 31362 this.panel.remove(); 31363 this.panel = null; 31364 } 31365 31366 return this._super(); 31367 } 31368 }); 31369}); 31370 31371// Included from: js/tinymce/classes/ui/ColorButton.js 31372 31373/** 31374 * ColorButton.js 31375 * 31376 * Copyright, Moxiecode Systems AB 31377 * Released under LGPL License. 31378 * 31379 * License: http://www.tinymce.com/license 31380 * Contributing: http://www.tinymce.com/contributing 31381 */ 31382 31383/** 31384 * This class creates a color button control. This is a split button in which the main 31385 * button has a visual representation of the currently selected color. When clicked 31386 * the caret button displays a color picker, allowing the user to select a new color. 31387 * 31388 * @-x-less ColorButton.less 31389 * @class tinymce.ui.ColorButton 31390 * @extends tinymce.ui.PanelButton 31391 */ 31392define("tinymce/ui/ColorButton", [ 31393 "tinymce/ui/PanelButton", 31394 "tinymce/dom/DOMUtils" 31395], function(PanelButton, DomUtils) { 31396 "use strict"; 31397 31398 var DOM = DomUtils.DOM; 31399 31400 return PanelButton.extend({ 31401 /** 31402 * Constructs a new ColorButton instance with the specified settings. 31403 * 31404 * @constructor 31405 * @param {Object} settings Name/value object with settings. 31406 */ 31407 init: function(settings) { 31408 this._super(settings); 31409 this.addClass('colorbutton'); 31410 }, 31411 31412 /** 31413 * Getter/setter for the current color. 31414 * 31415 * @method color 31416 * @param {String} [color] Color to set. 31417 * @return {String|tinymce.ui.ColorButton} Current color or current instance. 31418 */ 31419 color: function(color) { 31420 if (color) { 31421 this._color = color; 31422 this.getEl('preview').style.backgroundColor = color; 31423 return this; 31424 } 31425 31426 return this._color; 31427 }, 31428 31429 /** 31430 * Renders the control as a HTML string. 31431 * 31432 * @method renderHtml 31433 * @return {String} HTML representing the control. 31434 */ 31435 renderHtml: function() { 31436 var self = this, id = self._id, prefix = self.classPrefix; 31437 var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : ''; 31438 var image = self.settings.image ? ' style="background-image: url(\'' + self.settings.image + '\')"' : ''; 31439 31440 return ( 31441 '<div id="' + id + '" class="' + self.classes() + '" role="button" tabindex="-1" aria-haspopup="true">' + 31442 '<button role="presentation" hidefocus="1" type="button" tabindex="-1">' +
31443 (icon ? '<i class="' + icon + '"' + image + '></i>' : '') + 31444 '<span id="' + id + '-preview" class="' + prefix + 'preview"></span>' + 31445 (self._text ? (icon ? ' ' : '') + (self._text) : '') + 31446 '</button>' + 31447 '<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' + 31448 ' <i class="' + prefix + 'caret"></i>' + 31449 '</button>' + 31450 '</div>' 31451 ); 31452 }, 31453 31454 /** 31455 * Called after the control has been rendered. 31456 * 31457 * @method postRender 31458 */ 31459 postRender: function() { 31460 var self = this, onClickHandler = self.settings.onclick; 31461 31462 self.on('click', function(e) { 31463 if (e.aria && e.aria.key == 'down') { 31464 return; 31465 } 31466 31467 if (e.control == self && !DOM.getParent(e.target, '.' + self.classPrefix + 'open')) { 31468 e.stopImmediatePropagation(); 31469 onClickHandler.call(self, e); 31470 } 31471 }); 31472 31473 delete self.settings.onclick; 31474 31475 return self._super(); 31476 } 31477 }); 31478}); 31479 31480// Included from: js/tinymce/classes/util/Color.js 31481 31482/** 31483 * Color.js 31484 * 31485 * Copyright, Moxiecode Systems AB 31486 * Released under LGPL License. 31487 * 31488 * License: http://www.tinymce.com/license 31489 * Contributing: http://www.tinymce.com/contributing 31490 */ 31491 31492/** 31493 * This class lets you parse/serialize colors and convert rgb/hsb. 31494 * 31495 * @class tinymce.util.Color 31496 * @example 31497 * var white = new tinymce.util.Color({r: 255, g: 255, b: 255}); 31498 * var red = new tinymce.util.Color('#FF0000'); 31499 * 31500 * console.log(white.toHex(), red.toHsv()); 31501 */ 31502define("tinymce/util/Color", [], function() { 31503 var min = Math.min, max = Math.max, round = Math.round; 31504 31505 /** 31506 * Constructs a new color instance. 31507 * 31508 * @constructor 31509 * @method Color 31510 * @param {String} value Optional initial value to parse. 31511 */ 31512 function Color(value) { 31513 var self = this, r = 0, g = 0, b = 0; 31514 31515 function rgb2hsv(r, g, b) { 31516 var h, s, v, d, minRGB, maxRGB; 31517 31518 h = 0; 31519 s = 0; 31520 v = 0; 31521 r = r / 255; 31522 g = g / 255; 31523 b = b / 255; 31524 31525 minRGB = min(r, min(g, b)); 31526 maxRGB = max(r, max(g, b)); 31527 31528 if (minRGB == maxRGB) { 31529 v = minRGB; 31530 31531 return { 31532 h: 0, 31533 s: 0, 31534 v: v * 100 31535 }; 31536 } 31537 31538 /*eslint no-nested-ternary:0 */ 31539 d = (r == minRGB) ? g - b : ((b == minRGB) ? r - g : b - r); 31540 h = (r == minRGB) ? 3 : ((b == minRGB) ? 1 : 5); 31541 h = 60 * (h - d / (maxRGB - minRGB)); 31542 s = (maxRGB - minRGB) / maxRGB; 31543 v = maxRGB; 31544 31545 return { 31546 h: round(h), 31547 s: round(s * 100), 31548 v: round(v * 100) 31549 }; 31550 } 31551 31552 function hsvToRgb(hue, saturation, brightness) { 31553 var side, chroma, x, match; 31554 31555 hue = (parseInt(hue, 10) || 0) % 360; 31556 saturation = parseInt(saturation, 10) / 100; 31557 brightness = parseInt(brightness, 10) / 100; 31558 saturation = max(0, min(saturation, 1)); 31559 brightness = max(0, min(brightness, 1)); 31560 31561 if (saturation === 0) { 31562 r = g = b = round(255 * brightness); 31563 return; 31564 } 31565 31566 side = hue / 60; 31567 chroma = brightness * saturation; 31568 x = chroma * (1 - Math.abs(side % 2 - 1)); 31569 match = brightness - chroma; 31570 31571 switch (Math.floor(side)) { 31572 case 0: 31573 r = chroma; 31574 g = x; 31575 b = 0; 31576 break; 31577 31578 case 1: 31579 r = x; 31580 g = chroma; 31581 b = 0; 31582 break; 31583 31584 case 2: 31585 r = 0; 31586 g = chroma; 31587 b = x; 31588 break; 31589 31590 case 3: 31591 r = 0; 31592 g = x; 31593 b = chroma; 31594 break; 31595 31596 case 4: 31597 r = x; 31598 g = 0; 31599 b = chroma; 31600 break; 31601 31602 case 5: 31603 r = chroma; 31604 g = 0; 31605 b = x; 31606 break; 31607 31608 default: 31609 r = g = b = 0; 31610 } 31611 31612 r = round(255 * (r + match)); 31613 g = round(255 * (g + match)); 31614 b = round(255 * (b + match)); 31615 } 31616 31617 /** 31618 * Returns the hex string of the current color. For example: #ff00ff 31619 * 31620 * @method toHex 31621 * @return {String} Hex string of current color. 31622 */ 31623 function toHex() { 31624 function hex(val) { 31625 val = parseInt(val, 10).toString(16); 31626 31627 return val.length > 1 ? val : '0' + val; 31628 } 31629 31630 return '#' + hex(r) + hex(g) + hex(b); 31631 } 31632 31633 /** 31634 * Returns the r, g, b values of the color. Each channel has a range from 0-255. 31635 * 31636 * @method toRgb 31637 * @return {Object} Object with r, g, b fields. 31638 */ 31639 function toRgb() { 31640 return { 31641 r: r, 31642 g: g, 31643 b: b 31644 }; 31645 } 31646 31647 /** 31648 * Returns the h, s, v values of the color. Ranges: h=0-360, s=0-100, v=0-100. 31649 * 31650 * @method toHsv 31651 * @return {Object} Object with h, s, v fields. 31652 */ 31653 function toHsv() { 31654 return rgb2hsv(r, g, b); 31655 } 31656 31657 /** 31658 * Parses the specified value and populates the color instance. 31659 * 31660 * Supported format examples: 31661 * * rbg(255,0,0) 31662 * * #ff0000 31663 * * #fff 31664 * * {r: 255, g: 0, b: 0} 31665 * * {h: 360, s: 100, v: 100} 31666 * 31667 * @method parse 31668 * @param {Object/String} value Color value to parse. 31669 * @return {tinymce.util.Color} Current color instance. 31670 */ 31671 function parse(value) { 31672 var matches; 31673 31674 if (typeof value == 'object') { 31675 if ("r" in value) { 31676 r = value.r; 31677 g = value.g; 31678 b = value.b; 31679 } else if ("v" in value) { 31680 hsvToRgb(value.h, value.s, value.v); 31681 } 31682 } else { 31683 if ((matches = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)[^\)]*\)/gi.exec(value))) { 31684 r = parseInt(matches[1], 10); 31685 g = parseInt(matches[2], 10); 31686 b = parseInt(matches[3], 10); 31687 } else if ((matches = /#([0-F]{2})([0-F]{2})([0-F]{2})/gi.exec(value))) { 31688 r = parseInt(matches[1], 16); 31689 g = parseInt(matches[2], 16); 31690 b = parseInt(matches[3], 16); 31691 } else if ((matches = /#([0-F])([0-F])([0-F])/gi.exec(value))) { 31692 r = parseInt(matches[1] + matches[1], 16); 31693 g = parseInt(matches[2] + matches[2], 16); 31694 b = parseInt(matches[3] + matches[3], 16); 31695 } 31696 } 31697 31698 r = r < 0 ? 0 : (r > 255 ? 255 : r); 31699 g = g < 0 ? 0 : (g > 255 ? 255 : g); 31700 b = b < 0 ? 0 : (b > 255 ? 255 : b); 31701 31702 return self; 31703 } 31704 31705 if (value) { 31706 parse(value); 31707 } 31708 31709 self.toRgb = toRgb; 31710 self.toHsv = toHsv; 31711 self.toHex = toHex; 31712 self.parse = parse; 31713 } 31714 31715 return Color; 31716}); 31717 31718// Included from: js/tinymce/classes/ui/ColorPicker.js 31719 31720/** 31721 * ColorPicker.js 31722 * 31723 * Copyright, Moxiecode Systems AB 31724 * Released under LGPL License. 31725 * 31726 * License: http://www.tinymce.com/license 31727 * Contributing: http://www.tinymce.com/contributing 31728 */ 31729 31730/** 31731 * Color picker widget lets you select colors. 31732 * 31733 * @-x-less ColorPicker.less 31734 * @class tinymce.ui.ColorPicker 31735 * @extends tinymce.ui.Widget 31736 */
31737define("tinymce/ui/ColorPicker", [ 31738 "tinymce/ui/Widget", 31739 "tinymce/ui/DragHelper", 31740 "tinymce/ui/DomUtils", 31741 "tinymce/util/Color" 31742], function(Widget, DragHelper, DomUtils, Color) { 31743 "use strict"; 31744 31745 return Widget.extend({ 31746 Defaults: { 31747 classes: "widget colorpicker" 31748 }, 31749 31750 /** 31751 * Constructs a new colorpicker instance with the specified settings. 31752 * 31753 * @constructor 31754 * @param {Object} settings Name/value object with settings. 31755 * @setting {String} color Initial color value. 31756 */ 31757 init: function(settings) { 31758 this._super(settings); 31759 }, 31760 31761 postRender: function() { 31762 var self = this, color = self.color(), hsv, hueRootElm, huePointElm, svRootElm, svPointElm; 31763 31764 hueRootElm = self.getEl('h'); 31765 huePointElm = self.getEl('hp'); 31766 svRootElm = self.getEl('sv'); 31767 svPointElm = self.getEl('svp'); 31768 31769 function getPos(elm, event) { 31770 var pos = DomUtils.getPos(elm), x, y; 31771 31772 x = event.pageX - pos.x; 31773 y = event.pageY - pos.y; 31774 31775 x = Math.max(0, Math.min(x / elm.clientWidth, 1)); 31776 y = Math.max(0, Math.min(y / elm.clientHeight, 1)); 31777 31778 return { 31779 x: x, 31780 y: y 31781 }; 31782 } 31783 31784 function updateColor(hsv, hueUpdate) { 31785 var hue = (360 - hsv.h) / 360; 31786 31787 DomUtils.css(huePointElm, { 31788 top: (hue * 100) + '%' 31789 }); 31790 31791 if (!hueUpdate) { 31792 DomUtils.css(svPointElm, { 31793 left: hsv.s + '%', 31794 top: (100 - hsv.v) + '%' 31795 }); 31796 } 31797 31798 svRootElm.style.background = new Color({s: 100, v: 100, h: hsv.h}).toHex(); 31799 self.color().parse({s: hsv.s, v: hsv.v, h: hsv.h}); 31800 } 31801 31802 function updateSaturationAndValue(e) { 31803 var pos; 31804 31805 pos = getPos(svRootElm, e); 31806 hsv.s = pos.x * 100; 31807 hsv.v = (1 - pos.y) * 100; 31808 31809 updateColor(hsv); 31810 self.fire('change'); 31811 } 31812 31813 function updateHue(e) { 31814 var pos; 31815 31816 pos = getPos(hueRootElm, e); 31817 hsv = color.toHsv(); 31818 hsv.h = (1 - pos.y) * 360; 31819 updateColor(hsv, true); 31820 self.fire('change'); 31821 } 31822 31823 self._repaint = function() { 31824 hsv = color.toHsv(); 31825 updateColor(hsv); 31826 }; 31827 31828 self._super(); 31829 31830 self._svdraghelper = new DragHelper(self._id + '-sv', { 31831 start: updateSaturationAndValue, 31832 drag: updateSaturationAndValue 31833 }); 31834 31835 self._hdraghelper = new DragHelper(self._id + '-h', { 31836 start: updateHue, 31837 drag: updateHue 31838 }); 31839 31840 self._repaint(); 31841 }, 31842 31843 rgb: function() { 31844 return this.color().toRgb(); 31845 }, 31846 31847 value: function(value) { 31848 var self = this; 31849 31850 if (arguments.length) { 31851 self.color().parse(value); 31852 31853 if (self._rendered) { 31854 self._repaint(); 31855 } 31856 } else { 31857 return self.color().toHex(); 31858 } 31859 }, 31860 31861 color: function() { 31862 if (!this._color) { 31863 this._color = new Color(); 31864 } 31865 31866 return this._color; 31867 }, 31868 31869 /** 31870 * Renders the control as a HTML string. 31871 * 31872 * @method renderHtml 31873 * @return {String} HTML representing the control. 31874 */ 31875 renderHtml: function() { 31876 var self = this, id = self._id, prefix = self.classPrefix, hueHtml; 31877 var stops = '#ff0000,#ff0080,#ff00ff,#8000ff,#0000ff,#0080ff,#00ffff,#00ff80,#00ff00,#80ff00,#ffff00,#ff8000,#ff0000'; 31878 31879 function getOldIeFallbackHtml() { 31880 var i, l, html = '', gradientPrefix, stopsList; 31881 31882 gradientPrefix = 'filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='; 31883 stopsList = stops.split(','); 31884 for (i = 0, l = stopsList.length - 1; i < l; i++) { 31885 html += ( 31886 '<div class="' + prefix + 'colorpicker-h-chunk" style="' + 31887 'height:' + (100 / l) + '%;' + 31888 gradientPrefix + stopsList[i] + ',endColorstr=' + stopsList[i + 1] + ');' + 31889 '-ms-' + gradientPrefix + stopsList[i] + ',endColorstr=' + stopsList[i + 1] + ')' + 31890 '"></div>' 31891 ); 31892 } 31893 31894 return html; 31895 } 31896 31897 var gradientCssText = ( 31898 'background: -ms-linear-gradient(top,' + stops + ');' + 31899 'background: linear-gradient(to bottom,' + stops + ');' 31900 ); 31901 31902 hueHtml = ( 31903 '<div id="' + id + '-h" class="' + prefix + 'colorpicker-h" style="' + gradientCssText + '">' + 31904 getOldIeFallbackHtml() + 31905 '<div id="' + id + '-hp" class="' + prefix + 'colorpicker-h-marker"></div>' + 31906 '</div>' 31907 ); 31908 31909 return ( 31910 '<div id="' + id + '" class="' + self.classes() + '">' + 31911 '<div id="' + id + '-sv" class="' + prefix + 'colorpicker-sv">' + 31912 '<div class="' + prefix + 'colorpicker-overlay1">' + 31913 '<div class="' + prefix + 'colorpicker-overlay2">' + 31914 '<div id="' + id + '-svp" class="' + prefix + 'colorpicker-selector1">' + 31915 '<div class="' + prefix + 'colorpicker-selector2"></div>' + 31916 '</div>' + 31917 '</div>' + 31918 '</div>' + 31919 '</div>' + 31920 hueHtml + 31921 '</div>' 31922 ); 31923 } 31924 }); 31925}); 31926 31927// Included from: js/tinymce/classes/ui/Path.js 31928 31929/** 31930 * Path.js 31931 * 31932 * Copyright, Moxiecode Systems AB 31933 * Released under LGPL License. 31934 * 31935 * License: http://www.tinymce.com/license 31936 * Contributing: http://www.tinymce.com/contributing 31937 */ 31938 31939/** 31940 * Creates a new path control. 31941 * 31942 * @-x-less Path.less 31943 * @class tinymce.ui.Path 31944 * @extends tinymce.ui.Widget 31945 */
31946define("tinymce/ui/Path", [ 31947 "tinymce/ui/Widget" 31948], function(Widget) { 31949 "use strict"; 31950 31951 return Widget.extend({ 31952 /** 31953 * Constructs a instance with the specified settings. 31954 * 31955 * @constructor 31956 * @param {Object} settings Name/value object with settings. 31957 * @setting {String} delimiter Delimiter to display between items in path. 31958 */ 31959 init: function(settings) { 31960 var self = this; 31961 31962 if (!settings.delimiter) { 31963 settings.delimiter = '\u00BB'; 31964 } 31965 31966 self._super(settings); 31967 self.addClass('path'); 31968 self.canFocus = true; 31969 31970 self.on('click', function(e) { 31971 var index, target = e.target; 31972 31973 if ((index = target.getAttribute('data-index'))) { 31974 self.fire('select', {value: self.data()[index], index: index}); 31975 } 31976 }); 31977 }, 31978 31979 /** 31980 * Focuses the current control. 31981 * 31982 * @method focus 31983 * @return {tinymce.ui.Control} Current control instance. 31984 */ 31985 focus: function() { 31986 var self = this; 31987 31988 self.getEl().firstChild.focus(); 31989 31990 return self; 31991 }, 31992 31993 /** 31994 * Sets/gets the data to be used for the path. 31995 * 31996 * @method data 31997 * @param {Array} data Array with items name is rendered to path. 31998 */ 31999 data: function(data) { 32000 var self = this; 32001 32002 if (typeof(data) !== "undefined") { 32003 self._data = data; 32004 self.update(); 32005 32006 return self; 32007 } 32008 32009 return self._data; 32010 }, 32011 32012 /** 32013 * Updated the path. 32014 * 32015 * @private 32016 */ 32017 update: function() { 32018 this.innerHtml(this._getPathHtml()); 32019 }, 32020 32021 /** 32022 * Called after the control has been rendered. 32023 * 32024 * @method postRender 32025 */ 32026 postRender: function() { 32027 var self = this; 32028 32029 self._super(); 32030 32031 self.data(self.settings.data); 32032 }, 32033 32034 /** 32035 * Renders the control as a HTML string. 32036 * 32037 * @method renderHtml 32038 * @return {String} HTML representing the control. 32039 */ 32040 renderHtml: function() { 32041 var self = this; 32042 32043 return ( 32044 '<div id="' + self._id + '" class="' + self.classes() + '">' + 32045 self._getPathHtml() + 32046 '</div>' 32047 ); 32048 }, 32049 32050 _getPathHtml: function() { 32051 var self = this, parts = self._data || [], i, l, html = '', prefix = self.classPrefix; 32052 32053 for (i = 0, l = parts.length; i < l; i++) { 32054 html += ( 32055 (i > 0 ? '<div class="' + prefix + 'divider" aria-hidden="true"> ' + self.settings.delimiter + ' </div>' : '') + 32056 '<div role="button" class="' + prefix + 'path-item' + (i == l - 1 ? ' ' + prefix + 'last' : '') + '" data-index="' + 32057 i + '" tabindex="-1" id="' + self._id + '-' + i + '" aria-level="' + i + '">' + parts[i].name + '</div>' 32058 ); 32059 } 32060 32061 if (!html) { 32062 html = '<div class="' + prefix + 'path-item">\u00a0</div>'; 32063 } 32064 32065 return html; 32066 } 32067 }); 32068}); 32069 32070// Included from: js/tinymce/classes/ui/ElementPath.js 32071 32072/** 32073 * ElementPath.js 32074 * 32075 * Copyright, Moxiecode Systems AB 32076 * Released under LGPL License. 32077 * 32078 * License: http://www.tinymce.com/license 32079 * Contributing: http://www.tinymce.com/contributing 32080 */ 32081 32082/** 32083 * This control creates an path for the current selections parent elements in TinyMCE. 32084 * 32085 * @class tinymce.ui.ElementPath 32086 * @extends tinymce.ui.Path 32087 */ 32088define("tinymce/ui/ElementPath", [ 32089 "tinymce/ui/Path", 32090 "tinymce/EditorManager" 32091], function(Path, EditorManager) { 32092 return Path.extend({ 32093 /** 32094 * Post render method. Called after the control has been rendered to the target. 32095 * 32096 * @method postRender 32097 * @return {tinymce.ui.ElementPath} Current combobox instance. 32098 */ 32099 postRender: function() { 32100 var self = this, editor = EditorManager.activeEditor; 32101 32102 function isHidden(elm) { 32103 if (elm.nodeType === 1) { 32104 if (elm.nodeName == "BR" || !!elm.getAttribute('data-mce-bogus')) { 32105 return true; 32106 } 32107 32108 if (elm.getAttribute('data-mce-type') === 'bookmark') { 32109 return true; 32110 } 32111 } 32112 32113 return false; 32114 } 32115 32116 self.on('select', function(e) { 32117 editor.focus(); 32118 editor.selection.select(this.data()[e.index].element); 32119 editor.nodeChanged(); 32120 }); 32121 32122 editor.on('nodeChange', function(e) { 32123 var outParents = [], parents = e.parents, i = parents.length; 32124 32125 while (i--) { 32126 if (parents[i].nodeType == 1 && !isHidden(parents[i])) { 32127 var args = editor.fire('ResolveName', { 32128 name: parents[i].nodeName.toLowerCase(), 32129 target: parents[i] 32130 }); 32131 32132 if (!args.isDefaultPrevented()) { 32133 outParents.push({name: args.name, element: parents[i]}); 32134 } 32135 32136 if (args.isPropagationStopped()) { 32137 break; 32138 } 32139 } 32140 } 32141 32142 self.data(outParents); 32143 }); 32144 32145 return self._super(); 32146 } 32147 }); 32148}); 32149 32150// Included from: js/tinymce/classes/ui/FormItem.js 32151 32152/** 32153 * FormItem.js 32154 * 32155 * Copyright, Moxiecode Systems AB 32156 * Released under LGPL License. 32157 * 32158 * License: http://www.tinymce.com/license 32159 * Contributing: http://www.tinymce.com/contributing 32160 */ 32161 32162/** 32163 * This class is a container created by the form element with 32164 * a label and control item. 32165 * 32166 * @class tinymce.ui.FormItem 32167 * @extends tinymce.ui.Container
32168 * @setting {String} label Label to display for the form item. 32169 */ 32170define("tinymce/ui/FormItem", [ 32171 "tinymce/ui/Container" 32172], function(Container) { 32173 "use strict"; 32174 32175 return Container.extend({ 32176 Defaults: { 32177 layout: 'flex', 32178 align: 'center', 32179 defaults: { 32180 flex: 1 32181 } 32182 }, 32183 32184 /** 32185 * Renders the control as a HTML string. 32186 * 32187 * @method renderHtml 32188 * @return {String} HTML representing the control. 32189 */ 32190 renderHtml: function() { 32191 var self = this, layout = self._layout, prefix = self.classPrefix; 32192 32193 self.addClass('formitem'); 32194 layout.preRender(self); 32195 32196 return ( 32197 '<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' + 32198 (self.settings.title ? ('<div id="' + self._id + '-title" class="' + prefix + 'title">' + 32199 self.settings.title + '</div>') : '') + 32200 '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' + 32201 (self.settings.html || '') + layout.renderHtml(self) + 32202 '</div>' + 32203 '</div>' 32204 ); 32205 } 32206 }); 32207}); 32208 32209// Included from: js/tinymce/classes/ui/Form.js 32210 32211/** 32212 * Form.js 32213 * 32214 * Copyright, Moxiecode Systems AB 32215 * Released under LGPL License. 32216 * 32217 * License: http://www.tinymce.com/license 32218 * Contributing: http://www.tinymce.com/contributing 32219 */ 32220 32221/** 32222 * This class creates a form container. A form container has the ability 32223 * to automatically wrap items in tinymce.ui.FormItem instances. 32224 * 32225 * Each FormItem instance is a container for the label and the item. 32226 * 32227 * @example 32228 * tinymce.ui.Factory.create({ 32229 * type: 'form', 32230 * items: [ 32231 * {type: 'textbox', label: 'My text box'} 32232 * ] 32233 * }).renderTo(document.body); 32234 * 32235 * @class tinymce.ui.Form 32236 * @extends tinymce.ui.Container 32237 */ 32238define("tinymce/ui/Form", [ 32239 "tinymce/ui/Container", 32240 "tinymce/ui/FormItem", 32241 "tinymce/util/Tools" 32242], function(Container, FormItem, Tools) { 32243 "use strict"; 32244 32245 return Container.extend({ 32246 Defaults: { 32247 containerCls: 'form', 32248 layout: 'flex', 32249 direction: 'column', 32250 align: 'stretch', 32251 flex: 1, 32252 padding: 20, 32253 labelGap: 30, 32254 spacing: 10, 32255 callbacks: { 32256 submit: function() { 32257 this.submit(); 32258 } 32259 } 32260 }, 32261 32262 /** 32263 * This method gets invoked before the control is rendered. 32264 * 32265 * @method preRender 32266 */ 32267 preRender: function() { 32268 var self = this, items = self.items(); 32269 32270 if (!self.settings.formItemDefaults) { 32271 self.settings.formItemDefaults = { 32272 layout: 'flex', 32273 autoResize: "overflow", 32274 defaults: {flex: 1} 32275 }; 32276 } 32277 32278 // Wrap any labeled items in FormItems 32279 items.each(function(ctrl) { 32280 var formItem, label = ctrl.settings.label; 32281 32282 if (label) { 32283 formItem = new FormItem(Tools.extend({ 32284 items: { 32285 type: 'label', 32286 id: ctrl._id + '-l', 32287 text: label, 32288 flex: 0, 32289 forId: ctrl._id, 32290 disabled: ctrl.disabled() 32291 } 32292 }, self.settings.formItemDefaults)); 32293 32294 formItem.type = 'formitem'; 32295 ctrl.aria('labelledby', ctrl._id + '-l'); 32296 32297 if (typeof(ctrl.settings.flex) == "undefined") { 32298 ctrl.settings.flex = 1; 32299 } 32300 32301 self.replace(ctrl, formItem); 32302 formItem.add(ctrl); 32303 } 32304 }); 32305 }, 32306 32307 /** 32308 * Recalcs label widths. 32309 * 32310 * @private 32311 */ 32312 recalcLabels: function() { 32313 var self = this, maxLabelWidth = 0, labels = [], i, labelGap, items; 32314 32315 if (self.settings.labelGapCalc === false) { 32316 return; 32317 } 32318 32319 if (self.settings.labelGapCalc == "children") { 32320 items = self.find('formitem'); 32321 } else { 32322 items = self.items(); 32323 } 32324 32325 items.filter('formitem').each(function(item) { 32326 var labelCtrl = item.items()[0], labelWidth = labelCtrl.getEl().clientWidth; 32327 32328 maxLabelWidth = labelWidth > maxLabelWidth ? labelWidth : maxLabelWidth; 32329 labels.push(labelCtrl); 32330 }); 32331 32332 labelGap = self.settings.labelGap || 0; 32333 32334 i = labels.length; 32335 while (i--) { 32336 labels[i].settings.minWidth = maxLabelWidth + labelGap; 32337 } 32338 }, 32339 32340 /** 32341 * Getter/setter for the visibility state. 32342 * 32343 * @method visible 32344 * @param {Boolean} [state] True/false state to show/hide. 32345 * @return {tinymce.ui.Form|Boolean} True/false state or current control. 32346 */ 32347 visible: function(state) { 32348 var val = this._super(state); 32349 32350 if (state === true && this._rendered) { 32351 this.recalcLabels(); 32352 } 32353 32354 return val; 32355 }, 32356 32357 /** 32358 * Fires a submit event with the serialized form. 32359 * 32360 * @method submit 32361 * @return {Object} Event arguments object. 32362 */ 32363 submit: function() { 32364 return this.fire('submit', {data: this.toJSON()}); 32365 }, 32366 32367 /** 32368 * Post render method. Called after the control has been rendered to the target. 32369 * 32370 * @method postRender 32371 * @return {tinymce.ui.ComboBox} Current combobox instance. 32372 */ 32373 postRender: function() { 32374 var self = this; 32375 32376 self._super(); 32377 self.recalcLabels(); 32378 self.fromJSON(self.settings.data); 32379 } 32380 }); 32381}); 32382 32383// Included from: js/tinymce/classes/ui/FieldSet.js 32384 32385/** 32386 * FieldSet.js 32387 * 32388 * Copyright, Moxiecode Systems AB 32389 * Released under LGPL License. 32390 * 32391 * License: http://www.tinymce.com/license 32392 * Contributing: http://www.tinymce.com/contributing 32393 */ 32394 32395/** 32396 * This class creates fieldset containers. 32397 * 32398 * @-x-less FieldSet.less 32399 * @class tinymce.ui.FieldSet 32400 * @extends tinymce.ui.Form 32401 */
32402define("tinymce/ui/FieldSet", [ 32403 "tinymce/ui/Form" 32404], function(Form) { 32405 "use strict"; 32406 32407 return Form.extend({ 32408 Defaults: { 32409 containerCls: 'fieldset', 32410 layout: 'flex', 32411 direction: 'column', 32412 align: 'stretch', 32413 flex: 1, 32414 padding: "25 15 5 15", 32415 labelGap: 30, 32416 spacing: 10, 32417 border: 1 32418 }, 32419 32420 /** 32421 * Renders the control as a HTML string. 32422 * 32423 * @method renderHtml 32424 * @return {String} HTML representing the control. 32425 */ 32426 renderHtml: function() { 32427 var self = this, layout = self._layout, prefix = self.classPrefix; 32428 32429 self.preRender(); 32430 layout.preRender(self); 32431 32432 return ( 32433 '<fieldset id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' + 32434 (self.settings.title ? ('<legend id="' + self._id + '-title" class="' + prefix + 'fieldset-title">' + 32435 self.settings.title + '</legend>') : '') + 32436 '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' + 32437 (self.settings.html || '') + layout.renderHtml(self) + 32438 '</div>' + 32439 '</fieldset>' 32440 ); 32441 } 32442 }); 32443}); 32444 32445// Included from: js/tinymce/classes/ui/FilePicker.js 32446 32447/** 32448 * FilePicker.js 32449 * 32450 * Copyright, Moxiecode Systems AB 32451 * Released under LGPL License. 32452 * 32453 * License: http://www.tinymce.com/license 32454 * Contributing: http://www.tinymce.com/contributing 32455 */ 32456 32457/*global tinymce:true */ 32458 32459/** 32460 * This class creates a file picker control. 32461 * 32462 * @class tinymce.ui.FilePicker 32463 * @extends tinymce.ui.ComboBox 32464 */ 32465define("tinymce/ui/FilePicker", [ 32466 "tinymce/ui/ComboBox", 32467 "tinymce/util/Tools" 32468], function(ComboBox, Tools) { 32469 "use strict"; 32470 32471 return ComboBox.extend({ 32472 /** 32473 * Constructs a new control instance with the specified settings. 32474 * 32475 * @constructor 32476 * @param {Object} settings Name/value object with settings. 32477 */ 32478 init: function(settings) { 32479 var self = this, editor = tinymce.activeEditor, editorSettings = editor.settings; 32480 var actionCallback, fileBrowserCallback, fileBrowserCallbackTypes; 32481 32482 settings.spellcheck = false; 32483 32484 fileBrowserCallbackTypes = editorSettings.file_picker_types || editorSettings.file_browser_callback_types; 32485 if (fileBrowserCallbackTypes) { 32486 fileBrowserCallbackTypes = Tools.makeMap(fileBrowserCallbackTypes, /[, ]/); 32487 } 32488 32489 if (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype]) { 32490 fileBrowserCallback = editorSettings.file_picker_callback; 32491 if (fileBrowserCallback && (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype])) { 32492 actionCallback = function() { 32493 var meta = self.fire('beforecall').meta; 32494 32495 meta = Tools.extend({filetype: settings.filetype}, meta); 32496 32497 // file_picker_callback(callback, currentValue, metaData) 32498 fileBrowserCallback.call( 32499 editor, 32500 function(value, meta) { 32501 self.value(value).fire('change', {meta: meta}); 32502 }, 32503 self.value(), 32504 meta 32505 ); 32506 }; 32507 } else { 32508 // Legacy callback: file_picker_callback(id, currentValue, filetype, window) 32509 fileBrowserCallback = editorSettings.file_browser_callback; 32510 if (fileBrowserCallback && (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype])) { 32511 actionCallback = function() { 32512 fileBrowserCallback( 32513 self.getEl('inp').id, 32514 self.value(), 32515 settings.filetype, 32516 window 32517 ); 32518 }; 32519 } 32520 } 32521 } 32522 32523 if (actionCallback) { 32524 settings.icon = 'browse'; 32525 settings.onaction = actionCallback; 32526 } 32527 32528 self._super(settings); 32529 } 32530 }); 32531}); 32532 32533// Included from: js/tinymce/classes/ui/FitLayout.js 32534 32535/** 32536 * FitLayout.js 32537 * 32538 * Copyright, Moxiecode Systems AB 32539 * Released under LGPL License. 32540 * 32541 * License: http://www.tinymce.com/license 32542 * Contributing: http://www.tinymce.com/contributing 32543 */ 32544 32545/** 32546 * This layout manager will resize the control to be the size of it's parent container. 32547 * In other words width: 100% and height: 100%. 32548 * 32549 * @-x-less FitLayout.less 32550 * @class tinymce.ui.FitLayout 32551 * @extends tinymce.ui.AbsoluteLayout 32552 */ 32553define("tinymce/ui/FitLayout", [ 32554 "tinymce/ui/AbsoluteLayout" 32555], function(AbsoluteLayout) { 32556 "use strict"; 32557 32558 return AbsoluteLayout.extend({ 32559 /** 32560 * Recalculates the positions of the controls in the specified container. 32561 * 32562 * @method recalc 32563 * @param {tinymce.ui.Container} container Container instance to recalc. 32564 */ 32565 recalc: function(container) { 32566 var contLayoutRect = container.layoutRect(), paddingBox = container.paddingBox(); 32567 32568 container.items().filter(':visible').each(function(ctrl) { 32569 ctrl.layoutRect({ 32570 x: paddingBox.left, 32571 y: paddingBox.top, 32572 w: contLayoutRect.innerW - paddingBox.right - paddingBox.left, 32573 h: contLayoutRect.innerH - paddingBox.top - paddingBox.bottom 32574 }); 32575 32576 if (ctrl.recalc) { 32577 ctrl.recalc(); 32578 } 32579 }); 32580 } 32581 }); 32582}); 32583 32584// Included from: js/tinymce/classes/ui/FlexLayout.js 32585 32586/** 32587 * FlexLayout.js 32588 * 32589 * Copyright, Moxiecode Systems AB 32590 * Released under LGPL License. 32591 * 32592 * License: http://www.tinymce.com/license 32593 * Contributing: http://www.tinymce.com/contributing 32594 */ 32595 32596/** 32597 * This layout manager works similar to the CSS flex box. 32598 *
32599 * @setting {String} direction row|row-reverse|column|column-reverse 32600 * @setting {Number} flex A positive-number to flex by. 32601 * @setting {String} align start|end|center|stretch 32602 * @setting {String} pack start|end|justify 32603 * 32604 * @class tinymce.ui.FlexLayout 32605 * @extends tinymce.ui.AbsoluteLayout 32606 */ 32607define("tinymce/ui/FlexLayout", [ 32608 "tinymce/ui/AbsoluteLayout" 32609], function(AbsoluteLayout) { 32610 "use strict"; 32611 32612 return AbsoluteLayout.extend({ 32613 /** 32614 * Recalculates the positions of the controls in the specified container. 32615 * 32616 * @method recalc 32617 * @param {tinymce.ui.Container} container Container instance to recalc. 32618 */ 32619 recalc: function(container) { 32620 // A ton of variables, needs to be in the same scope for performance 32621 var i, l, items, contLayoutRect, contPaddingBox, contSettings, align, pack, spacing, totalFlex, availableSpace, direction; 32622 var ctrl, ctrlLayoutRect, ctrlSettings, flex, maxSizeItems = [], size, maxSize, ratio, rect, pos, maxAlignEndPos; 32623 var sizeName, minSizeName, posName, maxSizeName, beforeName, innerSizeName, deltaSizeName, contentSizeName; 32624 var alignAxisName, alignInnerSizeName, alignSizeName, alignMinSizeName, alignBeforeName, alignAfterName; 32625 var alignDeltaSizeName, alignContentSizeName; 32626 var max = Math.max, min = Math.min; 32627 32628 // Get container items, properties and settings 32629 items = container.items().filter(':visible'); 32630 contLayoutRect = container.layoutRect(); 32631 contPaddingBox = container._paddingBox; 32632 contSettings = container.settings; 32633 direction = container.isRtl() ? (contSettings.direction || 'row-reversed') : contSettings.direction; 32634 align = contSettings.align; 32635 pack = container.isRtl() ? (contSettings.pack || 'end') : contSettings.pack; 32636 spacing = contSettings.spacing || 0; 32637 32638 if (direction == "row-reversed" || direction == "column-reverse") { 32639 items = items.set(items.toArray().reverse()); 32640 direction = direction.split('-')[0]; 32641 } 32642 32643 // Setup axis variable name for row/column direction since the calculations is the same 32644 if (direction == "column") { 32645 posName = "y"; 32646 sizeName = "h"; 32647 minSizeName = "minH"; 32648 maxSizeName = "maxH"; 32649 innerSizeName = "innerH"; 32650 beforeName = 'top'; 32651 deltaSizeName = "deltaH"; 32652 contentSizeName = "contentH"; 32653 32654 alignBeforeName = "left"; 32655 alignSizeName = "w"; 32656 alignAxisName = "x"; 32657 alignInnerSizeName = "innerW"; 32658 alignMinSizeName = "minW"; 32659 alignAfterName = "right"; 32660 alignDeltaSizeName = "deltaW"; 32661 alignContentSizeName = "contentW"; 32662 } else { 32663 posName = "x"; 32664 sizeName = "w"; 32665 minSizeName = "minW"; 32666 maxSizeName = "maxW"; 32667 innerSizeName = "innerW"; 32668 beforeName = 'left'; 32669 deltaSizeName = "deltaW"; 32670 contentSizeName = "contentW"; 32671 32672 alignBeforeName = "top"; 32673 alignSizeName = "h"; 32674 alignAxisName = "y"; 32675 alignInnerSizeName = "innerH"; 32676 alignMinSizeName = "minH"; 32677 alignAfterName = "bottom"; 32678 alignDeltaSizeName = "deltaH"; 32679 alignContentSizeName = "contentH"; 32680 } 32681 32682 // Figure out total flex, availableSpace and collect any max size elements 32683 availableSpace = contLayoutRect[innerSizeName] - contPaddingBox[beforeName] - contPaddingBox[beforeName]; 32684 maxAlignEndPos = totalFlex = 0; 32685 for (i = 0, l = items.length; i < l; i++) { 32686 ctrl = items[i]; 32687 ctrlLayoutRect = ctrl.layoutRect(); 32688 ctrlSettings = ctrl.settings; 32689 flex = ctrlSettings.flex; 32690 availableSpace -= (i < l - 1 ? spacing : 0); 32691 32692 if (flex > 0) { 32693 totalFlex += flex; 32694 32695 // Flexed item has a max size then we need to check if we will hit that size 32696 if (ctrlLayoutRect[maxSizeName]) { 32697 maxSizeItems.push(ctrl); 32698 } 32699 32700 ctrlLayoutRect.flex = flex; 32701 } 32702 32703 availableSpace -= ctrlLayoutRect[minSizeName]; 32704 32705 // Calculate the align end position to be used to check for overflow/underflow 32706 size = contPaddingBox[alignBeforeName] + ctrlLayoutRect[alignMinSizeName] + contPaddingBox[alignAfterName]; 32707 if (size > maxAlignEndPos) { 32708 maxAlignEndPos = size; 32709 } 32710 } 32711 32712 // Calculate minW/minH 32713 rect = {}; 32714 if (availableSpace < 0) { 32715 rect[minSizeName] = contLayoutRect[minSizeName] - availableSpace + contLayoutRect[deltaSizeName]; 32716 } else { 32717 rect[minSizeName] = contLayoutRect[innerSizeName] - availableSpace + contLayoutRect[deltaSizeName]; 32718 } 32719 32720 rect[alignMinSizeName] = maxAlignEndPos + contLayoutRect[alignDeltaSizeName]; 32721 32722 rect[contentSizeName] = contLayoutRect[innerSizeName] - availableSpace; 32723 rect[alignContentSizeName] = maxAlignEndPos; 32724 rect.minW = min(rect.minW, contLayoutRect.maxW); 32725 rect.minH = min(rect.minH, contLayoutRect.maxH); 32726 rect.minW = max(rect.minW, contLayoutRect.startMinWidth); 32727 rect.minH = max(rect.minH, contLayoutRect.startMinHeight); 32728 32729 // Resize container container if minSize was changed 32730 if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) { 32731 rect.w = rect.minW; 32732 rect.h = rect.minH; 32733 32734 container.layoutRect(rect); 32735 this.recalc(container); 32736 32737 // Forced recalc for example if items are hidden/shown 32738 if (container._lastRect === null) { 32739 var parentCtrl = container.parent(); 32740 if (parentCtrl) { 32741 parentCtrl._lastRect = null; 32742 parentCtrl.recalc(); 32743 } 32744 } 32745 32746 return; 32747 } 32748 32749 // Handle max size elements, check if they will become to wide with current options 32750 ratio = availableSpace / totalFlex; 32751 for (i = 0, l = maxSizeItems.length; i < l; i++) { 32752 ctrl = maxSizeItems[i]; 32753 ctrlLayoutRect = ctrl.layoutRect(); 32754 maxSize = ctrlLayoutRect[maxSizeName]; 32755 size = ctrlLayoutRect[minSizeName] + ctrlLayoutRect.flex * ratio; 32756 32757 if (size > maxSize) { 32758 availableSpace -= (ctrlLayoutRect[maxSizeName] - ctrlLayoutRect[minSizeName]); 32759 totalFlex -= ctrlLayoutRect.flex; 32760 ctrlLayoutRect.flex = 0; 32761 ctrlLayoutRect.maxFlexSize = maxSize; 32762 } else { 32763 ctrlLayoutRect.maxFlexSize = 0; 32764 } 32765 } 32766 32767 // Setup new ratio, target layout rect, start position 32768 ratio = availableSpace / totalFlex; 32769 pos = contPaddingBox[beforeName]; 32770 rect = {}; 32771 32772 // Handle pack setting moves the start position to end, center 32773 if (totalFlex === 0) { 32774 if (pack == "end") { 32775 pos = availableSpace + contPaddingBox[beforeName]; 32776 } else if (pack == "center") { 32777 pos = Math.round( 32778 (contLayoutRect[innerSizeName] / 2) - ((contLayoutRect[innerSizeName] - availableSpace) / 2) 32779 ) + contPaddingBox[beforeName]; 32780 32781 if (pos < 0) { 32782 pos = contPaddingBox[beforeName]; 32783 } 32784 } else if (pack == "justify") { 32785 pos = contPaddingBox[beforeName]; 32786 spacing = Math.floor(availableSpace / (items.length - 1)); 32787 } 32788 } 32789 32790 // Default aligning (start) the other ones needs to be calculated while doing the layout 32791 rect[alignAxisName] = contPaddingBox[alignBeforeName]; 32792 32793 // Start laying out controls 32794 for (i = 0, l = items.length; i < l; i++) { 32795 ctrl = items[i]; 32796 ctrlLayoutRect = ctrl.layoutRect(); 32797 size = ctrlLayoutRect.maxFlexSize || ctrlLayoutRect[minSizeName]; 32798 32799 // Align the control on the other axis 32800 if (align === "center") { 32801 rect[alignAxisName] = Math.round((contLayoutRect[alignInnerSizeName] / 2) - (ctrlLayoutRect[alignSizeName] / 2)); 32802 } else if (align === "stretch") { 32803 rect[alignSizeName] = max( 32804 ctrlLayoutRect[alignMinSizeName] || 0, 32805 contLayoutRect[alignInnerSizeName] - contPaddingBox[alignBeforeName] - contPaddingBox[alignAfterName] 32806 ); 32807 rect[alignAxisName] = contPaddingBox[alignBeforeName]; 32808 } else if (align === "end") { 32809 rect[alignAxisName] = contLayoutRect[alignInnerSizeName] - ctrlLayoutRect[alignSizeName] - contPaddingBox.top; 32810 } 32811 32812 // Calculate new size based on flex 32813 if (ctrlLayoutRect.flex > 0) { 32814 size += ctrlLayoutRect.flex * ratio; 32815 } 32816 32817 rect[sizeName] = size; 32818 rect[posName] = pos; 32819 ctrl.layoutRect(rect); 32820 32821 // Recalculate containers 32822 if (ctrl.recalc) { 32823 ctrl.recalc(); 32824 } 32825 32826 // Move x/y position 32827 pos += size + spacing; 32828 } 32829 } 32830 }); 32831}); 32832 32833// Included from: js/tinymce/classes/ui/FlowLayout.js 32834 32835/** 32836 * FlowLayout.js 32837 * 32838 * Copyright, Moxiecode Systems AB 32839 * Released under LGPL License. 32840 * 32841 * License: http://www.tinymce.com/license 32842 * Contributing: http://www.tinymce.com/contributing 32843 */ 32844 32845/** 32846 * This layout manager will place the controls by using the browsers native layout. 32847 * 32848 * @-x-less FlowLayout.less 32849 * @class tinymce.ui.FlowLayout 32850 * @extends tinymce.ui.Layout 32851 */
32852define("tinymce/ui/FlowLayout", [ 32853 "tinymce/ui/Layout" 32854], function(Layout) { 32855 return Layout.extend({ 32856 Defaults: { 32857 containerClass: 'flow-layout', 32858 controlClass: 'flow-layout-item', 32859 endClass : 'break' 32860 }, 32861 32862 /** 32863 * Recalculates the positions of the controls in the specified container. 32864 * 32865 * @method recalc 32866 * @param {tinymce.ui.Container} container Container instance to recalc. 32867 */ 32868 recalc: function(container) { 32869 container.items().filter(':visible').each(function(ctrl) { 32870 if (ctrl.recalc) { 32871 ctrl.recalc(); 32872 } 32873 }); 32874 } 32875 }); 32876}); 32877 32878// Included from: js/tinymce/classes/ui/FormatControls.js 32879 32880/** 32881 * FormatControls.js 32882 * 32883 * Copyright, Moxiecode Systems AB 32884 * Released under LGPL License. 32885 * 32886 * License: http://www.tinymce.com/license 32887 * Contributing: http://www.tinymce.com/contributing 32888 */ 32889 32890/** 32891 * Internal class containing all TinyMCE specific control types such as 32892 * format listboxes, fontlist boxes, toolbar buttons etc. 32893 * 32894 * @class tinymce.ui.FormatControls 32895 */ 32896define("tinymce/ui/FormatControls", [ 32897 "tinymce/ui/Control", 32898 "tinymce/ui/Widget", 32899 "tinymce/ui/FloatPanel", 32900 "tinymce/util/Tools", 32901 "tinymce/EditorManager", 32902 "tinymce/Env" 32903], function(Control, Widget, FloatPanel, Tools, EditorManager, Env) { 32904 var each = Tools.each; 32905 32906 EditorManager.on('AddEditor', function(e) { 32907 if (e.editor.rtl) { 32908 Control.rtl = true; 32909 } 32910 32911 registerControls(e.editor); 32912 }); 32913 32914 Control.translate = function(text) { 32915 return EditorManager.translate(text); 32916 }; 32917 32918 Widget.tooltips = !Env.iOS; 32919 32920 function registerControls(editor) { 32921 var formatMenu; 32922 32923 function createListBoxChangeHandler(items, formatName) { 32924 return function() { 32925 var self = this; 32926 32927 editor.on('nodeChange', function(e) { 32928 var formatter = editor.formatter; 32929 var value = null; 32930 32931 each(e.parents, function(node) { 32932 each(items, function(item) { 32933 if (formatName) { 32934 if (formatter.matchNode(node, formatName, {value: item.value})) { 32935 value = item.value; 32936 } 32937 } else { 32938 if (formatter.matchNode(node, item.value)) { 32939 value = item.value; 32940 } 32941 } 32942 32943 if (value) { 32944 return false; 32945 } 32946 }); 32947 32948 if (value) { 32949 return false; 32950 } 32951 }); 32952 32953 self.value(value); 32954 }); 32955 }; 32956 } 32957 32958 function createFormats(formats) { 32959 formats = formats.replace(/;$/, '').split(';'); 32960 32961 var i = formats.length; 32962 while (i--) { 32963 formats[i] = formats[i].split('='); 32964 } 32965 32966 return formats; 32967 } 32968 32969 function createFormatMenu() { 32970 var count = 0, newFormats = []; 32971 32972 var defaultStyleFormats = [ 32973 {title: 'Headings', items: [ 32974 {title: 'Heading 1', format: 'h1'}, 32975 {title: 'Heading 2', format: 'h2'}, 32976 {title: 'Heading 3', format: 'h3'}, 32977 {title: 'Heading 4', format: 'h4'}, 32978 {title: 'Heading 5', format: 'h5'}, 32979 {title: 'Heading 6', format: 'h6'} 32980 ]}, 32981 32982 {title: 'Inline', items: [ 32983 {title: 'Bold', icon: 'bold', format: 'bold'}, 32984 {title: 'Italic', icon: 'italic', format: 'italic'}, 32985 {title: 'Underline', icon: 'underline', format: 'underline'}, 32986 {title: 'Strikethrough', icon: 'strikethrough', format: 'strikethrough'}, 32987 {title: 'Superscript', icon: 'superscript', format: 'superscript'}, 32988 {title: 'Subscript', icon: 'subscript', format: 'subscript'}, 32989 {title: 'Code', icon: 'code', format: 'code'} 32990 ]}, 32991 32992 {title: 'Blocks', items: [ 32993 {title: 'Paragraph', format: 'p'}, 32994 {title: 'Blockquote', format: 'blockquote'}, 32995 {title: 'Div', format: 'div'}, 32996 {title: 'Pre', format: 'pre'} 32997 ]}, 32998 32999 {title: 'Alignment', items: [ 33000 {title: 'Left', icon: 'alignleft', format: 'alignleft'}, 33001 {title: 'Center', icon: 'aligncenter', format: 'aligncenter'}, 33002 {title: 'Right', icon: 'alignright', format: 'alignright'}, 33003 {title: 'Justify', icon: 'alignjustify', format: 'alignjustify'} 33004 ]} 33005 ]; 33006 33007 function createMenu(formats) { 33008 var menu = []; 33009 33010 if (!formats) { 33011 return; 33012 } 33013 33014 each(formats, function(format) { 33015 var menuItem = { 33016 text: format.title, 33017 icon: format.icon 33018 }; 33019 33020 if (format.items) { 33021 menuItem.menu = createMenu(format.items); 33022 } else { 33023 var formatName = format.format || "custom" + count++; 33024 33025 if (!format.format) { 33026 format.name = formatName; 33027 newFormats.push(format); 33028 } 33029 33030 menuItem.format = formatName; 33031 menuItem.cmd = format.cmd; 33032 } 33033 33034 menu.push(menuItem); 33035 }); 33036 33037 return menu; 33038 } 33039 33040 function createStylesMenu() { 33041 var menu; 33042 33043 if (editor.settings.style_formats_merge) { 33044 if (editor.settings.style_formats) { 33045 menu = createMenu(defaultStyleFormats.concat(editor.settings.style_formats)); 33046 } else { 33047 menu = createMenu(defaultStyleFormats); 33048 } 33049 } else { 33050 menu = createMenu(editor.settings.style_formats || defaultStyleFormats); 33051 } 33052 33053 return menu; 33054 } 33055 33056 editor.on('init', function() { 33057 each(newFormats, function(format) { 33058 editor.formatter.register(format.name, format); 33059 }); 33060 }); 33061 33062 return { 33063 type: 'menu', 33064 items: createStylesMenu(), 33065 onPostRender: function(e) { 33066 editor.fire('renderFormatsMenu', {control: e.control}); 33067 }, 33068 itemDefaults: { 33069 preview: true, 33070 33071 textStyle: function() { 33072 if (this.settings.format) { 33073 return editor.formatter.getCssText(this.settings.format); 33074 } 33075 }, 33076 33077 onPostRender: function() { 33078 var self = this; 33079 33080 self.parent().on('show', function() { 33081 var formatName, command; 33082 33083 formatName = self.settings.format; 33084 if (formatName) { 33085 self.disabled(!editor.formatter.canApply(formatName)); 33086 self.active(editor.formatter.match(formatName)); 33087 } 33088 33089 command = self.settings.cmd; 33090 if (command) { 33091 self.active(editor.queryCommandState(command)); 33092 } 33093 }); 33094 }, 33095 33096 onclick: function() { 33097 if (this.settings.format) { 33098 toggleFormat(this.settings.format); 33099 } 33100 33101 if (this.settings.cmd) { 33102 editor.execCommand(this.settings.cmd); 33103 } 33104 } 33105 } 33106 }; 33107 } 33108 33109 formatMenu = createFormatMenu(); 33110 33111 // Simple format controls <control/format>:<UI text> 33112 each({ 33113 bold: 'Bold', 33114 italic: 'Italic', 33115 underline: 'Underline', 33116 strikethrough: 'Strikethrough', 33117 subscript: 'Subscript', 33118 superscript: 'Superscript' 33119 }, function(text, name) { 33120 editor.addButton(name, { 33121 tooltip: text, 33122 onPostRender: function() { 33123 var self = this; 33124 33125 // TODO: Fix this 33126 if (editor.formatter) { 33127 editor.formatter.formatChanged(name, function(state) { 33128 self.active(state); 33129 }); 33130 } else { 33131 editor.on('init', function() { 33132 editor.formatter.formatChanged(name, function(state) { 33133 self.active(state); 33134 }); 33135 }); 33136 } 33137 }, 33138 onclick: function() { 33139 toggleFormat(name); 33140 } 33141 }); 33142 }); 33143
33144 // Simple command controls <control>:[<UI text>,<Command>] 33145 each({ 33146 outdent: ['Decrease indent', 'Outdent'], 33147 indent: ['Increase indent', 'Indent'], 33148 cut: ['Cut', 'Cut'], 33149 copy: ['Copy', 'Copy'], 33150 paste: ['Paste', 'Paste'], 33151 help: ['Help', 'mceHelp'], 33152 selectall: ['Select all', 'SelectAll'], 33153 removeformat: ['Clear formatting', 'RemoveFormat'], 33154 visualaid: ['Visual aids', 'mceToggleVisualAid'], 33155 newdocument: ['New document', 'mceNewDocument'] 33156 }, function(item, name) { 33157 editor.addButton(name, { 33158 tooltip: item[0], 33159 cmd: item[1] 33160 }); 33161 }); 33162 33163 // Simple command controls with format state 33164 each({ 33165 blockquote: ['Blockquote', 'mceBlockQuote'], 33166 numlist: ['Numbered list', 'InsertOrderedList'], 33167 bullist: ['Bullet list', 'InsertUnorderedList'], 33168 subscript: ['Subscript', 'Subscript'], 33169 superscript: ['Superscript', 'Superscript'], 33170 alignleft: ['Align left', 'JustifyLeft'], 33171 aligncenter: ['Align center', 'JustifyCenter'], 33172 alignright: ['Align right', 'JustifyRight'], 33173 alignjustify: ['Justify', 'JustifyFull'] 33174 }, function(item, name) { 33175 editor.addButton(name, { 33176 tooltip: item[0], 33177 cmd: item[1], 33178 onPostRender: function() { 33179 var self = this; 33180 33181 // TODO: Fix this 33182 if (editor.formatter) { 33183 editor.formatter.formatChanged(name, function(state) { 33184 self.active(state); 33185 }); 33186 } else { 33187 editor.on('init', function() { 33188 editor.formatter.formatChanged(name, function(state) { 33189 self.active(state); 33190 }); 33191 }); 33192 } 33193 } 33194 }); 33195 }); 33196 33197 function toggleUndoRedoState(type) { 33198 return function() { 33199 var self = this; 33200 33201 type = type == 'redo' ? 'hasRedo' : 'hasUndo'; 33202 33203 function checkState() { 33204 return editor.undoManager ? editor.undoManager[type]() : false; 33205 } 33206 33207 self.disabled(!checkState()); 33208 editor.on('Undo Redo AddUndo TypingUndo ClearUndos', function() { 33209 self.disabled(!checkState()); 33210 }); 33211 }; 33212 } 33213 33214 function toggleVisualAidState() { 33215 var self = this; 33216 33217 editor.on('VisualAid', function(e) { 33218 self.active(e.hasVisual); 33219 }); 33220 33221 self.active(editor.hasVisual); 33222 } 33223 33224 editor.addButton('undo', { 33225 tooltip: 'Undo', 33226 onPostRender: toggleUndoRedoState('undo'), 33227 cmd: 'undo' 33228 }); 33229 33230 editor.addButton('redo', { 33231 tooltip: 'Redo', 33232 onPostRender: toggleUndoRedoState('redo'), 33233 cmd: 'redo' 33234 }); 33235 33236 editor.addMenuItem('newdocument', { 33237 text: 'New document', 33238 shortcut: 'Ctrl+N', 33239 icon: 'newdocument', 33240 cmd: 'mceNewDocument' 33241 }); 33242 33243 editor.addMenuItem('undo', { 33244 text: 'Undo', 33245 icon: 'undo', 33246 shortcut: 'Ctrl+Z', 33247 onPostRender: toggleUndoRedoState('undo'), 33248 cmd: 'undo' 33249 }); 33250 33251 editor.addMenuItem('redo', { 33252 text: 'Redo', 33253 icon: 'redo', 33254 shortcut: 'Ctrl+Y', 33255 onPostRender: toggleUndoRedoState('redo'), 33256 cmd: 'redo' 33257 }); 33258 33259 editor.addMenuItem('visualaid', { 33260 text: 'Visual aids', 33261 selectable: true, 33262 onPostRender: toggleVisualAidState, 33263 cmd: 'mceToggleVisualAid' 33264 }); 33265 33266 each({ 33267 cut: ['Cut', 'Cut', 'Ctrl+X'], 33268 copy: ['Copy', 'Copy', 'Ctrl+C'], 33269 paste: ['Paste', 'Paste', 'Ctrl+V'], 33270 selectall: ['Select all', 'SelectAll', 'Ctrl+A'], 33271 bold: ['Bold', 'Bold', 'Ctrl+B'], 33272 italic: ['Italic', 'Italic', 'Ctrl+I'], 33273 underline: ['Underline', 'Underline'], 33274 strikethrough: ['Strikethrough', 'Strikethrough'], 33275 subscript: ['Subscript', 'Subscript'], 33276 superscript: ['Superscript', 'Superscript'], 33277 removeformat: ['Clear formatting', 'RemoveFormat'] 33278 }, function(item, name) { 33279 editor.addMenuItem(name, { 33280 text: item[0], 33281 icon: name, 33282 shortcut: item[2], 33283 cmd: item[1] 33284 }); 33285 }); 33286 33287 editor.on('mousedown', function() { 33288 FloatPanel.hideAll(); 33289 }); 33290 33291 function toggleFormat(fmt) { 33292 if (fmt.control) { 33293 fmt = fmt.control.value(); 33294 } 33295 33296 if (fmt) { 33297 editor.execCommand('mceToggleFormat', false, fmt); 33298 } 33299 } 33300 33301 editor.addButton('styleselect', { 33302 type: 'menubutton', 33303 text: 'Formats', 33304 menu: formatMenu 33305 }); 33306 33307 editor.addButton('formatselect', function() { 33308 var items = [], blocks = createFormats(editor.settings.block_formats || 33309 'Paragraph=p;' + 33310 'Address=address;' + 33311 'Pre=pre;' + 33312 'Heading 1=h1;' + 33313 'Heading 2=h2;' + 33314 'Heading 3=h3;' + 33315 'Heading 4=h4;' + 33316 'Heading 5=h5;' + 33317 'Heading 6=h6' 33318 ); 33319 33320 each(blocks, function(block) { 33321 items.push({ 33322 text: block[0], 33323 value: block[1], 33324 textStyle: function() { 33325 return editor.formatter.getCssText(block[1]); 33326 } 33327 }); 33328 }); 33329 33330 return { 33331 type: 'listbox', 33332 text: blocks[0][0], 33333 values: items, 33334 fixedWidth: true, 33335 onselect: toggleFormat, 33336 onPostRender: createListBoxChangeHandler(items) 33337 }; 33338 }); 33339 33340 editor.addButton('fontselect', function() { 33341 var defaultFontsFormats =
33342 'Andale Mono=andale mono,times;' + 33343 'Arial=arial,helvetica,sans-serif;' + 33344 'Arial Black=arial black,avant garde;' + 33345 'Book Antiqua=book antiqua,palatino;' + 33346 'Comic Sans MS=comic sans ms,sans-serif;' + 33347 'Courier New=courier new,courier;' + 33348 'Georgia=georgia,palatino;' + 33349 'Helvetica=helvetica;' + 33350 'Impact=impact,chicago;' + 33351 'Open Sans=Open Sans, sans-serif;' + 33352 'Symbol=symbol;' + 33353 'Tahoma=tahoma,arial,helvetica,sans-serif;' + 33354 'Terminal=terminal,monaco;' + 33355 'Times New Roman=times new roman,times;' + 33356 'Trebuchet MS=trebuchet ms,geneva;' + 33357 'Verdana=verdana,geneva;' + 33358 'Webdings=webdings;' + 33359 'Wingdings=wingdings,zapf dingbats'; 33360 33361 var items = [], fonts = createFormats(editor.settings.font_formats || defaultFontsFormats); 33362 33363 each(fonts, function(font) { 33364 items.push({ 33365 text: {raw: font[0]}, 33366 value: font[1], 33367 textStyle: font[1].indexOf('dings') == -1 ? 'font-family:' + font[1] : '' 33368 }); 33369 }); 33370 33371 return { 33372 type: 'listbox', 33373 text: 'Font Family', 33374 tooltip: 'Font Family', 33375 values: items, 33376 fixedWidth: true, 33377 onPostRender: createListBoxChangeHandler(items, 'fontname'), 33378 onselect: function(e) { 33379 if (e.control.settings.value) { 33380 editor.execCommand('FontName', false, e.control.settings.value); 33381 } 33382 } 33383 }; 33384 }); 33385 33386 editor.addButton('fontsizeselect', function() { 33387 var items = [], defaultFontsizeFormats = '8pt 10pt 12pt 14pt 16pt 18pt 24pt 36pt'; 33388 var fontsize_formats = editor.settings.fontsize_formats || defaultFontsizeFormats; 33389 33390 each(fontsize_formats.split(' '), function(item) { 33391 var text = item, value = item; 33392 // Allow text=value font sizes. 33393 var values = item.split('='); 33394 if (values.length > 1) { 33395 text = values[0]; 33396 value = values[1]; 33397 } 33398 items.push({text: text, value: value}); 33399 }); 33400 33401 return { 33402 type: 'listbox', 33403 text: 'Font Sizes', 33404 tooltip: 'Font Sizes', 33405 values: items, 33406 fixedWidth: true, 33407 onPostRender: createListBoxChangeHandler(items, 'fontsize'), 33408 onclick: function(e) { 33409 if (e.control.settings.value) { 33410 editor.execCommand('FontSize', false, e.control.settings.value); 33411 } 33412 } 33413 }; 33414 }); 33415 33416 editor.addMenuItem('formats', { 33417 text: 'Formats', 33418 menu: formatMenu 33419 }); 33420 } 33421}); 33422 33423// Included from: js/tinymce/classes/ui/GridLayout.js 33424 33425/** 33426 * GridLayout.js 33427 * 33428 * Copyright, Moxiecode Systems AB 33429 * Released under LGPL License. 33430 * 33431 * License: http://www.tinymce.com/license 33432 * Contributing: http://www.tinymce.com/contributing 33433 */ 33434 33435/** 33436 * This layout manager places controls in a grid. 33437 * 33438 * @setting {Number} spacing Spacing between controls. 33439 * @setting {Number} spacingH Horizontal spacing between controls. 33440 * @setting {Number} spacingV Vertical spacing between controls. 33441 * @setting {Number} columns Number of columns to use. 33442 * @setting {String/Array} alignH start|end|center|stretch or array of values for each column. 33443 * @setting {String/Array} alignV start|end|center|stretch or array of values for each column. 33444 * @setting {String} pack start|end 33445 * 33446 * @class tinymce.ui.GridLayout 33447 * @extends tinymce.ui.AbsoluteLayout 33448 */ 33449define("tinymce/ui/GridLayout", [ 33450 "tinymce/ui/AbsoluteLayout" 33451], function(AbsoluteLayout) { 33452 "use strict"; 33453 33454 return AbsoluteLayout.extend({ 33455 /** 33456 * Recalculates the positions of the controls in the specified container. 33457 * 33458 * @method recalc 33459 * @param {tinymce.ui.Container} container Container instance to recalc. 33460 */ 33461 recalc: function(container) { 33462 var settings = container.settings, rows, cols, items, contLayoutRect, width, height, rect, 33463 ctrlLayoutRect, ctrl, x, y, posX, posY, ctrlSettings, contPaddingBox, align, spacingH, spacingV, alignH, alignV, maxX, maxY, 33464 colWidths = [], rowHeights = [], ctrlMinWidth, ctrlMinHeight, availableWidth, availableHeight, reverseRows, idx; 33465 33466 // Get layout settings 33467 settings = container.settings; 33468 items = container.items().filter(':visible'); 33469 contLayoutRect = container.layoutRect(); 33470 cols = settings.columns || Math.ceil(Math.sqrt(items.length));
33471 rows = Math.ceil(items.length / cols); 33472 spacingH = settings.spacingH || settings.spacing || 0; 33473 spacingV = settings.spacingV || settings.spacing || 0; 33474 alignH = settings.alignH || settings.align; 33475 alignV = settings.alignV || settings.align; 33476 contPaddingBox = container._paddingBox; 33477 reverseRows = 'reverseRows' in settings ? settings.reverseRows : container.isRtl(); 33478 33479 if (alignH && typeof(alignH) == "string") { 33480 alignH = [alignH]; 33481 } 33482 33483 if (alignV && typeof(alignV) == "string") { 33484 alignV = [alignV]; 33485 } 33486 33487 // Zero padd columnWidths 33488 for (x = 0; x < cols; x++) { 33489 colWidths.push(0); 33490 } 33491 33492 // Zero padd rowHeights 33493 for (y = 0; y < rows; y++) { 33494 rowHeights.push(0); 33495 } 33496 33497 // Calculate columnWidths and rowHeights 33498 for (y = 0; y < rows; y++) { 33499 for (x = 0; x < cols; x++) { 33500 ctrl = items[y * cols + x]; 33501 33502 // Out of bounds 33503 if (!ctrl) { 33504 break; 33505 } 33506 33507 ctrlLayoutRect = ctrl.layoutRect(); 33508 ctrlMinWidth = ctrlLayoutRect.minW; 33509 ctrlMinHeight = ctrlLayoutRect.minH; 33510 33511 colWidths[x] = ctrlMinWidth > colWidths[x] ? ctrlMinWidth : colWidths[x]; 33512 rowHeights[y] = ctrlMinHeight > rowHeights[y] ? ctrlMinHeight : rowHeights[y]; 33513 } 33514 } 33515 33516 // Calculate maxX 33517 availableWidth = contLayoutRect.innerW - contPaddingBox.left - contPaddingBox.right; 33518 for (maxX = 0, x = 0; x < cols; x++) { 33519 maxX += colWidths[x] + (x > 0 ? spacingH : 0); 33520 availableWidth -= (x > 0 ? spacingH : 0) + colWidths[x]; 33521 } 33522 33523 // Calculate maxY 33524 availableHeight = contLayoutRect.innerH - contPaddingBox.top - contPaddingBox.bottom; 33525 for (maxY = 0, y = 0; y < rows; y++) { 33526 maxY += rowHeights[y] + (y > 0 ? spacingV : 0); 33527 availableHeight -= (y > 0 ? spacingV : 0) + rowHeights[y]; 33528 } 33529 33530 maxX += contPaddingBox.left + contPaddingBox.right; 33531 maxY += contPaddingBox.top + contPaddingBox.bottom; 33532 33533 // Calculate minW/minH 33534 rect = {}; 33535 rect.minW = maxX + (contLayoutRect.w - contLayoutRect.innerW); 33536 rect.minH = maxY + (contLayoutRect.h - contLayoutRect.innerH); 33537 33538 rect.contentW = rect.minW - contLayoutRect.deltaW; 33539 rect.contentH = rect.minH - contLayoutRect.deltaH; 33540 rect.minW = Math.min(rect.minW, contLayoutRect.maxW); 33541 rect.minH = Math.min(rect.minH, contLayoutRect.maxH); 33542 rect.minW = Math.max(rect.minW, contLayoutRect.startMinWidth); 33543 rect.minH = Math.max(rect.minH, contLayoutRect.startMinHeight); 33544 33545 // Resize container container if minSize was changed 33546 if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) { 33547 rect.w = rect.minW; 33548 rect.h = rect.minH; 33549 33550 container.layoutRect(rect); 33551 this.recalc(container); 33552 33553 // Forced recalc for example if items are hidden/shown 33554 if (container._lastRect === null) { 33555 var parentCtrl = container.parent(); 33556 if (parentCtrl) { 33557 parentCtrl._lastRect = null; 33558 parentCtrl.recalc(); 33559 } 33560 } 33561 33562 return; 33563 } 33564 33565 // Update contentW/contentH so absEnd moves correctly 33566 if (contLayoutRect.autoResize) { 33567 rect = container.layoutRect(rect); 33568 rect.contentW = rect.minW - contLayoutRect.deltaW; 33569 rect.contentH = rect.minH - contLayoutRect.deltaH; 33570 } 33571 33572 var flexV; 33573 33574 if (settings.packV == 'start') { 33575 flexV = 0; 33576 } else { 33577 flexV = availableHeight > 0 ? Math.floor(availableHeight / rows) : 0; 33578 } 33579 33580 // Calculate totalFlex 33581 var totalFlex = 0; 33582 var flexWidths = settings.flexWidths; 33583 if (flexWidths) { 33584 for (x = 0; x < flexWidths.length; x++) { 33585 totalFlex += flexWidths[x]; 33586 } 33587 } else { 33588 totalFlex = cols; 33589 } 33590 33591 // Calculate new column widths based on flex values 33592 var ratio = availableWidth / totalFlex; 33593 for (x = 0; x < cols; x++) { 33594 colWidths[x] += flexWidths ? flexWidths[x] * ratio : ratio; 33595 } 33596 33597 // Move/resize controls 33598 posY = contPaddingBox.top; 33599 for (y = 0; y < rows; y++) { 33600 posX = contPaddingBox.left; 33601 height = rowHeights[y] + flexV; 33602 33603 for (x = 0; x < cols; x++) { 33604 if (reverseRows) { 33605 idx = y * cols + cols - 1 - x; 33606 } else { 33607 idx = y * cols + x; 33608 } 33609 33610 ctrl = items[idx]; 33611 33612 // No more controls to render then break 33613 if (!ctrl) { 33614 break; 33615 } 33616 33617 // Get control settings and calculate x, y 33618 ctrlSettings = ctrl.settings; 33619 ctrlLayoutRect = ctrl.layoutRect(); 33620 width = Math.max(colWidths[x], ctrlLayoutRect.startMinWidth); 33621 ctrlLayoutRect.x = posX; 33622 ctrlLayoutRect.y = posY; 33623 33624 // Align control horizontal
33625 align = ctrlSettings.alignH || (alignH ? (alignH[x] || alignH[0]) : null); 33626 if (align == "center") { 33627 ctrlLayoutRect.x = posX + (width / 2) - (ctrlLayoutRect.w / 2); 33628 } else if (align == "right") { 33629 ctrlLayoutRect.x = posX + width - ctrlLayoutRect.w; 33630 } else if (align == "stretch") { 33631 ctrlLayoutRect.w = width; 33632 } 33633 33634 // Align control vertical 33635 align = ctrlSettings.alignV || (alignV ? (alignV[x] || alignV[0]) : null); 33636 if (align == "center") { 33637 ctrlLayoutRect.y = posY + (height / 2) - (ctrlLayoutRect.h / 2); 33638 } else if (align == "bottom") { 33639 ctrlLayoutRect.y = posY + height - ctrlLayoutRect.h; 33640 } else if (align == "stretch") { 33641 ctrlLayoutRect.h = height; 33642 } 33643 33644 ctrl.layoutRect(ctrlLayoutRect); 33645 33646 posX += width + spacingH; 33647 33648 if (ctrl.recalc) { 33649 ctrl.recalc(); 33650 } 33651 } 33652 33653 posY += height + spacingV; 33654 } 33655 } 33656 }); 33657}); 33658 33659// Included from: js/tinymce/classes/ui/Iframe.js 33660 33661/** 33662 * Iframe.js 33663 * 33664 * Copyright, Moxiecode Systems AB 33665 * Released under LGPL License. 33666 * 33667 * License: http://www.tinymce.com/license 33668 * Contributing: http://www.tinymce.com/contributing 33669 */ 33670 33671/*jshint scripturl:true */ 33672 33673/** 33674 * This class creates an iframe. 33675 * 33676 * @setting {String} url Url to open in the iframe. 33677 * 33678 * @-x-less Iframe.less 33679 * @class tinymce.ui.Iframe 33680 * @extends tinymce.ui.Widget 33681 */ 33682define("tinymce/ui/Iframe", [ 33683 "tinymce/ui/Widget" 33684], function(Widget) { 33685 "use strict"; 33686 33687 return Widget.extend({ 33688 /** 33689 * Renders the control as a HTML string. 33690 * 33691 * @method renderHtml 33692 * @return {String} HTML representing the control. 33693 */ 33694 renderHtml: function() { 33695 var self = this; 33696 33697 self.addClass('iframe'); 33698 self.canFocus = false; 33699 33700 /*eslint no-script-url:0 */ 33701 return ( 33702 '<iframe id="' + self._id + '" class="' + self.classes() + '" tabindex="-1" src="' + 33703 (self.settings.url || "javascript:\'\'") + '" frameborder="0"></iframe>' 33704 ); 33705 }, 33706 33707 /** 33708 * Setter for the iframe source. 33709 * 33710 * @method src 33711 * @param {String} src Source URL for iframe. 33712 */ 33713 src: function(src) { 33714 this.getEl().src = src; 33715 }, 33716 33717 /** 33718 * Inner HTML for the iframe. 33719 * 33720 * @method html 33721 * @param {String} html HTML string to set as HTML inside the iframe. 33722 * @param {function} callback Optional callback to execute when the iframe body is filled with contents. 33723 * @return {tinymce.ui.Iframe} Current iframe control. 33724 */ 33725 html: function(html, callback) { 33726 var self = this, body = this.getEl().contentWindow.document.body; 33727 33728 // Wait for iframe to initialize IE 10 takes time 33729 if (!body) { 33730 setTimeout(function() { 33731 self.html(html); 33732 }, 0); 33733 } else { 33734 body.innerHTML = html; 33735 33736 if (callback) { 33737 callback(); 33738 } 33739 } 33740 33741 return this; 33742 } 33743 }); 33744}); 33745 33746// Included from: js/tinymce/classes/ui/Label.js 33747 33748/** 33749 * Label.js 33750 * 33751 * Copyright, Moxiecode Systems AB 33752 * Released under LGPL License. 33753 * 33754 * License: http://www.tinymce.com/license 33755 * Contributing: http://www.tinymce.com/contributing 33756 */ 33757 33758/** 33759 * This class creates a label element. A label is a simple text control 33760 * that can be bound to other controls. 33761 * 33762 * @-x-less Label.less 33763 * @class tinymce.ui.Label 33764 * @extends tinymce.ui.Widget 33765 */ 33766define("tinymce/ui/Label", [ 33767 "tinymce/ui/Widget", 33768 "tinymce/ui/DomUtils" 33769], function(Widget, DomUtils) { 33770 "use strict"; 33771 33772 return Widget.extend({ 33773 /** 33774 * Constructs a instance with the specified settings. 33775 * 33776 * @constructor 33777 * @param {Object} settings Name/value object with settings. 33778 * @param {Boolean} multiline Multiline label. 33779 */ 33780 init: function(settings) { 33781 var self = this; 33782 33783 self._super(settings); 33784 self.addClass('widget'); 33785 self.addClass('label'); 33786 self.canFocus = false; 33787 33788 if (settings.multiline) { 33789 self.addClass('autoscroll'); 33790 } 33791 33792 if (settings.strong) { 33793 self.addClass('strong'); 33794 } 33795 }, 33796 33797 /** 33798 * Initializes the current controls layout rect. 33799 * This will be executed by the layout managers to determine the 33800 * default minWidth/minHeight etc. 33801 * 33802 * @method initLayoutRect 33803 * @return {Object} Layout rect instance. 33804 */ 33805 initLayoutRect: function() { 33806 var self = this, layoutRect = self._super(); 33807 33808 if (self.settings.multiline) { 33809 var size = DomUtils.getSize(self.getEl()); 33810 33811 // Check if the text fits within maxW if not then try word wrapping it 33812 if (size.width > layoutRect.maxW) { 33813 layoutRect.minW = layoutRect.maxW; 33814 self.addClass('multiline'); 33815 } 33816 33817 self.getEl().style.width = layoutRect.minW + 'px'; 33818 layoutRect.startMinH = layoutRect.h = layoutRect.minH = Math.min(layoutRect.maxH, DomUtils.getSize(self.getEl()).height); 33819 } 33820 33821 return layoutRect; 33822 }, 33823 33824 /** 33825 * Repaints the control after a layout operation. 33826 * 33827 * @method repaint 33828 */ 33829 repaint: function() { 33830 var self = this; 33831 33832 if (!self.settings.multiline) { 33833 self.getEl().style.lineHeight = self.layoutRect().h + 'px'; 33834 } 33835 33836 return self._super(); 33837 }, 33838 33839 /** 33840 * Sets/gets the current label text. 33841 * 33842 * @method text 33843 * @param {String} [text] New label text. 33844 * @return {String|tinymce.ui.Label} Current text or current label instance. 33845 */ 33846 text: function(text) { 33847 var self = this; 33848 33849 if (self._rendered && text) { 33850 this.innerHtml(self.encode(text)); 33851 } 33852 33853 return self._super(text); 33854 }, 33855 33856 /** 33857 * Renders the control as a HTML string. 33858 * 33859 * @method renderHtml 33860 * @return {String} HTML representing the control. 33861 */ 33862 renderHtml: function() { 33863 var self = this, forId = self.settings.forId; 33864 33865 return ( 33866 '<label id="' + self._id + '" class="' + self.classes() + '"' + (forId ? ' for="' + forId + '"' : '') + '>
33866' + 33867 self.encode(self._text) + 33868 '</label>' 33869 ); 33870 } 33871 }); 33872}); 33873 33874// Included from: js/tinymce/classes/ui/Toolbar.js 33875 33876/** 33877 * Toolbar.js 33878 * 33879 * Copyright, Moxiecode Systems AB 33880 * Released under LGPL License. 33881 * 33882 * License: http://www.tinymce.com/license 33883 * Contributing: http://www.tinymce.com/contributing 33884 */ 33885 33886/** 33887 * Creates a new toolbar. 33888 * 33889 * @class tinymce.ui.Toolbar 33890 * @extends tinymce.ui.Container 33891 */ 33892define("tinymce/ui/Toolbar", [ 33893 "tinymce/ui/Container" 33894], function(Container) { 33895 "use strict"; 33896 33897 return Container.extend({ 33898 Defaults: { 33899 role: 'toolbar', 33900 layout: 'flow' 33901 }, 33902 33903 /** 33904 * Constructs a instance with the specified settings. 33905 * 33906 * @constructor 33907 * @param {Object} settings Name/value object with settings. 33908 */ 33909 init: function(settings) { 33910 var self = this; 33911 33912 self._super(settings); 33913 self.addClass('toolbar'); 33914 }, 33915 33916 /** 33917 * Called after the control has been rendered. 33918 * 33919 * @method postRender 33920 */ 33921 postRender: function() { 33922 var self = this; 33923 33924 self.items().addClass('toolbar-item'); 33925 33926 return self._super(); 33927 } 33928 }); 33929}); 33930 33931// Included from: js/tinymce/classes/ui/MenuBar.js 33932 33933/** 33934 * MenuBar.js 33935 * 33936 * Copyright, Moxiecode Systems AB 33937 * Released under LGPL License. 33938 * 33939 * License: http://www.tinymce.com/license 33940 * Contributing: http://www.tinymce.com/contributing 33941 */ 33942 33943/** 33944 * Creates a new menubar. 33945 * 33946 * @-x-less MenuBar.less 33947 * @class tinymce.ui.MenuBar 33948 * @extends tinymce.ui.Toolbar 33949 */ 33950define("tinymce/ui/MenuBar", [ 33951 "tinymce/ui/Toolbar" 33952], function(Toolbar) { 33953 "use strict"; 33954 33955 return Toolbar.extend({ 33956 Defaults: { 33957 role: 'menubar', 33958 containerCls: 'menubar', 33959 ariaRoot: true, 33960 defaults: { 33961 type: 'menubutton' 33962 } 33963 } 33964 }); 33965}); 33966 33967// Included from: js/tinymce/classes/ui/MenuButton.js 33968 33969/** 33970 * MenuButton.js 33971 * 33972 * Copyright, Moxiecode Systems AB 33973 * Released under LGPL License. 33974 * 33975 * License: http://www.tinymce.com/license 33976 * Contributing: http://www.tinymce.com/contributing 33977 */ 33978 33979/** 33980 * Creates a new menu button. 33981 * 33982 * @-x-less MenuButton.less 33983 * @class tinymce.ui.MenuButton 33984 * @extends tinymce.ui.Button 33985 */ 33986define("tinymce/ui/MenuButton", [ 33987 "tinymce/ui/Button", 33988 "tinymce/ui/Factory", 33989 "tinymce/ui/MenuBar" 33990], function(Button, Factory, MenuBar) { 33991 "use strict"; 33992 33993 // TODO: Maybe add as some global function 33994 function isChildOf(node, parent) { 33995 while (node) { 33996 if (parent === node) { 33997 return true; 33998 } 33999 34000 node = node.parentNode; 34001 } 34002 34003 return false; 34004 } 34005 34006 var MenuButton = Button.extend({ 34007 /** 34008 * Constructs a instance with the specified settings. 34009 * 34010 * @constructor 34011 * @param {Object} settings Name/value object with settings. 34012 */ 34013 init: function(settings) { 34014 var self = this; 34015 34016 self._renderOpen = true; 34017 self._super(settings); 34018 34019 self.addClass('menubtn'); 34020 34021 if (settings.fixedWidth) { 34022 self.addClass('fixed-width'); 34023 } 34024 34025 self.aria('haspopup', true); 34026 self.hasPopup = true; 34027 }, 34028 34029 /** 34030 * Shows the menu for the button. 34031 * 34032 * @method showMenu 34033 */ 34034 showMenu: function() { 34035 var self = this, settings = self.settings, menu; 34036 34037 if (self.menu && self.menu.visible()) { 34038 return self.hideMenu(); 34039 } 34040 34041 if (!self.menu) { 34042 menu = settings.menu || []; 34043 34044 // Is menu array then auto constuct menu control 34045 if (menu.length) { 34046 menu = { 34047 type: 'menu', 34048 items: menu 34049 }; 34050 } else { 34051 menu.type = menu.type || 'menu'; 34052 } 34053 34054 self.menu = Factory.create(menu).parent(self).renderTo(); 34055 self.fire('createmenu'); 34056 self.menu.reflow(); 34057 self.menu.on('cancel', function(e) { 34058 if (e.control.parent() === self.menu) { 34059 e.stopPropagation(); 34060 self.focus(); 34061 self.hideMenu(); 34062 } 34063 }); 34064 34065 // Move focus to button when a menu item is selected/clicked 34066 self.menu.on('select', function() { 34067 self.focus(); 34068 }); 34069 34070 self.menu.on('show hide', function(e) { 34071 if (e.control == self.menu) { 34072 self.activeMenu(e.type == 'show'); 34073 } 34074 34075 self.aria('expanded', e.type == 'show'); 34076 }).fire('show'); 34077 } 34078 34079 self.menu.show(); 34080 self.menu.layoutRect({w: self.layoutRect().w}); 34081 self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']); 34082 }, 34083 34084 /** 34085 * Hides the menu for the button. 34086 * 34087 * @method hideMenu 34088 */ 34089 hideMenu: function() { 34090 var self = this; 34091 34092 if (self.menu) { 34093 self.menu.items().each(function(item) { 34094 if (item.hideMenu) { 34095 item.hideMenu(); 34096 } 34097 }); 34098 34099 self.menu.hide(); 34100 } 34101 }, 34102 34103 /** 34104 * Sets the active menu state. 34105 * 34106 * @private 34107 */ 34108 activeMenu: function(state) { 34109 this.toggleClass('active', state); 34110 }, 34111 34112 /** 34113 * Renders the control as a HTML string. 34114 * 34115 * @method renderHtml 34116 * @return {String} HTML representing the control. 34117 */ 34118 renderHtml: function() { 34119 var self = this, id = self._id, prefix = self.classPrefix; 34120 var icon = self.settings.icon, image; 34121 34122 image = self.settings.image; 34123 if (image) { 34124 icon = 'none'; 34125
34126 // Support for [high dpi, low dpi] image sources 34127 if (typeof image != "string") { 34128 image = window.getSelection ? image[0] : image[1]; 34129 } 34130 34131 image = ' style="background-image: url(\'' + image + '\')"'; 34132 } else { 34133 image = ''; 34134 } 34135 34136 icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : ''; 34137 34138 self.aria('role', self.parent() instanceof MenuBar ? 'menuitem' : 'button'); 34139 34140 return ( 34141 '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1" aria-labelledby="' + id + '">' + 34142 '<button id="' + id + '-open" role="presentation" type="button" tabindex="-1">' + 34143 (icon ? '<i class="' + icon + '"' + image + '></i>' : '') + 34144 '<span>' + (self._text ? (icon ? '\u00a0' : '') + self.encode(self._text) : '') + '</span>' + 34145 ' <i class="' + prefix + 'caret"></i>' + 34146 '</button>' + 34147 '</div>' 34148 ); 34149 }, 34150 34151 /** 34152 * Gets invoked after the control has been rendered. 34153 * 34154 * @method postRender 34155 */ 34156 postRender: function() { 34157 var self = this; 34158 34159 self.on('click', function(e) { 34160 if (e.control === self && isChildOf(e.target, self.getEl())) { 34161 self.showMenu(); 34162 34163 if (e.aria) { 34164 self.menu.items()[0].focus(); 34165 } 34166 } 34167 }); 34168 34169 self.on('mouseenter', function(e) { 34170 var overCtrl = e.control, parent = self.parent(), hasVisibleSiblingMenu; 34171 34172 if (overCtrl && parent && overCtrl instanceof MenuButton && overCtrl.parent() == parent) { 34173 parent.items().filter('MenuButton').each(function(ctrl) { 34174 if (ctrl.hideMenu && ctrl != overCtrl) { 34175 if (ctrl.menu && ctrl.menu.visible()) { 34176 hasVisibleSiblingMenu = true; 34177 } 34178 34179 ctrl.hideMenu(); 34180 } 34181 }); 34182 34183 if (hasVisibleSiblingMenu) { 34184 overCtrl.focus(); // Fix for: #5887 34185 overCtrl.showMenu(); 34186 } 34187 } 34188 }); 34189 34190 return self._super(); 34191 }, 34192 34193 /** 34194 * Sets/gets the current button text. 34195 * 34196 * @method text 34197 * @param {String} [text] New button text. 34198 * @return {String|tinymce.ui.MenuButton} Current text or current MenuButton instance. 34199 */ 34200 text: function(text) { 34201 var self = this, i, children; 34202 34203 if (self._rendered) { 34204 children = self.getEl('open').getElementsByTagName('span'); 34205 for (i = 0; i < children.length; i++) { 34206 children[i].innerHTML = (self.settings.icon && text ? '\u00a0' : '') + self.encode(text); 34207 } 34208 } 34209 34210 return this._super(text); 34211 }, 34212 34213 /** 34214 * Removes the control and it's menus. 34215 * 34216 * @method remove 34217 */ 34218 remove: function() { 34219 this._super(); 34220 34221 if (this.menu) { 34222 this.menu.remove(); 34223 } 34224 } 34225 }); 34226 34227 return MenuButton; 34228}); 34229 34230// Included from: js/tinymce/classes/ui/ListBox.js 34231 34232/** 34233 * ListBox.js 34234 * 34235 * Copyright, Moxiecode Systems AB 34236 * Released under LGPL License. 34237 * 34238 * License: http://www.tinymce.com/license 34239 * Contributing: http://www.tinymce.com/contributing 34240 */ 34241 34242/** 34243 * Creates a new list box control. 34244 * 34245 * @-x-less ListBox.less 34246 * @class tinymce.ui.ListBox 34247 * @extends tinymce.ui.MenuButton 34248 */ 34249define("tinymce/ui/ListBox", [ 34250 "tinymce/ui/MenuButton" 34251], function(MenuButton) { 34252 "use strict"; 34253 34254 return MenuButton.extend({ 34255 /** 34256 * Constructs a instance with the specified settings. 34257 * 34258 * @constructor 34259 * @param {Object} settings Name/value object with settings. 34260 * @setting {Array} values Array with values to add to list box. 34261 */ 34262 init: function(settings) { 34263 var self = this, values, selected, selectedText, lastItemCtrl; 34264 34265 function setSelected(menuValues) { 34266 // Try to find a selected value 34267 for (var i = 0; i < menuValues.length; i++) { 34268 selected = menuValues[i].selected || settings.value === menuValues[i].value; 34269 34270 if (selected) { 34271 selectedText = selectedText || menuValues[i].text; 34272 self._value = menuValues[i].value; 34273 break; 34274 } 34275 34276 // If the value has a submenu, try to find the selected values in that menu 34277 if (menuValues[i].menu) { 34278 setSelected(menuValues[i].menu); 34279 } 34280 } 34281 } 34282 34283 self._values = values = settings.values; 34284 if (values) { 34285 if (typeof settings.value != "undefined") { 34286 setSelected(values); 34287 } 34288 34289 // Default with first item 34290 if (!selected && values.length > 0) { 34291 selectedText = values[0].text; 34292 self._value = values[0].value; 34293 } 34294 34295 settings.menu = values; 34296 } 34297 34298 settings.text = settings.text || selectedText || values[0].text; 34299 34300 self._super(settings); 34301 self.addClass('listbox'); 34302 34303 self.on('select', function(e) { 34304 var ctrl = e.control; 34305 34306 if (lastItemCtrl) { 34307 e.lastControl = lastItemCtrl; 34308 } 34309 34310 if (settings.multiple) { 34311 ctrl.active(!ctrl.active()); 34312 } else { 34313 self.value(e.control.settings.value); 34314 } 34315 34316 lastItemCtrl = ctrl; 34317 }); 34318 }, 34319 34320 /** 34321 * Getter/setter function for the control value. 34322 * 34323 * @method value 34324 * @param {String} [value] Value to be set. 34325 * @return {Boolean/tinymce.ui.ListBox} Value or self if it's a set operation. 34326 */ 34327 value: function(value) { 34328 var self = this, active, selectedText, menu; 34329 34330 function activateByValue(menu, value) { 34331 menu.items().each(function(ctrl) { 34332 active = ctrl.value() === value; 34333 34334 if (active) { 34335 selectedText = selectedText || ctrl.text(); 34336 } 34337 34338 ctrl.active(active); 34339 34340 if (ctrl.menu) { 34341 activateByValue(ctrl.menu, value); 34342 } 34343 }); 34344 } 34345 34346 function setActiveValues(menuValues) { 34347 for (var i = 0; i < menuValues.length; i++) { 34348 active = menuValues[i].value == value; 34349 34350 if (active) { 34351 selectedText = selectedText || menuValues[i].text; 34352 } 34353 34354 menuValues[i].active = active; 34355 34356 if (menuValues[i].menu) { 34357 setActiveValues(menuValues[i].menu); 34358 } 34359 } 34360 } 34361 34362 if (typeof(value) != "undefined") { 34363 if (self.menu) { 34364 activateByValue(self.menu, value); 34365 } else { 34366 menu = self.settings.menu; 34367 setActiveValues(menu); 34368 } 34369 34370 self.text(selectedText || this.settings.text); 34371 } 34372 34373 return self._super(value); 34374 } 34375 }); 34376}); 34377 34378// Included from: js/tinymce/classes/ui/MenuItem.js 34379 34380/** 34381 * MenuItem.js 34382 * 34383 * Copyright, Moxiecode Systems AB 34384 * Released under LGPL License. 34385 * 34386 * License: http://www.tinymce.com/license 34387 * Contributing: http://www.tinymce.com/contributing 34388 */ 34389 34390/** 34391 * Creates a new menu item. 34392 * 34393 * @-x-less MenuItem.less 34394 * @class tinymce.ui.MenuItem 34395 * @extends tinymce.ui.Widget 34396 */
34397define("tinymce/ui/MenuItem", [ 34398 "tinymce/ui/Widget", 34399 "tinymce/ui/Factory", 34400 "tinymce/Env" 34401], function(Widget, Factory, Env) { 34402 "use strict"; 34403 34404 return Widget.extend({ 34405 Defaults: { 34406 border: 0, 34407 role: 'menuitem' 34408 }, 34409 34410 /** 34411 * Constructs a instance with the specified settings. 34412 * 34413 * @constructor 34414 * @param {Object} settings Name/value object with settings. 34415 * @setting {Boolean} selectable Selectable menu. 34416 * @setting {Array} menu Submenu array with items. 34417 * @setting {String} shortcut Shortcut to display for menu item. Example: Ctrl+X 34418 */ 34419 init: function(settings) { 34420 var self = this; 34421 34422 self.hasPopup = true; 34423 34424 self._super(settings); 34425 34426 settings = self.settings; 34427 34428 self.addClass('menu-item'); 34429 34430 if (settings.menu) { 34431 self.addClass('menu-item-expand'); 34432 } 34433 34434 if (settings.preview) { 34435 self.addClass('menu-item-preview'); 34436 } 34437 34438 if (self._text === '-' || self._text === '|') { 34439 self.addClass('menu-item-sep'); 34440 self.aria('role', 'separator'); 34441 self._text = '-'; 34442 } 34443 34444 if (settings.selectable) { 34445 self.aria('role', 'menuitemcheckbox'); 34446 self.addClass('menu-item-checkbox'); 34447 settings.icon = 'selected'; 34448 } 34449 34450 if (!settings.preview && !settings.selectable) { 34451 self.addClass('menu-item-normal'); 34452 } 34453 34454 self.on('mousedown', function(e) { 34455 e.preventDefault(); 34456 }); 34457 34458 if (settings.menu && !settings.ariaHideMenu) { 34459 self.aria('haspopup', true); 34460 } 34461 }, 34462 34463 /** 34464 * Returns true/false if the menuitem has sub menu. 34465 * 34466 * @method hasMenus 34467 * @return {Boolean} True/false state if it has submenu. 34468 */ 34469 hasMenus: function() { 34470 return !!this.settings.menu; 34471 }, 34472 34473 /** 34474 * Shows the menu for the menu item. 34475 * 34476 * @method showMenu 34477 */ 34478 showMenu: function() { 34479 var self = this, settings = self.settings, menu, parent = self.parent(); 34480 34481 parent.items().each(function(ctrl) { 34482 if (ctrl !== self) { 34483 ctrl.hideMenu(); 34484 } 34485 }); 34486 34487 if (settings.menu) { 34488 menu = self.menu; 34489 34490 if (!menu) { 34491 menu = settings.menu; 34492 34493 // Is menu array then auto constuct menu control 34494 if (menu.length) { 34495 menu = { 34496 type: 'menu', 34497 items: menu 34498 }; 34499 } else { 34500 menu.type = menu.type || 'menu'; 34501 } 34502 34503 if (parent.settings.itemDefaults) { 34504 menu.itemDefaults = parent.settings.itemDefaults; 34505 } 34506 34507 menu = self.menu = Factory.create(menu).parent(self).renderTo(); 34508 menu.reflow(); 34509 menu.on('cancel', function(e) { 34510 e.stopPropagation(); 34511 self.focus(); 34512 menu.hide(); 34513 }); 34514 menu.on('show hide', function(e) { 34515 e.control.items().each(function(ctrl) { 34516 ctrl.active(ctrl.settings.selected); 34517 }); 34518 }).fire('show'); 34519 34520 menu.on('hide', function(e) { 34521 if (e.control === menu) { 34522 self.removeClass('selected'); 34523 } 34524 }); 34525 34526 menu.submenu = true; 34527 } else { 34528 menu.show(); 34529 } 34530 34531 menu._parentMenu = parent; 34532 34533 menu.addClass('menu-sub'); 34534 34535 var rel = menu.testMoveRel( 34536 self.getEl(), 34537 self.isRtl() ? ['tl-tr', 'bl-br', 'tr-tl', 'br-bl'] : ['tr-tl', 'br-bl', 'tl-tr', 'bl-br'] 34538 ); 34539 34540 menu.moveRel(self.getEl(), rel); 34541 menu.rel = rel; 34542 34543 rel = 'menu-sub-' + rel; 34544 menu.removeClass(menu._lastRel); 34545 menu.addClass(rel); 34546 menu._lastRel = rel; 34547 34548 self.addClass('selected'); 34549 self.aria('expanded', true); 34550 } 34551 }, 34552 34553 /** 34554 * Hides the menu for the menu item. 34555 * 34556 * @method hideMenu 34557 */ 34558 hideMenu: function() { 34559 var self = this; 34560 34561 if (self.menu) { 34562 self.menu.items().each(function(item) { 34563 if (item.hideMenu) { 34564 item.hideMenu(); 34565 } 34566 }); 34567 34568 self.menu.hide(); 34569 self.aria('expanded', false); 34570 } 34571 34572 return self; 34573 }, 34574 34575 /** 34576 * Renders the control as a HTML string. 34577 * 34578 * @method renderHtml 34579 * @return {String} HTML representing the control. 34580 */ 34581 renderHtml: function() { 34582 var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix, text = self.encode(self._text); 34583 var icon = self.settings.icon, image = '', shortcut = settings.shortcut; 34584 34585 if (icon) { 34586 self.parent().addClass('menu-has-icons'); 34587 } 34588 34589 if (settings.image) { 34590 icon = 'none'; 34591 image = ' style="background-image: url(\'' + settings.image + '\')"'; 34592 } 34593 34594 if (shortcut && Env.mac) { 34595 // format shortcut for Mac 34596 shortcut = shortcut.replace(/ctrl\+alt\+/i, '⌥⌘'); // ctrl+cmd 34597 shortcut = shortcut.replace(/ctrl\+/i, '⌘'); // ctrl symbol 34598 shortcut = shortcut.replace(/alt\+/i, '⌥'); // cmd symbol 34599 shortcut = shortcut.replace(/shift\+/i, '⇧'); // shift symbol 34600 } 34601 34602 icon = prefix + 'ico ' + prefix + 'i-' + (self.settings.icon || 'none'); 34603 34604 return ( 34605 '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' +
34606 (text !== '-' ? '<i class="' + icon + '"' + image + '></i>\u00a0' : '') + 34607 (text !== '-' ? '<span id="' + id + '-text" class="' + prefix + 'text">' + text + '</span>' : '') + 34608 (shortcut ? '<div id="' + id + '-shortcut" class="' + prefix + 'menu-shortcut">' + shortcut + '</div>' : '') + 34609 (settings.menu ? '<div class="' + prefix + 'caret"></div>' : '') + 34610 '</div>' 34611 ); 34612 }, 34613 34614 /** 34615 * Gets invoked after the control has been rendered. 34616 * 34617 * @method postRender 34618 */ 34619 postRender: function() { 34620 var self = this, settings = self.settings; 34621 34622 var textStyle = settings.textStyle; 34623 if (typeof(textStyle) == "function") { 34624 textStyle = textStyle.call(this); 34625 } 34626 34627 if (textStyle) { 34628 var textElm = self.getEl('text'); 34629 if (textElm) { 34630 textElm.setAttribute('style', textStyle); 34631 } 34632 } 34633 34634 self.on('mouseenter click', function(e) { 34635 if (e.control === self) { 34636 if (!settings.menu && e.type === 'click') { 34637 self.fire('select'); 34638 self.parent().hideAll(); 34639 } else { 34640 self.showMenu(); 34641 34642 if (e.aria) { 34643 self.menu.focus(true); 34644 } 34645 } 34646 } 34647 }); 34648 34649 self._super(); 34650 34651 return self; 34652 }, 34653 34654 active: function(state) { 34655 if (typeof(state) != "undefined") { 34656 this.aria('checked', state); 34657 } 34658 34659 return this._super(state); 34660 }, 34661 34662 /** 34663 * Removes the control and it's menus. 34664 * 34665 * @method remove 34666 */ 34667 remove: function() { 34668 this._super(); 34669 34670 if (this.menu) { 34671 this.menu.remove(); 34672 } 34673 } 34674 }); 34675}); 34676 34677// Included from: js/tinymce/classes/ui/Menu.js 34678 34679/** 34680 * Menu.js 34681 * 34682 * Copyright, Moxiecode Systems AB 34683 * Released under LGPL License. 34684 * 34685 * License: http://www.tinymce.com/license 34686 * Contributing: http://www.tinymce.com/contributing 34687 */ 34688 34689/** 34690 * Creates a new menu. 34691 * 34692 * @-x-less Menu.less 34693 * @class tinymce.ui.Menu 34694 * @extends tinymce.ui.FloatPanel 34695 */ 34696define("tinymce/ui/Menu", [ 34697 "tinymce/ui/FloatPanel", 34698 "tinymce/ui/MenuItem", 34699 "tinymce/util/Tools" 34700], function(FloatPanel, MenuItem, Tools) { 34701 "use strict"; 34702 34703 var Menu = FloatPanel.extend({ 34704 Defaults: { 34705 defaultType: 'menuitem', 34706 border: 1, 34707 layout: 'stack', 34708 role: 'application', 34709 bodyRole: 'menu', 34710 ariaRoot: true 34711 }, 34712 34713 /** 34714 * Constructs a instance with the specified settings. 34715 * 34716 * @constructor 34717 * @param {Object} settings Name/value object with settings. 34718 */ 34719 init: function(settings) { 34720 var self = this; 34721 34722 settings.autohide = true; 34723 settings.constrainToViewport = true; 34724 34725 if (settings.itemDefaults) { 34726 var items = settings.items, i = items.length; 34727 34728 while (i--) { 34729 items[i] = Tools.extend({}, settings.itemDefaults, items[i]); 34730 } 34731 } 34732 34733 self._super(settings); 34734 self.addClass('menu'); 34735 }, 34736 34737 /** 34738 * Repaints the control after a layout operation. 34739 * 34740 * @method repaint 34741 */ 34742 repaint: function() { 34743 this.toggleClass('menu-align', true); 34744 34745 this._super(); 34746 34747 this.getEl().style.height = ''; 34748 this.getEl('body').style.height = ''; 34749 34750 return this; 34751 }, 34752 34753 /** 34754 * Hides/closes the menu. 34755 * 34756 * @method cancel 34757 */ 34758 cancel: function() { 34759 var self = this; 34760 34761 self.hideAll(); 34762 self.fire('select'); 34763 }, 34764 34765 /** 34766 * Hide menu and all sub menus. 34767 * 34768 * @method hideAll 34769 */ 34770 hideAll: function() { 34771 var self = this; 34772 34773 this.find('menuitem').exec('hideMenu'); 34774 34775 return self._super(); 34776 }, 34777/* 34778 getContainerElm: function() { 34779 var doc = document, id = this.classPrefix + 'menucontainer'; 34780 34781 var elm = doc.getElementById(id); 34782 if (!elm) { 34783 elm = doc.createElement('div'); 34784 elm.id = id; 34785 elm.setAttribute('role', 'application'); 34786 elm.className = this.classPrefix + '-reset'; 34787 elm.style.position = 'absolute'; 34788 elm.style.top = elm.style.left = '0'; 34789 elm.style.overflow = 'visible'; 34790 doc.body.appendChild(elm); 34791 } 34792 34793 return elm; 34794 }, 34795*/ 34796 /** 34797 * Invoked before the menu is rendered. 34798 * 34799 * @method preRender 34800 */ 34801 preRender: function() { 34802 var self = this; 34803 34804 self.items().each(function(ctrl) { 34805 var settings = ctrl.settings; 34806 34807 if (settings.icon || settings.selectable) { 34808 self._hasIcons = true; 34809 return false; 34810 } 34811 }); 34812 34813 return self._super(); 34814 } 34815 }); 34816
34817 return Menu; 34818}); 34819 34820// Included from: js/tinymce/classes/ui/Radio.js 34821 34822/** 34823 * Radio.js 34824 * 34825 * Copyright, Moxiecode Systems AB 34826 * Released under LGPL License. 34827 * 34828 * License: http://www.tinymce.com/license 34829 * Contributing: http://www.tinymce.com/contributing 34830 */ 34831 34832/** 34833 * Creates a new radio button. 34834 * 34835 * @-x-less Radio.less 34836 * @class tinymce.ui.Radio 34837 * @extends tinymce.ui.Checkbox 34838 */ 34839define("tinymce/ui/Radio", [ 34840 "tinymce/ui/Checkbox" 34841], function(Checkbox) { 34842 "use strict"; 34843 34844 return Checkbox.extend({ 34845 Defaults: { 34846 classes: "radio", 34847 role: "radio" 34848 } 34849 }); 34850}); 34851 34852// Included from: js/tinymce/classes/ui/ResizeHandle.js 34853 34854/** 34855 * ResizeHandle.js 34856 * 34857 * Copyright, Moxiecode Systems AB 34858 * Released under LGPL License. 34859 * 34860 * License: http://www.tinymce.com/license 34861 * Contributing: http://www.tinymce.com/contributing 34862 */ 34863 34864/** 34865 * Renders a resize handle that fires ResizeStart, Resize and ResizeEnd events. 34866 * 34867 * @-x-less ResizeHandle.less 34868 * @class tinymce.ui.ResizeHandle 34869 * @extends tinymce.ui.Widget 34870 */ 34871define("tinymce/ui/ResizeHandle", [ 34872 "tinymce/ui/Widget", 34873 "tinymce/ui/DragHelper" 34874], function(Widget, DragHelper) { 34875 "use strict"; 34876 34877 return Widget.extend({ 34878 /** 34879 * Renders the control as a HTML string. 34880 * 34881 * @method renderHtml 34882 * @return {String} HTML representing the control. 34883 */ 34884 renderHtml: function() { 34885 var self = this, prefix = self.classPrefix; 34886 34887 self.addClass('resizehandle'); 34888 34889 if (self.settings.direction == "both") { 34890 self.addClass('resizehandle-both'); 34891 } 34892 34893 self.canFocus = false; 34894 34895 return ( 34896 '<div id="' + self._id + '" class="' + self.classes() + '">' + 34897 '<i class="' + prefix + 'ico ' + prefix + 'i-resize"></i>' + 34898 '</div>' 34899 ); 34900 }, 34901 34902 /** 34903 * Called after the control has been rendered. 34904 * 34905 * @method postRender 34906 */ 34907 postRender: function() { 34908 var self = this; 34909 34910 self._super(); 34911 34912 self.resizeDragHelper = new DragHelper(this._id, { 34913 start: function() { 34914 self.fire('ResizeStart'); 34915 }, 34916 34917 drag: function(e) { 34918 if (self.settings.direction != "both") { 34919 e.deltaX = 0; 34920 } 34921 34922 self.fire('Resize', e); 34923 }, 34924 34925 stop: function() { 34926 self.fire('ResizeEnd'); 34927 } 34928 }); 34929 }, 34930 34931 remove: function() { 34932 if (this.resizeDragHelper) { 34933 this.resizeDragHelper.destroy(); 34934 } 34935 34936 return this._super(); 34937 } 34938 }); 34939}); 34940 34941// Included from: js/tinymce/classes/ui/Spacer.js 34942 34943/** 34944 * Spacer.js 34945 * 34946 * Copyright, Moxiecode Systems AB 34947 * Released under LGPL License. 34948 * 34949 * License: http://www.tinymce.com/license 34950 * Contributing: http://www.tinymce.com/contributing 34951 */ 34952 34953/** 34954 * Creates a spacer. This control is used in flex layouts for example. 34955 * 34956 * @-x-less Spacer.less 34957 * @class tinymce.ui.Spacer 34958 * @extends tinymce.ui.Widget 34959 */ 34960define("tinymce/ui/Spacer", [ 34961 "tinymce/ui/Widget" 34962], function(Widget) { 34963 "use strict"; 34964 34965 return Widget.extend({ 34966 /** 34967 * Renders the control as a HTML string. 34968 * 34969 * @method renderHtml 34970 * @return {String} HTML representing the control. 34971 */ 34972 renderHtml: function() { 34973 var self = this; 34974 34975 self.addClass('spacer'); 34976 self.canFocus = false; 34977 34978 return '<div id="' + self._id + '" class="' + self.classes() + '"></div>'; 34979 } 34980 }); 34981}); 34982 34983// Included from: js/tinymce/classes/ui/SplitButton.js 34984 34985/** 34986 * SplitButton.js 34987 * 34988 * Copyright, Moxiecode Systems AB 34989 * Released under LGPL License. 34990 * 34991 * License: http://www.tinymce.com/license 34992 * Contributing: http://www.tinymce.com/contributing 34993 */ 34994 34995/** 34996 * Creates a split button. 34997 * 34998 * @-x-less SplitButton.less 34999 * @class tinymce.ui.SplitButton 35000 * @extends tinymce.ui.MenuButton 35001 */ 35002define("tinymce/ui/SplitButton", [ 35003 "tinymce/ui/MenuButton", 35004 "tinymce/ui/DomUtils" 35005], function(MenuButton, DomUtils) { 35006 return MenuButton.extend({ 35007 Defaults: { 35008 classes: "widget btn splitbtn", 35009 role: "button" 35010 }, 35011 35012 /** 35013 * Repaints the control after a layout operation. 35014 * 35015 * @method repaint 35016 */ 35017 repaint: function() { 35018 var self = this, elm = self.getEl(), rect = self.layoutRect(), mainButtonElm, menuButtonElm; 35019 35020 self._super(); 35021 35022 mainButtonElm = elm.firstChild; 35023 menuButtonElm = elm.lastChild; 35024 35025 DomUtils.css(mainButtonElm, { 35026 width: rect.w - DomUtils.getSize(menuButtonElm).width, 35027 height: rect.h - 2 35028 }); 35029 35030 DomUtils.css(menuButtonElm, { 35031 height: rect.h - 2 35032 }); 35033 35034 return self; 35035 }, 35036 35037 /** 35038 * Sets the active menu state. 35039 * 35040 * @private 35041 */ 35042 activeMenu: function(state) { 35043 var self = this; 35044 35045 DomUtils.toggleClass(self.getEl().lastChild, self.classPrefix + 'active', state); 35046 }, 35047 35048 /** 35049 * Renders the control as a HTML string. 35050 * 35051 * @method renderHtml 35052 * @return {String} HTML representing the control. 35053 */ 35054 renderHtml: function() { 35055 var self = this, id = self._id, prefix = self.classPrefix, image; 35056 var icon = self.settings.icon; 35057 35058 image = self.settings.image; 35059 if (image) { 35060 icon = 'none'; 35061
35062 // Support for [high dpi, low dpi] image sources 35063 if (typeof image != "string") { 35064 image = window.getSelection ? image[0] : image[1]; 35065 } 35066 35067 image = ' style="background-image: url(\'' + image + '\')"'; 35068 } else { 35069 image = ''; 35070 } 35071 35072 icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : ''; 35073 35074 return ( 35075 '<div id="' + id + '" class="' + self.classes() + '" role="button" tabindex="-1">' + 35076 '<button type="button" hidefocus="1" tabindex="-1">' + 35077 (icon ? '<i class="' + icon + '"' + image + '></i>' : '') + 35078 (self._text ? (icon ? ' ' : '') + self._text : '') + 35079 '</button>' + 35080 '<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' + 35081 //(icon ? '<i class="' + icon + '"></i>' : '') + 35082 (self._menuBtnText ? (icon ? '\u00a0' : '') + self._menuBtnText : '') + 35083 ' <i class="' + prefix + 'caret"></i>' + 35084 '</button>' + 35085 '</div>' 35086 ); 35087 }, 35088 35089 /** 35090 * Called after the control has been rendered. 35091 * 35092 * @method postRender 35093 */ 35094 postRender: function() { 35095 var self = this, onClickHandler = self.settings.onclick; 35096 35097 self.on('click', function(e) { 35098 var node = e.target; 35099 35100 if (e.control == this) { 35101 // Find clicks that is on the main button 35102 while (node) { 35103 if ((e.aria && e.aria.key != 'down') || (node.nodeName == 'BUTTON' && node.className.indexOf('open') == -1)) { 35104 e.stopImmediatePropagation(); 35105 onClickHandler.call(this, e); 35106 return; 35107 } 35108 35109 node = node.parentNode; 35110 } 35111 } 35112 }); 35113 35114 delete self.settings.onclick; 35115 35116 return self._super(); 35117 } 35118 }); 35119}); 35120 35121// Included from: js/tinymce/classes/ui/StackLayout.js 35122 35123/** 35124 * StackLayout.js 35125 * 35126 * Copyright, Moxiecode Systems AB 35127 * Released under LGPL License. 35128 * 35129 * License: http://www.tinymce.com/license 35130 * Contributing: http://www.tinymce.com/contributing 35131 */ 35132 35133/** 35134 * This layout uses the browsers layout when the items are blocks. 35135 * 35136 * @-x-less StackLayout.less 35137 * @class tinymce.ui.StackLayout 35138 * @extends tinymce.ui.FlowLayout 35139 */ 35140define("tinymce/ui/StackLayout", [ 35141 "tinymce/ui/FlowLayout" 35142], function(FlowLayout) { 35143 "use strict"; 35144 35145 return FlowLayout.extend({ 35146 Defaults: { 35147 containerClass: 'stack-layout', 35148 controlClass: 'stack-layout-item', 35149 endClass : 'break' 35150 } 35151 }); 35152}); 35153 35154// Included from: js/tinymce/classes/ui/TabPanel.js 35155 35156/** 35157 * TabPanel.js 35158 * 35159 * Copyright, Moxiecode Systems AB 35160 * Released under LGPL License. 35161 * 35162 * License: http://www.tinymce.com/license 35163 * Contributing: http://www.tinymce.com/contributing 35164 */ 35165 35166/** 35167 * Creates a tab panel control. 35168 * 35169 * @-x-less TabPanel.less 35170 * @class tinymce.ui.TabPanel 35171 * @extends tinymce.ui.Panel 35172 * 35173 * @setting {Number} activeTab Active tab index. 35174 */ 35175define("tinymce/ui/TabPanel", [ 35176 "tinymce/ui/Panel", 35177 "tinymce/ui/DomUtils" 35178], function(Panel, DomUtils) { 35179 "use strict"; 35180 35181 return Panel.extend({ 35182 Defaults: { 35183 layout: 'absolute', 35184 defaults: { 35185 type: 'panel' 35186 } 35187 }, 35188 35189 /** 35190 * Activates the specified tab by index. 35191 * 35192 * @method activateTab 35193 * @param {Number} idx Index of the tab to activate. 35194 */ 35195 activateTab: function(idx) { 35196 var activeTabElm; 35197 35198 if (this.activeTabId) { 35199 activeTabElm = this.getEl(this.activeTabId); 35200 DomUtils.removeClass(activeTabElm, this.classPrefix + 'active'); 35201 activeTabElm.setAttribute('aria-selected', "false"); 35202 } 35203 35204 this.activeTabId = 't' + idx; 35205 35206 activeTabElm = this.getEl('t' + idx); 35207 activeTabElm.setAttribute('aria-selected', "true"); 35208 DomUtils.addClass(activeTabElm, this.classPrefix + 'active'); 35209 35210 this.items()[idx].show().fire('showtab'); 35211 this.reflow(); 35212 35213 this.items().each(function(item, i) { 35214 if (idx != i) { 35215 item.hide(); 35216 } 35217 }); 35218 }, 35219 35220 /** 35221 * Renders the control as a HTML string. 35222 * 35223 * @method renderHtml 35224 * @return {String} HTML representing the control. 35225 */ 35226 renderHtml: function() { 35227 var self = this, layout = self._layout, tabsHtml = '', prefix = self.classPrefix; 35228 35229 self.preRender(); 35230 layout.preRender(self); 35231 35232 self.items().each(function(ctrl, i) { 35233 var id = self._id + '-t' + i; 35234 35235 ctrl.aria('role', 'tabpanel'); 35236 ctrl.aria('labelledby', id); 35237 35238 tabsHtml += ( 35239 '<div id="' + id + '" class="' + prefix + 'tab" ' + 35240 'unselectable="on" role="tab" aria-controls="' + ctrl._id + '" aria-selected="false" tabIndex="-1">' + 35241 self.encode(ctrl.settings.title) + 35242 '</div>' 35243 ); 35244 }); 35245 35246 return ( 35247 '<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' + 35248 '<div id="' + self._id + '-head" class="' + prefix + 'tabs" role="tablist">' + 35249 tabsHtml + 35250 '</div>' + 35251 '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' + 35252 layout.renderHtml(self) + 35253 '</div>' + 35254 '</div>' 35255 ); 35256 }, 35257 35258 /** 35259 * Called after the control has been rendered. 35260 * 35261 * @method postRender 35262 */ 35263 postRender: function() { 35264 var self = this; 35265 35266 self._super(); 35267 35268 self.settings.activeTab = self.settings.activeTab || 0; 35269 self.activateTab(self.settings.activeTab); 35270 35271 this.on('click', function(e) { 35272 var targetParent = e.target.parentNode; 35273 35274 if (e.target.parentNode.id == self._id + '-head') { 35275 var i = targetParent.childNodes.length; 35276 35277 while (i--) { 35278 if (targetParent.childNodes[i] == e.target) { 35279 self.activateTab(i); 35280 } 35281 } 35282 } 35283 }); 35284 }, 35285 35286 /** 35287 * Initializes the current controls layout rect. 35288 * This will be executed by the layout managers to determine the 35289 * default minWidth/minHeight etc. 35290 * 35291 * @method initLayoutRect 35292 * @return {Object} Layout rect instance. 35293 */ 35294 initLayoutRect: function() { 35295 var self = this, rect, minW, minH; 35296 35297 minW = DomUtils.getSize(self.getEl('head')).width; 35298 minW = minW < 0 ? 0 : minW; 35299 minH = 0; 35300 35301 self.items().each(function(item) { 35302 minW = Math.max(minW, item.layoutRect().minW); 35303 minH = Math.max(minH, item.layoutRect().minH); 35304 }); 35305 35306 self.items().each(function(ctrl) { 35307 ctrl.settings.x = 0; 35308 ctrl.settings.y = 0; 35309 ctrl.settings.w = minW; 35310 ctrl.settings.h = minH; 35311 35312 ctrl.layoutRect({ 35313 x: 0, 35314 y: 0, 35315 w: minW, 35316 h: minH 35317 }); 35318 }); 35319 35320 var headH = DomUtils.getSize(self.getEl('head')).height; 35321 35322 self.settings.minWidth = minW; 35323 self.settings.minHeight = minH + headH; 35324 35325 rect = self._super(); 35326 rect.deltaH += headH; 35327 rect.innerH = rect.h - rect.deltaH; 35328 35329 return rect; 35330 } 35331 }); 35332}); 35333 35334// Included from: js/tinymce/classes/ui/TextBox.js 35335 35336/** 35337 * TextBox.js 35338 * 35339 * Copyright, Moxiecode Systems AB 35340 * Released under LGPL License. 35341 * 35342 * License: http://www.tinymce.com/license 35343 * Contributing: http://www.tinymce.com/contributing 35344 */ 35345 35346/** 35347 * Creates a new textbox. 35348 * 35349 * @-x-less TextBox.less 35350 * @class tinymce.ui.TextBox 35351 * @extends tinymce.ui.Widget 35352 */
35353define("tinymce/ui/TextBox", [ 35354 "tinymce/ui/Widget", 35355 "tinymce/ui/DomUtils" 35356], function(Widget, DomUtils) { 35357 "use strict"; 35358 35359 return Widget.extend({ 35360 /** 35361 * Constructs a instance with the specified settings. 35362 * 35363 * @constructor 35364 * @param {Object} settings Name/value object with settings. 35365 * @setting {Boolean} multiline True if the textbox is a multiline control. 35366 * @setting {Number} maxLength Max length for the textbox. 35367 * @setting {Number} size Size of the textbox in characters. 35368 */ 35369 init: function(settings) { 35370 var self = this; 35371 35372 self._super(settings); 35373 35374 self._value = settings.value || ''; 35375 self.addClass('textbox'); 35376 35377 if (settings.multiline) { 35378 self.addClass('multiline'); 35379 } else { 35380 // TODO: Rework this 35381 self.on('keydown', function(e) { 35382 if (e.keyCode == 13) { 35383 self.parents().reverse().each(function(ctrl) { 35384 e.preventDefault(); 35385 35386 if (ctrl.hasEventListeners('submit') && ctrl.toJSON) { 35387 ctrl.fire('submit', {data: ctrl.toJSON()}); 35388 return false; 35389 } 35390 }); 35391 } 35392 }); 35393 } 35394 }, 35395 35396 /** 35397 * Getter/setter function for the disabled state. 35398 * 35399 * @method value 35400 * @param {Boolean} [state] State to be set. 35401 * @return {Boolean|tinymce.ui.ComboBox} True/false or self if it's a set operation. 35402 */ 35403 disabled: function(state) { 35404 var self = this; 35405 35406 if (self._rendered && typeof(state) != 'undefined') { 35407 self.getEl().disabled = state; 35408 } 35409 35410 return self._super(state); 35411 }, 35412 35413 /** 35414 * Getter/setter function for the control value. 35415 * 35416 * @method value 35417 * @param {String} [value] Value to be set. 35418 * @return {String|tinymce.ui.ComboBox} Value or self if it's a set operation. 35419 */ 35420 value: function(value) { 35421 var self = this; 35422 35423 if (typeof(value) != "undefined") { 35424 self._value = value; 35425 35426 if (self._rendered) { 35427 self.getEl().value = value; 35428 } 35429 35430 return self; 35431 } 35432 35433 if (self._rendered) { 35434 return self.getEl().value; 35435 } 35436 35437 return self._value; 35438 }, 35439 35440 /** 35441 * Repaints the control after a layout operation. 35442 * 35443 * @method repaint 35444 */ 35445 repaint: function() { 35446 var self = this, style, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect; 35447 35448 style = self.getEl().style; 35449 rect = self._layoutRect; 35450 lastRepaintRect = self._lastRepaintRect || {}; 35451 35452 // Detect old IE 7+8 add lineHeight to align caret vertically in the middle 35453 var doc = document; 35454 if (!self.settings.multiline && doc.all && (!doc.documentMode || doc.documentMode <= 8)) { 35455 style.lineHeight = (rect.h - borderH) + 'px'; 35456 } 35457 35458 borderBox = self._borderBox; 35459 borderW = borderBox.left + borderBox.right + 8; 35460 borderH = borderBox.top + borderBox.bottom + (self.settings.multiline ? 8 : 0); 35461 35462 if (rect.x !== lastRepaintRect.x) { 35463 style.left = rect.x + 'px'; 35464 lastRepaintRect.x = rect.x; 35465 } 35466 35467 if (rect.y !== lastRepaintRect.y) { 35468 style.top = rect.y + 'px'; 35469 lastRepaintRect.y = rect.y; 35470 } 35471 35472 if (rect.w !== lastRepaintRect.w) { 35473 style.width = (rect.w - borderW) + 'px'; 35474 lastRepaintRect.w = rect.w; 35475 } 35476 35477 if (rect.h !== lastRepaintRect.h) { 35478 style.height = (rect.h - borderH) + 'px'; 35479 lastRepaintRect.h = rect.h; 35480 } 35481 35482 self._lastRepaintRect = lastRepaintRect; 35483 self.fire('repaint', {}, false); 35484 35485 return self; 35486 }, 35487 35488 /** 35489 * Renders the control as a HTML string. 35490 * 35491 * @method renderHtml 35492 * @return {String} HTML representing the control. 35493 */ 35494 renderHtml: function() { 35495 var self = this, id = self._id, settings = self.settings, value = self.encode(self._value, false), extraAttrs = ''; 35496 35497 if ("spellcheck" in settings) { 35498 extraAttrs += ' spellcheck="' + settings.spellcheck + '"'; 35499 } 35500 35501 if (settings.maxLength) { 35502 extraAttrs += ' maxlength="' + settings.maxLength + '"'; 35503 } 35504 35505 if (settings.size) { 35506 extraAttrs += ' size="' + settings.size + '"'; 35507 } 35508 35509 if (settings.subtype) { 35510 extraAttrs += ' type="' + settings.subtype + '"'; 35511 } 35512 35513 if (self.disabled()) { 35514 extraAttrs += ' disabled="disabled"'; 35515 } 35516 35517 if (settings.multiline) { 35518 return ( 35519 '<textarea id="' + id + '" class="' + self.classes() + '" ' +
35520 (settings.rows ? ' rows="' + settings.rows + '"' : '') + 35521 ' hidefocus="1"' + extraAttrs + '>' + value + 35522 '</textarea>' 35523 ); 35524 } 35525 35526 return '<input id="' + id + '" class="' + self.classes() + '" value="' + value + '" hidefocus="1"' + extraAttrs + ' />'; 35527 }, 35528 35529 /** 35530 * Called after the control has been rendered. 35531 * 35532 * @method postRender 35533 */ 35534 postRender: function() { 35535 var self = this; 35536 35537 DomUtils.on(self.getEl(), 'change', function(e) { 35538 self.fire('change', e); 35539 }); 35540 35541 return self._super(); 35542 }, 35543 35544 remove: function() { 35545 DomUtils.off(this.getEl()); 35546 this._super(); 35547 } 35548 }); 35549}); 35550 35551// Included from: js/tinymce/classes/ui/Throbber.js 35552 35553/** 35554 * Throbber.js 35555 * 35556 * Copyright, Moxiecode Systems AB 35557 * Released under LGPL License. 35558 * 35559 * License: http://www.tinymce.com/license 35560 * Contributing: http://www.tinymce.com/contributing 35561 */ 35562 35563/** 35564 * This class enables you to display a Throbber for any element. 35565 * 35566 * @-x-less Throbber.less 35567 * @class tinymce.ui.Throbber 35568 */ 35569define("tinymce/ui/Throbber", [ 35570 "tinymce/ui/DomUtils", 35571 "tinymce/ui/Control" 35572], function(DomUtils, Control) { 35573 "use strict"; 35574 35575 /** 35576 * Constructs a new throbber. 35577 * 35578 * @constructor 35579 * @param {Element} elm DOM Html element to display throbber in. 35580 * @param {Boolean} inline Optional true/false state if the throbber should be appended to end of element for infinite scroll. 35581 */ 35582 return function(elm, inline) { 35583 var self = this, state, classPrefix = Control.classPrefix; 35584 35585 /** 35586 * Shows the throbber. 35587 * 35588 * @method show 35589 * @param {Number} [time] Time to wait before showing. 35590 * @return {tinymce.ui.Throbber} Current throbber instance. 35591 */ 35592 self.show = function(time) { 35593 self.hide(); 35594 35595 state = true; 35596 35597 window.setTimeout(function() { 35598 if (state) { 35599 elm.appendChild(DomUtils.createFragment( 35600 '<div class="' + classPrefix + 'throbber' + (inline ? ' ' + classPrefix + 'throbber-inline' : '') + '"></div>' 35601 )); 35602 } 35603 }, time || 0); 35604 35605 return self; 35606 }; 35607 35608 /** 35609 * Hides the throbber. 35610 * 35611 * @method hide 35612 * @return {tinymce.ui.Throbber} Current throbber instance. 35613 */ 35614 self.hide = function() { 35615 var child = elm.lastChild; 35616 35617 if (child && child.className.indexOf('throbber') != -1) { 35618 child.parentNode.removeChild(child); 35619 } 35620 35621 state = false; 35622 35623 return self; 35624 }; 35625 }; 35626}); 35627 35628expose(["tinymce/dom/EventUtils","tinymce/dom/Sizzle","tinymce/Env","tinymce/util/Tools","tinymce/dom/DomQuery","tinymce/html/Styles","tinymce/dom/TreeWalker","tinymce/dom/Range","tinymce/html/Entities","tinymce/dom/DOMUtils","tinymce/dom/ScriptLoader","tinymce/AddOnManager","tinymce/html/Node","tinymce/html/Schema","tinymce/html/SaxParser","tinymce/html/DomParser","tinymce/html/Writer","tinymce/html/Serializer","tinymce/dom/Serializer","tinymce/dom/TridentSelection","tinymce/util/VK","tinymce/dom/ControlSelection","tinymce/dom/BookmarkManager","tinymce/dom/Selection","tinymce/dom/ElementUtils","tinymce/Formatter","tinymce/UndoManager","tinymce/EnterKey","tinymce/ForceBlocks","tinymce/EditorCommands","tinymce/util/URI","tinymce/util/Class","tinymce/util/EventDispatcher","tinymce/ui/Selector","tinymce/ui/Collection","tinymce/ui/DomUtils","tinymce/ui/Control","tinymce/ui/Factory","tinymce/ui/KeyboardNavigation","tinymce/ui/Container","tinymce/ui/DragHelper","tinymce/ui/Scrollable","tinymce/ui/Panel","tinymce/ui/Movable","tinymce/ui/Resizable","tinymce/ui/FloatPanel","tinymce/ui/Window","tinymce/ui/MessageBox","tinymce/WindowManager","tinymce/util/Quirks","tinymce/util/Observable","tinymce/EditorObservable","tinymce/Shortcuts","tinymce/Editor","tinymce/util/I18n","tinymce/FocusManager","tinymce/EditorManager","tinymce/LegacyInput","tinymce/util/XHR","tinymce/util/JSON","tinymce/util/JSONRequest","tinymce/util/JSONP","tinymce/util/LocalStorage","tinymce/Compat","tinymce/ui/Layout","tinymce/ui/AbsoluteLayout","tinymce/ui/Tooltip","tinymce/ui/Widget","tinymce/ui/Button","tinymce/ui/ButtonGroup","tinymce/ui/Checkbox","tinymce/ui/ComboBox","tinymce/ui/ColorBox","tinymce/ui/PanelButton","tinymce/ui/ColorButton","tinymce/util/Color","tinymce/ui/ColorPicker","tinymce/ui
35628/Path","tinymce/ui/ElementPath","tinymce/ui/FormItem","tinymce/ui/Form","tinymce/ui/FieldSet","tinymce/ui/FilePicker","tinymce/ui/FitLayout","tinymce/ui/FlexLayout","tinymce/ui/FlowLayout","tinymce/ui/FormatControls","tinymce/ui/GridLayout","tinymce/ui/Iframe","tinymce/ui/Label","tinymce/ui/Toolbar","tinymce/ui/MenuBar","tinymce/ui/MenuButton","tinymce/ui/ListBox","tinymce/ui/MenuItem","tinymce/ui/Menu","tinymce/ui/Radio","tinymce/ui/ResizeHandle","tinymce/ui/Spacer","tinymce/ui/SplitButton","tinymce/ui/StackLayout","tinymce/ui/TabPanel","tinymce/ui/TextBox","tinymce/ui/Throbber"]); 35629})(this);
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.