1// 4.0.12 (2013-12-18) 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 99define("tinymce/dom/EventUtils", [], function() { 100 "use strict"; 101 102 var eventExpandoPrefix = "mce-data-"; 103 var mouseEventRe = /^(?:mouse|contextmenu)|click/; 104 var deprecated = {keyLocation: 1, layerX: 1, layerY: 1, returnValue: 1}; 105 106 /** 107 * Binds a native event to a callback on the speified target. 108 */ 109 function addEvent(target, name, callback, capture) { 110 if (target.addEventListener) { 111 target.addEventListener(name, callback, capture || false); 112 } else if (target.attachEvent) { 113 target.attachEvent('on' + name, callback); 114 } 115 } 116 117 /** 118 * Unbinds a native event callback on the specified target. 119 */ 120 function removeEvent(target, name, callback, capture) { 121 if (target.removeEventListener) { 122 target.removeEventListener(name, callback, capture || false); 123 } else if (target.detachEvent) { 124 target.detachEvent('on' + name, callback); 125 } 126 } 127 128 /** 129 * Normalizes a native event object or just adds the event specific methods on a custom event. 130 */ 131 function fix(originalEvent, data) { 132 var name, event = data || {}, undef; 133 134 // Dummy function that gets replaced on the delegation state functions 135 function returnFalse() { 136 return false; 137 } 138 139 // Dummy function that gets replaced on the delegation state functions 140 function returnTrue() { 141 return true; 142 } 143 144 // Copy all properties from the original event 145 for (name in originalEvent) { 146 // layerX/layerY is deprecated in Chrome and produces a warning 147 if (!deprecated[name]) { 148 event[name] = originalEvent[name]; 149 } 150 } 151 152 // Normalize target IE uses srcElement 153 if (!event.target) { 154 event.target = event.srcElement || document; 155 } 156 157 // Calculate pageX/Y if missing and clientX/Y available 158 if (originalEvent && mouseEventRe.test(originalEvent.type) && originalEvent.pageX === undef && originalEvent.clientX !== undef) { 159 var eventDoc = event.target.ownerDocument || document; 160 var doc = eventDoc.documentElement; 161 var body = eventDoc.body; 162 163 event.pageX = originalEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - 164 ( doc && doc.clientLeft || body && body.clientLeft || 0); 165 166 event.pageY = originalEvent.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0 ) - 167 ( doc && doc.clientTop || body && body.clientTop || 0); 168 } 169 170 // Add preventDefault method 171 event.preventDefault = function() { 172 event.isDefaultPrevented = returnTrue; 173 174 // Execute preventDefault on the original event object 175 if (originalEvent) { 176 if (originalEvent.preventDefault) { 177 originalEvent.preventDefault(); 178 } else { 179 originalEvent.returnValue = false; // IE 180 } 181 } 182 }; 183 184 // Add stopPropagation 185 event.stopPropagation = function() { 186 event.isPropagationStopped = returnTrue; 187 188 // Execute stopPropagation on the original event object 189 if (originalEvent) { 190 if (originalEvent.stopPropagation) { 191 originalEvent.stopPropagation(); 192 } else { 193 originalEvent.cancelBubble = true; // IE 194 } 195 } 196 }; 197 198 // Add stopImmediatePropagation 199 event.stopImmediatePropagation = function() { 200 event.isImmediatePropagationStopped = returnTrue; 201 event.stopPropagation(); 202 }; 203 204 // Add event delegation states 205 if (!event.isDefaultPrevented) { 206 event.isDefaultPrevented = returnFalse; 207 event.isPropagationStopped = returnFalse; 208 event.isImmediatePropagationStopped = returnFalse; 209 } 210 211 return event; 212 } 213 214 /** 215 * Bind a DOMContentLoaded event across browsers and executes the callback once the page DOM is initialized. 216 * It will also set/check the domLoaded state of the event_utils instance so ready isn't called multiple times. 217 */ 218 function bindOnReady(win, callback, eventUtils) { 219 var doc = win.document, event = {type: 'ready'}; 220 221 if (eventUtils.domLoaded) { 222 callback(event); 223 return; 224 } 225 226 // Gets called when the DOM is ready 227 function readyHandler() { 228 if (!eventUtils.domLoaded) { 229 eventUtils.domLoaded = true; 230 callback(event); 231 } 232 } 233 234 function waitForDomLoaded() { 235 if (doc.readyState === "complete") { 236 removeEvent(doc, "readystatechange", waitForDomLoaded); 237 readyHandler(); 238 } 239 } 240 241 function tryScroll() { 242 try { 243 // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author. 244 // http://javascript.nwbox.com/IEContentLoaded/ 245 doc.documentElement.doScroll("left"); 246 } catch (ex) { 247 setTimeout(tryScroll, 0); 248 return; 249 } 250 251 readyHandler(); 252 } 253 254 // Use W3C method 255 if (doc.addEventListener) { 256 if (doc.readyState === "complete") { 257 readyHandler(); 258 } else { 259 addEvent(win, 'DOMContentLoaded', readyHandler); 260 } 261 } else { 262 // Use IE method 263 addEvent(doc, "readystatechange", waitForDomLoaded); 264 265 // Wait until we can scroll, when we can the DOM is initialized 266 if (doc.documentElement.doScroll && win === win.top) { 267 tryScroll(); 268 } 269 } 270 271 // Fallback if any of the above methods should fail for some odd reason 272 addEvent(win, 'load', readyHandler); 273 } 274 275 /** 276 * This class enables you to bind/unbind native events to elements and normalize it's behavior across browsers. 277 */ 278 function EventUtils() { 279 var self = this, events = {}
279, count, expando, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave; 280 281 expando = eventExpandoPrefix + (+new Date()).toString(32); 282 hasMouseEnterLeave = "onmouseenter" in document.documentElement; 283 hasFocusIn = "onfocusin" in document.documentElement; 284 mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'}; 285 count = 1; 286 287 // State if the DOMContentLoaded was executed or not 288 self.domLoaded = false; 289 self.events = events; 290 291 /** 292 * Executes all event handler callbacks for a specific event. 293 * 294 * @private 295 * @param {Event} evt Event object. 296 * @param {String} id Expando id value to look for. 297 */ 298 function executeHandlers(evt, id) { 299 var callbackList, i, l, callback, container = events[id]; 300 301 callbackList = container && container[evt.type]; 302 if (callbackList) { 303 for (i = 0, l = callbackList.length; i < l; i++) { 304 callback = callbackList[i]; 305 306 // Check if callback exists might be removed if a unbind is called inside the callback 307 if (callback && callback.func.call(callback.scope, evt) === false) { 308 evt.preventDefault(); 309 } 310 311 // Should we stop propagation to immediate listeners 312 if (evt.isImmediatePropagationStopped()) { 313 return; 314 } 315 } 316 } 317 } 318 319 /** 320 * Binds a callback to an event on the specified target. 321 * 322 * @method bind 323 * @param {Object} target Target node/window or custom object. 324 * @param {String} names Name of the event to bind. 325 * @param {function} callback Callback function to execute when the event occurs. 326 * @param {Object} scope Scope to call the callback function on, defaults to target. 327 * @return {function} Callback function that got bound. 328 */ 329 self.bind = function(target, names, callback, scope) { 330 var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window; 331 332 // Native event handler function patches the event and executes the callbacks for the expando 333 function defaultNativeHandler(evt) { 334 executeHandlers(fix(evt || win.event), id); 335 } 336 337 // Don't bind to text nodes or comments 338 if (!target || target.nodeType === 3 || target.nodeType === 8) { 339 return; 340 } 341 342 // Create or get events id for the target 343 if (!target[expando]) { 344 id = count++; 345 target[expando] = id; 346 events[id] = {}; 347 } else { 348 id = target[expando]; 349 } 350 351 // Setup the specified scope or use the target as a default 352 scope = scope || target; 353 354 // Split names and bind each event, enables you to bind multiple events with one call 355 names = names.split(' '); 356 i = names.length; 357 while (i--) { 358 name = names[i]; 359 nativeHandler = defaultNativeHandler; 360 fakeName = capture = false; 361 362 // Use ready instead of DOMContentLoaded 363 if (name === "DOMContentLoaded") { 364 name = "ready"; 365 } 366 367 // DOM is already ready 368 if (self.domLoaded && name === "ready" && target.readyState == 'complete') { 369 callback.call(scope, fix({type: name})); 370 continue; 371 } 372 373 // Handle mouseenter/mouseleaver 374 if (!hasMouseEnterLeave) { 375 fakeName = mouseEnterLeave[name]; 376 377 if (fakeName) { 378 nativeHandler = function(evt) { 379 var current, related; 380 381 current = evt.currentTarget; 382 related = evt.relatedTarget; 383 384 // Check if related is inside the current target if it's not then the event should 385 // be ignored since it's a mouseover/mouseout inside the element 386 if (related && current.contains) { 387 // Use contains for performance 388 related = current.contains(related); 389 } else { 390 while (related && related !== current) { 391 related = related.parentNode; 392 } 393 } 394 395 // Fire fake event 396 if (!related) { 397 evt = fix(evt || win.event); 398 evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter'; 399 evt.target = current; 400 executeHandlers(evt, id); 401 } 402 }; 403 } 404 } 405 406 // Fake bubbeling of focusin/focusout 407 if (!hasFocusIn && (name === "focusin" || name === "focusout")) { 408 capture = true; 409 fakeName = name === "focusin" ? "focus" : "blur"; 410 nativeHandler = function(evt) { 411 evt = fix(evt || win.event);
412 evt.type = evt.type === 'focus' ? 'focusin' : 'focusout'; 413 executeHandlers(evt, id); 414 }; 415 } 416 417 // Setup callback list and bind native event 418 callbackList = events[id][name]; 419 if (!callbackList) { 420 events[id][name] = callbackList = [{func: callback, scope: scope}]; 421 callbackList.fakeName = fakeName; 422 callbackList.capture = capture; 423 424 // Add the nativeHandler to the callback list so that we can later unbind it 425 callbackList.nativeHandler = nativeHandler; 426 427 // Check if the target has native events support 428 429 if (name === "ready") { 430 bindOnReady(target, nativeHandler, self); 431 } else { 432 addEvent(target, fakeName || name, nativeHandler, capture); 433 } 434 } else { 435 if (name === "ready" && self.domLoaded) { 436 callback({type: name}); 437 } else { 438 // If it already has an native handler then just push the callback 439 callbackList.push({func: callback, scope: scope}); 440 } 441 } 442 } 443 444 target = callbackList = 0; // Clean memory for IE 445 446 return callback; 447 }; 448 449 /** 450 * Unbinds the specified event by name, name and callback or all events on the target. 451 * 452 * @method unbind 453 * @param {Object} target Target node/window or custom object. 454 * @param {String} names Optional event name to unbind. 455 * @param {function} callback Optional callback function to unbind. 456 * @return {EventUtils} Event utils instance. 457 */ 458 self.unbind = function(target, names, callback) { 459 var id, callbackList, i, ci, name, eventMap; 460 461 // Don't bind to text nodes or comments 462 if (!target || target.nodeType === 3 || target.nodeType === 8) { 463 return self; 464 } 465 466 // Unbind event or events if the target has the expando 467 id = target[expando]; 468 if (id) { 469 eventMap = events[id]; 470 471 // Specific callback 472 if (names) { 473 names = names.split(' '); 474 i = names.length; 475 while (i--) { 476 name = names[i]; 477 callbackList = eventMap[name]; 478 479 // Unbind the event if it exists in the map 480 if (callbackList) { 481 // Remove specified callback 482 if (callback) { 483 ci = callbackList.length; 484 while (ci--) { 485 if (callbackList[ci].func === callback) { 486 var nativeHandler = callbackList.nativeHandler; 487 488 // Clone callbackList since unbind inside a callback would otherwise break the handlers loop 489 callbackList = callbackList.slice(0, ci).concat(callbackList.slice(ci + 1)); 490 callbackList.nativeHandler = nativeHandler; 491 492 eventMap[name] = callbackList; 493 } 494 } 495 } 496 497 // Remove all callbacks if there isn't a specified callback or there is no callbacks left 498 if (!callback || callbackList.length === 0) { 499 delete eventMap[name]; 500 removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture); 501 } 502 } 503 } 504 } else { 505 // All events for a specific element 506 for (name in eventMap) { 507 callbackList = eventMap[name]; 508 removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture); 509 } 510 511 eventMap = {}; 512 } 513 514 // Check if object is empty, if it isn't then we won't remove the expando map 515 for (name in eventMap) { 516 return self; 517 } 518 519 // Delete event object 520 delete events[id]; 521 522 // Remove expando from target 523 try { 524 // IE will fail here since it can't delete properties from window 525 delete target[expando]; 526 } catch (ex) { 527 // IE will set it to null 528 target[expando] = null; 529 } 530 } 531 532 return self; 533 }; 534 535 /** 536 * Fires the specified event on the specified target. 537 * 538 * @method fire 539 * @param {Object} target Target node/window or custom object. 540 * @param {String} name Event name to fire. 541 * @param {Object} args Optional arguments to send to the observers. 542 * @return {EventUtils} Event utils instance. 543 */ 544 self.fire = function(target, name, args) { 545 var id; 546 547 // Don't bind to text nodes or comments 548 if (!target || target.nodeType === 3 || target.nodeType === 8) { 549 return self; 550 } 551 552 // Build event object by patching the args
553 args = fix(null, args); 554 args.type = name; 555 args.target = target; 556 557 do { 558 // Found an expando that means there is listeners to execute 559 id = target[expando]; 560 if (id) { 561 executeHandlers(args, id); 562 } 563 564 // Walk up the DOM 565 target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow; 566 } while (target && !args.isPropagationStopped()); 567 568 return self; 569 }; 570 571 /** 572 * Removes all bound event listeners for the specified target. This will also remove any bound 573 * listeners to child nodes within that target. 574 * 575 * @method clean 576 * @param {Object} target Target node/window object. 577 * @return {EventUtils} Event utils instance. 578 */ 579 self.clean = function(target) { 580 var i, children, unbind = self.unbind; 581 582 // Don't bind to text nodes or comments 583 if (!target || target.nodeType === 3 || target.nodeType === 8) { 584 return self; 585 } 586 587 // Unbind any element on the specificed target 588 if (target[expando]) { 589 unbind(target); 590 } 591 592 // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children 593 if (!target.getElementsByTagName) { 594 target = target.document; 595 } 596 597 // Remove events from each child element 598 if (target && target.getElementsByTagName) { 599 unbind(target); 600 601 children = target.getElementsByTagName('*'); 602 i = children.length; 603 while (i--) { 604 target = children[i]; 605 606 if (target[expando]) { 607 unbind(target); 608 } 609 } 610 } 611 612 return self; 613 }; 614 615 /** 616 * Destroys the event object. Call this on IE to remove memory leaks. 617 */ 618 self.destroy = function() { 619 events = {}; 620 }; 621 622 // Legacy function for canceling events 623 self.cancel = function(e) { 624 if (e) { 625 e.preventDefault(); 626 e.stopImmediatePropagation(); 627 } 628 629 return false; 630 }; 631 } 632 633 EventUtils.Event = new EventUtils(); 634 EventUtils.Event.bind(window, 'ready', function() {}); 635 636 return EventUtils; 637}); 638 639// Included from: js/tinymce/classes/dom/Sizzle.js 640 641/** 642 * Sizzle.js 643 * 644 * Copyright, Moxiecode Systems AB 645 * Released under LGPL License. 646 * 647 * License: http://www.tinymce.com/license 648 * Contributing: http://www.tinymce.com/contributing 649 * 650 * @ignore-file 651 */ 652 653/*jshint bitwise:false, expr:true, noempty:false, sub:true, eqnull:true, latedef:false, maxlen:255 */ 654 655/* 656 * Sizzle CSS Selector Engine 657 * Copyright, The Dojo Foundation 658 * Released under the MIT, BSD, and GPL Licenses. 659 * More information: http://sizzlejs.com/ 660 */ 661define("tinymce/dom/Sizzle", [], function() { 662var i, 663 cachedruns, 664 Expr, 665 getText, 666 isXML, 667 compile, 668 outermostContext, 669 recompare, 670 sortInput, 671 672 // Local document vars 673 setDocument, 674 document, 675 docElem, 676 documentIsHTML, 677 rbuggyQSA, 678 rbuggyMatches, 679 matches, 680 contains, 681 682 // Instance-specific data 683 expando = "sizzle" + -(new Date()), 684 preferredDoc = window.document, 685 support = {}, 686 dirruns = 0, 687 done = 0, 688 classCache = createCache(), 689 tokenCache = createCache(), 690 compilerCache = createCache(), 691 hasDuplicate = false, 692 sortOrder = function() { return 0; }, 693 694 // General-purpose constants 695 strundefined = typeof undefined, 696 MAX_NEGATIVE = 1 << 31, 697 698 // Array methods 699 arr = [], 700 pop = arr.pop, 701 push_native = arr.push, 702 push = arr.push, 703 slice = arr.slice, 704 // Use a stripped-down indexOf if we can't use a native one 705 indexOf = arr.indexOf || function( elem ) { 706 var i = 0, 707 len = this.length; 708 for ( ; i < len; i++ ) { 709 if ( this[i] === elem ) { 710 return i; 711 } 712 } 713 return -1; 714 }, 715 716 717 // Regular expressions 718 719 // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace 720 whitespace = "[\\x20\\t\\r\\n\\f]", 721 // http://www.w3.org/TR/css3-syntax/#characters 722 characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", 723 724 // Loosely modeled on CSS identifier characters 725 // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors 726 // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier 727 identifier = characterEncoding.replace( "w", "w#" ), 728 729 // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors 730 operators = "([*^$|!~]?=)", 731 attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace + 732 "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]", 733 734 // Prefer arguments quoted, 735 // then not containing pseudos/brackets, 736 // then attribute selectors/non-parenthetical expressions, 737 // then anything else 738 // These preferences are here to reduce the number of selectors 739 // needing tokenize in the PSEUDO preFilter 740 pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)", 741 742 // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter 743 rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), 744 745 rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), 746 rcombinators = new RegExp( "^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*" ), 747 rpseudo = new RegExp( pseudos ), 748 ridentifier = new RegExp( "^" + identifier + "$" ), 749 750 matchExpr = { 751 "ID": new RegExp( "^#(" + characterEncoding + ")" ), 752 "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), 753 "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ), 754 "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), 755 "ATTR": new RegExp( "^" + attributes ), 756 "PSEUDO": new RegExp( "^" + pseudos ), 757 "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + 758 "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + 759 "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), 760 // For use in libraries implementing .is() 761 // We use this for POS matching in `select` 762 "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + 763 whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) 764 }, 765 766 rsibling = /[\x20\t\r\n\f]*[+~]/, 767 768 rnative = /^[^{]+\{\s*\[native code/, 769
770 // Easily-parseable/retrievable ID or TAG or CLASS selectors 771 rquickExpr = /^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/, 772 773 rinputs = /^(?:input|select|textarea|button)$/i, 774 rheader = /^h\d$/i, 775 776 rescape = /'|\\/g, 777 rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g, 778 779 // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters 780 runescape = /\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g, 781 funescape = function( _, escaped ) { 782 var high = "0x" + escaped - 0x10000; 783 // NaN means non-codepoint 784 return high !== high ? 785 escaped : 786 // BMP codepoint 787 high < 0 ? 788 String.fromCharCode( high + 0x10000 ) : 789 // Supplemental Plane codepoint (surrogate pair) 790 String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); 791 }; 792 793// Optimize for push.apply( _, NodeList ) 794try { 795 push.apply( 796 (arr = slice.call( preferredDoc.childNodes )), 797 preferredDoc.childNodes 798 ); 799 // Support: Android<4.0 800 // Detect silently failing push.apply 801 arr[ preferredDoc.childNodes.length ].nodeType; 802} catch ( e ) { 803 push = { apply: arr.length ? 804 805 // Leverage slice if possible 806 function( target, els ) { 807 push_native.apply( target, slice.call(els) ); 808 } : 809 810 // Support: IE<9 811 // Otherwise append directly 812 function( target, els ) { 813 var j = target.length, 814 i = 0; 815 // Can't trust NodeList.length 816 while ( (target[j++] = els[i++]) ) {} 817 target.length = j - 1; 818 } 819 }; 820} 821 822/** 823 * For feature detection 824 * @param {Function} fn The function to test for native support 825 */ 826function isNative( fn ) { 827 return rnative.test( fn + "" ); 828} 829 830/** 831 * Create key-value caches of limited size 832 * @returns {Function(string, Object)} Returns the Object data after storing it on itself with 833 * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) 834 * deleting the oldest entry 835 */ 836function createCache() { 837 var cache, 838 keys = []; 839 840 cache = function( key, value ) { 841 // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) 842 if ( keys.push( key += " " ) > Expr.cacheLength ) { 843 // Only keep the most recent entries 844 delete cache[ keys.shift() ]; 845 } 846 cache[ key ] = value; 847 return value; 848 }; 849 850 return cache; 851} 852 853/** 854 * Mark a function for special use by Sizzle 855 * @param {Function} fn The function to mark 856 */ 857function markFunction( fn ) { 858 fn[ expando ] = true; 859 return fn; 860} 861 862/** 863 * Support testing using an element 864 * @param {Function} fn Passed the created div and expects a boolean result 865 */ 866function assert( fn ) { 867 var div = document.createElement("div"); 868 869 try { 870 return !!fn( div ); 871 } catch (e) { 872 return false; 873 } finally { 874 // release memory in IE 875 div = null; 876 } 877} 878 879function Sizzle( selector, context, results, seed ) { 880 var match, elem, m, nodeType, 881 // QSA vars 882 i, groups, old, nid, newContext, newSelector; 883 884 if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { 885 setDocument( context ); 886 } 887 888 context = context || document; 889 results = results || []; 890 891 if ( !selector || typeof selector !== "string" ) { 892 return results; 893 } 894 895 if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { 896 return []; 897 } 898 899 if ( documentIsHTML && !seed ) { 900 901 // Shortcuts 902 if ( (match = rquickExpr.exec( selector )) ) { 903 // Speed-up: Sizzle("#ID") 904 if ( (m = match[1]) ) { 905 if ( nodeType === 9 ) { 906 elem = context.getElementById( m ); 907 // Check parentNode to catch when Blackberry 4.6 returns 908 // nodes that are no longer in the document #6963 909 if ( elem && elem.parentNode ) { 910 // Handle the case where IE, Opera, and Webkit return items 911 // by name instead of ID 912 if ( elem.id === m ) { 913 results.push( elem ); 914 return results; 915 } 916 } else { 917 return results; 918 } 919 } else { 920 // Context is not a document 921 if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && 922 contains( context, elem ) && elem.id === m ) { 923 results.push( elem ); 924 return results; 925 } 926 } 927 928 // Speed-up: Sizzle("TAG") 929 } else if ( match[2] ) { 930 push.apply( results, context.getElementsByTagName( selector ) ); 931 return results; 932 933 // Speed-up: Sizzle(".CLASS") 934 } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) { 935 push.apply( results, context.getElementsByClassName( m ) ); 936 return results; 937 } 938 } 939 940 // QSA path 941 if ( support.qsa && !rbuggyQSA.test(selector) ) { 942 old = true; 943 nid = expando; 944 newContext = context; 945 newSelector = nodeType === 9 && selector; 946 947 // qSA works strangely on Element-rooted queries 948 // We can work around this by specifying an extra ID on the root 949 // and working up from there (Thanks to Andrew Dupont for the technique) 950 // IE 8 doesn't work on object elements 951 if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { 952 groups = tokenize( selector ); 953 954 if ( (old = context.getAttribute("id")) ) { 955 nid = old.replace( rescape, "\\$&" ); 956 } else { 957 context.setAttribute( "id", nid ); 958 } 959 nid = "[id='" + nid + "'] "; 960 961 i = groups.length; 962 while ( i-- ) { 963 groups[i] = nid + toSelector( groups[i] ); 964 } 965 newContext = rsibling.test( selector ) && context.parentNode || context; 966 newSelector = groups.join(","); 967 } 968 969 if ( newSelector ) { 970 try { 971 push.apply( results, 972 newContext.querySelectorAll( newSelector ) 973 ); 974 return results; 975 } catch(qsaError) { 976 } finally { 977 if ( !old ) { 978 context.removeAttribute("id"); 979 } 980 } 981 } 982 } 983 } 984 985 // All others 986 return select( selector.replace( rtrim, "$1" ), context, results, seed ); 987} 988 989/** 990 * Detect xml
991 * @param {Element|Object} elem An element or a document 992 */ 993isXML = Sizzle.isXML = function( elem ) { 994 // documentElement is verified for cases where it doesn't yet exist 995 // (such as loading iframes in IE - #4833) 996 var documentElement = elem && (elem.ownerDocument || elem).documentElement; 997 return documentElement ? documentElement.nodeName !== "HTML" : false; 998}; 999 1000/** 1001 * Sets document-related variables once based on the current document 1002 * @param {Element|Object} [doc] An element or document object to use to set the document 1003 * @returns {Object} Returns the current document 1004 */ 1005setDocument = Sizzle.setDocument = function( node ) { 1006 var doc = node ? node.ownerDocument || node : preferredDoc; 1007 1008 // If no document and documentElement is available, return 1009 if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { 1010 return document; 1011 } 1012 1013 // Set our document 1014 document = doc; 1015 docElem = doc.documentElement; 1016 1017 // Support tests 1018 documentIsHTML = !isXML( doc ); 1019 1020 // Check if getElementsByTagName("*") returns only elements 1021 support.getElementsByTagName = assert(function( div ) { 1022 div.appendChild( doc.createComment("") ); 1023 return !div.getElementsByTagName("*").length; 1024 }); 1025 1026 // Check if attributes should be retrieved by attribute nodes 1027 support.attributes = assert(function( div ) { 1028 div.innerHTML = "<select></select>";
1029 var type = typeof div.lastChild.getAttribute("multiple"); 1030 // IE8 returns a string for some attributes even when not present 1031 return type !== "boolean" && type !== "string"; 1032 }); 1033 1034 // Check if getElementsByClassName can be trusted 1035 support.getElementsByClassName = assert(function( div ) { 1036 // Opera can't find a second classname (in 9.6) 1037 div.innerHTML = "<div class='hidden e'></div><div class='hidden'></div>"; 1038 if ( !div.getElementsByClassName || !div.getElementsByClassName("e").length ) { 1039 return false; 1040 } 1041 1042 // Safari 3.2 caches class attributes and doesn't catch changes 1043 div.lastChild.className = "e"; 1044 return div.getElementsByClassName("e").length === 2; 1045 }); 1046 1047 // Check if getElementsByName privileges form controls or returns elements by ID 1048 // If so, assume (for broader support) that getElementById returns elements by name 1049 support.getByName = assert(function( div ) { 1050 // Inject content 1051 div.id = expando + 0; 1052 // Support: Windows 8 Native Apps 1053 // Assigning innerHTML with "name" attributes throws uncatchable exceptions 1054 // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx 1055 div.appendChild( document.createElement("a") ).setAttribute( "name", expando ); 1056 div.appendChild( document.createElement("i") ).setAttribute( "name", expando ); 1057 docElem.appendChild( div ); 1058 1059 // Test 1060 var pass = doc.getElementsByName && 1061 // buggy browsers will return fewer than the correct 2 1062 doc.getElementsByName( expando ).length === 2 + 1063 // buggy browsers will return more than the correct 0 1064 doc.getElementsByName( expando + 0 ).length; 1065 1066 // Cleanup 1067 docElem.removeChild( div ); 1068 1069 return pass; 1070 }); 1071 1072 // Support: Webkit<537.32 1073 // Detached nodes confoundingly follow *each other* 1074 support.sortDetached = assert(function( div1 ) { 1075 return div1.compareDocumentPosition && 1076 // Should return 1, but Webkit returns 4 (following) 1077 (div1.compareDocumentPosition( document.createElement("div") ) & 1); 1078 }); 1079 1080 // IE6/7 return modified attributes 1081 Expr.attrHandle = assert(function( div ) { 1082 div.innerHTML = "<a href='#'></a>"; 1083 return div.firstChild && typeof div.firstChild.getAttribute !== strundefined && 1084 div.firstChild.getAttribute("href") === "#"; 1085 }) ? 1086 {} : 1087 { 1088 "href": function( elem ) { 1089 return elem.getAttribute( "href", 2 ); 1090 }, 1091 "type": function( elem ) { 1092 return elem.getAttribute("type"); 1093 } 1094 }; 1095 1096 // ID find and filter 1097 if ( support.getByName ) { 1098 Expr.find["ID"] = function( id, context ) { 1099 if ( typeof context.getElementById !== strundefined && documentIsHTML ) { 1100 var m = context.getElementById( id ); 1101 // Check parentNode to catch when Blackberry 4.6 returns 1102 // nodes that are no longer in the document #6963 1103 return m && m.parentNode ? [m] : []; 1104 } 1105 }; 1106 Expr.filter["ID"] = function( id ) { 1107 var attrId = id.replace( runescape, funescape ); 1108 return function( elem ) { 1109 return elem.getAttribute("id") === attrId; 1110 }; 1111 }; 1112 } else { 1113 Expr.find["ID"] = function( id, context ) { 1114 if ( typeof context.getElementById !== strundefined && documentIsHTML ) { 1115 var m = context.getElementById( id ); 1116 1117 return m ? 1118 m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ? 1119 [m] : 1120 undefined : 1121 []; 1122 } 1123 }; 1124 Expr.filter["ID"] = function( id ) { 1125 var attrId = id.replace( runescape, funescape ); 1126 return function( elem ) { 1127 var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); 1128 return node && node.value === attrId; 1129 }; 1130 }; 1131 } 1132 1133 // Tag 1134 Expr.find["TAG"] = support.getElementsByTagName ? 1135 function( tag, context ) { 1136 if ( typeof context.getElementsByTagName !== strundefined ) { 1137 return context.getElementsByTagName( tag ); 1138 } 1139 } : 1140 function( tag, context ) { 1141 var elem, 1142 tmp = [], 1143 i = 0, 1144 results = context.getElementsByTagName( tag ); 1145 1146 // Filter out possible comments 1147 if ( tag === "*" ) { 1148 while ( (elem = results[i++]) ) { 1149 if ( elem.nodeType === 1 ) { 1150 tmp.push( elem ); 1151 } 1152 } 1153 1154 return tmp; 1155 } 1156 return results; 1157 }; 1158 1159 // Name 1160 Expr.find["NAME"] = support.getByName && function( tag, context ) { 1161 if ( typeof context.getElementsByName !== strundefined ) { 1162 return context.getElementsByName( name ); 1163 } 1164 }; 1165 1166 // Class 1167 Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { 1168 if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) { 1169 return context.getElementsByClassName( className ); 1170 } 1171 }; 1172 1173 // QSA and matchesSelector support 1174 1175 // matchesSelector(:active) reports false when true (IE9/Opera 11.5) 1176 rbuggyMatches = []; 1177 1178 // qSa(:focus) reports false when true (Chrome 21),
1179 // no need to also add to buggyMatches since matches checks buggyQSA 1180 // A support test would require too much code (would include document ready) 1181 rbuggyQSA = [ ":focus" ]; 1182 1183 if ( (support.qsa = isNative(doc.querySelectorAll)) ) { 1184 // Build QSA regex 1185 // Regex strategy adopted from Diego Perini 1186 assert(function( div ) { 1187 // Select is set to empty string on purpose 1188 // This is to test IE's treatment of not explicitly 1189 // setting a boolean content attribute, 1190 // since its presence should be enough 1191 // http://bugs.jquery.com/ticket/12359 1192 div.innerHTML = "<select><option selected=''></option></select>"; 1193 1194 // IE8 - Some boolean attributes are not treated correctly 1195 if ( !div.querySelectorAll("[selected]").length ) { 1196 rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" ); 1197 } 1198 1199 // Webkit/Opera - :checked should return selected option elements 1200 // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked 1201 // IE8 throws error here and will not see later tests 1202 if ( !div.querySelectorAll(":checked").length ) { 1203 rbuggyQSA.push(":checked"); 1204 } 1205 }); 1206 1207 assert(function( div ) { 1208 1209 // Opera 10-12/IE8 - ^= $= *= and empty values 1210 // Should not select anything 1211 div.innerHTML = "<input type='hidden' i=''/>"; 1212 if ( div.querySelectorAll("[i^='']").length ) { 1213 rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" ); 1214 } 1215 1216 // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) 1217 // IE8 throws error here and will not see later tests 1218 if ( !div.querySelectorAll(":enabled").length ) { 1219 rbuggyQSA.push( ":enabled", ":disabled" ); 1220 } 1221 1222 // Opera 10-11 does not throw on post-comma invalid pseudos 1223 div.querySelectorAll("*,:x"); 1224 rbuggyQSA.push(",.*:"); 1225 }); 1226 } 1227 1228 if ( (support.matchesSelector = isNative( (matches = docElem.matchesSelector || 1229 docElem.mozMatchesSelector || 1230 docElem.webkitMatchesSelector || 1231 docElem.oMatchesSelector || 1232 docElem.msMatchesSelector) )) ) { 1233 1234 assert(function( div ) { 1235 // Check to see if it's possible to do matchesSelector 1236 // on a disconnected node (IE 9) 1237 support.disconnectedMatch = matches.call( div, "div" ); 1238 1239 // This should fail with an exception 1240 // Gecko does not error, returns false instead 1241 matches.call( div, "[s!='']:x" ); 1242 rbuggyMatches.push( "!=", pseudos ); 1243 }); 1244 } 1245 1246 rbuggyQSA = new RegExp( rbuggyQSA.join("|") ); 1247 rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); 1248 1249 // Element contains another 1250 // Purposefully does not implement inclusive descendant 1251 // As in, an element does not contain itself 1252 contains = isNative(docElem.contains) || docElem.compareDocumentPosition ? 1253 function( a, b ) { 1254 var adown = a.nodeType === 9 ? a.documentElement : a, 1255 bup = b && b.parentNode; 1256 return a === bup || !!( bup && bup.nodeType === 1 && ( 1257 adown.contains ? 1258 adown.contains( bup ) : 1259 a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 1260 )); 1261 } : 1262 function( a, b ) { 1263 if ( b ) { 1264 while ( (b = b.parentNode) ) { 1265 if ( b === a ) { 1266 return true; 1267 } 1268 } 1269 } 1270 return false; 1271 }; 1272 1273 // Document order sorting 1274 sortOrder = docElem.compareDocumentPosition ? 1275 function( a, b ) { 1276 1277 // Flag for duplicate removal 1278 if ( a === b ) { 1279 hasDuplicate = true; 1280 return 0; 1281 } 1282 1283 var compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b ); 1284 1285 if ( compare ) { 1286 // Disconnected nodes 1287 if ( compare & 1 || 1288 (recompare && b.compareDocumentPosition( a ) === compare) ) { 1289 1290 // Choose the first element that is related to our preferred document 1291 if ( a === doc || contains(preferredDoc, a) ) { 1292 return -1; 1293 } 1294 if ( b === doc || contains(preferredDoc, b) ) { 1295 return 1; 1296 } 1297 1298 // Maintain original order 1299 return sortInput ? 1300 ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : 1301 0; 1302 } 1303 1304 return compare & 4 ? -1 : 1; 1305 } 1306 1307 // Not directly comparable, sort on existence of method 1308 return a.compareDocumentPosition ? -1 : 1; 1309 } : 1310 function( a, b ) { 1311 var cur, 1312 i = 0, 1313 aup = a.parentNode, 1314 bup = b.parentNode, 1315 ap = [ a ], 1316 bp = [ b ]; 1317 1318 // Exit early if the nodes are identical 1319 if ( a === b ) { 1320 hasDuplicate = true; 1321 return 0; 1322 1323 // Parentless nodes are either documents or disconnected 1324 } else if ( !aup || !bup ) { 1325 return a === doc ? -1 : 1326 b === doc ? 1 : 1327 aup ? -1 : 1328 bup ? 1 : 1329 0; 1330 1331 // If the nodes are siblings, we can do a quick check 1332 } else if ( aup === bup ) { 1333 return siblingCheck( a, b ); 1334 } 1335 1336 // Otherwise we need full lists of their ancestors for comparison 1337 cur = a; 1338 while ( (cur = cur.parentNode) ) { 1339 ap.unshift( cur ); 1340 } 1341 cur = b; 1342 while ( (cur = cur.parentNode) ) { 1343 bp.unshift( cur ); 1344 } 1345 1346 // Walk down the tree looking for a discrepancy 1347 while ( ap[i] === bp[i] ) { 1348 i++; 1349 } 1350 1351 return i ? 1352 // Do a sibling check if the nodes have a common ancestor 1353 siblingCheck( ap[i], bp[i] ) : 1354 1355 // Otherwise nodes in our document sort first 1356 ap[i] === preferredDoc ? -1 : 1357 bp[i] === preferredDoc ? 1 : 1358 0; 1359 }; 1360 1361 return document; 1362}; 1363 1364Sizzle.matches = function( expr, elements ) { 1365 return Sizzle( expr, null, null, elements ); 1366}; 1367 1368Sizzle.matchesSelector = function( elem, expr ) { 1369 // Set document vars if needed 1370 if ( ( elem.ownerDocument || elem ) !== document ) { 1371 setDocument( elem ); 1372 } 1373 1374 // Make sure that attribute selectors are quoted 1375 expr = expr.replace( rattributeQuotes, "='$1']" ); 1376 1377 // rbuggyQSA always contains :focus, so no need for an existence check 1378 if ( support.matchesSelector && documentIsHTML && (!rbuggyMatches || !rbuggyMatches.test(expr)) && !rbuggyQSA.test(expr) ) { 1379 try { 1380 var ret = matches.call( elem, expr ); 1381 1382 // IE 9's matchesSelector returns false on disconnected nodes 1383 if ( ret || support.disconnectedMatch || 1384 // As well, disconnected nodes are said to be in a document 1385 // fragment in IE 9 1386 elem.document && elem.document.nodeType !== 11 ) { 1387 return ret; 1388 } 1389 } catch(e) {} 1390 } 1391 1392 return Sizzle( expr, document, null, [elem] ).length > 0; 1393}; 1394 1395Sizzle.contains = function( context, elem ) { 1396 // Set document vars if needed 1397 if ( ( context.ownerDocument || context ) !== document ) { 1398 setDocument( context ); 1399 } 1400 return contains( context, elem ); 1401}; 1402 1403Sizzle.attr = function( elem, name ) { 1404 var val; 1405 1406 // Set document vars if needed 1407 if ( ( elem.ownerDocument || elem ) !== document ) { 1408 setDocument( elem ); 1409 } 1410 1411 if ( documentIsHTML ) { 1412 name = name.toLowerCase(); 1413 } 1414 if ( (val = Expr.attrHandle[ name ]) ) { 1415 return val( elem ); 1416 } 1417 if ( !documentIsHTML || support.attributes ) { 1418 return elem.getAttribute( name ); 1419 } 1420 return ( (val = elem.getAttributeNode( name )) || elem.getAttribute( name ) ) && elem[ name ] === true ? 1421 name : 1422 val && val.specified ? val.value : null; 1423}; 1424 1425Sizzle.error = function( msg ) { 1426 throw new Error( "Syntax error, unrecognized expression: " + msg ); 1427}; 1428 1429// Document sorting and removing duplicates 1430Sizzle.uniqueSort = function( results ) { 1431 var elem, 1432 duplicates = [], 1433 j = 0, 1434 i = 0; 1435 1436 // Unless we *know* we can detect duplicates, assume their presence 1437 hasDuplicate = !support.detectDuplicates; 1438 // Compensate for sort limitations 1439 recompare = !support.sortDetached; 1440 sortInput = !support.sortStable && results.slice( 0 ); 1441 results.sort( sortOrder ); 1442 1443 if ( hasDuplicate ) { 1444 while ( (elem = results[i++]) ) { 1445 if ( elem === results[ i ] ) { 1446 j = duplicates.push( i ); 1447 } 1448 } 1449 while ( j-- ) { 1450 results.splice( duplicates[ j ], 1 ); 1451 } 1452 } 1453 1454 return results; 1455}; 1456 1457/** 1458 * Checks document order of two siblings 1459 * @param {Element} a 1460 * @param {Element} b 1461 * @returns Returns -1 if a precedes b, 1 if a follows b 1462 */ 1463function siblingCheck( a, b ) { 1464 var cur = b && a, 1465 diff = cur && ( ~b.sourceIndex || MAX_NEGATIVE ) - ( ~a.sourceIndex || MAX_NEGATIVE ); 1466 1467 // Use IE sourceIndex if available on both nodes 1468 if ( diff ) { 1469 return diff; 1470 } 1471 1472 // Check if b follows a 1473 if ( cur ) { 1474 while ( (cur = cur.nextSibling) ) { 1475 if ( cur === b ) { 1476 return -1; 1477 } 1478 } 1479 } 1480 1481 return a ? 1 : -1; 1482} 1483 1484// Returns a function to use in pseudos for input types 1485function createInputPseudo( type ) { 1486 return function( elem ) { 1487 var name = elem.nodeName.toLowerCase(); 1488 return name === "input" && elem.type === type; 1489 }; 1490} 1491 1492// Returns a function to use in pseudos for buttons 1493function createButtonPseudo( type ) { 1494 return function( elem ) { 1495 var name = elem.nodeName.toLowerCase(); 1496 return (name === "input" || name === "button") && elem.type === type; 1497 }; 1498} 1499 1500// Returns a function to use in pseudos for positionals 1501function createPositionalPseudo( fn ) { 1502 return markFunction(function( argument ) { 1503 argument = +argument; 1504 return markFunction(function( seed, matches ) { 1505 var j, 1506 matchIndexes = fn( [], seed.length, argument ), 1507 i = matchIndexes.length; 1508 1509 // Match elements found at the specified indexes 1510 while ( i-- ) { 1511 if ( seed[ (j = matchIndexes[i]) ] ) { 1512 seed[j] = !(matches[j] = seed[j]); 1513 } 1514 } 1515 }); 1516 }); 1517} 1518 1519/** 1520 * Utility function for retrieving the text value of an array of DOM nodes 1521 * @param {Array|Element} elem 1522 */ 1523getText = Sizzle.getText = function( elem ) { 1524 var node, 1525 ret = "", 1526 i = 0, 1527 nodeType = elem.nodeType; 1528 1529 if ( !nodeType ) { 1530 // If no nodeType, this is expected to be an array 1531 for ( ; (node = elem[i]); i++ ) {
1532 // Do not traverse comment nodes 1533 ret += getText( node ); 1534 } 1535 } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { 1536 // Use textContent for elements 1537 // innerText usage removed for consistency of new lines (see #11153) 1538 if ( typeof elem.textContent === "string" ) { 1539 return elem.textContent; 1540 } else { 1541 // Traverse its children 1542 for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { 1543 ret += getText( elem ); 1544 } 1545 } 1546 } else if ( nodeType === 3 || nodeType === 4 ) { 1547 return elem.nodeValue; 1548 } 1549 // Do not include comment or processing instruction nodes 1550 1551 return ret; 1552}; 1553 1554Expr = Sizzle.selectors = { 1555 1556 // Can be adjusted by the user 1557 cacheLength: 50, 1558 1559 createPseudo: markFunction, 1560 1561 match: matchExpr, 1562 1563 find: {}, 1564 1565 relative: { 1566 ">": { dir: "parentNode", first: true }, 1567 " ": { dir: "parentNode" }, 1568 "+": { dir: "previousSibling", first: true }, 1569 "~": { dir: "previousSibling" } 1570 }, 1571 1572 preFilter: { 1573 "ATTR": function( match ) { 1574 match[1] = match[1].replace( runescape, funescape ); 1575 1576 // Move the given value to match[3] whether quoted or unquoted 1577 match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape ); 1578 1579 if ( match[2] === "~=" ) { 1580 match[3] = " " + match[3] + " "; 1581 } 1582 1583 return match.slice( 0, 4 ); 1584 }, 1585 1586 "CHILD": function( match ) { 1587 /* matches from matchExpr["CHILD"] 1588 1 type (only|nth|...) 1589 2 what (child|of-type) 1590 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) 1591 4 xn-component of xn+y argument ([+-]?\d*n|) 1592 5 sign of xn-component 1593 6 x of xn-component 1594 7 sign of y-component 1595 8 y of y-component 1596 */ 1597 match[1] = match[1].toLowerCase(); 1598 1599 if ( match[1].slice( 0, 3 ) === "nth" ) { 1600 // nth-* requires argument 1601 if ( !match[3] ) { 1602 Sizzle.error( match[0] ); 1603 } 1604 1605 // numeric x and y parameters for Expr.filter.CHILD 1606 // remember that false/true cast respectively to 0/1 1607 match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); 1608 match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); 1609 1610 // other types prohibit arguments 1611 } else if ( match[3] ) { 1612 Sizzle.error( match[0] ); 1613 } 1614 1615 return match; 1616 }, 1617 1618 "PSEUDO": function( match ) { 1619 var excess, 1620 unquoted = !match[5] && match[2]; 1621 1622 if ( matchExpr["CHILD"].test( match[0] ) ) { 1623 return null; 1624 } 1625 1626 // Accept quoted arguments as-is 1627 if ( match[4] ) { 1628 match[2] = match[4]; 1629 1630 // Strip excess characters from unquoted arguments 1631 } else if ( unquoted && rpseudo.test( unquoted ) && 1632 // Get excess from tokenize (recursively) 1633 (excess = tokenize( unquoted, true )) && 1634 // advance to the next closing parenthesis 1635 (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { 1636 1637 // excess is a negative index 1638 match[0] = match[0].slice( 0, excess ); 1639 match[2] = unquoted.slice( 0, excess ); 1640 } 1641 1642 // Return only captures needed by the pseudo filter method (type and argument) 1643 return match.slice( 0, 3 ); 1644 } 1645 }, 1646 1647 filter: { 1648 1649 "TAG": function( nodeName ) { 1650 if ( nodeName === "*" ) { 1651 return function() { return true; }; 1652 } 1653 1654 nodeName = nodeName.replace( runescape, funescape ).toLowerCase(); 1655 return function( elem ) { 1656 return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; 1657 }; 1658 }, 1659 1660 "CLASS": function( className ) { 1661 var pattern = classCache[ className + " " ]; 1662 1663 return pattern || 1664 (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && 1665 classCache( className, function( elem ) { 1666 return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" ); 1667 }); 1668 }, 1669 1670 "ATTR": function( name, operator, check ) { 1671 return function( elem ) { 1672 var result = Sizzle.attr( elem, name ); 1673 1674 if ( result == null ) { 1675 return operator === "!="; 1676 } 1677 if ( !operator ) { 1678 return true; 1679 } 1680 1681 result += ""; 1682 1683 return operator === "=" ? result === check : 1684 operator === "!=" ? result !== check :
1685 operator === "^=" ? check && result.indexOf( check ) === 0 : 1686 operator === "*=" ? check && result.indexOf( check ) > -1 : 1687 operator === "$=" ? check && result.slice( -check.length ) === check : 1688 operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : 1689 operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : 1690 false; 1691 }; 1692 }, 1693 1694 "CHILD": function( type, what, argument, first, last ) { 1695 var simple = type.slice( 0, 3 ) !== "nth", 1696 forward = type.slice( -4 ) !== "last", 1697 ofType = what === "of-type"; 1698 1699 return first === 1 && last === 0 ? 1700 1701 // Shortcut for :nth-*(n) 1702 function( elem ) { 1703 return !!elem.parentNode; 1704 } : 1705 1706 function( elem, context, xml ) { 1707 var cache, outerCache, node, diff, nodeIndex, start, 1708 dir = simple !== forward ? "nextSibling" : "previousSibling", 1709 parent = elem.parentNode, 1710 name = ofType && elem.nodeName.toLowerCase(), 1711 useCache = !xml && !ofType; 1712 1713 if ( parent ) { 1714 1715 // :(first|last|only)-(child|of-type) 1716 if ( simple ) { 1717 while ( dir ) { 1718 node = elem; 1719 while ( (node = node[ dir ]) ) { 1720 if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { 1721 return false; 1722 } 1723 } 1724 // Reverse direction for :only-* (if we haven't yet done so) 1725 start = dir = type === "only" && !start && "nextSibling"; 1726 } 1727 return true; 1728 } 1729 1730 start = [ forward ? parent.firstChild : parent.lastChild ]; 1731 1732 // non-xml :nth-child(...) stores cache data on `parent` 1733 if ( forward && useCache ) { 1734 // Seek `elem` from a previously-cached index 1735 outerCache = parent[ expando ] || (parent[ expando ] = {}); 1736 cache = outerCache[ type ] || []; 1737 nodeIndex = cache[0] === dirruns && cache[1]; 1738 diff = cache[0] === dirruns && cache[2]; 1739 node = nodeIndex && parent.childNodes[ nodeIndex ]; 1740 1741 while ( (node = ++nodeIndex && node && node[ dir ] || 1742 1743 // Fallback to seeking `elem` from the start 1744 (diff = nodeIndex = 0) || start.pop()) ) { 1745 1746 // When found, cache indexes on `parent` and break 1747 if ( node.nodeType === 1 && ++diff && node === elem ) { 1748 outerCache[ type ] = [ dirruns, nodeIndex, diff ]; 1749 break; 1750 } 1751 } 1752 1753 // Use previously-cached element index if available 1754 } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { 1755 diff = cache[1]; 1756 1757 // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) 1758 } else { 1759 // Use the same loop as above to seek `elem` from the start 1760 while ( (node = ++nodeIndex && node && node[ dir ] || 1761 (diff = nodeIndex = 0) || start.pop()) ) { 1762 1763 if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { 1764 // Cache the index of each encountered element 1765 if ( useCache ) { 1766 (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; 1767 } 1768 1769 if ( node === elem ) { 1770 break; 1771 } 1772 } 1773 } 1774 } 1775 1776 // Incorporate the offset, then check against cycle size 1777 diff -= last; 1778 return diff === first || ( diff % first === 0 && diff / first >= 0 ); 1779 } 1780 }; 1781 }, 1782 1783 "PSEUDO": function( pseudo, argument ) { 1784 // pseudo-class names are case-insensitive 1785 // http://www.w3.org/TR/selectors/#pseudo-classes 1786 // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters 1787 // Remember that setFilters inherits from pseudos 1788 var args, 1789 fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || 1790 Sizzle.error( "unsupported pseudo: " + pseudo ); 1791 1792 // The user may use createPseudo to indicate that 1793 // arguments are needed to create the filter function 1794 // just as Sizzle does 1795 if ( fn[ expando ] ) { 1796 return fn( argument ); 1797 } 1798 1799 // But maintain support for old signatures 1800 if ( fn.length > 1 ) { 1801 args = [ pseudo, pseudo, "", argument ]; 1802 return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? 1803 markFunction(function( seed, matches ) { 1804 var idx, 1805 matched = fn( seed, argument ), 1806 i = matched.length; 1807 while ( i-- ) { 1808 idx = indexOf.call( seed, matched[i] ); 1809 seed[ idx ] = !( matches[ idx ] = matched[i] ); 1810 } 1811 }) : 1812 function( elem ) { 1813 return fn( elem, 0, args ); 1814 }; 1815 } 1816 1817 return fn; 1818 } 1819 }, 1820 1821 pseudos: { 1822 // Potentially complex pseudos 1823 "not": markFunction(function( selector ) { 1824 // Trim the selector passed to compile 1825 // to avoid treating leading and trailing 1826 // spaces as combinators 1827 var input = [], 1828 results = [], 1829 matcher = compile( selector.replace( rtrim, "$1" ) ); 1830 1831 return matcher[ expando ] ? 1832 markFunction(function( seed, matches, context, xml ) { 1833 var elem, 1834 unmatched = matcher( seed, null, xml, [] ), 1835 i = seed.length; 1836 1837 // Match elements unmatched by `matcher` 1838 while ( i-- ) { 1839 if ( (elem = unmatched[i]) ) { 1840 seed[i] = !(matches[i] = elem); 1841 } 1842 } 1843 }) : 1844 function( elem, context, xml ) { 1845 input[0] = elem; 1846 matcher( input, null, xml, results ); 1847 return !results.pop(); 1848 }; 1849 }), 1850 1851 "has": markFunction(function( selector ) { 1852 return function( elem ) { 1853 return Sizzle( selector, elem ).length > 0; 1854 }; 1855 }), 1856 1857 "contains": markFunction(function( text ) { 1858 return function( elem ) { 1859 return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; 1860 }; 1861 }), 1862 1863 // "Whether an element is represented by a :lang() selector 1864 // is based solely on the element's language value 1865 // being equal to the identifier C, 1866 // or beginning with the identifier C immediately followed by "-". 1867 // The matching of C against the element's language value is performed case-insensitively. 1868 // The identifier C does not have to be a valid language name." 1869 // http://www.w3.org/TR/selectors/#lang-pseudo 1870 "lang": markFunction( function( lang ) { 1871 // lang value must be a valid identifier 1872 if ( !ridentifier.test(lang || "") ) { 1873 Sizzle.error( "unsupported lang: " + lang ); 1874 } 1875 lang = lang.replace( runescape, funescape ).toLowerCase(); 1876 return function( elem ) { 1877 var elemLang; 1878 do { 1879 if ( (elemLang = documentIsHTML ? 1880 elem.lang : 1881 elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { 1882 1883 elemLang = elemLang.toLowerCase(); 1884 return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; 1885 } 1886 } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); 1887 return false;
1888 }; 1889 }), 1890 1891 // Miscellaneous 1892 "target": function( elem ) { 1893 var hash = window.location && window.location.hash; 1894 return hash && hash.slice( 1 ) === elem.id; 1895 }, 1896 1897 "root": function( elem ) { 1898 return elem === docElem; 1899 }, 1900 1901 "focus": function( elem ) { 1902 return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); 1903 }, 1904 1905 // Boolean properties 1906 "enabled": function( elem ) { 1907 return elem.disabled === false; 1908 }, 1909 1910 "disabled": function( elem ) { 1911 return elem.disabled === true; 1912 }, 1913 1914 "checked": function( elem ) { 1915 // In CSS3, :checked should return both checked and selected elements 1916 // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked 1917 var nodeName = elem.nodeName.toLowerCase(); 1918 return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); 1919 }, 1920 1921 "selected": function( elem ) { 1922 // Accessing this property makes selected-by-default 1923 // options in Safari work properly 1924 if ( elem.parentNode ) { 1925 elem.parentNode.selectedIndex; 1926 } 1927 1928 return elem.selected === true; 1929 }, 1930 1931 // Contents 1932 "empty": function( elem ) { 1933 // http://www.w3.org/TR/selectors/#empty-pseudo 1934 // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)), 1935 // not comment, processing instructions, or others 1936 // Thanks to Diego Perini for the nodeName shortcut 1937 // Greater than "@" means alpha characters (specifically not starting with "#" or "?") 1938 for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { 1939 if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) { 1940 return false; 1941 } 1942 } 1943 return true; 1944 }, 1945 1946 "parent": function( elem ) { 1947 return !Expr.pseudos["empty"]( elem ); 1948 }, 1949 1950 // Element/input types 1951 "header": function( elem ) { 1952 return rheader.test( elem.nodeName ); 1953 }, 1954 1955 "input": function( elem ) { 1956 return rinputs.test( elem.nodeName ); 1957 }, 1958 1959 "button": function( elem ) { 1960 var name = elem.nodeName.toLowerCase(); 1961 return name === "input" && elem.type === "button" || name === "button"; 1962 }, 1963 1964 "text": function( elem ) { 1965 var attr; 1966 // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) 1967 // use getAttribute instead to test this case 1968 return elem.nodeName.toLowerCase() === "input" && 1969 elem.type === "text" && 1970 ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type ); 1971 }, 1972 1973 // Position-in-collection 1974 "first": createPositionalPseudo(function() { 1975 return [ 0 ]; 1976 }), 1977 1978 "last": createPositionalPseudo(function( matchIndexes, length ) { 1979 return [ length - 1 ]; 1980 }), 1981 1982 "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { 1983 return [ argument < 0 ? argument + length : argument ]; 1984 }), 1985 1986 "even": createPositionalPseudo(function( matchIndexes, length ) { 1987 var i = 0; 1988 for ( ; i < length; i += 2 ) { 1989 matchIndexes.push( i ); 1990 } 1991 return matchIndexes; 1992 }), 1993 1994 "odd": createPositionalPseudo(function( matchIndexes, length ) { 1995 var i = 1; 1996 for ( ; i < length; i += 2 ) { 1997 matchIndexes.push( i ); 1998 } 1999 return matchIndexes; 2000 }), 2001 2002 "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { 2003 var i = argument < 0 ? argument + length : argument; 2004 for ( ; --i >= 0; ) { 2005 matchIndexes.push( i ); 2006 } 2007 return matchIndexes; 2008 }), 2009 2010 "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { 2011 var i = argument < 0 ? argument + length : argument; 2012 for ( ; ++i < length; ) { 2013 matchIndexes.push( i ); 2014 } 2015 return matchIndexes; 2016 }) 2017 } 2018}; 2019 2020// Add button/input type pseudos 2021for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { 2022 Expr.pseudos[ i ] = createInputPseudo( i ); 2023} 2024for ( i in { submit: true, reset: true } ) { 2025 Expr.pseudos[ i ] = createButtonPseudo( i ); 2026} 2027 2028function tokenize( selector, parseOnly ) { 2029 var matched, match, tokens, type, 2030 soFar, groups, preFilters, 2031 cached = tokenCache[ selector + " " ]; 2032 2033 if ( cached ) { 2034 return parseOnly ? 0 : cached.slice( 0 ); 2035 } 2036 2037 soFar = selector; 2038 groups = []; 2039 preFilters = Expr.preFilter; 2040 2041 while ( soFar ) { 2042 2043 // Comma and first run 2044 if ( !matched || (match = rcomma.exec( soFar )) ) { 2045 if ( match ) { 2046 // Don't consume trailing commas as valid 2047 soFar = soFar.slice( match[0].length ) || soFar; 2048 } 2049 groups.push( tokens = [] ); 2050 } 2051 2052 matched = false;
2053 2054 // Combinators 2055 if ( (match = rcombinators.exec( soFar )) ) { 2056 matched = match.shift(); 2057 tokens.push( { 2058 value: matched, 2059 // Cast descendant combinators to space 2060 type: match[0].replace( rtrim, " " ) 2061 } ); 2062 soFar = soFar.slice( matched.length ); 2063 } 2064 2065 // Filters 2066 for ( type in Expr.filter ) { 2067 if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || 2068 (match = preFilters[ type ]( match ))) ) { 2069 matched = match.shift(); 2070 tokens.push( { 2071 value: matched, 2072 type: type, 2073 matches: match 2074 } ); 2075 soFar = soFar.slice( matched.length ); 2076 } 2077 } 2078 2079 if ( !matched ) { 2080 break; 2081 } 2082 } 2083 2084 // Return the length of the invalid excess 2085 // if we're just parsing 2086 // Otherwise, throw an error or return tokens 2087 return parseOnly ? 2088 soFar.length : 2089 soFar ? 2090 Sizzle.error( selector ) : 2091 // Cache the tokens 2092 tokenCache( selector, groups ).slice( 0 ); 2093} 2094 2095function toSelector( tokens ) { 2096 var i = 0, 2097 len = tokens.length, 2098 selector = ""; 2099 for ( ; i < len; i++ ) { 2100 selector += tokens[i].value; 2101 } 2102 return selector; 2103} 2104 2105function addCombinator( matcher, combinator, base ) { 2106 var dir = combinator.dir, 2107 checkNonElements = base && dir === "parentNode", 2108 doneName = done++; 2109 2110 return combinator.first ? 2111 // Check against closest ancestor/preceding element 2112 function( elem, context, xml ) { 2113 while ( (elem = elem[ dir ]) ) { 2114 if ( elem.nodeType === 1 || checkNonElements ) { 2115 return matcher( elem, context, xml ); 2116 } 2117 } 2118 } : 2119 2120 // Check against all ancestor/preceding elements 2121 function( elem, context, xml ) { 2122 var data, cache, outerCache, 2123 dirkey = dirruns + " " + doneName; 2124 2125 // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching 2126 if ( xml ) { 2127 while ( (elem = elem[ dir ]) ) { 2128 if ( elem.nodeType === 1 || checkNonElements ) { 2129 if ( matcher( elem, context, xml ) ) { 2130 return true; 2131 } 2132 } 2133 } 2134 } else { 2135 while ( (elem = elem[ dir ]) ) { 2136 if ( elem.nodeType === 1 || checkNonElements ) { 2137 outerCache = elem[ expando ] || (elem[ expando ] = {}); 2138 if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) { 2139 if ( (data = cache[1]) === true || data === cachedruns ) { 2140 return data === true; 2141 } 2142 } else { 2143 cache = outerCache[ dir ] = [ dirkey ]; 2144 cache[1] = matcher( elem, context, xml ) || cachedruns; 2145 if ( cache[1] === true ) { 2146 return true; 2147 } 2148 } 2149 } 2150 } 2151 } 2152 }; 2153} 2154 2155function elementMatcher( matchers ) { 2156 return matchers.length > 1 ? 2157 function( elem, context, xml ) { 2158 var i = matchers.length; 2159 while ( i-- ) { 2160 if ( !matchers[i]( elem, context, xml ) ) { 2161 return false; 2162 } 2163 } 2164 return true; 2165 } : 2166 matchers[0]; 2167} 2168 2169function condense( unmatched, map, filter, context, xml ) { 2170 var elem, 2171 newUnmatched = [], 2172 i = 0, 2173 len = unmatched.length, 2174 mapped = map != null; 2175 2176 for ( ; i < len; i++ ) { 2177 if ( (elem = unmatched[i]) ) { 2178 if ( !filter || filter( elem, context, xml ) ) { 2179 newUnmatched.push( elem ); 2180 if ( mapped ) { 2181 map.push( i ); 2182 } 2183 } 2184 } 2185 } 2186 2187 return newUnmatched; 2188} 2189 2190function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { 2191 if ( postFilter && !postFilter[ expando ] ) { 2192 postFilter = setMatcher( postFilter ); 2193 } 2194 if ( postFinder && !postFinder[ expando ] ) { 2195 postFinder = setMatcher( postFinder, postSelector ); 2196 } 2197 return markFunction(function( seed, results, context, xml ) { 2198 var temp, i, elem, 2199 preMap = [], 2200 postMap = [], 2201 preexisting = results.length, 2202 2203 // Get initial elements from seed or context 2204 elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), 2205 2206 // Prefilter to get matcher input, preserving a map for seed-results synchronization 2207 matcherIn = preFilter && ( seed || !selector ) ? 2208 condense( elems, preMap, preFilter, context, xml ) : 2209 elems, 2210 2211 matcherOut = matcher ? 2212 // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, 2213 postFinder || ( seed ? preFilter : preexisting || postFilter ) ? 2214 2215 // ...intermediate processing is necessary 2216 [] : 2217 2218 // ...otherwise use results directly 2219 results : 2220 matcherIn; 2221 2222 // Find primary matches 2223 if ( matcher ) { 2224 matcher( matcherIn, matcherOut, context, xml ); 2225 } 2226 2227 // Apply postFilter 2228 if ( postFilter ) { 2229 temp = condense( matcherOut, postMap ); 2230 postFilter( temp, [], context, xml ); 2231 2232 // Un-match failing elements by moving them back to matcherIn 2233 i = temp.length; 2234 while ( i-- ) { 2235 if ( (elem = temp[i]) ) { 2236 matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); 2237 } 2238 } 2239 } 2240 2241 if ( seed ) { 2242 if ( postFinder || preFilter ) { 2243 if ( postFinder ) { 2244 // Get the final matcherOut by condensing this intermediate into postFinder contexts 2245 temp = []; 2246 i = matcherOut.length; 2247 while ( i-- ) { 2248 if ( (elem = matcherOut[i]) ) { 2249 // Restore matcherIn since elem is not yet a final match 2250 temp.push( (matcherIn[i] = elem) ); 2251 } 2252 } 2253 postFinder( null, (matcherOut = []), temp, xml ); 2254 } 2255 2256 // Move matched elements from seed to results to keep them synchronized 2257 i = matcherOut.length; 2258 while ( i-- ) { 2259 if ( (elem = matcherOut[i]) && 2260 (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { 2261 2262 seed[temp] = !(results[temp] = elem); 2263 } 2264 } 2265 } 2266 2267 // Add elements to results, through postFinder if defined 2268 } else { 2269 matcherOut = condense( 2270 matcherOut === results ? 2271 matcherOut.splice( preexisting, matcherOut.length ) : 2272 matcherOut 2273 ); 2274 if ( postFinder ) { 2275 postFinder( null, results, matcherOut, xml ); 2276 } else { 2277 push.apply( results, matcherOut ); 2278 } 2279 } 2280 }); 2281} 2282 2283function matcherFromTokens( tokens ) { 2284 var checkContext, matcher, j, 2285 len = tokens.length, 2286 leadingRelative = Expr.relative[ tokens[0].type ],
2287 implicitRelative = leadingRelative || Expr.relative[" "], 2288 i = leadingRelative ? 1 : 0, 2289 2290 // The foundational matcher ensures that elements are reachable from top-level context(s) 2291 matchContext = addCombinator( function( elem ) { 2292 return elem === checkContext; 2293 }, implicitRelative, true ), 2294 matchAnyContext = addCombinator( function( elem ) { 2295 return indexOf.call( checkContext, elem ) > -1; 2296 }, implicitRelative, true ), 2297 matchers = [ function( elem, context, xml ) { 2298 return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( 2299 (checkContext = context).nodeType ? 2300 matchContext( elem, context, xml ) : 2301 matchAnyContext( elem, context, xml ) ); 2302 } ]; 2303 2304 for ( ; i < len; i++ ) { 2305 if ( (matcher = Expr.relative[ tokens[i].type ]) ) { 2306 matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; 2307 } else { 2308 matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); 2309 2310 // Return special upon seeing a positional matcher 2311 if ( matcher[ expando ] ) { 2312 // Find the next relative operator (if any) for proper handling 2313 j = ++i; 2314 for ( ; j < len; j++ ) { 2315 if ( Expr.relative[ tokens[j].type ] ) { 2316 break; 2317 } 2318 } 2319 return setMatcher( 2320 i > 1 && elementMatcher( matchers ), 2321 i > 1 && toSelector( tokens.slice( 0, i - 1 ) ).replace( rtrim, "$1" ), 2322 matcher, 2323 i < j && matcherFromTokens( tokens.slice( i, j ) ), 2324 j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), 2325 j < len && toSelector( tokens ) 2326 ); 2327 } 2328 matchers.push( matcher ); 2329 } 2330 } 2331 2332 return elementMatcher( matchers ); 2333} 2334 2335function matcherFromGroupMatchers( elementMatchers, setMatchers ) { 2336 // A counter to specify which element is currently being matched 2337 var matcherCachedRuns = 0, 2338 bySet = setMatchers.length > 0, 2339 byElement = elementMatchers.length > 0, 2340 superMatcher = function( seed, context, xml, results, expandContext ) { 2341 var elem, j, matcher, 2342 setMatched = [], 2343 matchedCount = 0, 2344 i = "0", 2345 unmatched = seed && [], 2346 outermost = expandContext != null, 2347 contextBackup = outermostContext, 2348 // We must always have either seed elements or context 2349 elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ), 2350 // Use integer dirruns iff this is the outermost matcher 2351 dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1); 2352 2353 if ( outermost ) { 2354 outermostContext = context !== document && context; 2355 cachedruns = matcherCachedRuns; 2356 } 2357 2358 // Add elements passing elementMatchers directly to results 2359 // Keep `i` a string if there are no elements so `matchedCount` will be "00" below 2360 for ( ; (elem = elems[i]) != null; i++ ) { 2361 if ( byElement && elem ) { 2362 j = 0; 2363 while ( (matcher = elementMatchers[j++]) ) { 2364 if ( matcher( elem, context, xml ) ) { 2365 results.push( elem ); 2366 break; 2367 } 2368 } 2369 if ( outermost ) { 2370 dirruns = dirrunsUnique; 2371 cachedruns = ++matcherCachedRuns; 2372 } 2373 } 2374 2375 // Track unmatched elements for set filters 2376 if ( bySet ) { 2377 // They will have gone through all possible matchers 2378 if ( (elem = !matcher && elem) ) { 2379 matchedCount--; 2380 } 2381 2382 // Lengthen the array for every element, matched or not 2383 if ( seed ) { 2384 unmatched.push( elem ); 2385 } 2386 } 2387 } 2388 2389 // Apply set filters to unmatched elements 2390 matchedCount += i; 2391 if ( bySet && i !== matchedCount ) { 2392 j = 0; 2393 while ( (matcher = setMatchers[j++]) ) { 2394 matcher( unmatched, setMatched, context, xml ); 2395 } 2396 2397 if ( seed ) { 2398 // Reintegrate element matches to eliminate the need for sorting 2399 if ( matchedCount > 0 ) { 2400 while ( i-- ) { 2401 if ( !(unmatched[i] || setMatched[i]) ) { 2402 setMatched[i] = pop.call( results ); 2403 } 2404 } 2405 } 2406 2407 // Discard index placeholder values to get only actual matches 2408 setMatched = condense( setMatched ); 2409 } 2410 2411 // Add matches to results 2412 push.apply( results, setMatched ); 2413 2414 // Seedless set matches succeeding multiple successful matchers stipulate sorting 2415 if ( outermost && !seed && setMatched.length > 0 && 2416 ( matchedCount + setMatchers.length ) > 1 ) { 2417 2418 Sizzle.uniqueSort( results ); 2419 } 2420 } 2421 2422 // Override manipulation of globals by nested matchers 2423 if ( outermost ) { 2424 dirruns = dirrunsUnique; 2425 outermostContext = contextBackup; 2426 } 2427 2428 return unmatched; 2429 }; 2430 2431 return bySet ? 2432 markFunction( superMatcher ) : 2433 superMatcher; 2434} 2435 2436compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) { 2437 var i, 2438 setMatchers = [], 2439 elementMatchers = [], 2440 cached = compilerCache[ selector + " " ]; 2441 2442 if ( !cached ) { 2443 // Generate a function of recursive functions that can be used to check each element 2444 if ( !group ) { 2445 group = tokenize( selector ); 2446 } 2447 i = group.length; 2448 while ( i-- ) { 2449 cached = matcherFromTokens( group[i] ); 2450 if ( cached[ expando ] ) { 2451 setMatchers.push( cached ); 2452 } else { 2453 elementMatchers.push( cached ); 2454 } 2455 } 2456 2457 // Cache the compiled function 2458 cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); 2459 } 2460 return cached; 2461}; 2462 2463function multipleContexts( selector, contexts, results ) { 2464 var i = 0, 2465 len = contexts.length; 2466 for ( ; i < len; i++ ) { 2467 Sizzle( selector, contexts[i], results ); 2468 } 2469 return results; 2470} 2471 2472function select( selector, context, results, seed ) { 2473 var i, tokens, token, type, find, 2474 match = tokenize( selector ); 2475 2476 if ( !seed ) { 2477 // Try to minimize operations if there is only one group 2478 if ( match.length === 1 ) { 2479 2480 // Take a shortcut and set the context if the root selector is an ID 2481 tokens = match[0] = match[0].slice( 0 ); 2482 if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && 2483 context.nodeType === 9 && documentIsHTML && 2484 Expr.relative[ tokens[1].type ] ) { 2485 2486 context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; 2487 if ( !context ) { 2488 return results; 2489 } 2490 2491 selector = selector.slice( tokens.shift().value.length ); 2492 } 2493 2494 // Fetch a seed set for right-to-left matching 2495 i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; 2496 while ( i-- ) { 2497 token = tokens[i]; 2498 2499 // Abort if we hit a combinator 2500 if ( Expr.relative[ (type = token.type) ] ) { 2501 break; 2502 } 2503 if ( (find = Expr.find[ type ]) ) { 2504 // Search, expanding context for leading sibling combinators 2505 if ( (seed = find( 2506 token.matches[0].replace( runescape, funescape ), 2507 rsibling.test( tokens[0].type ) && context.parentNode || context 2508 )) ) { 2509 2510 // If seed is empty or no tokens remain, we can return early 2511 tokens.splice( i, 1 ); 2512 selector = seed.length && toSelector( tokens ); 2513 if ( !selector ) { 2514 push.apply( results, seed ); 2515 return results; 2516 } 2517 2518 break; 2519 } 2520 } 2521 } 2522 } 2523 } 2524 2525 // Compile and execute a filtering function 2526 // Provide `match` to avoid retokenization if we modified the selector above 2527 compile( selector, match )( 2528 seed, 2529 context, 2530 !documentIsHTML, 2531 results, 2532 rsibling.test( selector ) 2533 ); 2534 return results; 2535} 2536 2537// Deprecated 2538Expr.pseudos["nth"] = Expr.pseudos["eq"]; 2539 2540// Easy API for creating new setFilters 2541function setFilters() {} 2542setFilters.prototype = Expr.filters = Expr.pseudos; 2543Expr.setFilters = new setFilters(); 2544
2545// Check sort stability 2546support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; 2547 2548// Initialize with the default document 2549setDocument(); 2550 2551// Always assume the presence of duplicates if sort doesn't 2552// pass them to our comparison function (as in Google Chrome). 2553[0, 0].sort( sortOrder ); 2554support.detectDuplicates = hasDuplicate; 2555 2556/* 2557// EXPOSE 2558if ( typeof define === "function" && define.amd ) { 2559 define(function() { return Sizzle; }); 2560} else { 2561 window.Sizzle = Sizzle; 2562} 2563*/ 2564 2565// EXPOSE 2566return Sizzle; 2567}); 2568 2569// Included from: js/tinymce/classes/dom/DomQuery.js 2570 2571/** 2572 * DomQuery.js 2573 * 2574 * Copyright, Moxiecode Systems AB 2575 * Released under LGPL License. 2576 * 2577 * License: http://www.tinymce.com/license 2578 * Contributing: http://www.tinymce.com/contributing 2579 * 2580 * Some of this logic is based on jQuery code that is released under 2581 * MIT license that grants us to sublicense it under LGPL. 2582 * 2583 * @ignore-file 2584 */ 2585 2586/** 2587 * @class tinymce.dom.DomQuery 2588 */ 2589define("tinymce/dom/DomQuery", [ 2590 "tinymce/dom/EventUtils", 2591 "tinymce/dom/Sizzle" 2592], function(EventUtils, Sizzle) { 2593 var doc = document, push = Array.prototype.push, slice = Array.prototype.slice; 2594 var rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/; 2595 var Event = EventUtils.Event; 2596 2597 function isDefined(obj) { 2598 return typeof obj !== "undefined"; 2599 } 2600 2601 function isString(obj) { 2602 return typeof obj === "string"; 2603 } 2604 2605 function createFragment(html) { 2606 var frag, node, container; 2607 2608 container = doc.createElement("div"); 2609 frag = doc.createDocumentFragment(); 2610 container.innerHTML = html; 2611 2612 while ((node = container.firstChild)) { 2613 frag.appendChild(node); 2614 } 2615 2616 return frag; 2617 } 2618 2619 function domManipulate(targetNodes, sourceItem, callback) { 2620 var i; 2621 2622 if (typeof sourceItem === "string") { 2623 sourceItem = createFragment(sourceItem); 2624 } else if (sourceItem.length) { 2625 for (i = 0; i < sourceItem.length; i++) { 2626 domManipulate(targetNodes, sourceItem[i], callback); 2627 } 2628 2629 return targetNodes; 2630 } 2631 2632 i = targetNodes.length; 2633 while (i--) { 2634 callback.call(targetNodes[i], sourceItem.parentNode ? sourceItem : sourceItem); 2635 } 2636 2637 return targetNodes; 2638 } 2639 2640 function hasClass(node, className) { 2641 return node && className && (' ' + node.className + ' ').indexOf(' ' + className + ' ') !== -1; 2642 } 2643 2644 /** 2645 * Makes a map object out of a string that gets separated by a delimiter. 2646 * 2647 * @method makeMap 2648 * @param {String} items Item string to split. 2649 * @param {Object} map Optional object to add items to. 2650 * @return {Object} name/value object with items as keys. 2651 */ 2652 function makeMap(items, map) { 2653 var i; 2654 2655 items = items || []; 2656 2657 if (typeof(items) == "string") { 2658 items = items.split(' '); 2659 } 2660 2661 map = map || {}; 2662 2663 i = items.length; 2664 while (i--) { 2665 map[items[i]] = {}; 2666 } 2667 2668 return map; 2669 } 2670 2671 var numericCssMap = makeMap('fillOpacity fontWeight lineHeight opacity orphans widows zIndex zoom'); 2672 2673 function DomQuery(selector, context) { 2674 return new DomQuery.fn.init(selector, context); 2675 } 2676 2677 /** 2678 * Extends the specified object with another object. 2679 * 2680 * @method extend 2681 * @param {Object} target Object to extend. 2682 * @param {Object..} obj Multiple objects to extend with. 2683 * @return {Object} Same as target, the extended object. 2684 */ 2685 function extend(target) { 2686 var args = arguments, arg, i, key; 2687 2688 for (i = 1; i < args.length; i++) { 2689 arg = args[i]; 2690 2691 for (key in arg) { 2692 target[key] = arg[key]; 2693 } 2694 } 2695 2696 return target; 2697 } 2698 2699 /** 2700 * Converts the specified object into a real JavaScript array. 2701 * 2702 * @method toArray 2703 * @param {Object} obj Object to convert into array. 2704 * @return {Array} Array object based in input. 2705 */ 2706 function toArray(obj) { 2707 var array = [], i, l; 2708 2709 for (i = 0, l = obj.length; i < l; i++) { 2710 array[i] = obj[i]; 2711 } 2712 2713 return array; 2714 } 2715 2716 /** 2717 * Returns the index of the specified item inside the array. 2718 * 2719 * @method inArray 2720 * @param {Object} item Item to look for. 2721 * @param {Array} array Array to look for item in. 2722 * @return {Number} Index of the item or -1. 2723 */ 2724 function inArray(item, array) { 2725 var i; 2726 2727 if (array.indexOf) { 2728 return array.indexOf(item); 2729 } 2730 2731 i = array.length; 2732 while (i--) { 2733 if (array[i] === item) { 2734 return i; 2735 } 2736 } 2737 2738 return -1; 2739 } 2740 2741 /** 2742 * Returns true/false if the specified object is an array.
2743 * 2744 * @method isArray 2745 * @param {Object} obj Object to check if it's an array. 2746 * @return {Boolean} true/false if the input object is array or not. 2747 */ 2748 var isArray = Array.isArray || function(obj) { 2749 return Object.prototype.toString.call(obj) === "[object Array]"; 2750 }; 2751 2752 var whiteSpaceRegExp = /^\s*|\s*$/g; 2753 var trim = function(str) { 2754 return (str === null || str === undefined) ? '' : ("" + str).replace(whiteSpaceRegExp, ''); 2755 }; 2756 2757 /** 2758 * Executes the callback function for each item in array/object. If you return false in the 2759 * callback it will break the loop. 2760 * 2761 * @method each 2762 * @param {Object} obj Object to iterate. 2763 * @param {function} callback Callback function to execute for each item. 2764 */ 2765 function each(obj, callback) { 2766 var length, key, i, undef, value; 2767 2768 if (obj) { 2769 length = obj.length; 2770 2771 if (length === undef) { 2772 // Loop object items 2773 for (key in obj) { 2774 if (obj.hasOwnProperty(key)) { 2775 value = obj[key]; 2776 if (callback.call(value, value, key) === false) { 2777 break; 2778 } 2779 } 2780 } 2781 } else { 2782 // Loop array items 2783 for (i = 0; i < length; i++) { 2784 value = obj[i]; 2785 if (callback.call(value, value, key) === false) { 2786 break; 2787 } 2788 } 2789 } 2790 } 2791 2792 return obj; 2793 } 2794 2795 DomQuery.fn = DomQuery.prototype = { 2796 constructor: DomQuery, 2797 selector: "", 2798 length: 0, 2799 2800 init: function(selector, context) { 2801 var self = this, match, node; 2802 2803 if (!selector) { 2804 return self; 2805 } 2806 2807 if (selector.nodeType) { 2808 self.context = self[0] = selector; 2809 self.length = 1; 2810 2811 return self; 2812 } 2813 2814 if (isString(selector)) { 2815 if (selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3) { 2816 match = [null, selector, null]; 2817 } else { 2818 match = rquickExpr.exec(selector); 2819 } 2820 2821 if (match) { 2822 if (match[1]) { 2823 node = createFragment(selector).firstChild; 2824 while (node) { 2825 this.add(node); 2826 node = node.nextSibling; 2827 } 2828 } else { 2829 node = doc.getElementById(match[2]); 2830 2831 if (node.id !== match[2]) { 2832 return self.find(selector); 2833 } 2834 2835 self.length = 1; 2836 self[0] = node; 2837 } 2838 } else { 2839 return DomQuery(context || document).find(selector); 2840 } 2841 } else { 2842 this.add(selector); 2843 } 2844 2845 return self; 2846 }, 2847 2848 toArray: function() { 2849 return toArray(this); 2850 }, 2851 2852 add: function(items) { 2853 var self = this; 2854 2855 // Force single item into array 2856 if (!isArray(items)) { 2857 if (items instanceof DomQuery) { 2858 self.add(items.toArray()); 2859 } else { 2860 push.call(self, items); 2861 } 2862 } else { 2863 push.apply(self, items); 2864 } 2865 2866 return self; 2867 }, 2868 2869 attr: function(name, value) { 2870 var self = this; 2871 2872 if (typeof name === "object") { 2873 each(name, function(value, name) { 2874 self.attr(name, value); 2875 }); 2876 } else if (isDefined(value)) { 2877 this.each(function() { 2878 if (this.nodeType === 1) { 2879 this.setAttribute(name, value); 2880 } 2881 }); 2882 } else { 2883 return self[0] && self[0].nodeType === 1 ? self[0].getAttribute(name) : undefined; 2884 } 2885 2886 return self; 2887 }, 2888 2889 css: function(name, value) { 2890 var self = this; 2891 2892 if (typeof name === "object") { 2893 each(name, function(value, name) { 2894 self.css(name, value); 2895 }); 2896 } else { 2897 // Camelcase it, if needed 2898 name = name.replace(/-(\D)/g, function(a, b) { 2899 return b.toUpperCase(); 2900 }); 2901 2902 if (isDefined(value)) { 2903 // Default px suffix on these 2904 if (typeof(value) === 'number' && !numericCssMap[name]) { 2905 value += 'px'; 2906 } 2907 2908 self.each(function() { 2909 var style = this.style; 2910 2911 // IE specific opacity 2912 if (name === "opacity" && this.runtimeStyle && typeof(this.runtimeStyle.opacity) === "undefined") { 2913 style.filter = value === '' ? '' : "alpha(opacity=" + (value * 100) + ")"; 2914 } 2915 2916 try { 2917 style[name] = value; 2918 } catch (ex) { 2919 // Ignore 2920 } 2921 }); 2922 } else { 2923 return self[0] ? self[0].style[name] : undefined; 2924 } 2925 } 2926 2927 return self; 2928 }, 2929 2930 remove: function() { 2931 var self = this, node, i = this.length; 2932 2933 while (i--) { 2934 node = self[i]; 2935 Event.clean(node); 2936 2937 if (node.parentNode) { 2938 node.parentNode.removeChild(node); 2939 } 2940 } 2941 2942 return this; 2943 }, 2944 2945 empty: function() { 2946 var self = this, node, i = this.length; 2947 2948 while (i--) { 2949 node = self[i]; 2950 while (node.firstChild) { 2951 node.removeChild(node.firstChild); 2952 } 2953 } 2954 2955 return this; 2956 }, 2957 2958 html: function(value) { 2959 var self = this, i; 2960 2961 if (isDefined(value)) { 2962 i = self.length; 2963 while (i--) { 2964 self[i].innerHTML = value; 2965 } 2966 2967 return self; 2968 } 2969 2970 return self[0] ? self[0].innerHTML : ''; 2971 }, 2972 2973 text: function(value) { 2974 var self = this, i; 2975 2976 if (isDefined(value)) { 2977 i = self.length; 2978 while (i--) { 2979 self[i].innerText = self[0].textContent = value; 2980 } 2981 2982 return self; 2983 } 2984 2985 return self[0] ? self[0].innerText || self[0].textContent : ''; 2986 }, 2987 2988 append: function() { 2989 return domManipulate(this, arguments, function(node) { 2990 if (this.nodeType === 1) { 2991 this.appendChild(node); 2992 } 2993 }); 2994 }, 2995 2996 prepend: function() { 2997 return domManipulate(this, arguments, function(node) { 2998 if (this.nodeType === 1) { 2999 this.insertBefore(node, this.firstChild); 3000 } 3001 }); 3002 }, 3003 3004 before: function() { 3005 var self = this; 3006 3007 if (self[0] && self[0].parentNode) { 3008 return domManipulate(self, arguments, function(node) { 3009 this.parentNode.insertBefore(node, this.nextSibling); 3010 }); 3011 } 3012 3013 return self; 3014 }, 3015 3016 after: function() { 3017 var self = this; 3018 3019 if (self[0] && self[0].parentNode) { 3020 return domManipulate(self, arguments, function(node) { 3021 this.parentNode.insertBefore(node, this); 3022 }); 3023 } 3024 3025 return self; 3026 }, 3027 3028 appendTo: function(val) { 3029 DomQuery(val).append(this); 3030 3031 return this; 3032 }, 3033 3034 addClass: function(className) { 3035 return this.toggleClass(className, true); 3036 }, 3037 3038 removeClass: function(className) { 3039 return this.toggleClass(className, false); 3040 }, 3041 3042 toggleClass: function(className, state) { 3043 var self = this; 3044 3045 if (className.indexOf(' ') !== -1) { 3046 each(className.split(' '), function() { 3047 self.toggleClass(this, state); 3048 }); 3049 } else { 3050 self.each(function() { 3051 var node = this, existingClassName; 3052 3053 if (hasClass(node, className) !== state) {
3054 existingClassName = node.className; 3055 3056 if (state) { 3057 node.className += existingClassName ? ' ' + className : className; 3058 } else { 3059 node.className = trim((" " + existingClassName + " ").replace(' ' + className + ' ', ' ')); 3060 } 3061 } 3062 }); 3063 } 3064 3065 return self; 3066 }, 3067 3068 hasClass: function(className) { 3069 return hasClass(this[0], className); 3070 }, 3071 3072 each: function(callback) { 3073 return each(this, callback); 3074 }, 3075 3076 on: function(name, callback) { 3077 return this.each(function() { 3078 Event.bind(this, name, callback); 3079 }); 3080 }, 3081 3082 off: function(name, callback) { 3083 return this.each(function() { 3084 Event.unbind(this, name, callback); 3085 }); 3086 }, 3087 3088 show: function() { 3089 return this.css('display', ''); 3090 }, 3091 3092 hide: function() { 3093 return this.css('display', 'none'); 3094 }, 3095 3096 slice: function() { 3097 return new DomQuery(slice.apply(this, arguments)); 3098 }, 3099 3100 eq: function(index) { 3101 return index === -1 ? this.slice(index) : this.slice(index, +index + 1); 3102 }, 3103 3104 first: function() { 3105 return this.eq(0); 3106 }, 3107 3108 last: function() { 3109 return this.eq(-1); 3110 }, 3111 3112 replaceWith: function(content) { 3113 var self = this; 3114 3115 if (self[0]) { 3116 self[0].parentNode.replaceChild(DomQuery(content)[0], self[0]); 3117 } 3118 3119 return self; 3120 }, 3121 3122 wrap: function(wrapper) { 3123 wrapper = DomQuery(wrapper)[0]; 3124 3125 return this.each(function() { 3126 var self = this, newWrapper = wrapper.cloneNode(false); 3127 self.parentNode.insertBefore(newWrapper, self); 3128 newWrapper.appendChild(self); 3129 }); 3130 }, 3131 3132 unwrap: function() { 3133 return this.each(function() { 3134 var self = this, node = self.firstChild, currentNode; 3135 3136 while (node) { 3137 currentNode = node; 3138 node = node.nextSibling; 3139 self.parentNode.insertBefore(currentNode, self); 3140 } 3141 }); 3142 }, 3143 3144 clone: function() { 3145 var result = []; 3146 3147 this.each(function() { 3148 result.push(this.cloneNode(true)); 3149 }); 3150 3151 return DomQuery(result); 3152 }, 3153 3154 find: function(selector) { 3155 var i, l, ret = []; 3156 3157 for (i = 0, l = this.length; i < l; i++) { 3158 DomQuery.find(selector, this[i], ret); 3159 } 3160 3161 return DomQuery(ret); 3162 }, 3163 3164 push: push, 3165 sort: [].sort, 3166 splice: [].splice 3167 }; 3168 3169 // Static members 3170 extend(DomQuery, { 3171 extend: extend, 3172 toArray: toArray, 3173 inArray: inArray, 3174 isArray: isArray, 3175 each: each, 3176 trim: trim, 3177 makeMap: makeMap, 3178 3179 // Sizzle 3180 find: Sizzle, 3181 expr: Sizzle.selectors, 3182 unique: Sizzle.uniqueSort, 3183 text: Sizzle.getText, 3184 isXMLDoc: Sizzle.isXML, 3185 contains: Sizzle.contains, 3186 filter: function(expr, elems, not) { 3187 if (not) { 3188 expr = ":not(" + expr + ")"; 3189 } 3190 3191 return elems.length === 1 ? 3192 DomQuery.find.matchesSelector(elems[0], expr) ? [elems[0]] : [] : 3193 DomQuery.find.matches(expr, elems); 3194 } 3195 }); 3196 3197 function dir(el, prop, until) { 3198 var matched = [], cur = el[prop]; 3199 3200 while (cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !DomQuery(cur).is(until))) { 3201 if (cur.nodeType === 1) { 3202 matched.push(cur); 3203 } 3204 3205 cur = cur[prop]; 3206 } 3207 3208 return matched; 3209 } 3210 3211 function sibling(n, el, siblingName, nodeType) { 3212 var r = []; 3213 3214 for(; n; n = n[siblingName]) { 3215 if ((!nodeType || n.nodeType === nodeType) && n !== el) { 3216 r.push(n); 3217 } 3218 } 3219 3220 return r; 3221 } 3222 3223 each({ 3224 parent: function(node) { 3225 var parent = node.parentNode; 3226 3227 return parent && parent.nodeType !== 11 ? parent : null; 3228 }, 3229 3230 parents: function(node) { 3231 return dir(node, "parentNode"); 3232 }, 3233 3234 parentsUntil: function(node, until) { 3235 return dir(node, "parentNode", until); 3236 }, 3237 3238 next: function(node) { 3239 return sibling(node, 'nextSibling', 1); 3240 }, 3241 3242 prev: function(node) { 3243 return sibling(node, 'previousSibling', 1); 3244 }, 3245 3246 nextNodes: function(node) { 3247 return sibling(node, 'nextSibling'); 3248 }, 3249 3250 prevNodes: function(node) { 3251 return sibling(node, 'previousSibling'); 3252 }, 3253 3254 children: function(node) { 3255 return sibling(node.firstChild, 'nextSibling', 1); 3256 }, 3257 3258 contents: function(node) { 3259 return toArray((node.nodeName === "iframe" ? node.contentDocument || node.contentWindow.document : node).childNodes); 3260 } 3261 }, function(name, fn){ 3262 DomQuery.fn[name] = function(selector) { 3263 var self = this, result; 3264 3265 if (self.length > 1) { 3266 throw new Error("DomQuery only supports traverse functions on a single node."); 3267 } 3268 3269 if (self[0]) { 3270 result = fn(self[0], selector); 3271 } 3272 3273 result = DomQuery(result); 3274 3275 if (selector && name !== "parentsUntil") { 3276 return result.filter(selector); 3277 } 3278 3279 return result; 3280 }; 3281 }); 3282 3283 DomQuery.fn.filter = function(selector) { 3284 return DomQuery.filter(selector); 3285 }; 3286 3287 DomQuery.fn.is = function(selector) { 3288 return !!selector && this.filter(selector).length > 0; 3289 }; 3290 3291 DomQuery.fn.init.prototype = DomQuery.fn; 3292 3293 return DomQuery; 3294}); 3295 3296// Included from: js/tinymce/classes/html/Styles.js 3297 3298/** 3299 * Styles.js 3300 * 3301 * Copyright, Moxiecode Systems AB 3302 * Released under LGPL License. 3303 * 3304 * License: http://www.tinymce.com/license 3305 * Contributing: http://www.tinymce.com/contributing 3306 */ 3307 3308/** 3309 * This class is used to parse CSS styles it also compresses styles to reduce the output size. 3310 * 3311 * @example 3312 * var Styles = new tinymce.html.Styles({ 3313 * url_converter: function(url) { 3314 * return url; 3315 * } 3316 * }); 3317 * 3318 * styles = Styles.parse('border: 1px solid red'); 3319 * styles.color = 'red'; 3320 * 3321 * console.log(new tinymce.html.StyleSerializer().serialize(styles)); 3322 * 3323 * @class tinymce.html.Styles 3324 * @version 3.4 3325 */ 3326define("tinymce/html/Styles", [], function() { 3327 return function(settings, schema) { 3328 /*jshint maxlen:255 */ 3329 var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi, 3330 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi, 3331 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g, 3332 trimRightRegExp = /\s+$/, 3333 undef, i, encodingLookup = {}, encodingItems, invisibleChar = '\uFEFF'; 3334 3335 settings = settings || {}; 3336 3337 encodingItems = ('\\" \\\' \\; \\: ; : ' + invisibleChar).split(' '); 3338 for (i = 0; i < encodingItems.length; i++) { 3339 encodingLookup[encodingItems[i]] = invisibleChar + i; 3340 encodingLookup[invisibleChar + i] = encodingItems[i]; 3341 } 3342 3343 function toHex(match, r, g, b) { 3344 function hex(val) { 3345 val = parseInt(val, 10).toString(16); 3346 3347 return val.length > 1 ? val : '0' + val; // 0 -> 00 3348 } 3349 3350 return '#' + hex(r) + hex(g) + hex(b); 3351 } 3352 3353 return { 3354 /** 3355 * Parses the specified RGB color value and returns a hex version of that color. 3356 * 3357 * @method toHex 3358 * @param {String} color RGB string value like rgb(1,2,3) 3359 * @return {String} Hex version of that RGB value like #FF00FF. 3360 */ 3361 toHex: function(color) { 3362 return color.replace(rgbRegExp, toHex); 3363 }, 3364 3365 /**
3366 * Parses the specified style value into an object collection. This parser will also 3367 * merge and remove any redundant items that browsers might have added. It will also convert non hex 3368 * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings. 3369 * 3370 * @method parse 3371 * @param {String} css Style value to parse for example: border:1px solid red;. 3372 * @return {Object} Object representation of that style like {border: '1px solid red'} 3373 */ 3374 parse: function(css) { 3375 var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter; 3376 var urlConverterScope = settings.url_converter_scope || this; 3377 3378 function compress(prefix, suffix, noJoin) { 3379 var top, right, bottom, left; 3380 3381 top = styles[prefix + '-top' + suffix]; 3382 if (!top) { 3383 return; 3384 } 3385 3386 right = styles[prefix + '-right' + suffix]; 3387 if (!right) { 3388 return; 3389 } 3390 3391 bottom = styles[prefix + '-bottom' + suffix]; 3392 if (!bottom) { 3393 return; 3394 } 3395 3396 left = styles[prefix + '-left' + suffix]; 3397 if (!left) { 3398 return; 3399 } 3400 3401 var box = [top, right, bottom, left]; 3402 i = box.length - 1; 3403 while (i--) { 3404 if (box[i] !== box[i + 1]) { 3405 break; 3406 } 3407 } 3408 3409 if (i > -1 && noJoin) { 3410 return; 3411 } 3412 3413 styles[prefix + suffix] = i == -1 ? box[0] : box.join(' '); 3414 delete styles[prefix + '-top' + suffix]; 3415 delete styles[prefix + '-right' + suffix]; 3416 delete styles[prefix + '-bottom' + suffix]; 3417 delete styles[prefix + '-left' + suffix]; 3418 } 3419 3420 /** 3421 * Checks if the specific style can be compressed in other words if all border-width are equal. 3422 */ 3423 function canCompress(key) { 3424 var value = styles[key], i; 3425 3426 if (!value) { 3427 return; 3428 } 3429 3430 value = value.split(' '); 3431 i = value.length; 3432 while (i--) { 3433 if (value[i] !== value[0]) { 3434 return false; 3435 } 3436 } 3437 3438 styles[key] = value[0]; 3439 3440 return true; 3441 } 3442 3443 /** 3444 * Compresses multiple styles into one style. 3445 */ 3446 function compress2(target, a, b, c) { 3447 if (!canCompress(a)) { 3448 return; 3449 } 3450 3451 if (!canCompress(b)) { 3452 return; 3453 } 3454 3455 if (!canCompress(c)) { 3456 return; 3457 } 3458 3459 // Compress 3460 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c]; 3461 delete styles[a]; 3462 delete styles[b]; 3463 delete styles[c]; 3464 } 3465 3466 // Encodes the specified string by replacing all \" \' ; : with _<num> 3467 function encode(str) { 3468 isEncoded = true; 3469 3470 return encodingLookup[str]; 3471 } 3472 3473 // Decodes the specified string by replacing all _<num> with it's original value \" \' etc 3474 // It will also decode the \" \' if keep_slashes is set to fale or omitted 3475 function decode(str, keep_slashes) { 3476 if (isEncoded) { 3477 str = str.replace(/\uFEFF[0-9]/g, function(str) { 3478 return encodingLookup[str]; 3479 }); 3480 } 3481 3482 if (!keep_slashes) { 3483 str = str.replace(/\\([\'\";:])/g, "$1"); 3484 } 3485 3486 return str; 3487 } 3488 3489 function processUrl(match, url, url2, url3, str, str2) { 3490 str = str || str2; 3491 3492 if (str) { 3493 str = decode(str); 3494 3495 // Force strings into single quote format 3496 return "'" + str.replace(/\'/g, "\\'") + "'"; 3497 } 3498 3499 url = decode(url || url2 || url3); 3500 3501 if (!settings.allow_script_urls && /(java|vb)script:/i.test(url.replace(/[\s\r\n]+/, ''))) { 3502 return ""; 3503 } 3504 3505 // Convert the URL to relative/absolute depending on config 3506 if (urlConverter) { 3507 url = urlConverter.call(urlConverterScope, url, 'style'); 3508 } 3509 3510 // Output new URL format 3511 return "url('" + url.replace(/\'/g, "\\'") + "')"; 3512 } 3513 3514 if (css) { 3515 css = css.replace(/[\u0000-\u001F]/g, ''); 3516 3517 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing 3518 css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) { 3519 return str.replace(/[;:]/g, encode); 3520 }); 3521 3522 // Parse styles 3523 while ((matches = styleRegExp.exec(css))) { 3524 name = matches[1].replace(trimRightRegExp, '').toLowerCase(); 3525 value = matches[2].replace(trimRightRegExp, ''); 3526 3527 if (name && value.length > 0) { 3528 if (!settings.allow_script_urls && (name == "behavior" || /expression\s*\(/.test(value))) { 3529 continue; 3530 } 3531 3532 // Opera will produce 700 instead of bold in their style values 3533 if (name === 'font-weight' && value === '700') { 3534 value = 'bold'; 3535 } else if (name === 'color' || name === 'background-color') { // Lowercase colors like RED 3536 value = value.toLowerCase(); 3537 } 3538 3539 // Convert RGB colors to HEX 3540 value = value.replace(rgbRegExp, toHex); 3541 3542 // Convert URLs and force them into url('value') format 3543 value = value.replace(urlOrStrRegExp, processUrl); 3544 styles[name] = isEncoded ? decode(value, true) : value; 3545 } 3546 3547 styleRegExp.lastIndex = matches.index + matches[0].length; 3548 } 3549 // Compress the styles to reduce it's size for example IE will expand styles 3550 compress("border", "", true); 3551 compress("border", "-width"); 3552 compress("border", "-color"); 3553 compress("border", "-style"); 3554 compress("padding", ""); 3555 compress("margin", ""); 3556 compress2('border', 'border-width', 'border-style', 'border-color'); 3557 3558 // Remove pointless border, IE produces these 3559 if (styles.border === 'medium none') { 3560 delete styles.border; 3561 } 3562 3563 // IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p> 3564 // So lets asume it shouldn't be there 3565 if (styles['border-image'] === 'none') { 3566 delete styles['border-image']; 3567 } 3568 } 3569 3570 return styles; 3571 }, 3572 3573 /** 3574 * Serializes the specified style object into a string. 3575 * 3576 * @method serialize 3577 * @param {Object} styles Object to serialize as string for example: {border: '1px solid red'} 3578 * @param {String} element_name Optional element name, if specified only the styles that matches the schema will be serialized. 3579 * @return {String} String representation of the style object for example: border: 1px solid red. 3580 */ 3581 serialize: function(styles, element_name) { 3582 var css = '', name, value; 3583 3584 function serializeStyles(name) { 3585 var styleList, i, l, value; 3586 3587 styleList = schema.styles[name]; 3588 if (styleList) { 3589 for (i = 0, l = styleList.length; i < l; i++) { 3590 name = styleList[i]; 3591 value = styles[name]; 3592 3593 if (value !== undef && value.length > 0) { 3594 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; 3595 } 3596 } 3597 } 3598 } 3599 3600 // Serialize styles according to schema 3601 if (element_name && schema && schema.styles) { 3602 // Serialize global styles and element specific styles 3603 serializeStyles('*'); 3604 serializeStyles(element_name); 3605 } else { 3606 // Output the styles in the order they are inside the object 3607 for (name in styles) { 3608 value = styles[name]; 3609 3610 if (value !== undef && value.length > 0) { 3611 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; 3612 } 3613 } 3614 } 3615 3616 return css; 3617 } 3618 }; 3619 }; 3620}); 3621 3622// Included from: js/tinymce/classes/dom/TreeWalker.js 3623 3624/** 3625 * TreeWalker.js 3626 * 3627 * Copyright, Moxiecode Systems AB 3628 * Released under LGPL License. 3629 * 3630 * License: http://www.tinymce.com/license 3631 * Contributing: http://www.tinymce.com/contributing 3632 */ 3633 3634/** 3635 * TreeWalker class enables you to walk the DOM in a linear manner. 3636 * 3637 * @class tinymce.dom.TreeWalker 3638 */ 3639define("tinymce/dom/TreeWalker", [], function() { 3640 return function(start_node, root_node) { 3641 var node = start_node; 3642 3643 function findSibling(node, start_name, sibling_name, shallow) { 3644 var sibling, parent; 3645 3646 if (node) { 3647 // Walk into nodes if it has a start 3648 if (!shallow && node[start_name]) { 3649 return node[start_name]; 3650 } 3651 3652 // Return the sibling if it has one 3653 if (node != root_node) { 3654 sibling = node[sibling_name]; 3655 if (sibling) { 3656 return sibling; 3657 } 3658 3659 // Walk up the parents to look for siblings 3660 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) { 3661 sibling = parent[sibling_name]; 3662 if (sibling) { 3663 return sibling; 3664 } 3665 } 3666 } 3667 } 3668 } 3669 3670 /** 3671 * Returns the current node. 3672 * 3673 * @method current 3674 * @return {Node} Current node where the walker is. 3675 */ 3676 this.current = function() { 3677 return node; 3678 }; 3679 3680 /** 3681 * Walks to the next node in tree. 3682 * 3683 * @method next 3684 * @return {Node}
3684 Current node where the walker is after moving to the next node. 3685 */ 3686 this.next = function(shallow) { 3687 node = findSibling(node, 'firstChild', 'nextSibling', shallow); 3688 return node; 3689 }; 3690 3691 /** 3692 * Walks to the previous node in tree. 3693 * 3694 * @method prev 3695 * @return {Node} Current node where the walker is after moving to the previous node. 3696 */ 3697 this.prev = function(shallow) { 3698 node = findSibling(node, 'lastChild', 'previousSibling', shallow); 3699 return node; 3700 }; 3701 }; 3702}); 3703 3704// Included from: js/tinymce/classes/util/Tools.js 3705 3706/** 3707 * Tools.js 3708 * 3709 * Copyright, Moxiecode Systems AB 3710 * Released under LGPL License. 3711 * 3712 * License: http://www.tinymce.com/license 3713 * Contributing: http://www.tinymce.com/contributing 3714 */ 3715 3716/** 3717 * This class contains various utlity functions. These are also exposed 3718 * directly on the tinymce namespace. 3719 * 3720 * @class tinymce.util.Tools 3721 */ 3722define("tinymce/util/Tools", [], function() { 3723 /** 3724 * Removes whitespace from the beginning and end of a string. 3725 * 3726 * @method trim 3727 * @param {String} s String to remove whitespace from. 3728 * @return {String} New string with removed whitespace. 3729 */ 3730 var whiteSpaceRegExp = /^\s*|\s*$/g; 3731 var trim = function(str) { 3732 return (str === null || str === undefined) ? '' : ("" + str).replace(whiteSpaceRegExp, ''); 3733 }; 3734 3735 /** 3736 * Returns true/false if the object is an array or not. 3737 * 3738 * @method isArray 3739 * @param {Object} obj Object to check. 3740 * @return {boolean} true/false state if the object is an array or not. 3741 */ 3742 var isArray = Array.isArray || function(obj) { 3743 return Object.prototype.toString.call(obj) === "[object Array]"; 3744 }; 3745 3746 /** 3747 * Checks if a object is of a specific type for example an array. 3748 * 3749 * @method is 3750 * @param {Object} o Object to check type of. 3751 * @param {string} t Optional type to check for. 3752 * @return {Boolean} true/false if the object is of the specified type. 3753 */ 3754 function is(o, t) { 3755 if (!t) { 3756 return o !== undefined; 3757 } 3758 3759 if (t == 'array' && isArray(o)) { 3760 return true; 3761 } 3762 3763 return typeof(o) == t; 3764 } 3765 3766 /** 3767 * Converts the specified object into a real JavaScript array. 3768 * 3769 * @method toArray 3770 * @param {Object} obj Object to convert into array. 3771 * @return {Array} Array object based in input. 3772 */ 3773 function toArray(obj) { 3774 var array = [], i, l; 3775 3776 for (i = 0, l = obj.length; i < l; i++) { 3777 array[i] = obj[i]; 3778 } 3779 3780 return array; 3781 } 3782 3783 /** 3784 * Makes a name/object map out of an array with names. 3785 * 3786 * @method makeMap 3787 * @param {Array/String} items Items to make map out of. 3788 * @param {String} delim Optional delimiter to split string by. 3789 * @param {Object} map Optional map to add items to. 3790 * @return {Object} Name/value map of items. 3791 */ 3792 function makeMap(items, delim, map) { 3793 var i; 3794 3795 items = items || []; 3796 delim = delim || ','; 3797 3798 if (typeof(items) == "string") { 3799 items = items.split(delim); 3800 } 3801 3802 map = map || {}; 3803 3804 i = items.length; 3805 while (i--) { 3806 map[items[i]] = {}; 3807 } 3808 3809 return map; 3810 } 3811 3812 /** 3813 * Performs an iteration of all items in a collection such as an object or array. This method will execure the 3814 * callback function for each item in the collection, if the callback returns false the iteration will terminate. 3815 * The callback has the following format: cb(value, key_or_index). 3816 * 3817 * @method each 3818 * @param {Object} o Collection to iterate. 3819 * @param {function} cb Callback function to execute for each item. 3820 * @param {Object} s Optional scope to execute the callback in. 3821 * @example 3822 * // Iterate an array 3823 * tinymce.each([1,2,3], function(v, i) { 3824 * console.debug("Value: " + v + ", Index: " + i); 3825 * }); 3826 * 3827 * // Iterate an object 3828 * tinymce.each({a: 1, b: 2, c: 3], function(v, k) { 3829 * console.debug("Value: " + v + ", Key: " + k); 3830 * }); 3831 */ 3832 function each(o, cb, s) { 3833 var n, l; 3834 3835 if (!o) { 3836 return 0; 3837 } 3838 3839 s = s || o; 3840 3841 if (o.length !== undefined) { 3842 // Indexed arrays, needed for Safari 3843 for (n=0, l = o.length; n < l; n++) { 3844 if (cb.call(s, o[n], n, o) === false) { 3845 return 0; 3846 } 3847 } 3848 } else { 3849 // Hashtables 3850 for (n in o) { 3851 if (o.hasOwnProperty(n)) { 3852 if (cb.call(s, o[n], n, o) === false) { 3853 return 0; 3854 } 3855 } 3856 } 3857 } 3858 3859 return 1; 3860 } 3861 3862 /** 3863 * Creates a new array by the return value of each iteration function call. This enables you to convert 3864 * one array list into another. 3865 * 3866 * @method map 3867 * @param {Array} a Array of items to iterate. 3868 * @param {function} f Function to call for each item. It's return value will be the new value. 3869 * @return {Array}
3869 Array with new values based on function return values. 3870 */ 3871 function map(a, f) { 3872 var o = []; 3873 3874 each(a, function(v) { 3875 o.push(f(v)); 3876 }); 3877 3878 return o; 3879 } 3880 3881 /** 3882 * Filters out items from the input array by calling the specified function for each item. 3883 * If the function returns false the item will be excluded if it returns true it will be included. 3884 * 3885 * @method grep 3886 * @param {Array} a Array of items to loop though. 3887 * @param {function} f Function to call for each item. Include/exclude depends on it's return value. 3888 * @return {Array} New array with values imported and filtered based in input. 3889 * @example 3890 * // Filter out some items, this will return an array with 4 and 5 3891 * var items = tinymce.grep([1,2,3,4,5], function(v) {return v > 3;}); 3892 */ 3893 function grep(a, f) { 3894 var o = []; 3895 3896 each(a, function(v) { 3897 if (!f || f(v)) { 3898 o.push(v); 3899 } 3900 }); 3901 3902 return o; 3903 } 3904 3905 /** 3906 * Creates a class, subclass or static singleton. 3907 * More details on this method can be found in the Wiki. 3908 * 3909 * @method create 3910 * @param {String} s Class name, inheritage and prefix. 3911 * @param {Object} p Collection of methods to add to the class. 3912 * @param {Object} root Optional root object defaults to the global window object. 3913 * @example 3914 * // Creates a basic class 3915 * tinymce.create('tinymce.somepackage.SomeClass', { 3916 * SomeClass: function() { 3917 * // Class constructor 3918 * }, 3919 * 3920 * method: function() { 3921 * // Some method 3922 * } 3923 * }); 3924 * 3925 * // Creates a basic subclass class 3926 * tinymce.create('tinymce.somepackage.SomeSubClass:tinymce.somepackage.SomeClass', { 3927 * SomeSubClass: function() { 3928 * // Class constructor 3929 * this.parent(); // Call parent constructor 3930 * }, 3931 * 3932 * method: function() { 3933 * // Some method 3934 * this.parent(); // Call parent method 3935 * }, 3936 * 3937 * 'static': { 3938 * staticMethod: function() { 3939 * // Static method 3940 * } 3941 * } 3942 * }); 3943 * 3944 * // Creates a singleton/static class 3945 * tinymce.create('static tinymce.somepackage.SomeSingletonClass', { 3946 * method: function() { 3947 * // Some method 3948 * } 3949 * }); 3950 */ 3951 function create(s, p, root) { 3952 var t = this, sp, ns, cn, scn, c, de = 0; 3953 3954 // Parse : <prefix> <class>:<super class> 3955 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s); 3956 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name 3957 3958 // Create namespace for new class 3959 ns = t.createNS(s[3].replace(/\.\w+$/, ''), root); 3960 3961 // Class already exists 3962 if (ns[cn]) { 3963 return; 3964 } 3965 3966 // Make pure static class 3967 if (s[2] == 'static') { 3968 ns[cn] = p; 3969 3970 if (this.onCreate) { 3971 this.onCreate(s[2], s[3], ns[cn]); 3972 } 3973 3974 return; 3975 } 3976 3977 // Create default constructor 3978 if (!p[cn]) { 3979 p[cn] = function() {}; 3980 de = 1; 3981 } 3982 3983 // Add constructor and methods 3984 ns[cn] = p[cn]; 3985 t.extend(ns[cn].prototype, p); 3986 3987 // Extend 3988 if (s[5]) { 3989 sp = t.resolve(s[5]).prototype; 3990 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name 3991 3992 // Extend constructor 3993 c = ns[cn]; 3994 if (de) { 3995 // Add passthrough constructor 3996 ns[cn] = function() { 3997 return sp[scn].apply(this, arguments); 3998 }; 3999 } else { 4000 // Add inherit constructor 4001 ns[cn] = function() { 4002 this.parent = sp[scn]; 4003 return c.apply(this, arguments); 4004 }; 4005 } 4006 ns[cn].prototype[cn] = ns[cn]; 4007 4008 // Add super methods 4009 t.each(sp, function(f, n) { 4010 ns[cn].prototype[n] = sp[n]; 4011 }); 4012 4013 // Add overridden methods 4014 t.each(p, function(f, n) { 4015 // Extend methods if needed 4016 if (sp[n]) { 4017 ns[cn].prototype[n] = function() { 4018 this.parent = sp[n]; 4019 return f.apply(this, arguments); 4020 }; 4021 } else { 4022 if (n != cn) { 4023 ns[cn].prototype[n] = f; 4024 } 4025 } 4026 }); 4027 } 4028 4029 // Add static methods 4030 /*jshint sub:true*/ 4031 t.each(p['static'], function(f, n) { 4032 ns[cn][n] = f; 4033 }); 4034 } 4035 4036 /** 4037 * Returns the index of a value in an array, this method will return -1 if the item wasn't found. 4038 * 4039 * @method inArray 4040 * @param {Array} a Array/Object to search for value in. 4041 * @param {Object} v Value to check for inside the array. 4042 * @return {Number/String} Index of item inside the array inside an object. Or -1 if it wasn't found. 4043 * @example 4044 * // Get index of value in array this will alert 1 since 2 is at that index 4045 * alert(tinymce.inArray([1,2,3], 2)); 4046 */ 4047 function inArray(a, v) { 4048 var i, l; 4049 4050 if (a) { 4051 for (i = 0, l = a.length; i < l; i++) { 4052 if (a[i] === v) { 4053 return i; 4054 } 4055 } 4056 } 4057 4058 return -1; 4059 } 4060 4061 function extend(obj, ext) { 4062 var i, l, name, args = arguments, value; 4063 4064 for (i = 1, l = args.length; i < l; i++) { 4065 ext = args[i]; 4066 for (name in ext) { 4067 if (ext.hasOwnProperty(name)) { 4068 value = ext[name]; 4069 4070 if (value !== undefined) { 4071 obj[name] = value; 4072 } 4073 } 4074 } 4075 } 4076 4077 return obj; 4078 } 4079 4080 /** 4081 * Executed the specified function for each item in a object tree. 4082 * 4083 * @method walk 4084 * @param {Object} o Object tree to walk though. 4085 * @param {function} f Function to call for each item. 4086 * @param {String} n Optional name of collection inside the objects to walk for example childNodes. 4087 * @param {String} s Optional scope to execute the function in. 4088 */ 4089 function walk(o, f, n, s) { 4090 s = s || this; 4091 4092 if (o) { 4093 if (n) { 4094 o = o[n]; 4095 } 4096 4097 each(o, function(o, i) { 4098 if (f.call(s, o, i, n) === false) { 4099 return false;
4100 } 4101 4102 walk(o, f, n, s); 4103 }); 4104 } 4105 } 4106 4107 /** 4108 * Creates a namespace on a specific object. 4109 * 4110 * @method createNS 4111 * @param {String} n Namespace to create for example a.b.c.d. 4112 * @param {Object} o Optional object to add namespace to, defaults to window. 4113 * @return {Object} New namespace object the last item in path. 4114 * @example 4115 * // Create some namespace 4116 * tinymce.createNS('tinymce.somepackage.subpackage'); 4117 * 4118 * // Add a singleton 4119 * var tinymce.somepackage.subpackage.SomeSingleton = { 4120 * method: function() { 4121 * // Some method 4122 * } 4123 * }; 4124 */ 4125 function createNS(n, o) { 4126 var i, v; 4127 4128 o = o || window; 4129 4130 n = n.split('.'); 4131 for (i=0; i<n.length; i++) { 4132 v = n[i]; 4133 4134 if (!o[v]) { 4135 o[v] = {}; 4136 } 4137 4138 o = o[v]; 4139 } 4140 4141 return o; 4142 } 4143 4144 /** 4145 * Resolves a string and returns the object from a specific structure. 4146 * 4147 * @method resolve 4148 * @param {String} n Path to resolve for example a.b.c.d. 4149 * @param {Object} o Optional object to search though, defaults to window. 4150 * @return {Object} Last object in path or null if it couldn't be resolved. 4151 * @example 4152 * // Resolve a path into an object reference 4153 * var obj = tinymce.resolve('a.b.c.d'); 4154 */ 4155 function resolve(n, o) { 4156 var i, l; 4157 4158 o = o || window; 4159 4160 n = n.split('.'); 4161 for (i = 0, l = n.length; i < l; i++) { 4162 o = o[n[i]]; 4163 4164 if (!o) { 4165 break; 4166 } 4167 } 4168 4169 return o; 4170 } 4171 4172 /** 4173 * Splits a string but removes the whitespace before and after each value. 4174 * 4175 * @method explode 4176 * @param {string} s String to split. 4177 * @param {string} d Delimiter to split by. 4178 * @example 4179 * // Split a string into an array with a,b,c 4180 * var arr = tinymce.explode('a, b, c'); 4181 */ 4182 function explode(s, d) { 4183 if (!s || is(s, 'array')) { 4184 return s; 4185 } 4186 4187 return map(s.split(d || ','), trim); 4188 } 4189 4190 return { 4191 trim: trim, 4192 isArray: isArray, 4193 is: is, 4194 toArray: toArray, 4195 makeMap: makeMap, 4196 each: each, 4197 map: map, 4198 grep: grep, 4199 inArray: inArray, 4200 extend: extend, 4201 create: create, 4202 walk: walk, 4203 createNS: createNS, 4204 resolve: resolve, 4205 explode: explode 4206 }; 4207}); 4208 4209// Included from: js/tinymce/classes/dom/Range.js 4210 4211/** 4212 * Range.js 4213 * 4214 * Copyright, Moxiecode Systems AB 4215 * Released under LGPL License. 4216 * 4217 * License: http://www.tinymce.com/license 4218 * Contributing: http://www.tinymce.com/contributing 4219 */ 4220 4221define("tinymce/dom/Range", [ 4222 "tinymce/util/Tools" 4223], function(Tools) { 4224 // Range constructor 4225 function Range(dom) { 4226 var t = this, 4227 doc = dom.doc, 4228 EXTRACT = 0, 4229 CLONE = 1, 4230 DELETE = 2, 4231 TRUE = true, 4232 FALSE = false, 4233 START_OFFSET = 'startOffset', 4234 START_CONTAINER = 'startContainer', 4235 END_CONTAINER = 'endContainer', 4236 END_OFFSET = 'endOffset', 4237 extend = Tools.extend, 4238 nodeIndex = dom.nodeIndex; 4239 4240 function createDocumentFragment() { 4241 return doc.createDocumentFragment(); 4242 } 4243 4244 function setStart(n, o) { 4245 _setEndPoint(TRUE, n, o); 4246 } 4247 4248 function setEnd(n, o) { 4249 _setEndPoint(FALSE, n, o); 4250 } 4251 4252 function setStartBefore(n) { 4253 setStart(n.parentNode, nodeIndex(n)); 4254 } 4255 4256 function setStartAfter(n) { 4257 setStart(n.parentNode, nodeIndex(n) + 1); 4258 } 4259 4260 function setEndBefore(n) { 4261 setEnd(n.parentNode, nodeIndex(n)); 4262 } 4263 4264 function setEndAfter(n) { 4265 setEnd(n.parentNode, nodeIndex(n) + 1); 4266 } 4267 4268 function collapse(ts) { 4269 if (ts) { 4270 t[END_CONTAINER] = t[START_CONTAINER]; 4271 t[END_OFFSET] = t[START_OFFSET]; 4272 } else { 4273 t[START_CONTAINER] = t[END_CONTAINER]; 4274 t[START_OFFSET] = t[END_OFFSET]; 4275 } 4276 4277 t.collapsed = TRUE; 4278 } 4279 4280 function selectNode(n) { 4281 setStartBefore(n); 4282 setEndAfter(n); 4283 } 4284 4285 function selectNodeContents(n) { 4286 setStart(n, 0); 4287 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); 4288 } 4289 4290 function compareBoundaryPoints(h, r) { 4291 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET], 4292 rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset; 4293 4294 // Check START_TO_START 4295 if (h === 0) { 4296 return _compareBoundaryPoints(sc, so, rsc, rso); 4297 } 4298 4299 // Check START_TO_END 4300 if (h === 1) { 4301 return _compareBoundaryPoints(ec, eo, rsc, rso); 4302 } 4303 4304 // Check END_TO_END 4305 if (h === 2) { 4306 return _compareBoundaryPoints(ec, eo, rec, reo); 4307 } 4308 4309 // Check END_TO_START 4310 if (h === 3) { 4311 return _compareBoundaryPoints(sc, so, rec, reo); 4312 } 4313 } 4314 4315 function deleteContents() { 4316 _traverse(DELETE); 4317 } 4318 4319 function extractContents() { 4320 return _traverse(EXTRACT); 4321 } 4322 4323 function cloneContents() { 4324 return _traverse(CLONE); 4325 } 4326
4327 function insertNode(n) { 4328 var startContainer = this[START_CONTAINER], 4329 startOffset = this[START_OFFSET], nn, o; 4330 4331 // Node is TEXT_NODE or CDATA 4332 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) { 4333 if (!startOffset) { 4334 // At the start of text 4335 startContainer.parentNode.insertBefore(n, startContainer); 4336 } else if (startOffset >= startContainer.nodeValue.length) { 4337 // At the end of text 4338 dom.insertAfter(n, startContainer); 4339 } else { 4340 // Middle, need to split 4341 nn = startContainer.splitText(startOffset); 4342 startContainer.parentNode.insertBefore(n, nn); 4343 } 4344 } else { 4345 // Insert element node 4346 if (startContainer.childNodes.length > 0) { 4347 o = startContainer.childNodes[startOffset]; 4348 } 4349 4350 if (o) { 4351 startContainer.insertBefore(n, o); 4352 } else { 4353 if (startContainer.nodeType == 3) { 4354 dom.insertAfter(n, startContainer); 4355 } else { 4356 startContainer.appendChild(n); 4357 } 4358 } 4359 } 4360 } 4361 4362 function surroundContents(n) { 4363 var f = t.extractContents(); 4364 4365 t.insertNode(n); 4366 n.appendChild(f); 4367 t.selectNode(n); 4368 } 4369 4370 function cloneRange() { 4371 return extend(new Range(dom), { 4372 startContainer: t[START_CONTAINER], 4373 startOffset: t[START_OFFSET], 4374 endContainer: t[END_CONTAINER], 4375 endOffset: t[END_OFFSET], 4376 collapsed: t.collapsed, 4377 commonAncestorContainer: t.commonAncestorContainer 4378 }); 4379 } 4380 4381 // Private methods 4382 4383 function _getSelectedNode(container, offset) { 4384 var child; 4385 4386 if (container.nodeType == 3 /* TEXT_NODE */) { 4387 return container; 4388 } 4389 4390 if (offset < 0) { 4391 return container; 4392 } 4393 4394 child = container.firstChild; 4395 while (child && offset > 0) { 4396 --offset; 4397 child = child.nextSibling; 4398 } 4399 4400 if (child) { 4401 return child; 4402 } 4403 4404 return container; 4405 } 4406 4407 function _isCollapsed() { 4408 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]); 4409 } 4410 4411 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
4412 var c, offsetC, n, cmnRoot, childA, childB; 4413 4414 // In the first case the boundary-points have the same container. A is before B 4415 // if its offset is less than the offset of B, A is equal to B if its offset is 4416 // equal to the offset of B, and A is after B if its offset is greater than the 4417 // offset of B. 4418 if (containerA == containerB) { 4419 if (offsetA == offsetB) { 4420 return 0; // equal 4421 } 4422 4423 if (offsetA < offsetB) { 4424 return -1; // before 4425 } 4426 4427 return 1; // after 4428 } 4429 4430 // In the second case a child node C of the container of A is an ancestor 4431 // container of B. In this case, A is before B if the offset of A is less than or 4432 // equal to the index of the child node C and A is after B otherwise. 4433 c = containerB; 4434 while (c && c.parentNode != containerA) { 4435 c = c.parentNode; 4436 } 4437 4438 if (c) { 4439 offsetC = 0; 4440 n = containerA.firstChild; 4441 4442 while (n != c && offsetC < offsetA) { 4443 offsetC++; 4444 n = n.nextSibling; 4445 } 4446 4447 if (offsetA <= offsetC) { 4448 return -1; // before 4449 } 4450 4451 return 1; // after 4452 } 4453 4454 // In the third case a child node C of the container of B is an ancestor container 4455 // of A. In this case, A is before B if the index of the child node C is less than 4456 // the offset of B and A is after B otherwise. 4457 c = containerA; 4458 while (c && c.parentNode != containerB) { 4459 c = c.parentNode; 4460 } 4461 4462 if (c) { 4463 offsetC = 0; 4464 n = containerB.firstChild; 4465 4466 while (n != c && offsetC < offsetB) { 4467 offsetC++; 4468 n = n.nextSibling; 4469 } 4470 4471 if (offsetC < offsetB) { 4472 return -1; // before 4473 } 4474 4475 return 1; // after 4476 } 4477 4478 // In the fourth case, none of three other cases hold: the containers of A and B 4479 // are siblings or descendants of sibling nodes. In this case, A is before B if 4480 // the container of A is before the container of B in a pre-order traversal of the 4481 // Ranges' context tree and A is after B otherwise. 4482 cmnRoot = dom.findCommonAncestor(containerA, containerB); 4483 childA = containerA; 4484 4485 while (childA && childA.parentNode != cmnRoot) { 4486 childA = childA.parentNode; 4487 } 4488 4489 if (!childA) { 4490 childA = cmnRoot; 4491 } 4492 4493 childB = containerB; 4494 while (childB && childB.parentNode != cmnRoot) { 4495 childB = childB.parentNode; 4496 } 4497 4498 if (!childB) { 4499 childB = cmnRoot; 4500 } 4501 4502 if (childA == childB) { 4503 return 0; // equal 4504 } 4505 4506 n = cmnRoot.firstChild; 4507 while (n) { 4508 if (n == childA) { 4509 return -1; // before 4510 } 4511 4512 if (n == childB) { 4513 return 1; // after 4514 } 4515 4516 n = n.nextSibling; 4517 } 4518 } 4519 4520 function _setEndPoint(st, n, o) { 4521 var ec, sc; 4522 4523 if (st) { 4524 t[START_CONTAINER] = n; 4525 t[START_OFFSET] = o; 4526 } else { 4527 t[END_CONTAINER] = n; 4528 t[END_OFFSET] = o; 4529 } 4530 4531 // If one boundary-point of a Range is set to have a root container 4532 // other than the current one for the Range, the Range is collapsed to 4533 // the new position. This enforces the restriction that both boundary- 4534 // points of a Range must have the same root container. 4535 ec = t[END_CONTAINER]; 4536 while (ec.parentNode) { 4537 ec = ec.parentNode; 4538 } 4539 4540 sc = t[START_CONTAINER]; 4541 while (sc.parentNode) { 4542 sc = sc.parentNode; 4543 } 4544 4545 if (sc == ec) { 4546 // The start position of a Range is guaranteed to never be after the 4547 // end position. To enforce this restriction, if the start is set to 4548 // be at a position after the end, the Range is collapsed to that 4549 // position. 4550 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0) { 4551 t.collapse(st); 4552 } 4553 } else { 4554 t.collapse(st); 4555 } 4556 4557 t.collapsed = _isCollapsed(); 4558 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]); 4559 } 4560 4561 function _traverse(how) { 4562 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; 4563 4564 if (t[START_CONTAINER] == t[END_CONTAINER]) { 4565 return _traverseSameContainer(how); 4566 } 4567 4568 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { 4569 if (p == t[START_CONTAINER]) { 4570 return _traverseCommonStartContainer(c, how); 4571 } 4572 4573 ++endContainerDepth; 4574 } 4575 4576 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { 4577 if (p == t[END_CONTAINER]) { 4578 return _traverseCommonEndContainer(c, how); 4579 } 4580 4581 ++startContainerDepth; 4582 } 4583 4584 depthDiff = startContainerDepth - endContainerDepth; 4585 4586 startNode = t[START_CONTAINER]; 4587 while (depthDiff > 0) { 4588 startNode = startNode.parentNode; 4589 depthDiff--; 4590 } 4591 4592 endNode = t[END_CONTAINER]; 4593 while (depthDiff < 0) { 4594 endNode = endNode.parentNode; 4595 depthDiff++; 4596 } 4597 4598 // ascend the ancestor hierarchy until we have a common parent. 4599 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) { 4600 startNode = sp; 4601 endNode = ep; 4602 } 4603 4604 return _traverseCommonAncestors(startNode, endNode, how); 4605 } 4606 4607 function _traverseSameContainer(how) { 4608 var frag, s, sub, n, cnt, sibling, xferNode, start, len; 4609 4610 if (how != DELETE) { 4611 frag = createDocumentFragment(); 4612 } 4613 4614 // If selection is empty, just return the fragment 4615 if (t[START_OFFSET] == t[END_OFFSET]) { 4616 return frag; 4617 } 4618 4619 // Text node needs special case handling 4620 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) { 4621 // get the substring 4622 s = t[START_CONTAINER].nodeValue; 4623 sub = s.substring(t[START_OFFSET], t[END_OFFSET]); 4624 4625 // set the original text node to its new value 4626 if (how != CLONE) { 4627 n = t[START_CONTAINER]; 4628 start = t[START_OFFSET]; 4629 len = t[END_OFFSET] - t[START_OFFSET]; 4630 4631 if (start === 0 && len >= n.nodeValue.length - 1) { 4632 n.parentNode.removeChild(n); 4633 } else { 4634 n.deleteData(start, len); 4635 } 4636 4637 // Nothing is partially selected, so collapse to start point 4638 t.collapse(TRUE); 4639 } 4640 4641 if (how == DELETE) { 4642 return; 4643 } 4644 4645 if (sub.length > 0) { 4646 frag.appendChild(doc.createTextNode(sub)); 4647 } 4648 4649 return frag; 4650 } 4651 4652 // Copy nodes between the start/end offsets. 4653 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]); 4654 cnt = t[END_OFFSET] - t[START_OFFSET]; 4655 4656 while (n && cnt > 0) { 4657 sibling = n.nextSibling; 4658 xferNode = _traverseFullySelected(n, how); 4659 4660 if (frag) { 4661 frag.appendChild(xferNode); 4662 } 4663 4664 --cnt; 4665 n = sibling; 4666 } 4667 4668 // Nothing is partially selected, so collapse to start point 4669 if (how != CLONE) { 4670 t.collapse(TRUE); 4671 } 4672 4673 return frag; 4674 } 4675 4676 function _traverseCommonStartContainer(endAncestor, how) { 4677 var frag, n, endIdx, cnt, sibling, xferNode; 4678 4679 if (how != DELETE) { 4680 frag = createDocumentFragment(); 4681 } 4682 4683 n = _traverseRightBoundary(endAncestor, how); 4684 4685 if (frag) { 4686 frag.appendChild(n); 4687 } 4688 4689 endIdx = nodeIndex(endAncestor); 4690 cnt = endIdx - t[START_OFFSET]; 4691 4692 if (cnt <= 0) { 4693 // Collapse to just before the endAncestor, which 4694 // is partially selected. 4695 if (how != CLONE) { 4696 t.setEndBefore(endAncestor); 4697 t.collapse(FALSE); 4698 } 4699 4700 return frag; 4701 } 4702 4703 n = endAncestor.previousSibling; 4704 while (cnt > 0) { 4705 sibling = n.previousSibling; 4706 xferNode = _traverseFullySelected(n, how); 4707 4708 if (frag) { 4709 frag.insertBefore(xferNode, frag.firstChild); 4710 } 4711 4712 --cnt; 4713 n = sibling; 4714 } 4715 4716 // Collapse to just before the endAncestor, which 4717 // is partially selected. 4718 if (how != CLONE) { 4719 t.setEndBefore(endAncestor); 4720 t.collapse(FALSE); 4721 } 4722 4723 return frag; 4724 } 4725 4726 function _traverseCommonEndContainer(startAncestor, how) { 4727 var frag, startIdx, n, cnt, sibling, xferNode; 4728 4729 if (how != DELETE) { 4730 frag = createDocumentFragment(); 4731 } 4732 4733 n = _traverseLeftBoundary(startAncestor, how); 4734 if (frag) { 4735 frag.appendChild(n); 4736 } 4737 4738 startIdx = nodeIndex(startAncestor); 4739 ++startIdx; // Because we already traversed it 4740 4741 cnt = t[END_OFFSET] - startIdx; 4742 n = startAncestor.nextSibling; 4743 while (n && cnt > 0) { 4744 sibling = n.nextSibling; 4745 xferNode = _traverseFullySelected(n, how); 4746 4747 if (frag) { 4748 frag.appendChild(xferNode); 4749 } 4750 4751 --cnt; 4752 n = sibling; 4753 } 4754 4755 if (how != CLONE) { 4756 t.setStartAfter(startAncestor); 4757 t.collapse(TRUE); 4758 } 4759 4760 return frag; 4761 } 4762 4763 function _traverseCommonAncestors(startAncestor, endAncestor, how) { 4764 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; 4765 4766 if (how != DELETE) { 4767 frag = createDocumentFragment(); 4768 } 4769 4770 n = _traverseLeftBoundary(startAncestor, how); 4771 if (frag) { 4772 frag.appendChild(n); 4773 } 4774 4775 commonParent = startAncestor.parentNode; 4776 startOffset = nodeIndex(startAncestor); 4777 endOffset = nodeIndex(endAncestor); 4778 ++startOffset; 4779 4780 cnt = endOffset - startOffset; 4781 sibling = startAncestor.nextSibling; 4782 4783 while (cnt > 0) { 4784 nextSibling = sibling.nextSibling; 4785 n = _traverseFullySelected(sibling, how); 4786 4787 if (frag) { 4788 frag.appendChild(n); 4789 } 4790 4791 sibling = nextSibling; 4792 --cnt; 4793 } 4794 4795 n = _traverseRightBoundary(endAncestor, how); 4796 4797 if (frag) { 4798 frag.appendChild(n); 4799 } 4800 4801 if (how != CLONE) { 4802 t.setStartAfter(startAncestor); 4803 t.collapse(TRUE); 4804 } 4805 4806 return frag; 4807 } 4808 4809 function _traverseRightBoundary(root, how) {
4810 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent; 4811 var prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER]; 4812 4813 if (next == root) { 4814 return _traverseNode(next, isFullySelected, FALSE, how); 4815 } 4816 4817 parent = next.parentNode; 4818 clonedParent = _traverseNode(parent, FALSE, FALSE, how); 4819 4820 while (parent) { 4821 while (next) { 4822 prevSibling = next.previousSibling; 4823 clonedChild = _traverseNode(next, isFullySelected, FALSE, how); 4824 4825 if (how != DELETE) { 4826 clonedParent.insertBefore(clonedChild, clonedParent.firstChild); 4827 } 4828 4829 isFullySelected = TRUE; 4830 next = prevSibling; 4831 } 4832 4833 if (parent == root) { 4834 return clonedParent; 4835 } 4836 4837 next = parent.previousSibling; 4838 parent = parent.parentNode; 4839 4840 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how); 4841 4842 if (how != DELETE) { 4843 clonedGrandParent.appendChild(clonedParent); 4844 } 4845 4846 clonedParent = clonedGrandParent; 4847 } 4848 } 4849 4850 function _traverseLeftBoundary(root, how) { 4851 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER]; 4852 var parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; 4853 4854 if (next == root) { 4855 return _traverseNode(next, isFullySelected, TRUE, how); 4856 } 4857 4858 parent = next.parentNode; 4859 clonedParent = _traverseNode(parent, FALSE, TRUE, how); 4860 4861 while (parent) { 4862 while (next) { 4863 nextSibling = next.nextSibling; 4864 clonedChild = _traverseNode(next, isFullySelected, TRUE, how); 4865 4866 if (how != DELETE) { 4867 clonedParent.appendChild(clonedChild); 4868 } 4869 4870 isFullySelected = TRUE; 4871 next = nextSibling; 4872 } 4873 4874 if (parent == root) { 4875 return clonedParent; 4876 } 4877 4878 next = parent.nextSibling; 4879 parent = parent.parentNode; 4880 4881 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how); 4882 4883 if (how != DELETE) { 4884 clonedGrandParent.appendChild(clonedParent); 4885 } 4886 4887 clonedParent = clonedGrandParent; 4888 } 4889 } 4890 4891 function _traverseNode(n, isFullySelected, isLeft, how) { 4892 var txtValue, newNodeValue, oldNodeValue, offset, newNode; 4893 4894 if (isFullySelected) { 4895 return _traverseFullySelected(n, how); 4896 } 4897 4898 if (n.nodeType == 3 /* TEXT_NODE */) { 4899 txtValue = n.nodeValue; 4900 4901 if (isLeft) { 4902 offset = t[START_OFFSET]; 4903 newNodeValue = txtValue.substring(offset); 4904 oldNodeValue = txtValue.substring(0, offset); 4905 } else { 4906 offset = t[END_OFFSET]; 4907 newNodeValue = txtValue.substring(0, offset); 4908 oldNodeValue = txtValue.substring(offset); 4909 } 4910 4911 if (how != CLONE) { 4912 n.nodeValue = oldNodeValue; 4913 } 4914 4915 if (how == DELETE) { 4916 return; 4917 } 4918 4919 newNode = dom.clone(n, FALSE); 4920 newNode.nodeValue = newNodeValue; 4921 4922 return newNode; 4923 } 4924 4925 if (how == DELETE) { 4926 return; 4927 } 4928 4929 return dom.clone(n, FALSE); 4930 } 4931 4932 function _traverseFullySelected(n, how) { 4933 if (how != DELETE) { 4934 return how == CLONE ? dom.clone(n, TRUE) : n; 4935 } 4936 4937 n.parentNode.removeChild(n); 4938 } 4939 4940 function toStringIE() { 4941 return dom.create('body', null, cloneContents()).outerText; 4942 } 4943 4944 extend(t, { 4945 // Inital states 4946 startContainer: doc, 4947 startOffset: 0, 4948 endContainer: doc, 4949 endOffset: 0, 4950 collapsed: TRUE, 4951 commonAncestorContainer: doc, 4952 4953 // Range constants 4954 START_TO_START: 0, 4955 START_TO_END: 1, 4956 END_TO_END: 2, 4957 END_TO_START: 3, 4958 4959 // Public methods 4960 setStart: setStart, 4961 setEnd: setEnd, 4962 setStartBefore: setStartBefore, 4963 setStartAfter: setStartAfter, 4964 setEndBefore: setEndBefore, 4965 setEndAfter: setEndAfter, 4966 collapse: collapse, 4967 selectNode: selectNode, 4968 selectNodeContents: selectNodeContents, 4969 compareBoundaryPoints: compareBoundaryPoints, 4970 deleteContents: deleteContents, 4971 extractContents: extractContents, 4972 cloneContents: cloneContents, 4973 insertNode: insertNode, 4974 surroundContents: surroundContents, 4975 cloneRange: cloneRange, 4976 toStringIE: toStringIE 4977 }); 4978 4979 return t; 4980 } 4981 4982 // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype 4983 Range.prototype.toString = function() { 4984 return this.toStringIE(); 4985 }; 4986 4987 return Range; 4988}); 4989 4990// Included from: js/tinymce/classes/html/Entities.js 4991 4992/** 4993 * Entities.js 4994 * 4995 * Copyright, Moxiecode Systems AB 4996 * Released under LGPL License. 4997 * 4998 * License: http://www.tinymce.com/license 4999 * Contributing: http://www.tinymce.com/contributing 5000 */ 5001 5002/*jshint bitwise:false */ 5003 5004/** 5005 * Entity encoder class. 5006 * 5007 * @class tinymce.html.Entities 5008 * @static 5009 * @version 3.4 5010 */ 5011define("tinymce/html/Entities", [ 5012 "tinymce/util/Tools" 5013], function(Tools) { 5014 var makeMap = Tools.makeMap; 5015 5016 var namedEntities, baseEntities, reverseEntities, 5017 attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 5018 textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 5019 rawCharsRegExp = /[<>&\"\']/g, 5020 entityRegExp = /&(#x|#)?([\w]+);/g, 5021 asciiMap = { 5022 128: "\u20AC", 130: "\u201A", 131: "\u0192", 132: "\u201E", 133: "\u2026", 134: "\u2020", 5023 135: "\u2021", 136: "\u02C6", 137: "\u2030", 138: "\u0160", 139: "\u2039", 140: "\u0152", 5024 142: "\u017D", 145: "\u2018", 146: "\u2019", 147: "\u201C", 148: "\u201D", 149: "\u2022", 5025 150: "\u2013", 151: "\u2014", 152: "\u02DC", 153: "\u2122", 154: "\u0161", 155: "\u203A", 5026 156: "\u0153", 158: "\u017E", 159: "\u0178" 5027 }; 5028 5029 // Raw entities 5030 baseEntities = { 5031 '\"': '"
5031', // Needs to be escaped since the YUI compressor would otherwise break the code 5032 "'": ''', 5033 '<': '<', 5034 '>': '>', 5035 '&': '&' 5036 }; 5037 5038 // Reverse lookup table for raw entities 5039 reverseEntities = { 5040 '<': '<', 5041 '>': '>', 5042 '&': '&', 5043 '"': '"', 5044 ''': "'" 5045 }; 5046 5047 // Decodes text by using the browser 5048 function nativeDecode(text) { 5049 var elm; 5050 5051 elm = document.createElement("div"); 5052 elm.innerHTML = text; 5053 5054 return elm.textContent || elm.innerText || text; 5055 } 5056 5057 // Build a two way lookup table for the entities 5058 function buildEntitiesLookup(items, radix) { 5059 var i, chr, entity, lookup = {}; 5060 5061 if (items) { 5062 items = items.split(','); 5063 radix = radix || 10; 5064 5065 // Build entities lookup table 5066 for (i = 0; i < items.length; i += 2) { 5067 chr = String.fromCharCode(parseInt(items[i], radix)); 5068 5069 // Only add non base entities 5070 if (!baseEntities[chr]) { 5071 entity = '&' + items[i + 1] + ';'; 5072 lookup[chr] = entity; 5073 lookup[entity] = chr; 5074 } 5075 } 5076 5077 return lookup; 5078 } 5079 } 5080 5081 // Unpack entities lookup where the numbers are in radix 32 to reduce the size 5082 namedEntities = buildEntitiesLookup( 5083 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' + 5084 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' + 5085 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' + 5086 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' + 5087 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' + 5088 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' + 5089 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' + 5090 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' + 5091 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' + 5092 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' + 5093 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' + 5094 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' + 5095 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' + 5096 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' + 5097 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' + 5098 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' + 5099 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' + 5100 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' + 5101 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' + 5102 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' + 5103 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' + 5104 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' + 5105 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' + 5106 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' + 5107 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32); 5108 5109 var Entities = { 5110 /** 5111 * Encodes the specified string using raw entities. This means only the required XML base entities will be endoded. 5112 * 5113 * @method encodeRaw 5114 * @param {String} text Text to encode. 5115 * @param {Boolean} attr Optional flag to specify if the text is attribute contents. 5116 * @return {String} Entity encoded text. 5117 */ 5118 encodeRaw: function(text, attr) { 5119 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 5120 return baseEntities[chr] || chr; 5121 }); 5122 }, 5123 5124 /** 5125 * Encoded the specified text with both the attributes and text entities. This function will produce larger text contents 5126 * since it doesn't know if the context is within a attribute or text node. This was added for c
5126ompatibility 5127 * and is exposed as the DOMUtils.encode function. 5128 * 5129 * @method encodeAllRaw 5130 * @param {String} text Text to encode. 5131 * @return {String} Entity encoded text. 5132 */ 5133 encodeAllRaw: function(text) { 5134 return ('' + text).replace(rawCharsRegExp, function(chr) { 5135 return baseEntities[chr] || chr; 5136 }); 5137 }, 5138 5139 /** 5140 * Encodes the specified string using numeric entities. The core entities will be 5141 * encoded as named ones but all non lower ascii characters will be encoded into numeric entities. 5142 * 5143 * @method encodeNumeric 5144 * @param {String} text Text to encode. 5145 * @param {Boolean} attr Optional flag to specify if the text is attribute contents. 5146 * @return {String} Entity encoded text. 5147 */ 5148 encodeNumeric: function(text, attr) { 5149 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 5150 // Multi byte sequence convert it to a single entity 5151 if (chr.length > 1) { 5152 return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';'; 5153 } 5154 5155 return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';'; 5156 }); 5157 }, 5158 5159 /** 5160 * Encodes the specified string using named entities. The core entities will be encoded 5161 * as named ones but all non lower ascii characters will be encoded into named entities. 5162 * 5163 * @method encodeNamed 5164 * @param {String} text Text to encode. 5165 * @param {Boolean} attr Optional flag to specify if the text is attribute contents. 5166 * @param {Object} entities Optional parameter with entities to use. 5167 * @return {String} Entity encoded text. 5168 */ 5169 encodeNamed: function(text, attr, entities) { 5170 entities = entities || namedEntities; 5171 5172 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 5173 return baseEntities[chr] || entities[chr] || chr; 5174 }); 5175 }, 5176 5177 /** 5178 * Returns an encode function based on the name(s) and it's optional entities. 5179 * 5180 * @method getEncodeFunc 5181 * @param {String} name Comma separated list of encoders for example named,numeric. 5182 * @param {String} entities Optional parameter with entities to use instead of the built in set. 5183 * @return {function} Encode function to be used. 5184 */ 5185 getEncodeFunc: function(name, entities) { 5186 entities = buildEntitiesLookup(entities) || namedEntities; 5187 5188 function encodeNamedAndNumeric(text, attr) { 5189 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 5190 return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr; 5191 }); 5192 } 5193 5194 function encodeCustomNamed(text, attr) { 5195 return Entities.encodeNamed(text, attr, entities); 5196 } 5197 5198 // Replace + with , to be compatible with previous TinyMCE versions 5199 name = makeMap(name.replace(/\+/g, ',')); 5200 5201 // Named and numeric encoder 5202 if (name.named && name.numeric) { 5203 return encodeNamedAndNumeric; 5204 } 5205 5206 // Named encoder 5207 if (name.named) { 5208 // Custom names 5209 if (entities) { 5210 return encodeCustomNamed; 5211 } 5212 5213 return Entities.encodeNamed; 5214 } 5215 5216 // Numeric 5217 if (name.numeric) { 5218 return Entities.encodeNumeric; 5219 } 5220 5221 // Raw encoder 5222 return Entities.encodeRaw; 5223 }, 5224 5225 /** 5226 * Decodes the specified string, this will replace entities with raw UTF characters. 5227 * 5228 * @method decode 5229 * @param {String} text Text to entity decode. 5230 * @return {String} Entity decoded string. 5231 */ 5232 decode: function(text) { 5233 return text.replace(entityRegExp, function(all, numeric, value) { 5234 if (numeric) { 5235 value = parseInt(value, numeric.length === 2 ? 16 : 10); 5236 5237 // Support upper UTF 5238 if (value > 0xFFFF) { 5239 value -= 0x10000; 5240 5241 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF)); 5242 } else { 5243 return asciiMap[value] || String.fromCharCode(value); 5244 } 5245 } 5246 5247 return reverseEntities[all] || namedEntities[all] || nativeDecode(all); 5248 }); 5249 } 5250 }; 5251 5252 return Entities; 5253}); 5254 5255// Included from: js/tinymce/classes/Env.js 5256 5257/** 5258 * Env.js 5259 * 5260 * Copyright, Moxiecode Systems AB 5261 * Released under LGPL License. 5262 * 5263 * License: http://www.tinymce.com/license 5264 * Contributing: http://www.tinymce.com/contributing 5265 */ 5266 5267/**
5268 * This class contains various environment constants like browser versions etc. 5269 * Normally you don't want to sniff specific browser versions but sometimes you have 5270 * to when it's impossible to feature detect. So use this with care. 5271 * 5272 * @class tinymce.Env 5273 * @static 5274 */ 5275define("tinymce/Env", [], function() { 5276 var nav = navigator, userAgent = nav.userAgent; 5277 var opera, webkit, ie, ie11, gecko, mac, iDevice; 5278 5279 opera = window.opera && window.opera.buildNumber; 5280 webkit = /WebKit/.test(userAgent); 5281 ie = !webkit && !opera && (/MSIE/gi).test(userAgent) && (/Explorer/gi).test(nav.appName); 5282 ie = ie && /MSIE (\w+)\./.exec(userAgent)[1]; 5283 ie11 = userAgent.indexOf('Trident/') != -1 && (userAgent.indexOf('rv:') != -1 || nav.appName.indexOf('Netscape') != -1) ? 11 : false; 5284 ie = ie || ie11; 5285 gecko = !webkit && !ie11 && /Gecko/.test(userAgent); 5286 mac = userAgent.indexOf('Mac') != -1; 5287 iDevice = /(iPad|iPhone)/.test(userAgent); 5288 5289 // Is a iPad/iPhone and not on iOS5 sniff the WebKit version since older iOS WebKit versions 5290 // says it has contentEditable support but there is no visible caret. 5291 var contentEditable = !iDevice || userAgent.match(/AppleWebKit\/(\d*)/)[1] >= 534; 5292 5293 return { 5294 /** 5295 * Constant that is true if the browser is Opera. 5296 * 5297 * @property opera 5298 * @type Boolean 5299 * @final 5300 */ 5301 opera: opera, 5302 5303 /** 5304 * Constant that is true if the browser is WebKit (Safari/Chrome). 5305 * 5306 * @property webKit 5307 * @type Boolean 5308 * @final 5309 */ 5310 webkit: webkit, 5311 5312 /** 5313 * Constant that is more than zero if the browser is IE. 5314 * 5315 * @property ie 5316 * @type Boolean 5317 * @final 5318 */ 5319 ie: ie, 5320 5321 /** 5322 * Constant that is true if the browser is Gecko. 5323 * 5324 * @property gecko 5325 * @type Boolean 5326 * @final 5327 */ 5328 gecko: gecko, 5329 5330 /** 5331 * Constant that is true if the os is Mac OS. 5332 * 5333 * @property mac 5334 * @type Boolean 5335 * @final 5336 */ 5337 mac: mac, 5338 5339 /** 5340 * Constant that is true if the os is iOS. 5341 * 5342 * @property iOS 5343 * @type Boolean 5344 * @final 5345 */ 5346 iOS: iDevice, 5347 5348 /** 5349 * Constant that is true if the browser supports editing. 5350 * 5351 * @property contentEditable 5352 * @type Boolean 5353 * @final 5354 */ 5355 contentEditable: contentEditable, 5356 5357 /** 5358 * Transparent image data url. 5359 * 5360 * @property transparentSrc 5361 * @type Boolean 5362 * @final 5363 */ 5364 transparentSrc: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", 5365 5366 /** 5367 * Returns true/false if the browser can or can't place the caret after a inline block like an image. 5368 * 5369 * @property noCaretAfter 5370 * @type Boolean 5371 * @final 5372 */ 5373 caretAfter: ie != 8, 5374 5375 /** 5376 * Constant that is true if the browser supports native DOM Ranges. IE 9+. 5377 * 5378 * @property range 5379 * @type Boolean 5380 */ 5381 range: window.getSelection && "Range" in window, 5382 5383 /** 5384 * Returns the IE document mode for non IE browsers this will fake IE 10. 5385 * 5386 * @property documentMode 5387 * @type Number 5388 */ 5389 documentMode: ie ? (document.documentMode || 7) : 10 5390 }; 5391}); 5392 5393// Included from: js/tinymce/classes/dom/DOMUtils.js 5394 5395/** 5396 * DOMUtils.js 5397 * 5398 * Copyright, Moxiecode Systems AB 5399 * Released under LGPL License. 5400 * 5401 * License: http://www.tinymce.com/license 5402 * Contributing: http://www.tinymce.com/contributing 5403 */ 5404 5405/** 5406 * Utility class for various DOM manipulation and retrieval functions. 5407 * 5408 * @class tinymce.dom.DOMUtils 5409 * @example 5410 * // Add a class to an element by id in the page 5411 * tinymce.DOM.addClass('someid', 'someclass'); 5412 * 5413 * // Add a class to an element by id inside the editor 5414 * tinymce.activeEditor.dom.addClass('someid', 'someclass'); 5415 */ 5416define("tinymce/dom/DOMUtils", [ 5417 "tinymce/dom/Sizzle", 5418 "tinymce/html/Styles", 5419 "tinymce/dom/EventUtils", 5420 "tinymce/dom/TreeWalker", 5421 "tinymce/dom/Range", 5422 "tinymce/html/Entities", 5423 "tinymce/Env", 5424 "tinymce/util/Tools" 5425], function(Sizzle, Styles, EventUtils, TreeWalker, Range, Entities, Env, Tools) { 5426 // Shorten names 5427 var each = Tools.each, is = Tools.is, grep = Tools.grep, trim = Tools.trim, extend = Tools.extend; 5428 var isWebKit = Env.webkit, isIE = Env.ie; 5429 var simpleSelectorRe = /^([a-z0-9],?)+$/i; 5430 var whiteSpaceRegExp = /^[ \t\r\n]*$/; 5431 var numericCssMap = Tools.makeMap('fillOpacity fontWeight lineHeight opacity orphans widows zIndex zoom', ' '); 5432 5433 /** 5434 * Constructs a new DOMUtils instance. Consult the Wiki for more details on settings etc for this class. 5435 * 5436 * @constructor 5437 * @method DOMUtils 5438 * @param {Document} d Document reference to bind the utility class to. 5439 * @param {settings} s Optional settings collection. 5440 */ 5441 function DOMUtils(doc, settings) { 5442 var self = this, blockElementsMap; 5443 5444 self.doc = doc; 5445 self.win = window; 5446 self.files = {}; 5447 self.counter = 0; 5448 self.stdMode = !isIE || doc.documentMode >= 8; 5449 self.boxModel = !isIE || doc.compatMode == "CSS1Compat" || self.stdMode;
5450 self.hasOuterHTML = "outerHTML" in doc.createElement("a"); 5451 this.boundEvents = []; 5452 5453 self.settings = settings = extend({ 5454 keep_values: false, 5455 hex_colors: 1 5456 }, settings); 5457 5458 self.schema = settings.schema; 5459 self.styles = new Styles({ 5460 url_converter: settings.url_converter, 5461 url_converter_scope: settings.url_converter_scope 5462 }, settings.schema); 5463 5464 self.fixDoc(doc); 5465 self.events = settings.ownEvents ? new EventUtils(settings.proxy) : EventUtils.Event; 5466 blockElementsMap = settings.schema ? settings.schema.getBlockElements() : {}; 5467 5468 /** 5469 * Returns true/false if the specified element is a block element or not. 5470 * 5471 * @method isBlock 5472 * @param {Node/String} node Element/Node to check. 5473 * @return {Boolean} True/False state if the node is a block element or not. 5474 */ 5475 self.isBlock = function(node) { 5476 // Fix for #5446 5477 if (!node) { 5478 return false; 5479 } 5480 5481 // This function is called in module pattern style since it might be executed with the wrong this scope 5482 var type = node.nodeType; 5483 5484 // If it's a node then check the type and use the nodeName 5485 if (type) { 5486 return !!(type === 1 && blockElementsMap[node.nodeName]); 5487 } 5488 5489 return !!blockElementsMap[node]; 5490 }; 5491 } 5492 5493 DOMUtils.prototype = { 5494 root: null, 5495 props: { 5496 "for": "htmlFor", 5497 "class": "className", 5498 className: "className", 5499 checked: "checked", 5500 disabled: "disabled", 5501 maxlength: "maxLength", 5502 readonly: "readOnly", 5503 selected: "selected", 5504 value: "value", 5505 id: "id", 5506 name: "name", 5507 type: "type" 5508 }, 5509 5510 fixDoc: function(doc) { 5511 var settings = this.settings, name; 5512 5513 if (isIE && settings.schema) { 5514 // Add missing HTML 4/5 elements to IE 5515 ('abbr article aside audio canvas ' + 5516 'details figcaption figure footer ' + 5517 'header hgroup mark menu meter nav ' + 5518 'output progress section summary ' + 5519 'time video').replace(/\w+/g, function(name) { 5520 doc.createElement(name); 5521 }); 5522 5523 // Create all custom elements 5524 for (name in settings.schema.getCustomElements()) { 5525 doc.createElement(name); 5526 } 5527 } 5528 }, 5529 5530 clone: function(node, deep) { 5531 var self = this, clone, doc; 5532 5533 // TODO: Add feature detection here in the future 5534 if (!isIE || node.nodeType !== 1 || deep) { 5535 return node.cloneNode(deep); 5536 } 5537 5538 doc = self.doc; 5539 5540 // Make a HTML5 safe shallow copy 5541 if (!deep) { 5542 clone = doc.createElement(node.nodeName); 5543 5544 // Copy attribs 5545 each(self.getAttribs(node), function(attr) { 5546 self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName)); 5547 }); 5548 5549 return clone; 5550 } 5551/* 5552 // Setup HTML5 patched document fragment 5553 if (!self.frag) { 5554 self.frag = doc.createDocumentFragment(); 5555 self.fixDoc(self.frag); 5556 } 5557 5558 // Make a deep copy by adding it to the document fragment then removing it this removed the :section 5559 clone = doc.createElement('div'); 5560 self.frag.appendChild(clone); 5561 clone.innerHTML = node.outerHTML; 5562 self.frag.removeChild(clone); 5563*/ 5564 return clone.firstChild; 5565 }, 5566 5567 /** 5568 * Returns the root node of the document. This is normally the body but might be a DIV. Parents like getParent will not 5569 * go above the point of this root node. 5570 * 5571 * @method getRoot 5572 * @return {Element} Root element for the utility class. 5573 */ 5574 getRoot: function() { 5575 var self = this; 5576 5577 return self.get(self.settings.root_element) || self.doc.body; 5578 }, 5579 5580 /** 5581 * Returns the viewport of the window. 5582 * 5583 * @method getViewPort 5584 * @param {Window} win Optional window to get viewport of. 5585 * @return {Object} Viewport object with fields x, y, w and h. 5586 */ 5587 getViewPort: function(win) { 5588 var doc, rootElm; 5589 5590 win = !win ? this.win : win; 5591 doc = win.document; 5592 rootElm = this.boxModel ? doc.documentElement : doc.body; 5593 5594 // Returns viewport size excluding scrollbars 5595 return { 5596 x: win.pageXOffset || rootElm.scrollLeft, 5597 y: win.pageYOffset || rootElm.scrollTop, 5598 w: win.innerWidth || rootElm.clientWidth, 5599 h: win.innerHeight || rootElm.clientHeight 5600 }; 5601 }, 5602 5603 /** 5604 * Returns the rectangle for a specific element. 5605 * 5606 * @method getRect 5607 * @param {Element/String} elm Element object or element ID to get rectangle from. 5608 * @return {object} Rectangle for specified element object with x, y, w, h fields. 5609 */ 5610 getRect: function(elm) { 5611 var self = this, pos, size; 5612 5613 elm = self.get(elm); 5614 pos = self.getPos(elm); 5615 size = self.getSize(elm); 5616 5617 return { 5618 x: pos.x, y: pos.y, 5619 w: size.w, h: size.h 5620 }; 5621 }, 5622 5623 /** 5624 * Returns the size dimensions of the specified element. 5625 * 5626 * @method getSize 5627 * @param {Element/String} elm Element object or element ID to get rectangle from. 5628 * @return {object} Rectangle for specified element object with w, h fields. 5629 */ 5630 getSize: function(elm) { 5631 var self = this, w, h; 5632 5633 elm = self.get(elm); 5634 w = self.getStyle(elm, 'width'); 5635 h = self.getStyle(elm, 'height'); 5636 5637 // Non pixel value, then force offset/clientWidth 5638 if (w.indexOf('px') === -1) { 5639 w = 0; 5640 } 5641 5642 // Non pixel value, then force offset/clientWidth 5643 if (h.indexOf('px') === -1) { 5644 h = 0; 5645 } 5646 5647 return { 5648 w: parseInt(w, 10) || elm.offsetWidth || elm.clientWidth, 5649 h: parseInt(h, 10) || elm.offsetHeight || elm.clientHeight 5650 }; 5651 }, 5652 5653 /** 5654 * Returns a node by the specified selector function. This function will 5655 * loop through all parent nodes and call the specified function for each node. 5656 * If the function then returns true indicating that it has found what it was looking for, the loop execution will then end 5657 * and the node it found will be returned. 5658 * 5659 * @method getParent 5660 * @param {Node/String}
5660 node DOM node to search parents on or ID string. 5661 * @param {function} selector Selection function or CSS selector to execute on each node. 5662 * @param {Node} root Optional root element, never go below this point. 5663 * @return {Node} DOM Node or null if it wasn't found. 5664 */ 5665 getParent: function(node, selector, root) { 5666 return this.getParents(node, selector, root, false); 5667 }, 5668 5669 /** 5670 * Returns a node list of all parents matching the specified selector function or pattern. 5671 * If the function then returns true indicating that it has found what it was looking for and that node will be collected. 5672 * 5673 * @method getParents 5674 * @param {Node/String} node DOM node to search parents on or ID string. 5675 * @param {function} selector Selection function to execute on each node or CSS pattern. 5676 * @param {Node} root Optional root element, never go below this point. 5677 * @return {Array} Array of nodes or null if it wasn't found. 5678 */ 5679 getParents: function(node, selector, root, collect) { 5680 var self = this, selectorVal, result = []; 5681 5682 node = self.get(node); 5683 collect = collect === undefined; 5684 5685 // Default root on inline mode 5686 root = root || (self.getRoot().nodeName != 'BODY' ? self.getRoot().parentNode : null); 5687 5688 // Wrap node name as func 5689 if (is(selector, 'string')) { 5690 selectorVal = selector; 5691 5692 if (selector === '*') { 5693 selector = function(node) {return node.nodeType == 1;}; 5694 } else { 5695 selector = function(node) { 5696 return self.is(node, selectorVal); 5697 }; 5698 } 5699 } 5700 5701 while (node) { 5702 if (node == root || !node.nodeType || node.nodeType === 9) { 5703 break; 5704 } 5705 5706 if (!selector || selector(node)) { 5707 if (collect) { 5708 result.push(node); 5709 } else { 5710 return node; 5711 } 5712 } 5713 5714 node = node.parentNode; 5715 } 5716 5717 return collect ? result : null; 5718 }, 5719 5720 /** 5721 * Returns the specified element by ID or the input element if it isn't a string. 5722 * 5723 * @method get 5724 * @param {String/Element} n Element id to look for or element to just pass though. 5725 * @return {Element} Element matching the specified id or null if it wasn't found. 5726 */ 5727 get: function(elm) { 5728 var name; 5729 5730 if (elm && this.doc && typeof(elm) == 'string') { 5731 name = elm; 5732 elm = this.doc.getElementById(elm); 5733 5734 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick 5735 if (elm && elm.id !== name) { 5736 return this.doc.getElementsByName(name)[1]; 5737 } 5738 } 5739 5740 return elm; 5741 }, 5742 5743 /** 5744 * Returns the next node that matches selector or function 5745 * 5746 * @method getNext 5747 * @param {Node} node Node to find siblings from. 5748 * @param {String/function} selector Selector CSS expression or function. 5749 * @return {Node} Next node item matching the selector or null if it wasn't found. 5750 */ 5751 getNext: function(node, selector) { 5752 return this._findSib(node, selector, 'nextSibling'); 5753 }, 5754 5755 /** 5756 * Returns the previous node that matches selector or function 5757 * 5758 * @method getPrev 5759 * @param {Node} node Node to find siblings from. 5760 * @param {String/function} selector Selector CSS expression or function. 5761 * @return {Node} Previous node item matching the selector or null if it wasn't found. 5762 */ 5763 getPrev: function(node, selector) { 5764 return this._findSib(node, selector, 'previousSibling'); 5765 }, 5766 5767 // #ifndef jquery 5768 5769 /** 5770 * Selects specific elements by a CSS level 3 pattern. For example "div#a1 p.test". 5771 * This function is optimized for the most common patterns needed in TinyMCE but it also performs well enough 5772 * on more complex patterns. 5773 * 5774 * @method select 5775 * @param {String} selector CSS level 3 pattern to select/find elements by. 5776 * @param {Object} scope Optional root element/scope element to search in. 5777 * @return {Array} Array with all matched elements. 5778 * @example 5779 * // Adds a class to all paragraphs in the currently active editor 5780 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass'); 5781 * 5782 * // Adds a class to all spans that have the test class in the currently active editor 5783 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('span.test'), 'someclass') 5784 */ 5785 select: function(selector, scope) { 5786 var self = this; 5787 5788 //Sizzle.selectors.cacheLength = 0; 5789 return Sizzle(selector, self.get(scope) || self.get(self.settings.root_element) || self.doc, []); 5790 }, 5791 5792 /** 5793 * Returns true/false if the specified element matches the specified css pattern. 5794 * 5795 * @method is 5796 * @param {Node/NodeList} elm DOM node to match or an array of nodes to match. 5797 * @param {String} selector CSS pattern to match the element against. 5798 */ 5799 is: function(elm, selector) { 5800 var i; 5801 5802 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance 5803 if (elm.length === undefined) { 5804 // Simple all selector 5805 if (selector === '*') { 5806 return elm.nodeType == 1; 5807 } 5808 5809 // Simple selector just elements 5810 if (simpleSelectorRe.test(selector)) { 5811 selector = selector.toLowerCase().split(/,/); 5812 elm = elm.nodeName.toLowerCase(); 5813 5814 for (i = selector.length - 1; i >= 0; i--) { 5815 if (selector[i] == elm) { 5816 return true; 5817 } 5818 } 5819 5820 return false;
5821 } 5822 } 5823 5824 // Is non element 5825 if (elm.nodeType && elm.nodeType != 1) { 5826 return false; 5827 } 5828 5829 return Sizzle.matches(selector, elm.nodeType ? [elm] : elm).length > 0; 5830 }, 5831 5832 // #endif 5833 5834 /** 5835 * Adds the specified element to another element or elements. 5836 * 5837 * @method add 5838 * @param {String/Element/Array} parentElm Element id string, DOM node element or array of ids or elements to add to. 5839 * @param {String/Element} name Name of new element to add or existing element to add. 5840 * @param {Object} attrs Optional object collection with arguments to add to the new element(s). 5841 * @param {String} html Optional inner HTML contents to add for each element. 5842 * @return {Element/Array} Element that got created, or an array of created elements if multiple input elements 5843 * were passed in. 5844 * @example 5845 * // Adds a new paragraph to the end of the active editor 5846 * tinymce.activeEditor.dom.add(tinymce.activeEditor.getBody(), 'p', {title: 'my title'}, 'Some content'); 5847 */ 5848 add: function(parentElm, name, attrs, html, create) { 5849 var self = this; 5850 5851 return this.run(parentElm, function(parentElm) { 5852 var newElm; 5853 5854 newElm = is(name, 'string') ? self.doc.createElement(name) : name; 5855 self.setAttribs(newElm, attrs); 5856 5857 if (html) { 5858 if (html.nodeType) { 5859 newElm.appendChild(html); 5860 } else { 5861 self.setHTML(newElm, html); 5862 } 5863 } 5864 5865 return !create ? parentElm.appendChild(newElm) : newElm; 5866 }); 5867 }, 5868 5869 /** 5870 * Creates a new element. 5871 * 5872 * @method create 5873 * @param {String} name Name of new element. 5874 * @param {Object} attrs Optional object name/value collection with element attributes. 5875 * @param {String} html Optional HTML string to set as inner HTML of the element. 5876 * @return {Element} HTML DOM node element that got created. 5877 * @example 5878 * // Adds an element where the caret/selection is in the active editor 5879 * var el = tinymce.activeEditor.dom.create('div', {id: 'test', 'class': 'myclass'}, 'some content'); 5880 * tinymce.activeEditor.selection.setNode(el); 5881 */ 5882 create: function(name, attrs, html) { 5883 return this.add(this.doc.createElement(name), name, attrs, html, 1); 5884 }, 5885 5886 /** 5887 * Creates HTML string for element. The element will be closed unless an empty inner HTML string is passed in. 5888 * 5889 * @method createHTML 5890 * @param {String} name Name of new element. 5891 * @param {Object} attrs Optional object name/value collection with element attributes. 5892 * @param {String} html Optional HTML string to set as inner HTML of the element. 5893 * @return {String} String with new HTML element, for example: <a href="#">test</a>. 5894 * @example 5895 * // Creates a html chunk and inserts it at the current selection/caret location 5896 * tinymce.activeEditor.selection.setContent(tinymce.activeEditor.dom.createHTML('a', {href: 'test.html'}, 'some line')); 5897 */ 5898 createHTML: function(name, attrs, html) { 5899 var outHtml = '', key; 5900 5901 outHtml += '<' + name; 5902 5903 for (key in attrs) { 5904 if (attrs.hasOwnProperty(key) && attrs[key] !== null) { 5905 outHtml += ' ' + key + '="' + this.encode(attrs[key]) + '"'; 5906 } 5907 } 5908 5909 // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime 5910 if (typeof(html) != "undefined") { 5911 return outHtml + '>' + html + '</' + name + '>'; 5912 } 5913 5914 return outHtml + ' />'; 5915 }, 5916 5917 /** 5918 * Creates a document fragment out of the specified HTML string. 5919 * 5920 * @method createFragment 5921 * @param {String} html Html string to create fragment from. 5922 * @return {DocumentFragment} Document fragment node. 5923 */ 5924 createFragment: function(html) { 5925 var frag, node, doc = this.doc, container; 5926 5927 container = doc.createElement("div"); 5928 frag = doc.createDocumentFragment(); 5929 5930 if (html) { 5931 container.innerHTML = html; 5932 } 5933 5934 while ((node = container.firstChild)) { 5935 frag.appendChild(node); 5936 } 5937 5938 return frag; 5939 }, 5940 5941 /** 5942 * Removes/deletes the specified element(s) from the DOM. 5943 * 5944 * @method remove 5945 * @param {String/Element/Array} node ID of element or DOM element object or array containing multiple elements/ids. 5946 * @param {Boolean} keep_children Optional state to keep children or not. If set to true all children will be 5947 * placed at the location of the removed element. 5948 * @return {Element/Array} HTML DOM element that got removed, or an array of removed elements if multiple input elements 5949 * were passed in. 5950 * @example 5951 * // Removes all paragraphs in the active editor 5952 * tinymce.activeEditor.dom.remove(tinymce.activeEditor.dom.select('p')); 5953 * 5954 * // Removes an element by id in the document 5955 * tinymce.DOM.remove('mydiv'); 5956 */ 5957 remove: function(node, keep_children) { 5958 return this.run(node, function(node) { 5959 var child, parent = node.parentNode; 5960 5961 if (!parent) { 5962 return null; 5963 } 5964 5965 if (keep_children) { 5966 while ((child = node.firstChild)) { 5967 // IE 8 will crash if you don't remove completely empty text nodes 5968 if (!isIE || child.nodeType !== 3 || child.nodeValue) { 5969 parent.insertBefore(child, node); 5970 } else { 5971 node.removeChild(child); 5972 } 5973 } 5974 } 5975 5976 return parent.removeChild(node); 5977 }); 5978 }, 5979 5980 /** 5981 * Sets the CSS style value on a HTML element. The name can be a camelcase string 5982 * or the CSS style name like background-color. 5983 * 5984 * @method setStyle 5985 * @param {String/Element/Array} n HTML element/Element ID or Array of elements/ids to set CSS style value on. 5986 * @param {String} na Name of the style value to set. 5987 * @param {String} v Value to set on the style. 5988 * @example
5989 * // Sets a style value on all paragraphs in the currently active editor 5990 * tinymce.activeEditor.dom.setStyle(tinymce.activeEditor.dom.select('p'), 'background-color', 'red'); 5991 * 5992 * // Sets a style value to an element by id in the current document 5993 * tinymce.DOM.setStyle('mydiv', 'background-color', 'red'); 5994 */ 5995 setStyle: function(elm, name, value) { 5996 return this.run(elm, function(elm) { 5997 var self = this, style, key; 5998 5999 if (name) { 6000 if (typeof(name) === 'string') { 6001 style = elm.style; 6002 6003 // Camelcase it, if needed 6004 name = name.replace(/-(\D)/g, function(a, b) { 6005 return b.toUpperCase(); 6006 }); 6007 6008 // Default px suffix on these 6009 if (typeof(value) === 'number' && !numericCssMap[name]) { 6010 value += 'px'; 6011 } 6012 6013 // IE specific opacity 6014 if (name === "opacity" && elm.runtimeStyle && typeof(elm.runtimeStyle.opacity) === "undefined") { 6015 style.filter = value === '' ? '' : "alpha(opacity=" + (value * 100) + ")"; 6016 } 6017 6018 if (name == "float") { 6019 // Old IE vs modern browsers 6020 name = "cssFloat" in elm.style ? "cssFloat" : "styleFloat"; 6021 } 6022 6023 try { 6024 style[name] = value; 6025 } catch (ex) { 6026 // Ignore IE errors 6027 } 6028 6029 // Force update of the style data 6030 if (self.settings.update_styles) { 6031 elm.removeAttribute('data-mce-style'); 6032 } 6033 } else { 6034 for (key in name) { 6035 self.setStyle(elm, key, name[key]); 6036 } 6037 } 6038 } 6039 }); 6040 }, 6041 6042 /** 6043 * Returns the current style or runtime/computed value of an element. 6044 * 6045 * @method getStyle 6046 * @param {String/Element} elm HTML element or element id string to get style from. 6047 * @param {String} name Style name to return. 6048 * @param {Boolean} computed Computed style. 6049 * @return {String} Current style or computed style value of an element. 6050 */ 6051 getStyle: function(elm, name, computed) { 6052 elm = this.get(elm); 6053 6054 if (!elm) { 6055 return; 6056 } 6057 6058 // W3C 6059 if (this.doc.defaultView && computed) { 6060 // Remove camelcase 6061 name = name.replace(/[A-Z]/g, function(a){ 6062 return '-' + a; 6063 }); 6064 6065 try { 6066 return this.doc.defaultView.getComputedStyle(elm, null).getPropertyValue(name); 6067 } catch (ex) { 6068 // Old safari might fail 6069 return null; 6070 } 6071 } 6072 6073 // Camelcase it, if needed 6074 name = name.replace(/-(\D)/g, function(a, b) { 6075 return b.toUpperCase(); 6076 }); 6077 6078 if (name == 'float') { 6079 name = isIE ? 'styleFloat' : 'cssFloat'; 6080 } 6081 6082 // IE & Opera 6083 if (elm.currentStyle && computed) { 6084 return elm.currentStyle[name]; 6085 } 6086 6087 return elm.style ? elm.style[name] : undefined; 6088 }, 6089 6090 /** 6091 * Sets multiple styles on the specified element(s). 6092 * 6093 * @method setStyles 6094 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set styles on. 6095 * @param {Object} o Name/Value collection of style items to add to the element(s). 6096 * @example 6097 * // Sets styles on all paragraphs in the currently active editor 6098 * tinymce.activeEditor.dom.setStyles(tinymce.activeEditor.dom.select('p'), {'background-color': 'red', 'color': 'green'}); 6099 *
6100 * // Sets styles to an element by id in the current document 6101 * tinymce.DOM.setStyles('mydiv', {'background-color': 'red', 'color': 'green'}); 6102 */ 6103 setStyles: function(elm, styles) { 6104 this.setStyle(elm, styles); 6105 }, 6106 6107 css: function(elm, name, value) { 6108 this.setStyle(elm, name, value); 6109 }, 6110 6111 /** 6112 * Removes all attributes from an element or elements. 6113 * 6114 * @method removeAllAttribs 6115 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to remove attributes from. 6116 */ 6117 removeAllAttribs: function(e) { 6118 return this.run(e, function(e) { 6119 var i, attrs = e.attributes; 6120 for (i = attrs.length - 1; i >= 0; i--) { 6121 e.removeAttributeNode(attrs.item(i)); 6122 } 6123 }); 6124 }, 6125 6126 /** 6127 * Sets the specified attribute of an element or elements. 6128 * 6129 * @method setAttrib 6130 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set attribute on. 6131 * @param {String} n Name of attribute to set. 6132 * @param {String} v Value to set on the attribute - if this value is falsy like null, 0 or '' it will remove the attribute instead. 6133 * @example 6134 * // Sets class attribute on all paragraphs in the active editor 6135 * tinymce.activeEditor.dom.setAttrib(tinymce.activeEditor.dom.select('p'), 'class', 'myclass'); 6136 *
6137 * // Sets class attribute on a specific element in the current page 6138 * tinymce.dom.setAttrib('mydiv', 'class', 'myclass'); 6139 */ 6140 setAttrib: function(e, n, v) { 6141 var t = this; 6142 6143 // What's the point 6144 if (!e || !n) { 6145 return; 6146 } 6147 6148 return this.run(e, function(e) { 6149 var s = t.settings; 6150 var originalValue = e.getAttribute(n); 6151 if (v !== null) { 6152 switch (n) { 6153 case "style": 6154 if (!is(v, 'string')) { 6155 each(v, function(v, n) { 6156 t.setStyle(e, n, v); 6157 }); 6158 6159 return; 6160 } 6161 6162 // No mce_style for elements with these since they might get resized by the user 6163 if (s.keep_values) { 6164 if (v) { 6165 e.setAttribute('data-mce-style', v, 2); 6166 } else { 6167 e.removeAttribute('data-mce-style', 2); 6168 } 6169 } 6170 6171 e.style.cssText = v; 6172 break; 6173 6174 case "class": 6175 e.className = v || ''; // Fix IE null bug 6176 break; 6177 6178 case "src": 6179 case "href": 6180 if (s.keep_values) { 6181 if (s.url_converter) {
6182 v = s.url_converter.call(s.url_converter_scope || t, v, n, e); 6183 } 6184 6185 t.setAttrib(e, 'data-mce-' + n, v, 2); 6186 } 6187 6188 break; 6189 6190 case "shape": 6191 e.setAttribute('data-mce-style', v); 6192 break; 6193 } 6194 } 6195 if (is(v) && v !== null && v.length !== 0) { 6196 e.setAttribute(n, '' + v, 2); 6197 } else { 6198 e.removeAttribute(n, 2); 6199 } 6200 6201 // fire onChangeAttrib event for attributes that have changed 6202 if (originalValue != v && s.onSetAttrib) { 6203 s.onSetAttrib({attrElm: e, attrName: n, attrValue: v}); 6204 } 6205 }); 6206 }, 6207 6208 /** 6209 * Sets two or more specified attributes of an element or elements. 6210 * 6211 * @method setAttribs 6212 * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set attributes on. 6213 * @param {Object} attrs Name/Value collection of attribute items to add to the element(s). 6214 * @example 6215 * // Sets class and title attributes on all paragraphs in the active editor 6216 * tinymce.activeEditor.dom.setAttribs(tinymce.activeEditor.dom.select('p'), {'class': 'myclass', title: 'some title'}); 6217 *
6218 * // Sets class and title attributes on a specific element in the current page 6219 * tinymce.DOM.setAttribs('mydiv', {'class': 'myclass', title: 'some title'}); 6220 */ 6221 setAttribs: function(elm, attrs) { 6222 var self = this; 6223 6224 return this.run(elm, function(elm) { 6225 each(attrs, function(value, name) { 6226 self.setAttrib(elm, name, value); 6227 }); 6228 }); 6229 }, 6230 6231 /** 6232 * Returns the specified attribute by name. 6233 * 6234 * @method getAttrib 6235 * @param {String/Element} elm Element string id or DOM element to get attribute from. 6236 * @param {String} name Name of attribute to get. 6237 * @param {String} defaultVal Optional default value to return if the attribute didn't exist. 6238 * @return {String} Attribute value string, default value or null if the attribute wasn't found. 6239 */ 6240 getAttrib: function(elm, name, defaultVal) { 6241 var value, self = this, undef; 6242 6243 elm = self.get(elm); 6244 6245 if (!elm || elm.nodeType !== 1) { 6246 return defaultVal === undef ? false : defaultVal; 6247 } 6248 6249 if (!is(defaultVal)) { 6250 defaultVal = ''; 6251 } 6252 6253 // Try the mce variant for these 6254 if (/^(src|href|style|coords|shape)$/.test(name)) { 6255 value = elm.getAttribute("data-mce-" + name); 6256 6257 if (value) { 6258 return value; 6259 } 6260 } 6261 6262 if (isIE && self.props[name]) { 6263 value = elm[self.props[name]]; 6264 value = value && value.nodeValue ? value.nodeValue : value; 6265 } 6266 6267 if (!value) { 6268 value = elm.getAttribute(name, 2); 6269 } 6270 6271 // Check boolean attribs 6272 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(name)) { 6273 if (elm[self.props[name]] === true && value === '') { 6274 return name; 6275 } 6276 6277 return value ? name : ''; 6278 } 6279 6280 // Inner input elements will override attributes on form elements 6281 if (elm.nodeName === "FORM" && elm.getAttributeNode(name)) { 6282 return elm.getAttributeNode(name).nodeValue; 6283 } 6284 6285 if (name === 'style') { 6286 value = value || elm.style.cssText; 6287 6288 if (value) { 6289 value = self.serializeStyle(self.parseStyle(value), elm.nodeName); 6290 6291 if (self.settings.keep_values) { 6292 elm.setAttribute('data-mce-style', value); 6293 } 6294 } 6295 } 6296 6297 // Remove Apple and WebKit stuff 6298 if (isWebKit && name === "class" && value) { 6299 value = value.replace(/(apple|webkit)\-[a-z\-]+/gi, ''); 6300 } 6301 6302 // Handle IE issues 6303 if (isIE) { 6304 switch (name) { 6305 case 'rowspan': 6306 case 'colspan': 6307 // IE returns 1 as default value 6308 if (value === 1) { 6309 value = ''; 6310 } 6311 6312 break; 6313 6314 case 'size': 6315 // IE returns +0 as default value for size 6316 if (value === '+0' || value === 20 || value === 0) { 6317 value = ''; 6318 } 6319 6320 break; 6321 6322 case 'width': 6323 case 'height': 6324 case 'vspace': 6325 case 'checked': 6326 case 'disabled': 6327 case 'readonly': 6328 if (value === 0) { 6329 value = ''; 6330 } 6331 6332 break; 6333 6334 case 'hspace': 6335 // IE returns -1 as default value 6336 if (value === -1) { 6337 value = ''; 6338 } 6339 6340 break; 6341 6342 case 'maxlength': 6343 case 'tabindex': 6344 // IE returns default value 6345 if (value === 32768 || value === 2147483647 || value === '32768') { 6346 value = ''; 6347 } 6348 6349 break; 6350 6351 case 'multiple': 6352 case 'compact': 6353 case 'noshade': 6354 case 'nowrap': 6355 if (value === 65535) { 6356 return name; 6357 } 6358 6359 return defaultVal; 6360 6361 case 'shape': 6362 value = value.toLowerCase(); 6363 break; 6364 6365 default: 6366 // IE has odd anonymous function for event attributes 6367 if (name.indexOf('on') === 0 && value) { 6368 value = ('' + value).replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1'); 6369 } 6370 } 6371 } 6372 6373 return (value !== undef && value !== null && value !== '') ? '' + value : defaultVal; 6374 }, 6375 6376 /** 6377 * Returns the absolute x, y position of a node. The position will be returned in an object with x, y fields. 6378 * 6379 * @method getPos 6380 * @param {Element/String} elm HTML element or element id to get x, y position from. 6381 * @param {Element} rootElm Optional root element to stop calculations at. 6382 * @return {object} Absolute position of the specified element object with x, y fields. 6383 */ 6384 getPos: function(elm, rootElm) { 6385 var self = this, x = 0, y = 0, offsetParent, doc = self.doc, pos; 6386 6387 elm = self.get(elm); 6388 rootElm = rootElm || doc.body; 6389 6390 if (elm) { 6391 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes 6392 if (rootElm === doc.body && elm.getBoundingClientRect) { 6393 pos = elm.getBoundingClientRect(); 6394 rootElm = self.boxModel ? doc.documentElement : doc.body; 6395 6396 // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit 6397 // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position 6398 x = pos.left + (doc.documentElement.scrollLeft || doc.body.scrollLeft) - rootElm.clientTop; 6399 y = pos.top + (doc.documentElement.scrollTop || doc.body.scrollTop) - rootElm.clientLeft; 6400 6401 return {x: x, y: y}; 6402 } 6403 6404 offsetParent = elm; 6405 while (offsetParent && offsetParent != rootElm && offsetParent.
6405nodeType) { 6406 x += offsetParent.offsetLeft || 0; 6407 y += offsetParent.offsetTop || 0; 6408 offsetParent = offsetParent.offsetParent; 6409 } 6410 6411 offsetParent = elm.parentNode; 6412 while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) { 6413 x -= offsetParent.scrollLeft || 0; 6414 y -= offsetParent.scrollTop || 0; 6415 offsetParent = offsetParent.parentNode; 6416 } 6417 } 6418 6419 return {x: x, y: y}; 6420 }, 6421 6422 /** 6423 * Parses the specified style value into an object collection. This parser will also 6424 * merge and remove any redundant items that browsers might have added. It will also convert non-hex 6425 * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings. 6426 * 6427 * @method parseStyle 6428 * @param {String} cssText Style value to parse, for example: border:1px solid red;. 6429 * @return {Object} Object representation of that style, for example: {border: '1px solid red'} 6430 */ 6431 parseStyle: function(cssText) { 6432 return this.styles.parse(cssText); 6433 }, 6434 6435 /** 6436 * Serializes the specified style object into a string. 6437 * 6438 * @method serializeStyle 6439 * @param {Object} styles Object to serialize as string, for example: {border: '1px solid red'} 6440 * @param {String} name Optional element name. 6441 * @return {String} String representation of the style object, for example: border: 1px solid red. 6442 */ 6443 serializeStyle: function(styles, name) { 6444 return this.styles.serialize(styles, name); 6445 }, 6446 6447 /** 6448 * Adds a style element at the top of the document with the specified cssText content. 6449 * 6450 * @method addStyle 6451 * @param {String} cssText CSS Text style to add to top of head of document. 6452 */ 6453 addStyle: function(cssText) { 6454 var self = this, doc = self.doc, head, styleElm; 6455 6456 // Prevent inline from loading the same styles twice 6457 if (self !== DOMUtils.DOM && doc === document) { 6458 var addedStyles = DOMUtils.DOM.addedStyles; 6459 6460 addedStyles = addedStyles || []; 6461 if (addedStyles[cssText]) { 6462 return; 6463 } 6464 6465 addedStyles[cssText] = true; 6466 DOMUtils.DOM.addedStyles = addedStyles; 6467 } 6468 6469 // Create style element if needed 6470 styleElm = doc.getElementById('mceDefaultStyles'); 6471 if (!styleElm) { 6472 styleElm = doc.createElement('style'); 6473 styleElm.id = 'mceDefaultStyles'; 6474 styleElm.type = 'text/css'; 6475 6476 head = doc.getElementsByTagName('head')[0]; 6477 if (head.firstChild) { 6478 head.insertBefore(styleElm, head.firstChild); 6479 } else { 6480 head.appendChild(styleElm); 6481 } 6482 } 6483 6484 // Append style data to old or new style element 6485 if (styleElm.styleSheet) { 6486 styleElm.styleSheet.cssText += cssText; 6487 } else { 6488 styleElm.appendChild(doc.createTextNode(cssText)); 6489 } 6490 }, 6491 6492 /** 6493 * Imports/loads the specified CSS file into the document bound to the class. 6494 * 6495 * @method loadCSS 6496 * @param {String} u URL to CSS file to load. 6497 * @example 6498 * // Loads a CSS file dynamically into the current document 6499 * tinymce.DOM.loadCSS('somepath/some.css'); 6500 * 6501 * // Loads a CSS file into the currently active editor instance 6502 * tinymce.activeEditor.dom.loadCSS('somepath/some.css'); 6503 * 6504 * // Loads a CSS file into an editor instance by id 6505 * tinymce.get('someid').dom.loadCSS('somepath/some.css'); 6506 * 6507 * // Loads multiple CSS files into the current document 6508 * tinymce.DOM.loadCSS('somepath/some.css,somepath/someother.css'); 6509 */ 6510 loadCSS: function(url) { 6511 var self = this, doc = self.doc, head; 6512 6513 // Prevent inline from loading the same CSS file twice 6514 if (self !== DOMUtils.DOM && doc === document) { 6515 DOMUtils.DOM.loadCSS(url); 6516 return; 6517 } 6518 6519 if (!url) { 6520 url = ''; 6521 } 6522 6523 head = doc.getElementsByTagName('head')[0]; 6524 6525 each(url.split(','), function(url) { 6526 var link; 6527 6528 if (self.files[url]) { 6529 return; 6530 } 6531 6532 self.files[url] = true; 6533 link = self.create('link', {rel: 'stylesheet', href: url}); 6534 6535 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug 6536 // This fix seems to resolve that issue by recalcing the document once a stylesheet finishes loading 6537 // It's ugly but it seems to work fine. 6538 if (isIE && doc.documentMode && doc.recalc) { 6539 link.onload = function() { 6540 if (doc.recalc) { 6541 doc.recalc(); 6542 } 6543 6544 link.onload = null; 6545 }; 6546 } 6547 6548 head.appendChild(link); 6549 }); 6550 }, 6551 6552 /** 6553 * Adds a class to the specified element or elements. 6554 * 6555 * @method addClass 6556 * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs. 6557 * @param {String} cls Class name to add to each element. 6558 * @return {String/Array}
6558 String with new class value or array with new class values for all elements. 6559 * @example 6560 * // Adds a class to all paragraphs in the active editor 6561 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'myclass'); 6562 * 6563 * // Adds a class to a specific element in the current page 6564 * tinymce.DOM.addClass('mydiv', 'myclass'); 6565 */ 6566 addClass: function(elm, cls) { 6567 return this.run(elm, function(elm) { 6568 var clsVal; 6569 6570 if (!cls) { 6571 return 0; 6572 } 6573 6574 if (this.hasClass(elm, cls)) { 6575 return elm.className; 6576 } 6577 6578 clsVal = this.removeClass(elm, cls); 6579 elm.className = clsVal = (clsVal !== '' ? (clsVal + ' ') : '') + cls; 6580 6581 return clsVal; 6582 }); 6583 }, 6584 6585 /** 6586 * Removes a class from the specified element or elements. 6587 * 6588 * @method removeClass 6589 * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs. 6590 * @param {String} cls Class name to remove from each element. 6591 * @return {String/Array} String of remaining class name(s), or an array of strings if multiple input elements 6592 * were passed in. 6593 * @example 6594 * // Removes a class from all paragraphs in the active editor 6595 * tinymce.activeEditor.dom.removeClass(tinymce.activeEditor.dom.select('p'), 'myclass'); 6596 * 6597 * // Removes a class from a specific element in the current page 6598 * tinymce.DOM.removeClass('mydiv', 'myclass'); 6599 */ 6600 removeClass: function(elm, cls) { 6601 var self = this, re; 6602 6603 return self.run(elm, function(elm) { 6604 var val; 6605 6606 if (self.hasClass(elm, cls)) { 6607 if (!re) { 6608 re = new RegExp("(^|\\s+)" + cls + "(\\s+|$)", "g"); 6609 } 6610 6611 val = elm.className.replace(re, ' '); 6612 val = trim(val != ' ' ? val : ''); 6613 6614 elm.className = val; 6615 6616 // Empty class attr 6617 if (!val) { 6618 elm.removeAttribute('class'); 6619 elm.removeAttribute('className'); 6620 } 6621 6622 return val; 6623 } 6624 6625 return elm.className; 6626 }); 6627 }, 6628 6629 /** 6630 * Returns true if the specified element has the specified class. 6631 * 6632 * @method hasClass 6633 * @param {String/Element} n HTML element or element id string to check CSS class on. 6634 * @param {String} c CSS class to check for. 6635 * @return {Boolean} true/false if the specified element has the specified class. 6636 */ 6637 hasClass: function(elm, cls) { 6638 elm = this.get(elm); 6639 6640 if (!elm || !cls) { 6641 return false; 6642 } 6643 6644 return (' ' + elm.className + ' ').indexOf(' ' + cls + ' ') !== -1; 6645 }, 6646 6647 /** 6648 * Toggles the specified class on/off. 6649 * 6650 * @method toggleClass 6651 * @param {Element} elm Element to toggle class on. 6652 * @param {[type]} cls Class to toggle on/off. 6653 * @param {[type]} state Optional state to set. 6654 */ 6655 toggleClass: function(elm, cls, state) { 6656 state = state === undefined ? !this.hasClass(elm, cls) : state; 6657 6658 if (this.hasClass(elm, cls) !== state) { 6659 if (state) { 6660 this.addClass(elm, cls); 6661 } else { 6662 this.removeClass(elm, cls); 6663 } 6664 } 6665 }, 6666 6667 /** 6668 * Shows the specified element(s) by ID by setting the "display" style. 6669 * 6670 * @method show 6671 * @param {String/Element/Array} elm ID of DOM element or DOM element or array with elements or IDs to show. 6672 */ 6673 show: function(elm) { 6674 return this.setStyle(elm, 'display', 'block'); 6675 }, 6676 6677 /** 6678 * Hides the specified element(s) by ID by setting the "display" style. 6679 * 6680 * @method hide 6681 * @param {String/Element/Array} e ID of DOM element or DOM element or array with elements or IDs to hide. 6682 * @example 6683 * // Hides an element by id in the document 6684 * tinymce.DOM.hide('myid'); 6685 */ 6686 hide: function(elm) { 6687 return this.setStyle(elm, 'display', 'none'); 6688 }, 6689 6690 /** 6691 * Returns true/false if the element is hidden or not by checking the "display" style. 6692 * 6693 * @method isHidden 6694 * @param {String/Element} e Id or element to check display state on. 6695 * @return {Boolean} true/false if the element is hidden or not. 6696 */ 6697 isHidden: function(elm) { 6698 elm = this.get(elm); 6699 6700 return !elm || elm.style.display == 'none' || this.getStyle(elm, 'display') == 'none'; 6701 }, 6702 6703 /** 6704 * Returns a unique id. This can be useful when generating elements on the fly. 6705 * This method will not check if the element already exists. 6706 * 6707 * @method uniqueId 6708 * @param {String} prefix Optional prefix to add in front of all ids - defaults to "mce_". 6709 * @return {String} Unique id. 6710 */ 6711 uniqueId: function(prefix) { 6712 return (!prefix ? 'mce_' : prefix) + (this.counter++); 6713 }, 6714 6715 /** 6716 * Sets the specified HTML content inside the element or elements. The HTML will first be processed. This means 6717 * URLs will get converted, hex color values fixed etc. Check processHTML for details. 6718 * 6719 * @method setHTML 6720 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set HTML inside of. 6721 * @param {String} h HTML content to set as inner HTML of the element. 6722 * @example
6723 * // Sets the inner HTML of all paragraphs in the active editor 6724 * tinymce.activeEditor.dom.setHTML(tinymce.activeEditor.dom.select('p'), 'some inner html'); 6725 * 6726 * // Sets the inner HTML of an element by id in the document 6727 * tinymce.DOM.setHTML('mydiv', 'some inner html'); 6728 */ 6729 setHTML: function(element, html) { 6730 var self = this; 6731 6732 return self.run(element, function(element) { 6733 if (isIE) { 6734 // Remove all child nodes, IE keeps empty text nodes in DOM 6735 while (element.firstChild) { 6736 element.removeChild(element.firstChild); 6737 } 6738 6739 try { 6740 // IE will remove comments from the beginning 6741 // unless you padd the contents with something 6742 element.innerHTML = '<br />' + html; 6743 element.removeChild(element.firstChild); 6744 } catch (ex) { 6745 // IE sometimes produces an unknown runtime error on innerHTML if it's a block element 6746 // within a block element for example a div inside a p 6747 // This seems to fix this problem 6748 6749 // Create new div with HTML contents and a BR in front to keep comments 6750 var newElement = self.create('div'); 6751 newElement.innerHTML = '<br />' + html; 6752 6753 // Add all children from div to target 6754 each (grep(newElement.childNodes), function(node, i) { 6755 // Skip br element 6756 if (i && element.canHaveHTML) { 6757 element.appendChild(node); 6758 } 6759 }); 6760 } 6761 } else { 6762 element.innerHTML = html; 6763 } 6764 6765 return html; 6766 }); 6767 }, 6768 6769 /** 6770 * Returns the outer HTML of an element. 6771 * 6772 * @method getOuterHTML 6773 * @param {String/Element} elm Element ID or element object to get outer HTML from. 6774 * @return {String} Outer HTML string. 6775 * @example 6776 * tinymce.DOM.getOuterHTML(editorElement); 6777 * tinymce.activeEditor.getOuterHTML(tinymce.activeEditor.getBody()); 6778 */ 6779 getOuterHTML: function(elm) { 6780 var doc, self = this; 6781 6782 elm = self.get(elm); 6783 6784 if (!elm) { 6785 return null; 6786 } 6787 6788 if (elm.nodeType === 1 && self.hasOuterHTML) { 6789 return elm.outerHTML; 6790 } 6791 6792 doc = (elm.ownerDocument || self.doc).createElement("body"); 6793 doc.appendChild(elm.cloneNode(true)); 6794 6795 return doc.innerHTML; 6796 }, 6797 6798 /** 6799 * Sets the specified outer HTML on an element or elements. 6800 * 6801 * @method setOuterHTML 6802 * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set outer HTML on. 6803 * @param {Object} html HTML code to set as outer value for the element. 6804 * @param {Document} doc Optional document scope to use in this process - defaults to the document of the DOM class. 6805 * @example 6806 * // Sets the outer HTML of all paragraphs in the active editor 6807 * tinymce.activeEditor.dom.setOuterHTML(tinymce.activeEditor.dom.select('p'), '<div>some html</div>'); 6808 *
6809 * // Sets the outer HTML of an element by id in the document 6810 * tinymce.DOM.setOuterHTML('mydiv', '<div>some html</div>'); 6811 */ 6812 setOuterHTML: function(elm, html, doc) { 6813 var self = this; 6814 6815 return self.run(elm, function(elm) { 6816 function set() { 6817 var node, tempElm; 6818 6819 tempElm = doc.createElement("body"); 6820 tempElm.innerHTML = html; 6821 6822 node = tempElm.lastChild; 6823 while (node) { 6824 self.insertAfter(node.cloneNode(true), elm); 6825 node = node.previousSibling; 6826 } 6827 6828 self.remove(elm); 6829 } 6830 6831 // Only set HTML on elements 6832 if (elm.nodeType == 1) { 6833 doc = doc || elm.ownerDocument || self.doc; 6834 6835 if (isIE) { 6836 try { 6837 // Try outerHTML for IE it sometimes produces an unknown runtime error 6838 if (elm.nodeType == 1 && self.hasOuterHTML) { 6839 elm.outerHTML = html; 6840 } else { 6841 set(); 6842 } 6843 } catch (ex) { 6844 // Fix for unknown runtime error 6845 set(); 6846 } 6847 } else { 6848 set(); 6849 } 6850 } 6851 }); 6852 }, 6853 6854 /** 6855 * Entity decodes a string. This method decodes any HTML entities, such as å. 6856 * 6857 * @method decode 6858 * @param {String} s String to decode entities on. 6859 * @return {String} Entity decoded string. 6860 */ 6861 decode: Entities.decode, 6862 6863 /** 6864 * Entity encodes a string. This method encodes the most common entities, such as <>"&. 6865 * 6866 * @method encode 6867 * @param {String} text String to encode with entities. 6868 * @return {String} Entity encoded string. 6869 */ 6870 encode: Entities.encodeAllRaw, 6871 6872 /** 6873 * Inserts an element after the reference element. 6874 * 6875 * @method insertAfter 6876 * @param {Element} node Element to insert after the reference. 6877 * @param {Element/String/Array} reference_node Reference element, element id or array of elements to insert after. 6878 * @return {Element/Array} Element that got added or an array with elements. 6879 */ 6880 insertAfter: function(node, reference_node) { 6881 reference_node = this.get(reference_node); 6882 6883 return this.run(node, function(node) { 6884 var parent, nextSibling; 6885 6886 parent = reference_node.parentNode; 6887 nextSibling = reference_node.nextSibling; 6888 6889 if (nextSibling) { 6890 parent.insertBefore(node, nextSibling); 6891 } else { 6892 parent.appendChild(node); 6893 } 6894 6895 return node; 6896 }); 6897 }, 6898 6899 /** 6900 * Replaces the specified element or elements with the new element specified. The new element will 6901 * be cloned if multiple input elements are passed in. 6902 * 6903 * @method replace 6904 * @param {Element} newElm New element to replace old ones with. 6905 * @param {Element/String/Array} oldELm Element DOM node, element id or array of elements or ids to replace. 6906 * @param {Boolean} k Optional keep children state, if set to true child nodes from the old object will be added to new ones. 6907 */ 6908 replace: function(newElm, oldElm, keepChildren) { 6909 var self = this; 6910 6911 return self.run(oldElm, function(oldElm) { 6912 if (is(oldElm, 'array')) { 6913 newElm = newElm.cloneNode(true); 6914 } 6915 6916 if (keepChildren) { 6917 each(grep(oldElm.childNodes), function(node) { 6918 newElm.appendChild(node); 6919 }); 6920 } 6921 6922 return oldElm.parentNode.replaceChild(newElm, oldElm); 6923 }); 6924 }, 6925 6926 /** 6927 * Renames the specified element and keeps its attributes and children. 6928 * 6929 * @method rename 6930 * @param {Element} elm Element to rename. 6931 * @param {String} name Name of the new element. 6932 * @return {Element} New element or the old element if it needed renaming. 6933 */ 6934 rename: function(elm, name) { 6935 var self = this, newElm; 6936 6937 if (elm.nodeName != name.toUpperCase()) { 6938 // Rename block element 6939 newElm = self.create(name); 6940 6941 // Copy attribs to new block 6942 each(self.getAttribs(elm), function(attr_node) { 6943 self.setAttrib(newElm, attr_node.nodeName, self.getAttrib(elm, attr_node.nodeName)); 6944 }); 6945 6946 // Replace block 6947 self.replace(newElm, elm, 1); 6948 } 6949 6950 return newElm || elm; 6951 }, 6952 6953 /** 6954 * Find the common ancestor of two elements. This is a shorter method than using the DOM Range logic. 6955 * 6956 * @method findCommonAncestor 6957 * @param {Element} a Element to find common ancestor of. 6958 * @param {Element} b Element to find common ancestor of. 6959 * @return {Element} Common ancestor element of the two input elements. 6960 */ 6961 findCommonAncestor: function(a, b) { 6962 var ps = a, pe; 6963 6964 while (ps) { 6965 pe = b; 6966 6967 while (pe && ps != pe) { 6968 pe = pe.parentNode; 6969 } 6970 6971 if (ps == pe) { 6972 break; 6973 } 6974 6975 ps = ps.parentNode; 6976 } 6977 6978 if (!ps && a.ownerDocument) { 6979 return a.ownerDocument.documentElement; 6980 } 6981 6982 return ps; 6983 }, 6984 6985 /** 6986 * Parses the specified RGB color value and returns a hex version of that color. 6987 * 6988 * @method toHex 6989 * @param {String} rgbVal RGB string value like rgb(1,2,3) 6990 * @return {String} Hex version of that RGB value like #FF00FF. 6991 */ 6992 toHex: function(rgbVal) { 6993 return this.styles.toHex(Tools.trim(rgbVal)); 6994 }, 6995 6996 /** 6997 * Executes the specified function on the element by id or dom element node or array of elements/id. 6998 * 6999 * @method run 7000 * @param {String/Element/Array} Element ID or DOM element object or array with ids or elements. 7001 * @param {function} f Function to execute for each item. 7002 * @param {Object} s Optional scope to execute the function in. 7003 * @return {Object/Array} Single object, or an array of objects if multiple input elements were passed in. 7004 */ 7005 run: function(elm, func, scope) { 7006 var self = this, result; 7007 7008 if (typeof(elm) === 'string') { 7009 elm = self.get(elm); 7010 } 7011 7012 if (!elm) { 7013 return false;
7014 } 7015 7016 scope = scope || this; 7017 if (!elm.nodeType && (elm.length || elm.length === 0)) { 7018 result = []; 7019 7020 each(elm, function(elm, i) { 7021 if (elm) { 7022 if (typeof(elm) == 'string') { 7023 elm = self.get(elm); 7024 } 7025 7026 result.push(func.call(scope, elm, i)); 7027 } 7028 }); 7029 7030 return result; 7031 } 7032 7033 return func.call(scope, elm); 7034 }, 7035 7036 /** 7037 * Returns a NodeList with attributes for the element. 7038 * 7039 * @method getAttribs 7040 * @param {HTMLElement/string} elm Element node or string id to get attributes from. 7041 * @return {NodeList} NodeList with attributes. 7042 */ 7043 getAttribs: function(elm) { 7044 var attrs; 7045 7046 elm = this.get(elm); 7047 7048 if (!elm) { 7049 return []; 7050 } 7051 7052 if (isIE) { 7053 attrs = []; 7054 7055 // Object will throw exception in IE 7056 if (elm.nodeName == 'OBJECT') { 7057 return elm.attributes; 7058 } 7059 7060 // IE doesn't keep the selected attribute if you clone option elements 7061 if (elm.nodeName === 'OPTION' && this.getAttrib(elm, 'selected')) { 7062 attrs.push({specified: 1, nodeName: 'selected'}); 7063 } 7064 7065 // It's crazy that this is faster in IE but it's because it returns all attributes all the time 7066 var attrRegExp = /<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi; 7067 elm.cloneNode(false).outerHTML.replace(attrRegExp, '').replace(/[\w:\-]+/gi, function(a) { 7068 attrs.push({specified: 1, nodeName: a}); 7069 }); 7070 7071 return attrs; 7072 } 7073 7074 return elm.attributes; 7075 }, 7076 7077 /** 7078 * Returns true/false if the specified node is to be considered empty or not. 7079 * 7080 * @example 7081 * tinymce.DOM.isEmpty(node, {img: true}); 7082 * @method isEmpty 7083 * @param {Object} elements Optional name/value object with elements that are automatically treated as non-empty elements. 7084 * @return {Boolean} true/false if the node is empty or not. 7085 */ 7086 isEmpty: function(node, elements) { 7087 var self = this, i, attributes, type, walker, name, brCount = 0; 7088 7089 node = node.firstChild; 7090 if (node) { 7091 walker = new TreeWalker(node, node.parentNode); 7092 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null; 7093 7094 do { 7095 type = node.nodeType; 7096 7097 if (type === 1) { 7098 // Ignore bogus elements 7099 if (node.getAttribute('data-mce-bogus')) { 7100 continue; 7101 } 7102 7103 // Keep empty elements like <img /> 7104 name = node.nodeName.toLowerCase(); 7105 if (elements && elements[name]) { 7106 // Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p> 7107 if (name === 'br') { 7108 brCount++; 7109 continue; 7110 } 7111 7112 return false; 7113 } 7114 7115 // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a> 7116 attributes = self.getAttribs(node); 7117 i = node.attributes.length; 7118 while (i--) { 7119 name = node.attributes[i].nodeName; 7120 if (name === "name" || name === 'data-mce-bookmark') { 7121 return false; 7122 } 7123 } 7124 } 7125 7126 // Keep comment nodes 7127 if (type == 8) { 7128 return false; 7129 } 7130 7131 // Keep non whitespace text nodes 7132 if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) { 7133 return false; 7134 } 7135 } while ((node = walker.next())); 7136 } 7137 7138 return brCount <= 1; 7139 }, 7140 7141 /** 7142 * Creates a new DOM Range object. This will use the native DOM Range API if it's 7143 * available. If it's not, it will fall back to the custom TinyMCE implementation. 7144 * 7145 * @method createRng 7146 * @return {DOMRange} DOM Range object. 7147 * @example 7148 * var rng = tinymce.DOM.createRng(); 7149 * alert(rng.startContainer + "," + rng.startOffset); 7150 */ 7151 createRng: function() { 7152 var doc = this.doc; 7153 7154 return doc.createRange ? doc.createRange() : new Range(this); 7155 }, 7156 7157 /** 7158 * Returns the index of the specified node within its parent. 7159 * 7160 * @method nodeIndex 7161 * @param {Node} node Node to look for. 7162 * @param {boolean} normalized Optional true/false state if the index is what it would be after a normalization. 7163 * @return {Number} Index of the specified node. 7164 */ 7165 nodeIndex: function(node, normalized) { 7166 var idx = 0, lastNodeType, lastNode, nodeType; 7167 7168 if (node) { 7169 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) { 7170 nodeType = node.nodeType; 7171 7172 // Normalize text nodes 7173 if (normalized && nodeType == 3) { 7174 if (nodeType == lastNodeType || !node.nodeValue.length) { 7175 continue; 7176 } 7177 } 7178 idx++; 7179 lastNodeType = nodeType; 7180 } 7181 } 7182 7183 return idx; 7184 }, 7185 7186 /** 7187 * Splits an element into two new elements and places the specified split 7188 * element or elements between the new ones. For example splitting the paragraph at the bold element in 7189 * this example <p>abc<b>abc</b>123</p> would produce <p>abc</p><b>abc</b><p>123</p>. 7190 * 7191 * @method split 7192 * @param {Element} parentElm Parent element to split. 7193 * @param {Element} splitElm Element to split at. 7194 * @param {Element} replacementElm Optional replacement element to replace the split element with. 7195 * @return {Element} Returns the split element or the replacement element if that is specified. 7196 */ 7197 split: function(parentElm, splitElm, replacementElm) { 7198 var self = this, r = self.createRng(), bef, aft, pa; 7199 7200 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents - this makes sense
7201 // but we don't want that in our code since it serves no purpose for the end user 7202 // For example splitting this html at the bold element: 7203 // <p>text 1<span><b>CHOP</b></span>text 2</p> 7204 // would produce: 7205 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p> 7206 // this function will then trim off empty edges and produce: 7207 // <p>text 1</p><b>CHOP</b><p>text 2</p> 7208 function trimNode(node) { 7209 var i, children = node.childNodes, type = node.nodeType; 7210 7211 function surroundedBySpans(node) { 7212 var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN'; 7213 var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN'; 7214 return previousIsSpan && nextIsSpan; 7215 } 7216 7217 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') { 7218 return; 7219 } 7220 7221 for (i = children.length - 1; i >= 0; i--) { 7222 trimNode(children[i]); 7223 } 7224 7225 if (type != 9) { 7226 // Keep non whitespace text nodes 7227 if (type == 3 && node.nodeValue.length > 0) { 7228 // If parent element isn't a block or there isn't any useful contents for example "<p> </p>" 7229 // Also keep text nodes with only spaces if surrounded by spans. 7230 // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b 7231 var trimmedLength = trim(node.nodeValue).length; 7232 if (!self.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) { 7233 return; 7234 } 7235 } else if (type == 1) { 7236 // If the only child is a bookmark then move it up 7237 children = node.childNodes; 7238 7239 // TODO fix this complex if 7240 if (children.length == 1 && children[0] && children[0].nodeType == 1 && 7241 children[0].getAttribute('data-mce-type') == 'bookmark') { 7242 node.parentNode.insertBefore(children[0], node); 7243 } 7244 7245 // Keep non empty elements or img, hr etc 7246 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) { 7247 return; 7248 } 7249 } 7250 7251 self.remove(node); 7252 } 7253 7254 return node; 7255 } 7256 7257 if (parentElm && splitElm) { 7258 // Get before chunk 7259 r.setStart(parentElm.parentNode, self.nodeIndex(parentElm)); 7260 r.setEnd(splitElm.parentNode, self.nodeIndex(splitElm)); 7261 bef = r.extractContents(); 7262 7263 // Get after chunk 7264 r = self.createRng(); 7265 r.setStart(splitElm.parentNode, self.nodeIndex(splitElm) + 1); 7266 r.setEnd(parentElm.parentNode, self.nodeIndex(parentElm) + 1); 7267 aft = r.extractContents(); 7268 7269 // Insert before chunk 7270 pa = parentElm.parentNode; 7271 pa.insertBefore(trimNode(bef), parentElm); 7272 7273 // Insert middle chunk 7274 if (replacementElm) { 7275 pa.replaceChild(replacementElm, splitElm); 7276 } else { 7277 pa.insertBefore(splitElm, parentElm); 7278 } 7279 7280 // Insert after chunk 7281 pa.insertBefore(trimNode(aft), parentElm); 7282 self.remove(parentElm); 7283 7284 return replacementElm || splitElm; 7285 } 7286 }, 7287 7288 /** 7289 * Adds an event handler to the specified object. 7290 * 7291 * @method bind 7292 * @param {Element/Document/Window/Array} target Target element to bind events to. 7293 * handler to or an array of elements/ids/documents. 7294 * @param {String} name Name of event handler to add, for example: click. 7295 * @param {function} func Function to execute when the event occurs. 7296 * @param {Object} scope Optional scope to execute the function in. 7297 * @return {function} Function callback handler the same as the one passed in. 7298 */ 7299 bind: function(target, name, func, scope) { 7300 var self = this; 7301 7302 if (Tools.isArray(target)) { 7303 var i = target.length; 7304 7305 while (i--) { 7306 target[i] = self.bind(target[i], name, func, scope); 7307 } 7308 7309 return target; 7310 } 7311 7312 // Collect all window/document events bound by editor instance 7313 if (self.settings.collect && (target === self.doc || target === self.win)) { 7314 self.boundEvents.push([target, name, func, scope]); 7315 } 7316 7317 return self.events.bind(target, name, func, scope || self); 7318 }, 7319 7320 /** 7321 * Removes the specified event handler by name and function from an element or collection of elements. 7322 * 7323 * @method unbind 7324 * @param {Element/Document/Window/Array} target Target element to unbind events on. 7325 * @param {String} name Event handler name, for example: "click" 7326 * @param {function}
7326 func Function to remove. 7327 * @return {bool/Array} Bool state of true if the handler was removed, or an array of states if multiple input elements 7328 * were passed in. 7329 */ 7330 unbind: function(target, name, func) { 7331 var self = this, i; 7332 7333 if (Tools.isArray(target)) { 7334 i = target.length; 7335 7336 while (i--) { 7337 target[i] = self.unbind(target[i], name, func); 7338 } 7339 7340 return target; 7341 } 7342 7343 // Remove any bound events matching the input 7344 if (self.boundEvents && (target === self.doc || target === self.win)) { 7345 i = self.boundEvents.length; 7346 7347 while (i--) { 7348 var item = self.boundEvents[i]; 7349 7350 if (target == item[0] && (!name || name == item[1]) && (!func || func == item[2])) { 7351 this.events.unbind(item[0], item[1], item[2]); 7352 } 7353 } 7354 } 7355 7356 return this.events.unbind(target, name, func); 7357 }, 7358 7359 /** 7360 * Fires the specified event name with object on target. 7361 * 7362 * @method fire 7363 * @param {Node/Document/Window} target Target element or object to fire event on. 7364 * @param {String} name Name of the event to fire. 7365 * @param {Object} evt Event object to send. 7366 * @return {Event} Event object. 7367 */ 7368 fire: function(target, name, evt) { 7369 return this.events.fire(target, name, evt); 7370 }, 7371 7372 // Returns the content editable state of a node 7373 getContentEditable: function(node) { 7374 var contentEditable; 7375 7376 // Check type 7377 if (node.nodeType != 1) { 7378 return null; 7379 } 7380 7381 // Check for fake content editable 7382 contentEditable = node.getAttribute("data-mce-contenteditable"); 7383 if (contentEditable && contentEditable !== "inherit") { 7384 return contentEditable; 7385 } 7386 7387 // Check for real content editable 7388 return node.contentEditable !== "inherit" ? node.contentEditable : null; 7389 }, 7390 7391 /** 7392 * Destroys all internal references to the DOM to solve IE leak issues. 7393 * 7394 * @method destroy 7395 */ 7396 destroy: function() { 7397 var self = this; 7398 7399 // Unbind all events bound to window/document by editor instance 7400 if (self.boundEvents) { 7401 var i = self.boundEvents.length; 7402 7403 while (i--) { 7404 var item = self.boundEvents[i]; 7405 this.events.unbind(item[0], item[1], item[2]); 7406 } 7407 7408 self.boundEvents = null; 7409 } 7410 7411 // Restore sizzle document to window.document 7412 // Since the current document might be removed producing "Permission denied" on IE see #6325 7413 if (Sizzle.setDocument) { 7414 Sizzle.setDocument(); 7415 } 7416 7417 self.win = self.doc = self.root = self.events = self.frag = null; 7418 }, 7419 7420 // #ifdef debug 7421 7422 dumpRng: function(r) { 7423 return ( 7424 'startContainer: ' + r.startContainer.nodeName + 7425 ', startOffset: ' + r.startOffset + 7426 ', endContainer: ' + r.endContainer.nodeName + 7427 ', endOffset: ' + r.endOffset 7428 ); 7429 }, 7430 7431 // #endif 7432 7433 _findSib: function(node, selector, name) { 7434 var self = this, func = selector; 7435 7436 if (node) { 7437 // If expression make a function of it using is 7438 if (typeof(func) == 'string') { 7439 func = function(node) { 7440 return self.is(node, selector); 7441 }; 7442 } 7443 7444 // Loop all siblings 7445 for (node = node[name]; node; node = node[name]) { 7446 if (func(node)) { 7447 return node; 7448 } 7449 } 7450 } 7451 7452 return null; 7453 } 7454 }; 7455 7456 /** 7457 * Instance of DOMUtils for the current document. 7458 * 7459 * @static 7460 * @property DOM 7461 * @type tinymce.dom.DOMUtils 7462 * @example 7463 * // Example of how to add a class to some element by id 7464 * tinymce.DOM.addClass('someid', 'someclass'); 7465 */ 7466 DOMUtils.DOM = new DOMUtils(document); 7467 7468 return DOMUtils; 7469}); 7470 7471// Included from: js/tinymce/classes/dom/ScriptLoader.js 7472 7473/** 7474 * ScriptLoader.js 7475 * 7476 * Copyright, Moxiecode Systems AB 7477 * Released under LGPL License. 7478 * 7479 * License: http://www.tinymce.com/license 7480 * Contributing: http://www.tinymce.com/contributing 7481 */ 7482 7483/*globals console*/ 7484 7485/** 7486 * This class handles asynchronous/synchronous loading of JavaScript files it will execute callbacks 7487 * when various items gets loaded. This class is useful to load external JavaScript files. 7488 * 7489 * @class tinymce.dom.ScriptLoader 7490 * @example 7491 * // Load a script from a specific URL using the global script loader 7492 * tinymce.ScriptLoader.load('somescript.js'); 7493 *
7494 * // Load a script using a unique instance of the script loader 7495 * var scriptLoader = new tinymce.dom.ScriptLoader(); 7496 * 7497 * scriptLoader.load('somescript.js'); 7498 * 7499 * // Load multiple scripts 7500 * var scriptLoader = new tinymce.dom.ScriptLoader(); 7501 * 7502 * scriptLoader.add('somescript1.js'); 7503 * scriptLoader.add('somescript2.js'); 7504 * scriptLoader.add('somescript3.js'); 7505 * 7506 * scriptLoader.loadQueue(function() { 7507 * alert('All scripts are now loaded.'); 7508 * }); 7509 */ 7510define("tinymce/dom/ScriptLoader", [ 7511 "tinymce/dom/DOMUtils", 7512 "tinymce/util/Tools" 7513], function(DOMUtils, Tools) { 7514 var DOM = DOMUtils.DOM; 7515 var each = Tools.each, grep = Tools.grep; 7516 7517 function ScriptLoader() { 7518 var QUEUED = 0, 7519 LOADING = 1, 7520 LOADED = 2, 7521 states = {}, 7522 queue = [], 7523 scriptLoadedCallbacks = {}, 7524 queueLoadedCallbacks = [], 7525 loading = 0, 7526 undef; 7527 7528 /** 7529 * Loads a specific script directly without adding it to the load queue. 7530 * 7531 * @method load 7532 * @param {String} url Absolute URL to script to add. 7533 * @param {function} callback Optional callback function to execute ones this script gets loaded. 7534 * @param {Object} scope Optional scope to execute callback in. 7535 */ 7536 function loadScript(url, callback) { 7537 var dom = DOM, elm, id; 7538 7539 // Execute callback when script is loaded 7540 function done() { 7541 dom.remove(id); 7542 7543 if (elm) { 7544 elm.onreadystatechange = elm.onload = elm = null; 7545 } 7546 7547 callback(); 7548 } 7549 7550 function error() { 7551 // Report the error so it's easier for people to spot loading errors 7552 if (typeof(console) !== "undefined" && console.log) { 7553 console.log("Failed to load: " + url); 7554 } 7555 7556 // We can't mark it as done if there is a load error since 7557 // A) We don't want to produce 404 errors on the server and 7558 // B) the onerror event won't fire on all browsers. 7559 // done(); 7560 } 7561 7562 id = dom.uniqueId(); 7563 7564 // Create new script element 7565 elm = document.createElement('script'); 7566 elm.id = id; 7567 elm.type = 'text/javascript'; 7568 elm.src = url; 7569 7570 // Seems that onreadystatechange works better on IE 10 onload seems to fire incorrectly 7571 if ("onreadystatechange" in elm) { 7572 elm.onreadystatechange = function() { 7573 if (/loaded|complete/.test(elm.readyState)) { 7574 done(); 7575 } 7576 }; 7577 } else { 7578 elm.onload = done; 7579 } 7580 7581 // Add onerror event will get fired on some browsers but not all of them 7582 elm.onerror = error; 7583 7584 // Add script to document 7585 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm); 7586 } 7587 7588 /** 7589 * Returns true/false if a script has been loaded or not. 7590 * 7591 * @method isDone 7592 * @param {String} url URL to check for. 7593 * @return {Boolean} true/false if the URL is loaded. 7594 */ 7595 this.isDone = function(url) { 7596 return states[url] == LOADED; 7597 }; 7598 7599 /** 7600 * Marks a specific script to be loaded. This can be useful if a script got loaded outside 7601 * the script loader or to skip it from loading some script. 7602 * 7603 * @method markDone 7604 * @param {string} u Absolute URL to the script to mark as loaded. 7605 */ 7606 this.markDone = function(url) { 7607 states[url] = LOADED; 7608 }; 7609 7610 /** 7611 * Adds a specific script to the load queue of the script loader. 7612 * 7613 * @method add 7614 * @param {String} url Absolute URL to script to add. 7615 * @param {function} callback Optional callback function to execute ones this script gets loaded. 7616 * @param {Object} scope Optional scope to execute callback in. 7617 */ 7618 this.add = this.load = function(url, callback, scope) { 7619 var state = states[url]; 7620 7621 // Add url to load queue 7622 if (state == undef) { 7623 queue.push(url); 7624 states[url] = QUEUED; 7625 } 7626 7627 if (callback) { 7628 // Store away callback for later execution 7629 if (!scriptLoadedCallbacks[url]) { 7630 scriptLoadedCallbacks[url] = []; 7631 } 7632 7633 scriptLoadedCallbacks[url].push({ 7634 func: callback, 7635 scope: scope || this 7636 }); 7637 } 7638 }; 7639 7640 /** 7641 * Starts the loading of the queue. 7642 * 7643 * @method loadQueue 7644 * @param {function} callback Optional callback to execute when all queued items are loaded. 7645 * @param {Object} scope Optional scope to execute the callback in. 7646 */ 7647 this.loadQueue = function(callback, scope) { 7648 this.loadScripts(queue, callback, scope); 7649 }; 7650 7651 /** 7652 * Loads the specified queue of files and executes the callback ones they are loaded. 7653 * This method is generally not used outside this class but it might be useful in some scenarios. 7654 * 7655 * @method loadScripts 7656 * @param {Array} scripts Array of queue items to load. 7657 * @param {function} callback Optional callback to execute ones all items are loaded. 7658 * @param {Object} scope Optional scope to execute callback in. 7659 */ 7660 this.loadScripts = function(scripts, callback, scope) { 7661 var loadScripts; 7662 7663 function execScriptLoadedCallbacks(url) { 7664 // Execute URL callback functions 7665 each(scriptLoadedCallbacks[url], function(callback) { 7666 callback.func.call(callback.scope); 7667 }); 7668 7669 scriptLoadedCallbacks[url] = undef; 7670 } 7671 7672 queueLoadedCallbacks.push({ 7673 func: callback, 7674 scope: scope || this 7675 }); 7676 7677 loadScripts = function() { 7678 var loadingScripts = grep(scripts); 7679 7680 // Current scripts has been handled 7681 scripts.length = 0; 7682 7683 // Load scripts that needs to be loaded 7684 each(loadingScripts, function(url) { 7685 // Script is already loaded then execute script callbacks directly 7686 if (states[url] == LOADED) { 7687 execScriptLoadedCallbacks(url); 7688 return; 7689 } 7690 7691 // Is script not loading then start loading it 7692 if (states[url] != LOADING) { 7693 states[url] = LOADING; 7694 loading++; 7695 7696 loadScript(url, function() { 7697 states[url] = LOADED; 7698 loading--; 7699 7700 execScriptLoadedCallbacks(url); 7701 7702 // Load more scripts if they where added by the recently loaded script 7703 loadScripts(); 7704 }); 7705 } 7706 }); 7707 7708 // No scripts are currently loading then execute all pending queue loaded callbacks 7709 if (!loading) { 7710 each(queueLoadedCallbacks, function(callback) { 7711 callback.func.call(callback.scope); 7712 }); 7713 7714 queueLoadedCallbacks.length = 0; 7715 } 7716 }; 7717 7718 loadScripts(); 7719 }; 7720 } 7721 7722 ScriptLoader.ScriptLoader = new ScriptLoader(); 7723 7724 return ScriptLoader; 7725}); 7726 7727// Included from: js/tinymce/classes/AddOnManager.js 7728 7729/** 7730 * AddOnManager.js 7731 * 7732 * Copyright, Moxiecode Systems AB 7733 * Released under LGPL License. 7734 * 7735 * License: http://www.tinymce.com/license 7736 * Contributing: http://www.tinymce.com/contributing 7737 */ 7738 7739/** 7740 * This class handles the loading of themes/plugins or other add-ons and their language packs. 7741 * 7742 * @class tinymce.AddOnManager 7743 */ 7744define("tinymce/AddOnManager", [ 7745 "tinymce/dom/ScriptLoader", 7746 "tinymce/util/Tools" 7747], function(ScriptLoader, Tools) { 7748 var each = Tools.each; 7749 7750 function AddOnManager() { 7751 var self = this; 7752 7753 self.items = []; 7754 self.urls = {}; 7755 self.lookup = {}; 7756 } 7757 7758 AddOnManager.prototype = { 7759 /** 7760 * Returns the specified add on by the short name. 7761 * 7762 * @method get 7763 * @param {String} name Add-on to look for. 7764 * @return {tinymce.Theme/tinymce.Plugin}
7764 Theme or plugin add-on instance or undefined. 7765 */ 7766 get: function(name) { 7767 if (this.lookup[name]) { 7768 return this.lookup[name].instance; 7769 } else { 7770 return undefined; 7771 } 7772 }, 7773 7774 dependencies: function(name) { 7775 var result; 7776 7777 if (this.lookup[name]) { 7778 result = this.lookup[name].dependencies; 7779 } 7780 7781 return result || []; 7782 }, 7783 7784 /** 7785 * Loads a language pack for the specified add-on. 7786 * 7787 * @method requireLangPack 7788 * @param {String} name Short name of the add-on. 7789 * @param {String} languages Optional comma or space separated list of languages to check if it matches the name. 7790 */ 7791 requireLangPack: function(name, languages) { 7792 if (AddOnManager.language && AddOnManager.languageLoad !== false) { 7793 if (languages && new RegExp('([, ]|\\b)' + AddOnManager.language + '([, ]|\\b)').test(languages) === false) { 7794 return; 7795 } 7796 7797 ScriptLoader.ScriptLoader.add(this.urls[name] + '/langs/' + AddOnManager.language + '.js'); 7798 } 7799 }, 7800 7801 /** 7802 * Adds a instance of the add-on by it's short name. 7803 * 7804 * @method add 7805 * @param {String} id Short name/id for the add-on. 7806 * @param {tinymce.Theme/tinymce.Plugin} addOn Theme or plugin to add. 7807 * @return {tinymce.Theme/tinymce.Plugin} The same theme or plugin instance that got passed in. 7808 * @example 7809 * // Create a simple plugin 7810 * tinymce.create('tinymce.plugins.TestPlugin', { 7811 * TestPlugin: function(ed, url) { 7812 * ed.on('click', function(e) { 7813 * ed.windowManager.alert('Hello World!'); 7814 * }); 7815 * } 7816 * }); 7817 * 7818 * // Register plugin using the add method 7819 * tinymce.PluginManager.add('test', tinymce.plugins.TestPlugin); 7820 * 7821 * // Initialize TinyMCE 7822 * tinymce.init({ 7823 * ... 7824 * plugins: '-test' // Init the plugin but don't try to load it 7825 * }); 7826 */ 7827 add: function(id, addOn, dependencies) { 7828 this.items.push(addOn); 7829 this.lookup[id] = {instance: addOn, dependencies: dependencies}; 7830 7831 return addOn; 7832 }, 7833 7834 createUrl: function(baseUrl, dep) { 7835 if (typeof dep === "object") { 7836 return dep; 7837 } else { 7838 return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix}; 7839 } 7840 }, 7841 7842 /** 7843 * Add a set of components that will make up the add-on. Using the url of the add-on name as the base url. 7844 * This should be used in development mode. A new compressor/javascript munger process will ensure that the 7845 * components are put together into the plugin.js file and compressed correctly. 7846 * 7847 * @method addComponents 7848 * @param {String} pluginName name of the plugin to load scripts from (will be used to get the base url for the plugins). 7849 * @param {Array} scripts Array containing the names of the scripts to load. 7850 */ 7851 addComponents: function(pluginName, scripts) { 7852 var pluginUrl = this.urls[pluginName]; 7853 7854 each(scripts, function(script) { 7855 ScriptLoader.ScriptLoader.add(pluginUrl + "/" + script); 7856 }); 7857 }, 7858 7859 /** 7860 * Loads an add-on from a specific url. 7861 * 7862 * @method load 7863 * @param {String} n Short name of the add-on that gets loaded. 7864 * @param {String} u URL to the add-on that will get loaded. 7865 * @param {function} cb Optional callback to execute ones the add-on is loaded. 7866 * @param {Object} s Optional scope to execute the callback in. 7867 * @example 7868 * // Loads a plugin from an external URL 7869 * tinymce.PluginManager.load('myplugin', '/some/dir/someplugin/plugin.js'); 7870 * 7871 * // Initialize TinyMCE 7872 * tinymce.init({ 7873 * ... 7874 * plugins: '-myplugin' // Don't try to load it again 7875 * }); 7876 */ 7877 load: function(n, u, cb, s) { 7878 var t = this, url = u; 7879 7880 function loadDependencies() { 7881 var dependencies = t.dependencies(n); 7882 7883 each(dependencies, function(dep) { 7884 var newUrl = t.createUrl(u, dep); 7885 7886 t.load(newUrl.resource, newUrl, undefined, undefined); 7887 }); 7888 7889 if (cb) { 7890 if (s) { 7891 cb.call(s); 7892 } else { 7893 cb.call(ScriptLoader); 7894 } 7895 } 7896 } 7897 7898 if (t.urls[n]) { 7899 return; 7900 } 7901 7902 if (typeof u === "object") { 7903 url = u.prefix + u.resource + u.suffix; 7904 } 7905 7906 if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) { 7907 url = AddOnManager.baseURL + '/' + url; 7908 } 7909 7910 t.urls[n] = url.substring(0, url.lastIndexOf('/')); 7911 7912 if (t.lookup[n]) { 7913 loadDependencies(); 7914 } else { 7915 ScriptLoader.ScriptLoader.add(url, loadDependencies, s); 7916 } 7917 } 7918 }; 7919 7920 AddOnManager.PluginManager = new AddOnManager(); 7921 AddOnManager.ThemeManager = new AddOnManager(); 7922 7923 return AddOnManager; 7924}); 7925 7926/** 7927 * TinyMCE theme class. 7928 * 7929 * @class tinymce.Theme 7930 */ 7931 7932/** 7933 * This method is responsible for rendering/generating the overall user interface with toolbars, buttons, iframe containers etc. 7934 * 7935 * @method renderUI 7936 * @param {Object} obj Object parameter containing the targetNode DOM node that will be replaced visually with an editor instance. 7937 * @return {Object}
7937 an object with items like iframeContainer, editorContainer, sizeContainer, deltaWidth, deltaHeight. 7938 */ 7939 7940/** 7941 * 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. 7942 * 7943 * @class tinymce.Plugin 7944 * @example 7945 * tinymce.PluginManager.add('example', function(editor, url) { 7946 * // Add a button that opens a window 7947 * editor.addButton('example', { 7948 * text: 'My button', 7949 * icon: false, 7950 * onclick: function() { 7951 * // Open window 7952 * editor.windowManager.open({ 7953 * title: 'Example plugin', 7954 * body: [ 7955 * {type: 'textbox', name: 'title', label: 'Title'} 7956 * ], 7957 * onsubmit: function(e) { 7958 * // Insert content when the window form is submitted 7959 * editor.insertContent('Title: ' + e.data.title); 7960 * } 7961 * }); 7962 * } 7963 * }); 7964 * 7965 * // Adds a menu item to the tools menu 7966 * editor.addMenuItem('example', { 7967 * text: 'Example plugin', 7968 * context: 'tools', 7969 * onclick: function() { 7970 * // Open window with a specific url 7971 * editor.windowManager.open({ 7972 * title: 'TinyMCE site', 7973 * url: 'http://www.tinymce.com', 7974 * width: 800, 7975 * height: 600, 7976 * buttons: [{ 7977 * text: 'Close', 7978 * onclick: 'close' 7979 * }] 7980 * }); 7981 * } 7982 * }); 7983 * }); 7984 */ 7985 7986// Included from: js/tinymce/classes/html/Node.js 7987 7988/** 7989 * Node.js 7990 * 7991 * Copyright, Moxiecode Systems AB 7992 * Released under LGPL License. 7993 * 7994 * License: http://www.tinymce.com/license 7995 * Contributing: http://www.tinymce.com/contributing 7996 */ 7997 7998/** 7999 * This class is a minimalistic implementation of a DOM like node used by the DomParser class. 8000 * 8001 * @example 8002 * var node = new tinymce.html.Node('strong', 1); 8003 * someRoot.append(node); 8004 * 8005 * @class tinymce.html.Node 8006 * @version 3.4 8007 */ 8008define("tinymce/html/Node", [], function() { 8009 var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = { 8010 '#text': 3, 8011 '#comment': 8, 8012 '#cdata': 4, 8013 '#pi': 7, 8014 '#doctype': 10, 8015 '#document-fragment': 11 8016 }; 8017 8018 // Walks the tree left/right 8019 function walk(node, root_node, prev) { 8020 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next'; 8021 8022 // Walk into nodes if it has a start 8023 if (node[startName]) { 8024 return node[startName]; 8025 } 8026 8027 // Return the sibling if it has one 8028 if (node !== root_node) { 8029 sibling = node[siblingName]; 8030 8031 if (sibling) { 8032 return sibling; 8033 } 8034 8035 // Walk up the parents to look for siblings 8036 for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) { 8037 sibling = parent[siblingName]; 8038 8039 if (sibling) { 8040 return sibling; 8041 } 8042 } 8043 } 8044 } 8045 8046 /** 8047 * Constructs a new Node instance. 8048 * 8049 * @constructor 8050 * @method Node 8051 * @param {String} name Name of the node type. 8052 * @param {Number} type Numeric type representing the node. 8053 */ 8054 function Node(name, type) { 8055 this.name = name; 8056 this.type = type; 8057 8058 if (type === 1) { 8059 this.attributes = []; 8060 this.attributes.map = {}; 8061 } 8062 } 8063 8064 Node.prototype = { 8065 /** 8066 * Replaces the current node with the specified one. 8067 * 8068 * @example 8069 * someNode.replace(someNewNode); 8070 * 8071 * @method replace 8072 * @param {tinymce.html.Node} node Node to replace the current node with. 8073 * @return {tinymce.html.Node} The old node that got replaced. 8074 */ 8075 replace: function(node) { 8076 var self = this; 8077 8078 if (node.parent) { 8079 node.remove(); 8080 } 8081 8082 self.insert(node, self); 8083 self.remove(); 8084 8085 return self; 8086 }, 8087 8088 /** 8089 * Gets/sets or removes an attribute by name. 8090 * 8091 * @example 8092 * someNode.attr("name", "value"); // Sets an attribute 8093 * console.log(someNode.attr("name")); // Gets an attribute 8094 * someNode.attr("name", null); // Removes an attribute 8095 * 8096 * @method attr 8097 * @param {String} name Attribute name to set or get. 8098 * @param {String} value Optional value to set. 8099 * @return {String/tinymce.html.Node} String or undefined on a get operation or the current node on a set operation. 8100 */ 8101 attr: function(name, value) { 8102 var self = this, attrs, i, undef; 8103 8104 if (typeof name !== "string") { 8105 for (i in name) { 8106 self.attr(i, name[i]); 8107 } 8108 8109 return self; 8110 } 8111 8112 if ((attrs = self.attributes)) { 8113 if (value !== undef) { 8114 // Remove attribute 8115 if (value === null) { 8116 if (name in attrs.map) { 8117 delete attrs.map[name]; 8118 8119 i = attrs.length; 8120 while (i--) { 8121 if (attrs[i].name === name) { 8122 attrs = attrs.splice(i, 1); 8123 return self; 8124 } 8125 } 8126 } 8127 8128 return self; 8129 } 8130 8131 // Set attribute 8132 if (name in attrs.map) { 8133 // Set attribute 8134 i = attrs.length; 8135 while (i--) { 8136 if (attrs[i].name === name) { 8137 attrs[i].value = value; 8138 break; 8139 } 8140 } 8141 } else { 8142 attrs.push({name: name, value: value}); 8143 } 8144 8145 attrs.map[name] = value; 8146 8147 return self; 8148 } else { 8149 return attrs.map[name]; 8150 } 8151 } 8152 }, 8153 8154 /**
8155 * Does a shallow clones the node into a new node. It will also exclude id attributes since 8156 * there should only be one id per document. 8157 * 8158 * @example 8159 * var clonedNode = node.clone(); 8160 * 8161 * @method clone 8162 * @return {tinymce.html.Node} New copy of the original node. 8163 */ 8164 clone: function() { 8165 var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs; 8166 8167 // Clone element attributes 8168 if ((selfAttrs = self.attributes)) { 8169 cloneAttrs = []; 8170 cloneAttrs.map = {}; 8171 8172 for (i = 0, l = selfAttrs.length; i < l; i++) { 8173 selfAttr = selfAttrs[i]; 8174 8175 // Clone everything except id 8176 if (selfAttr.name !== 'id') { 8177 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value}; 8178 cloneAttrs.map[selfAttr.name] = selfAttr.value; 8179 } 8180 } 8181 8182 clone.attributes = cloneAttrs; 8183 } 8184 8185 clone.value = self.value; 8186 clone.shortEnded = self.shortEnded; 8187 8188 return clone; 8189 }, 8190 8191 /** 8192 * Wraps the node in in another node. 8193 * 8194 * @example 8195 * node.wrap(wrapperNode); 8196 * 8197 * @method wrap 8198 */ 8199 wrap: function(wrapper) { 8200 var self = this; 8201 8202 self.parent.insert(wrapper, self); 8203 wrapper.append(self); 8204 8205 return self; 8206 }, 8207 8208 /** 8209 * Unwraps the node in other words it removes the node but keeps the children. 8210 * 8211 * @example 8212 * node.unwrap(); 8213 * 8214 * @method unwrap 8215 */ 8216 unwrap: function() { 8217 var self = this, node, next; 8218 8219 for (node = self.firstChild; node; ) { 8220 next = node.next; 8221 self.insert(node, self, true); 8222 node = next; 8223 } 8224 8225 self.remove(); 8226 }, 8227 8228 /** 8229 * Removes the node from it's parent. 8230 * 8231 * @example 8232 * node.remove(); 8233 * 8234 * @method remove 8235 * @return {tinymce.html.Node} Current node that got removed. 8236 */ 8237 remove: function() { 8238 var self = this, parent = self.parent, next = self.next, prev = self.prev; 8239 8240 if (parent) { 8241 if (parent.firstChild === self) { 8242 parent.firstChild = next; 8243 8244 if (next) { 8245 next.prev = null; 8246 } 8247 } else { 8248 prev.next = next; 8249 } 8250 8251 if (parent.lastChild === self) { 8252 parent.lastChild = prev; 8253 8254 if (prev) { 8255 prev.next = null; 8256 } 8257 } else { 8258 next.prev = prev; 8259 } 8260 8261 self.parent = self.next = self.prev = null; 8262 } 8263 8264 return self; 8265 }, 8266 8267 /** 8268 * Appends a new node as a child of the current node. 8269 * 8270 * @example 8271 * node.append(someNode); 8272 * 8273 * @method append 8274 * @param {tinymce.html.Node} node Node to append as a child of the current one. 8275 * @return {tinymce.html.Node} The node that got appended. 8276 */ 8277 append: function(node) { 8278 var self = this, last; 8279 8280 if (node.parent) { 8281 node.remove(); 8282 } 8283 8284 last = self.lastChild; 8285 if (last) { 8286 last.next = node; 8287 node.prev = last; 8288 self.lastChild = node; 8289 } else { 8290 self.lastChild = self.firstChild = node; 8291 } 8292 8293 node.parent = self; 8294 8295 return node; 8296 }, 8297 8298 /** 8299 * Inserts a node at a specific position as a child of the current node. 8300 * 8301 * @example 8302 * parentNode.insert(newChildNode, oldChildNode); 8303 * 8304 * @method insert 8305 * @param {tinymce.html.Node} node Node to insert as a child of the current node. 8306 * @param {tinymce.html.Node} ref_node Reference node to set node before/after. 8307 * @param {Boolean} before Optional state to insert the node before the reference node. 8308 * @return {tinymce.html.Node} The node that got inserted. 8309 */ 8310 insert: function(node, ref_node, before) { 8311 var parent; 8312 8313 if (node.parent) { 8314 node.remove(); 8315 } 8316 8317 parent = ref_node.parent || this; 8318 8319 if (before) { 8320 if (ref_node === parent.firstChild) { 8321 parent.firstChild = node; 8322 } else { 8323 ref_node.prev.next = node; 8324 } 8325 8326 node.prev = ref_node.prev; 8327 node.next = ref_node; 8328 ref_node.prev = node; 8329 } else { 8330 if (ref_node === parent.lastChild) { 8331 parent.lastChild = node; 8332 } else { 8333 ref_node.next.prev = node; 8334 } 8335 8336 node.next = ref_node.next; 8337 node.prev = ref_node; 8338 ref_node.next = node; 8339 } 8340 8341 node.parent = parent; 8342 8343 return node; 8344 }, 8345 8346 /** 8347 * Get all children by name. 8348 * 8349 * @method getAll 8350 * @param {String} name Name of the child nodes to collect. 8351 * @return {Array} Array with child nodes matchin the specified name. 8352 */ 8353 getAll: function(name) { 8354 var self = this, node, collection = []; 8355 8356 for (node = self.firstChild; node; node = walk(node, self)) { 8357 if (node.name === name) { 8358 collection.push(node); 8359 } 8360 } 8361 8362 return collection; 8363 }, 8364 8365 /** 8366 * Removes all children of the current node. 8367 * 8368 * @method empty 8369 * @return {tinymce.html.Node} The current node that got cleared. 8370 */ 8371 empty: function() { 8372 var self = this, nodes, i, node; 8373 8374 // Remove all children 8375 if (self.firstChild) { 8376 nodes = []; 8377 8378 // Collect the children 8379 for (node = self.firstChild; node; node = walk(node, self)) { 8380 nodes.push(node); 8381 } 8382 8383 // Remove the children 8384 i = nodes.length; 8385 while (i--) { 8386 node = nodes[i]; 8387 node.parent = node.firstChild = node.lastChild = node.next = node.prev = null; 8388 } 8389 } 8390 8391 self.firstChild = self.lastChild = null; 8392 8393 return self; 8394 }, 8395 8396 /** 8397 * Returns true/false if the node is to be considered empty or not. 8398 * 8399 * @example 8400 * node.isEmpty({img: true}); 8401 * @method isEmpty 8402 * @param {Object} elements Name/value object with elements that are automatically treated as non empty elements. 8403 * @return {Boolean} true/false if the node is empty or not. 8404 */ 8405 isEmpty: function(elements) { 8406 var self = this, node = self.firstChild, i, name; 8407 8408 if (node) { 8409 do { 8410 if (node.type === 1) { 8411 // Ignore bogus elements 8412 if (node.attributes.map['data-mce-bogus']) { 8413 continue; 8414 } 8415 8416 // Keep empty elements like <img /> 8417 if (elements[node.name]) { 8418 return false;
8419 } 8420 8421 // Keep elements with data attributes or name attribute like <a name="1"></a> 8422 i = node.attributes.length; 8423 while (i--) { 8424 name = node.attributes[i].name; 8425 if (name === "name" || name.indexOf('data-mce-') === 0) { 8426 return false; 8427 } 8428 } 8429 } 8430 8431 // Keep comments 8432 if (node.type === 8) { 8433 return false; 8434 } 8435 8436 // Keep non whitespace text nodes 8437 if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) { 8438 return false; 8439 } 8440 } while ((node = walk(node, self))); 8441 } 8442 8443 return true; 8444 }, 8445 8446 /** 8447 * Walks to the next or previous node and returns that node or null if it wasn't found. 8448 * 8449 * @method walk 8450 * @param {Boolean} prev Optional previous node state defaults to false. 8451 * @return {tinymce.html.Node} Node that is next to or previous of the current node. 8452 */ 8453 walk: function(prev) { 8454 return walk(this, null, prev); 8455 } 8456 }; 8457 8458 /** 8459 * Creates a node of a specific type. 8460 * 8461 * @static 8462 * @method create 8463 * @param {String} name Name of the node type to create for example "b" or "#text". 8464 * @param {Object} attrs Name/value collection of attributes that will be applied to elements. 8465 */ 8466 Node.create = function(name, attrs) { 8467 var node, attrName; 8468 8469 // Create node 8470 node = new Node(name, typeLookup[name] || 1); 8471 8472 // Add attributes if needed 8473 if (attrs) { 8474 for (attrName in attrs) { 8475 node.attr(attrName, attrs[attrName]); 8476 } 8477 } 8478 8479 return node; 8480 }; 8481 8482 return Node; 8483}); 8484 8485// Included from: js/tinymce/classes/html/Schema.js 8486 8487/** 8488 * Schema.js 8489 * 8490 * Copyright, Moxiecode Systems AB 8491 * Released under LGPL License. 8492 * 8493 * License: http://www.tinymce.com/license 8494 * Contributing: http://www.tinymce.com/contributing 8495 */ 8496 8497/** 8498 * Schema validator class. 8499 * 8500 * @class tinymce.html.Schema 8501 * @example 8502 * if (tinymce.activeEditor.schema.isValidChild('p', 'span')) 8503 * alert('span is valid child of p.'); 8504 * 8505 * if (tinymce.activeEditor.schema.getElementRule('p')) 8506 * alert('P is a valid element.'); 8507 * 8508 * @class tinymce.html.Schema 8509 * @version 3.4 8510 */ 8511define("tinymce/html/Schema", [ 8512 "tinymce/util/Tools" 8513], function(Tools) { 8514 var mapCache = {}; 8515 var makeMap = Tools.makeMap, each = Tools.each, extend = Tools.extend, explode = Tools.explode, inArray = Tools.inArray; 8516 8517 function split(items, delim) { 8518 return items ? items.split(delim || ' ') : []; 8519 } 8520 8521 /** 8522 * Builds a schema lookup table 8523 * 8524 * @private 8525 * @param {String} type html4, html5 or html5-strict schema type. 8526 * @return {Object} Schema lookup table. 8527 */ 8528 function compileSchema(type) { 8529 var schema = {}, globalAttributes, eventAttributes, blockContent; 8530 var phrasingContent, flowContent, html4BlockContent, html4PhrasingContent; 8531 8532 function add(name, attributes, children) { 8533 var ni, i, attributesOrder, args = arguments; 8534 8535 function arrayToMap(array) { 8536 var map = {}, i, l; 8537 8538 for (i = 0, l = array.length; i < l; i++) { 8539 map[array[i]] = {}; 8540 } 8541 8542 return map; 8543 } 8544 8545 children = children || []; 8546 attributes = attributes || ""; 8547 8548 if (typeof(children) === "string") { 8549 children = split(children); 8550 } 8551 8552 // Split string children 8553 for (i = 3; i < args.length; i++) { 8554 if (typeof(args[i]) === "string") { 8555 args[i] = split(args[i]); 8556 } 8557 8558 children.push.apply(children, args[i]); 8559 } 8560 8561 name = split(name); 8562 ni = name.length; 8563 while (ni--) { 8564 attributesOrder = [].concat(globalAttributes, split(attributes)); 8565 schema[name[ni]] = { 8566 attributes: arrayToMap(attributesOrder), 8567 attributesOrder: attributesOrder, 8568 children: arrayToMap(children) 8569 }; 8570 } 8571 } 8572 8573 function addAttrs(name, attributes) { 8574 var ni, schemaItem, i, l; 8575 8576 name = split(name); 8577 ni = name.length; 8578 attributes = split(attributes); 8579 while (ni--) { 8580 schemaItem = schema[name[ni]]; 8581 for (i = 0, l = attributes.length; i < l; i++) { 8582 schemaItem.attributes[attributes[i]] = {}; 8583 schemaItem.attributesOrder.push(attributes[i]); 8584 } 8585 } 8586 } 8587 8588 // Use cached schema 8589 if (mapCache[type]) { 8590 return mapCache[type]; 8591 } 8592 8593 // Attributes present on all elements 8594 globalAttributes = split("id accesskey class dir lang style tabindex title"); 8595 8596 // Event attributes can be opt-in/opt-out 8597 eventAttributes = split("onabort onblur oncancel oncanplay oncanplaythrough onchange onclick onclose oncontextmenu oncuechange " + 8598 "ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended " + 8599 "onerror onfocus oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart " + 8600 "onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onpause onplay onplaying onprogress onratechange " + 8601 "onreset onscroll onseeked onseeking onseeking onselect onshow onstalled onsubmit onsuspend ontimeupdate onvolumechange " + 8602 "onwaiting" 8603 ); 8604 8605 // Block content elements 8606 blockContent = split( 8607 "address blockquote div dl fieldset form h1 h2 h3 h4 h5 h6 hr menu ol p pre table ul" 8608 ); 8609 8610 // Phrasing content elements from the HTML5 spec (inline) 8611 phrasingContent = split( 8612 "a abbr b bdo br button cite code del dfn em embed i iframe img input ins kbd " + 8613 "label map noscript object q s samp script select small span strong sub sup " + 8614 "textarea u var #text #comment" 8615 ); 8616 8617 // Add HTML5 items to globalAttributes, blockContent, phrasingContent 8618 if (type != "html4") { 8619 globalAttributes.push.apply(globalAttributes, split("contenteditable contextmenu draggable dropzone " + 8620 "hidden spellcheck translate")); 8621 blockContent.push.apply(blockContent, split("article aside details dialog figure header footer hgroup section nav"));
8622 phrasingContent.push.apply(phrasingContent, split("audio canvas command datalist mark meter output progress time wbr " + 8623 "video ruby bdi keygen")); 8624 } 8625 8626 // Add HTML4 elements unless it's html5-strict 8627 if (type != "html5-strict") { 8628 globalAttributes.push("xml:lang"); 8629 8630 html4PhrasingContent = split("acronym applet basefont big font strike tt"); 8631 phrasingContent.push.apply(phrasingContent, html4PhrasingContent); 8632 8633 each(html4PhrasingContent, function(name) { 8634 add(name, "", phrasingContent); 8635 }); 8636 8637 html4BlockContent = split("center dir isindex noframes"); 8638 blockContent.push.apply(blockContent, html4BlockContent); 8639 8640 // Flow content elements from the HTML5 spec (block+inline) 8641 flowContent = [].concat(blockContent, phrasingContent); 8642 8643 each(html4BlockContent, function(name) { 8644 add(name, "", flowContent); 8645 }); 8646 } 8647 8648 // Flow content elements from the HTML5 spec (block+inline) 8649 flowContent = flowContent || [].concat(blockContent, phrasingContent); 8650 8651 // HTML4 base schema TODO: Move HTML5 specific attributes to HTML5 specific if statement 8652 // Schema items <element name>, <specific attributes>, <children ..> 8653 add("html", "manifest", "head body"); 8654 add("head", "", "base command link meta noscript script style title"); 8655 add("title hr noscript br"); 8656 add("base", "href target"); 8657 add("link", "href rel media hreflang type sizes hreflang"); 8658 add("meta", "name http-equiv content charset"); 8659 add("style", "media type scoped"); 8660 add("script", "src async defer type charset"); 8661 add("body", "onafterprint onbeforeprint onbeforeunload onblur onerror onfocus " + 8662 "onhashchange onload onmessage onoffline ononline onpagehide onpageshow " + 8663 "onpopstate onresize onscroll onstorage onunload", flowContent); 8664 add("address dt dd div caption", "", flowContent); 8665 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); 8666 add("blockquote", "cite", flowContent); 8667 add("ol", "reversed start type", "li"); 8668 add("ul", "", "li"); 8669 add("li", "value", flowContent); 8670 add("dl", "", "dt dd"); 8671 add("a", "href target rel media hreflang type", phrasingContent); 8672 add("q", "cite", phrasingContent); 8673 add("ins del", "cite datetime", flowContent); 8674 add("img", "src alt usemap ismap width height"); 8675 add("iframe", "src name width height", flowContent); 8676 add("embed", "src type width height"); 8677 add("object", "data type typemustmatch name usemap form width height", flowContent, "param"); 8678 add("param", "name value"); 8679 add("map", "name", flowContent, "area"); 8680 add("area", "alt coords shape href target rel media hreflang type"); 8681 add("table", "border", "caption colgroup thead tfoot tbody tr" + (type == "html4" ? " col" : "")); 8682 add("colgroup", "span", "col"); 8683 add("col", "span"); 8684 add("tbody thead tfoot", "", "tr"); 8685 add("tr", "", "td th"); 8686 add("td", "colspan rowspan headers", flowContent); 8687 add("th", "colspan rowspan headers scope abbr", flowContent); 8688 add("form", "accept-charset action autocomplete enctype method name novalidate target", flowContent); 8689 add("fieldset", "disabled form name", flowContent, "legend"); 8690 add("label", "form for", phrasingContent); 8691 add("input", "accept alt autocomplete checked dirname disabled form formaction formenctype formmethod formnovalidate " + 8692 "formtarget height list max maxlength min multiple name pattern readonly required size src step type value width" 8693 ); 8694 add("button", "disabled form formaction formenctype formmethod formnovalidate formtarget name type value", 8695 type == "html4" ? flowContent : phrasingContent); 8696 add("select", "disabled form multiple name required size", "option optgroup"); 8697 add("optgroup", "disabled label", "option"); 8698 add("option", "disabled label selected value"); 8699 add("textarea", "cols dirname disabled form maxlength name readonly required rows wrap"); 8700 add("menu", "type label", flowContent, "li"); 8701 add("noscript", "", flowContent); 8702 8703 // Extend with HTML5 elements 8704 if (type != "html4") { 8705 add("wbr"); 8706 add("ruby", "", phrasingContent, "rt rp"); 8707 add("figcaption", "", flowContent); 8708 add("mark rt rp summary bdi", "", phrasingContent); 8709 add("canvas", "width height", flowContent); 8710 add("video", "src crossorigin poster preload autoplay mediagroup loop " + 8711 "muted controls width height", flowContent, "track source"); 8712 add("audio", "src crossorigin preload autoplay mediagroup loop muted controls", flowContent, "track source"); 8713 add("source", "src type media"); 8714 add("track", "kind src srclang label default"); 8715 add("datalist", "", phrasingContent, "option"); 8716 add("article section nav aside header footer", "", flowContent); 8717 add("hgroup", "", "h1 h2 h3 h4 h5 h6"); 8718 add("figure", "", flowContent, "figcaption"); 8719 add("time", "datetime", phrasingContent); 8720 add("dialog", "open", flowContent); 8721 add("command", "type label icon disabled checked radiogroup command"); 8722 add("output", "for form name", phrasingContent); 8723 add("progress", "value max", phrasingContent); 8724 add("meter", "value min max low high optimum", phrasingContent); 8725 add("details", "open", flowContent, "summary"); 8726 add("keygen", "autofocus challenge disabled form keytype name"); 8727 } 8728 8729 // Extend with HTML4 attributes unless it's html5-strict 8730 if (type != "html5-strict") { 8731 addAttrs("script", "language xml:space"); 8732 addAttrs("style", "xml:space"); 8733 addAttrs("object", "declare classid codebase codetype archive standby align border hspace vspace"); 8734 addAttrs("param", "valuetype type"); 8735 addAttrs("a", "charset name rev shape coords"); 8736 addAttrs("br", "clear"); 8737 addAttrs("applet", "codebase archive code object alt name width height align hspace vspace"); 8738 addAttrs("img", "name longdesc align border hspace vspace"); 8739 addAttrs("iframe", "longdesc frameborder marginwidth marginheight scrolling align"); 8740 addAttrs("font basefont", "size color face"); 8741 addAttrs("input", "usemap align"); 8742 addAttrs("select", "onchange"); 8743 addAttrs("textarea"); 8744 addAttrs("h1 h2 h3 h4 h5 h6 div p legend caption", "align"); 8745 addAttrs("ul", "type compact"); 8746 addAttrs("li", "type"); 8747 addAttrs("ol dl menu dir", "compact"); 8748 addAttrs("pre", "width xml:space"); 8749 addAttrs("hr", "align noshade size width"); 8750 addAttrs("isindex", "prompt"); 8751 addAttrs("table", "summary width frame rules cellspacing cellpadding align bgcolor"); 8752 addAttrs("col", "width align char charoff valign"); 8753 addAttrs("colgroup", "width align char charoff valign"); 8754 addAttrs("thead", "align char charoff valign"); 8755 addAttrs("tr", "align char charoff valign bgcolor"); 8756 addAttrs("th", "axis align char charoff valign nowrap bgcolor width height"); 8757 addAttrs("form", "accept"); 8758 addAttrs("td", "abbr axis scope align char charoff valign nowrap bgcolor width height"); 8759 addAttrs("tfoot", "align char charoff valign"); 8760 addAttrs("tbody", "align char charoff valign"); 8761 addAttrs("area", "nohref"); 8762 addAttrs("body", "background bgcolor text link vlink alink"); 8763 } 8764 8765 // Extend with HTML5 attributes unless it's html4 8766 if (type != "html4") { 8767 addAttrs("input button select textarea", "autofocus"); 8768 addAttrs("input textarea", "placeholder"); 8769 addAttrs("a", "download"); 8770 addAttrs("link script img", "crossorigin"); 8771 addAttrs("iframe", "srcdoc sandbox seamless allowfullscreen"); 8772 } 8773 8774 // Special: iframe, ruby, video, audio, label 8775 8776 // Delete children of the same name from it's parent 8777 // For example: form can't have a child of the name form 8778 each(split('a form meter progress dfn'), function(name) { 8779 if (schema[name]) { 8780 delete schema[name].children[name]; 8781 } 8782 }); 8783 8784 // Delete header, footer, sectioning and heading content descendants 8785 /*each('dt th address', function(name) { 8786 delete schema[name].children[name]; 8787 });*/ 8788 8789 // Caption can't have tables 8790 delete schema.caption.children.table; 8791 8792 // TODO: LI:s can only have value if parent is OL 8793 8794 // TODO: Handle transparent elements 8795 // a ins del canvas map 8796 8797 mapCache[type] = schema; 8798 8799 return schema; 8800 } 8801 8802 /** 8803 * Constructs a new Schema instance. 8804 * 8805 * @constructor 8806 * @method Schema 8807 * @param {Object} settings Name/value settings object. 8808 */ 8809 return function(settings) { 8810 var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems; 8811 var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap; 8812 var blockElementsMap, nonEmptyElementsMap, textBlockElementsMap, customElementsMap = {}, specialElements = {}; 8813 8814 // Creates an lookup table map object for the specified option or the default value 8815 function createLookupTable(option, default_value, extendWith) { 8816 var value = settings[option]; 8817 8818 if (!value) { 8819 // Get cached default map or make it if needed 8820 value = mapCache[option]; 8821 8822 if (!value) { 8823 value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' ')); 8824 value = extend(value, extendWith); 8825 8826 mapCache[option] = value; 8827 } 8828 } else { 8829 // Create custom map 8830 value = makeMap(value, ',', makeMap(value.toUpperCase(), ' ')); 8831 } 8832 8833 return value; 8834 } 8835 8836 settings = settings || {}; 8837 schemaItems = compileSchema(settings.schema); 8838 8839 // Allow all elements and attributes if verify_html is set to false 8840 if (settings.verify_html === false) {
8841 settings.valid_elements = '*[*]'; 8842 } 8843 8844 // Build styles list 8845 if (settings.valid_styles) { 8846 validStyles = {}; 8847 8848 // Convert styles into a rule list 8849 each(settings.valid_styles, function(value, key) { 8850 validStyles[key] = explode(value); 8851 }); 8852 } 8853 8854 // Setup map objects 8855 whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea video audio iframe object'); 8856 selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr'); 8857 shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link ' + 8858 'meta param embed source wbr track'); 8859 boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize ' + 8860 'noshade nowrap readonly selected autoplay loop controls'); 8861 nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object script', shortEndedElementsMap); 8862 textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' + 8863 'blockquote center dir fieldset header footer article section hgroup aside nav figure'); 8864 blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' + 8865 'th tr td li ol ul caption dl dt dd noscript menu isindex samp option ' + 8866 'datalist select optgroup', textBlockElementsMap); 8867 8868 each((settings.special || 'script noscript style textarea').split(' '), function(name) { 8869 specialElements[name] = new RegExp('<\/' + name + '[^>]*>','gi'); 8870 }); 8871 8872 // Converts a wildcard expression string to a regexp for example *a will become /.*a/. 8873 function patternToRegExp(str) { 8874 return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$'); 8875 } 8876 8877 // Parses the specified valid_elements string and adds to the current rules 8878 // This function is a bit hard to read since it's heavily optimized for speed 8879 function addValidElements(valid_elements) { 8880 var ei, el, ai, al, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder, 8881 prefix, outputName, globalAttributes, globalAttributesOrder, key, value, 8882 elementRuleRegExp = /^([#+\-])?([^\[!\/]+)(?:\/([^\[!]+))?(?:(!?)\[([^\]]+)\])?$/, 8883 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/, 8884 hasPatternsRegExp = /[*?+]/; 8885 8886 if (valid_elements) { 8887 // Split valid elements into an array with rules 8888 valid_elements = split(valid_elements, ','); 8889 8890 if (elements['@']) { 8891 globalAttributes = elements['@'].attributes; 8892 globalAttributesOrder = elements['@'].attributesOrder; 8893 } 8894 8895 // Loop all rules 8896 for (ei = 0, el = valid_elements.length; ei < el; ei++) { 8897 // Parse element rule 8898 matches = elementRuleRegExp.exec(valid_elements[ei]); 8899 if (matches) { 8900 // Setup local names for matches 8901 prefix = matches[1]; 8902 elementName = matches[2]; 8903 outputName = matches[3]; 8904 attrData = matches[5]; 8905 8906 // Create new attributes and attributesOrder 8907 attributes = {}; 8908 attributesOrder = []; 8909 8910 // Create the new element 8911 element = { 8912 attributes: attributes, 8913 attributesOrder: attributesOrder 8914 }; 8915 8916 // Padd empty elements prefix 8917 if (prefix === '#') { 8918 element.paddEmpty = true; 8919 } 8920 8921 // Remove empty elements prefix 8922 if (prefix === '-') { 8923 element.removeEmpty = true; 8924 } 8925 8926 if (matches[4] === '!') { 8927 element.removeEmptyAttrs = true; 8928 } 8929 8930 // Copy attributes from global rule into current rule 8931 if (globalAttributes) { 8932 for (key in globalAttributes) { 8933 attributes[key] = globalAttributes[key]; 8934 } 8935 8936 attributesOrder.push.apply(attributesOrder, globalAttributesOrder); 8937 } 8938 8939 // Attributes defined 8940 if (attrData) { 8941 attrData = split(attrData, '|'); 8942 for (ai = 0, al = attrData.length; ai < al; ai++) { 8943 matches = attrRuleRegExp.exec(attrData[ai]); 8944 if (matches) { 8945 attr = {}; 8946 attrType = matches[1]; 8947 attrName = matches[2].replace(/::/g, ':'); 8948 prefix = matches[3]; 8949 value = matches[4]; 8950 8951 // Required 8952 if (attrType === '!') { 8953 element.attributesRequired = element.attributesRequired || []; 8954 element.attributesRequired.push(attrName); 8955 attr.required = true; 8956 } 8957
8958 // Denied from global 8959 if (attrType === '-') { 8960 delete attributes[attrName]; 8961 attributesOrder.splice(inArray(attributesOrder, attrName), 1); 8962 continue; 8963 } 8964 8965 // Default value 8966 if (prefix) { 8967 // Default value 8968 if (prefix === '=') { 8969 element.attributesDefault = element.attributesDefault || []; 8970 element.attributesDefault.push({name: attrName, value: value}); 8971 attr.defaultValue = value; 8972 } 8973 8974 // Forced value 8975 if (prefix === ':') { 8976 element.attributesForced = element.attributesForced || []; 8977 element.attributesForced.push({name: attrName, value: value}); 8978 attr.forcedValue = value; 8979 } 8980 8981 // Required values 8982 if (prefix === '<') { 8983 attr.validValues = makeMap(value, '?'); 8984 } 8985 } 8986 8987 // Check for attribute patterns 8988 if (hasPatternsRegExp.test(attrName)) { 8989 element.attributePatterns = element.attributePatterns || []; 8990 attr.pattern = patternToRegExp(attrName); 8991 element.attributePatterns.push(attr); 8992 } else { 8993 // Add attribute to order list if it doesn't already exist 8994 if (!attributes[attrName]) { 8995 attributesOrder.push(attrName); 8996 } 8997 8998 attributes[attrName] = attr; 8999 } 9000 } 9001 } 9002 } 9003 9004 // Global rule, store away these for later usage 9005 if (!globalAttributes && elementName == '@') { 9006 globalAttributes = attributes; 9007 globalAttributesOrder = attributesOrder; 9008 } 9009 9010 // Handle substitute elements such as b/strong 9011 if (outputName) { 9012 element.outputName = elementName; 9013 elements[outputName] = element; 9014 } 9015 9016 // Add pattern or exact element 9017 if (hasPatternsRegExp.test(elementName)) { 9018 element.pattern = patternToRegExp(elementName); 9019 patternElements.push(element); 9020 } else { 9021 elements[elementName] = element; 9022 } 9023 } 9024 } 9025 } 9026 } 9027 9028 function setValidElements(valid_elements) { 9029 elements = {}; 9030 patternElements = []; 9031 9032 addValidElements(valid_elements); 9033 9034 each(schemaItems, function(element, name) { 9035 children[name] = element.children; 9036 }); 9037 } 9038 9039 // Adds custom non HTML elements to the schema 9040 function addCustomElements(custom_elements) { 9041 var customElementRegExp = /^(~)?(.+)$/; 9042 9043 if (custom_elements) { 9044 each(split(custom_elements, ','), function(rule) { 9045 var matches = customElementRegExp.exec(rule), 9046 inline = matches[1] === '~', 9047 cloneName = inline ? 'span' : 'div', 9048 name = matches[2]; 9049 9050 children[name] = children[cloneName]; 9051 customElementsMap[name] = cloneName; 9052 9053 // If it's not marked as inline then add it to valid block elements 9054 if (!inline) { 9055 blockElementsMap[name.toUpperCase()] = {}; 9056 blockElementsMap[name] = {}; 9057 } 9058 9059 // Add elements clone if needed 9060 if (!elements[name]) { 9061 var customRule = elements[cloneName]; 9062 9063 customRule = extend({}, customRule); 9064 delete customRule.removeEmptyAttrs; 9065 delete customRule.removeEmpty; 9066 9067 elements[name] = customRule; 9068 } 9069 9070 // Add custom elements at span/div positions 9071 each(children, function(element) { 9072 if (element[cloneName]) { 9073 element[name] = element[cloneName]; 9074 } 9075 }); 9076 }); 9077 } 9078 } 9079 9080 // Adds valid children to the schema object 9081 function addValidChildren(valid_children) { 9082 var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/; 9083 9084 if (valid_children) { 9085 each(split(valid_children, ','), function(rule) { 9086 var matches = childRuleRegExp.exec(rule), parent, prefix; 9087 9088 if (matches) { 9089 prefix = matches[1]; 9090 9091 // Add/remove items from default 9092 if (prefix) { 9093 parent = children[matches[2]]; 9094 } else { 9095 parent = children[matches[2]] = {'#comment': {}}; 9096 } 9097 9098 parent = children[matches[2]]; 9099 9100 each(split(matches[3], '|'), function(child) { 9101 if (prefix === '-') { 9102 delete parent[child]; 9103 } else { 9104 parent[child] = {}; 9105 } 9106 }); 9107 } 9108 }); 9109 } 9110 } 9111 9112 function getElementRule(name) { 9113 var element = elements[name], i; 9114 9115 // Exact match found 9116 if (element) { 9117 return element; 9118 } 9119 9120 // No exact match then try the patterns 9121 i = patternElements.length; 9122 while (i--) { 9123 element = patternElements[i]; 9124 9125 if (element.pattern.test(name)) { 9126 return element; 9127 } 9128 } 9129 } 9130
9131 if (!settings.valid_elements) { 9132 // No valid elements defined then clone the elements from the schema spec 9133 each(schemaItems, function(element, name) { 9134 elements[name] = { 9135 attributes: element.attributes, 9136 attributesOrder: element.attributesOrder 9137 }; 9138 9139 children[name] = element.children; 9140 }); 9141 9142 // Switch these on HTML4 9143 if (settings.schema != "html5") { 9144 each(split('strong/b em/i'), function(item) { 9145 item = split(item, '/'); 9146 elements[item[1]].outputName = item[0]; 9147 }); 9148 } 9149 9150 // Add default alt attribute for images 9151 elements.img.attributesDefault = [{name: 'alt', value: ''}]; 9152 9153 // Remove these if they are empty by default 9154 each(split('ol ul sub sup blockquote span font a table tbody tr strong em b i'), function(name) { 9155 if (elements[name]) { 9156 elements[name].removeEmpty = true; 9157 } 9158 }); 9159 9160 // Padd these by default 9161 each(split('p h1 h2 h3 h4 h5 h6 th td pre div address caption'), function(name) { 9162 elements[name].paddEmpty = true; 9163 }); 9164 9165 // Remove these if they have no attributes 9166 each(split('span'), function(name) { 9167 elements[name].removeEmptyAttrs = true; 9168 }); 9169 9170 // Remove these by default 9171 // TODO: Reenable in 4.1 9172 /*each(split('script style'), function(name) { 9173 delete elements[name]; 9174 });*/ 9175 } else { 9176 setValidElements(settings.valid_elements); 9177 } 9178 9179 addCustomElements(settings.custom_elements); 9180 addValidChildren(settings.valid_children); 9181 addValidElements(settings.extended_valid_elements); 9182 9183 // Todo: Remove this when we fix list handling to be valid 9184 addValidChildren('+ol[ul|ol],+ul[ul|ol]'); 9185 9186 // Delete invalid elements 9187 if (settings.invalid_elements) { 9188 each(explode(settings.invalid_elements), function(item) { 9189 if (elements[item]) { 9190 delete elements[item]; 9191 } 9192 }); 9193 } 9194 9195 // If the user didn't allow span only allow internal spans 9196 if (!getElementRule('span')) { 9197 addValidElements('span[!data-mce-type|*]'); 9198 } 9199 9200 /** 9201 * Name/value map object with valid parents and children to those parents. 9202 * 9203 * @example 9204 * children = { 9205 * div:{p:{}, h1:{}} 9206 * }; 9207 * @field children 9208 * @type Object 9209 */ 9210 self.children = children; 9211 9212 /** 9213 * Name/value map object with valid styles for each element. 9214 * 9215 * @field styles 9216 * @type Object 9217 */ 9218 self.styles = validStyles; 9219 9220 /** 9221 * Returns a map with boolean attributes. 9222 * 9223 * @method getBoolAttrs 9224 * @return {Object} Name/value lookup map for boolean attributes. 9225 */ 9226 self.getBoolAttrs = function() { 9227 return boolAttrMap; 9228 }; 9229 9230 /** 9231 * Returns a map with block elements. 9232 * 9233 * @method getBlockElements 9234 * @return {Object} Name/value lookup map for block elements. 9235 */ 9236 self.getBlockElements = function() { 9237 return blockElementsMap; 9238 }; 9239 9240 /** 9241 * Returns a map with text block elements. Such as: p,h1-h6,div,address 9242 * 9243 * @method getTextBlockElements 9244 * @return {Object} Name/value lookup map for block elements. 9245 */ 9246 self.getTextBlockElements = function() { 9247 return textBlockElementsMap; 9248 }; 9249 9250 /** 9251 * Returns a map with short ended elements such as BR or IMG. 9252 * 9253 * @method getShortEndedElements 9254 * @return {Object} Name/value lookup map for short ended elements. 9255 */ 9256 self.getShortEndedElements = function() { 9257 return shortEndedElementsMap; 9258 }; 9259 9260 /** 9261 * Returns a map with self closing tags such as <li>. 9262 * 9263 * @method getSelfClosingElements 9264 * @return {Object} Name/value lookup map for self closing tags elements. 9265 */ 9266 self.getSelfClosingElements = function() { 9267 return selfClosingElementsMap; 9268 }; 9269 9270 /** 9271 * Returns a map with elements that should be treated as contents regardless if it has text 9272 * content in them or not such as TD, VIDEO or IMG. 9273 * 9274 * @method getNonEmptyElements 9275 * @return {Object} Name/value lookup map for non empty elements. 9276 */ 9277 self.getNonEmptyElements = function() { 9278 return nonEmptyElementsMap; 9279 }; 9280 9281 /** 9282 * Returns a map with elements where white space is to be preserved like PRE or SCRIPT. 9283 * 9284 * @method getWhiteSpaceElements 9285 * @return {Object} Name/value lookup map for white space elements. 9286 */ 9287 self.getWhiteSpaceElements = function() { 9288 return whiteSpaceElementsMap; 9289 }; 9290 9291 /** 9292 * Returns a map with special elements. These are elements that needs to be parsed 9293 * in a special way such as script, style, textarea etc. The map object values 9294 * are regexps used to find the end of the element. 9295 * 9296 * @method getSpecialElements 9297 * @return {Object} Name/value lookup map for special elements. 9298 */ 9299 self.getSpecialElements = function() { 9300 return specialElements; 9301 }; 9302 9303 /** 9304 * Returns true/false if the specified element and it's child is valid or not 9305 * according to the schema. 9306 * 9307 * @method isValidChild 9308 * @param {String} name Element name to check for. 9309 * @param {String} child Element child to verify. 9310 * @return {Boolean} True/false if the element is a valid child of the specified parent. 9311 */ 9312 self.isValidChild = function(name, child) { 9313 var parent = children[name]; 9314 9315 return !!(parent && parent[child]); 9316 }; 9317 9318 /** 9319 * Returns true/false if the specified element name and optional attribute is 9320 * valid according to the schema. 9321 * 9322 * @method isValid 9323 * @param {String} name Name of element to check. 9324 * @param {String} attr Optional attribute name to check for. 9325 * @return {Boolean} True/false if the element and attribute is valid. 9326 */ 9327 self.isValid = function(name, attr) { 9328 var attrPatterns, i, rule = getElementRule(name); 9329 9330 // Check if it's a valid element 9331 if (rule) { 9332 if (attr) { 9333 // Check if attribute name exists 9334 if (rule.attributes[attr]) { 9335 return true; 9336 } 9337 9338 // Check if attribute matches a regexp pattern 9339 attrPatterns = rule.attributePatterns; 9340 if (attrPatterns) { 9341 i = attrPatterns.length; 9342 while (i--) { 9343 if (attrPatterns[i].pattern.test(name)) { 9344 return true; 9345 } 9346 } 9347 } 9348 } else { 9349 return true; 9350 } 9351 } 9352 9353 // No match 9354 return false;
9355 }; 9356 9357 /** 9358 * Returns true/false if the specified element is valid or not 9359 * according to the schema. 9360 * 9361 * @method getElementRule 9362 * @param {String} name Element name to check for. 9363 * @return {Object} Element object or undefined if the element isn't valid. 9364 */ 9365 self.getElementRule = getElementRule; 9366 9367 /** 9368 * Returns an map object of all custom elements. 9369 * 9370 * @method getCustomElements 9371 * @return {Object} Name/value map object of all custom elements. 9372 */ 9373 self.getCustomElements = function() { 9374 return customElementsMap; 9375 }; 9376 9377 /** 9378 * Parses a valid elements string and adds it to the schema. The valid elements 9379 format is for example "element[attr=default|otherattr]". 9380 * Existing rules will be replaced with the ones specified, so this extends the schema. 9381 * 9382 * @method addValidElements 9383 * @param {String} valid_elements String in the valid elements format to be parsed. 9384 */ 9385 self.addValidElements = addValidElements; 9386 9387 /** 9388 * Parses a valid elements string and sets it to the schema. The valid elements 9389 * format is for example "element[attr=default|otherattr]". 9390 * Existing rules will be replaced with the ones specified, so this extends the schema. 9391 * 9392 * @method setValidElements 9393 * @param {String} valid_elements String in the valid elements format to be parsed. 9394 */ 9395 self.setValidElements = setValidElements; 9396 9397 /** 9398 * Adds custom non HTML elements to the schema. 9399 * 9400 * @method addCustomElements 9401 * @param {String} custom_elements Comma separated list of custom elements to add. 9402 */ 9403 self.addCustomElements = addCustomElements; 9404 9405 /** 9406 * Parses a valid children string and adds them to the schema structure. The valid children 9407 * format is for example: "element[child1|child2]". 9408 * 9409 * @method addValidChildren 9410 * @param {String} valid_children Valid children elements string to parse 9411 */ 9412 self.addValidChildren = addValidChildren; 9413 9414 self.elements = elements; 9415 }; 9416}); 9417 9418// Included from: js/tinymce/classes/html/SaxParser.js 9419 9420/** 9421 * SaxParser.js 9422 * 9423 * Copyright, Moxiecode Systems AB 9424 * Released under LGPL License. 9425 * 9426 * License: http://www.tinymce.com/license 9427 * Contributing: http://www.tinymce.com/contributing 9428 */ 9429 9430/** 9431 * This class parses HTML code using pure JavaScript and executes various events for each item it finds. It will 9432 * always execute the events in the right order for tag soup code like <b><p></b></p>. It will also remove elements 9433 * and attributes that doesn't fit the schema if the validate setting is enabled. 9434 * 9435 * @example 9436 * var parser = new tinymce.html.SaxParser({ 9437 * validate: true, 9438 * 9439 * comment: function(text) { 9440 * console.log('Comment:', text); 9441 * }, 9442 * 9443 * cdata: function(text) { 9444 * console.log('CDATA:', text); 9445 * }, 9446 * 9447 * text: function(text, raw) { 9448 * console.log('Text:', text, 'Raw:', raw); 9449 * }, 9450 * 9451 * start: function(name, attrs, empty) { 9452 * console.log('Start:', name, attrs, empty); 9453 * }, 9454 * 9455 * end: function(name) { 9456 * console.log('End:', name); 9457 * }, 9458 * 9459 * pi: function(name, text) { 9460 * console.log('PI:', name, text); 9461 * }, 9462 * 9463 * doctype: function(text) { 9464 * console.log('DocType:', text); 9465 * } 9466 * }, schema); 9467 * @class tinymce.html.SaxParser 9468 * @version 3.4 9469 */ 9470define("tinymce/html/SaxParser", [ 9471 "tinymce/html/Schema", 9472 "tinymce/html/Entities", 9473 "tinymce/util/Tools" 9474], function(Schema, Entities, Tools) { 9475 var each = Tools.each; 9476 9477 /** 9478 * Constructs a new SaxParser instance. 9479 * 9480 * @constructor 9481 * @method SaxParser 9482 * @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks. 9483 * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing. 9484 */ 9485 return function(settings, schema) { 9486 var self = this, noop = function() {}; 9487 9488 settings = settings || {}; 9489 self.schema = schema = schema || new Schema(); 9490 9491 if (settings.fix_self_closing !== false) { 9492 settings.fix_self_closing = true; 9493 } 9494 9495 // Add handler functions from settings and setup default handlers 9496 each('comment cdata text start end pi doctype'.split(' '), function(name) { 9497 if (name) { 9498 self[name] = settings[name] || noop; 9499 } 9500 }); 9501 9502 /** 9503 * Parses the specified HTML string and executes the callbacks for each item it finds. 9504 * 9505 * @example 9506 * new SaxParser({...}).parse('<b>text</b>'); 9507 * @method parse 9508 * @param {String} html Html string to sax parse. 9509 */ 9510 self.parse = function(html) {
9511 var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name; 9512 var isInternalElement, removeInternalElements, shortEndedElements, fillAttrsMap, isShortEnded; 9513 var validate, elementRule, isValidElement, attr, attribsValue, validAttributesMap, validAttributePatterns; 9514 var attributesRequired, attributesDefault, attributesForced; 9515 var anyAttributesRequired, selfClosing, tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0; 9516 var decode = Entities.decode, fixSelfClosing, filteredUrlAttrs = Tools.makeMap('src,href'); 9517 var scriptUriRegExp = /(java|vb)script:/i; 9518 9519 function processEndTag(name) { 9520 var pos, i; 9521 9522 // Find position of parent of the same type 9523 pos = stack.length; 9524 while (pos--) { 9525 if (stack[pos].name === name) { 9526 break; 9527 } 9528 } 9529 9530 // Found parent 9531 if (pos >= 0) { 9532 // Close all the open elements 9533 for (i = stack.length - 1; i >= pos; i--) { 9534 name = stack[i]; 9535 9536 if (name.valid) { 9537 self.end(name.name); 9538 } 9539 } 9540 9541 // Remove the open elements from the stack 9542 stack.length = pos; 9543 } 9544 } 9545 9546 function parseAttribute(match, name, value, val2, val3) { 9547 var attrRule, i, trimRegExp = /[\s\u0000-\u001F]+/g; 9548 9549 name = name.toLowerCase(); 9550 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute 9551 9552 // Validate name and value pass through all data- attributes 9553 if (validate && !isInternalElement && name.indexOf('data-') !== 0) { 9554 attrRule = validAttributesMap[name]; 9555 9556 // Find rule by pattern matching 9557 if (!attrRule && validAttributePatterns) { 9558 i = validAttributePatterns.length; 9559 while (i--) { 9560 attrRule = validAttributePatterns[i]; 9561 if (attrRule.pattern.test(name)) { 9562 break; 9563 } 9564 } 9565 9566 // No rule matched 9567 if (i === -1) { 9568 attrRule = null; 9569 } 9570 } 9571 9572 // No attribute rule found 9573 if (!attrRule) { 9574 return; 9575 } 9576 9577 // Validate value 9578 if (attrRule.validValues && !(value in attrRule.validValues)) { 9579 return; 9580 } 9581 } 9582 9583 // Block any javascript: urls 9584 if (filteredUrlAttrs[name] && !settings.allow_script_urls) { 9585 var uri = value.replace(trimRegExp, ''); 9586 9587 try { 9588 // Might throw malformed URI sequence 9589 uri = decodeURIComponent(uri); 9590 if (scriptUriRegExp.test(uri)) { 9591 return; 9592 } 9593 } catch (ex) { 9594 // Fallback to non UTF-8 decoder 9595 uri = unescape(uri); 9596 if (scriptUriRegExp.test(uri)) { 9597 return; 9598 } 9599 } 9600 } 9601 9602 // Add attribute to list and map 9603 attrList.map[name] = value; 9604 attrList.push({ 9605 name: name, 9606 value: value 9607 }); 9608 } 9609 9610 // Precompile RegExps and map objects 9611 tokenRegExp = new RegExp('<(?:' + 9612 '(?:!--([\\w\\W]*?)-->)|' + // Comment 9613 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA 9614 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE 9615 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI 9616 '(?:\\/([^>]+)>)|' + // End element 9617 '(?:([A-Za-z0-9\\-\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element 9618 ')', 'g'); 9619 9620 attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g; 9621 9622 // Setup lookup tables for empty elements and boolean attributes 9623 shortEndedElements = schema.getShortEndedElements(); 9624 selfClosing = settings.self_closing_elements || schema.getSelfClosingElements(); 9625 fillAttrsMap = schema.getBoolAttrs(); 9626 validate = settings.validate; 9627 removeInternalElements = settings.remove_internals; 9628 fixSelfClosing = settings.fix_self_closing; 9629 specialElements = schema.getSpecialElements(); 9630 9631 while ((matches = tokenRegExp.exec(html))) { 9632 // Text 9633 if (index < matches.index) { 9634 self.text(decode(html.substr(index, matches.index - index))); 9635 } 9636 9637 if ((value = matches[6])) { // End element 9638 value = value.toLowerCase(); 9639 9640 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements 9641 if (value.charAt(0) === ':') { 9642 value = value.substr(1); 9643 } 9644 9645 processEndTag(value); 9646 } else if ((value = matches[7])) { // Start element 9647 value = value.toLowerCase(); 9648 9649 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements 9650 if (value.charAt(0) === ':') { 9651 value = value.substr(1); 9652 } 9653 9654 isShortEnded = value in shortEndedElements; 9655 9656 // Is self closing tag for example an <li> after an open <li> 9657 if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) { 9658 processEndTag(value); 9659 } 9660 9661 // Validate element 9662 if (!validate || (elementRule = schema.getElementRule(value))) { 9663 isValidElement = true; 9664 9665 // Grab attributes map and patters when validation is enabled 9666 if (validate) { 9667 validAttributesMap = elementRule.attributes; 9668 validAttributePatterns = elementRule.attributePatterns; 9669 } 9670 9671 // Parse attributes 9672 if ((attribsValue = matches[8])) { 9673 isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element 9674 9675 // If the element has internal attributes then remove it if we are told to do so 9676 if (isInternalElement && removeInternalElements) { 9677 isValidElement = false;
9678 } 9679 9680 attrList = []; 9681 attrList.map = {}; 9682 9683 attribsValue.replace(attrRegExp, parseAttribute); 9684 } else { 9685 attrList = []; 9686 attrList.map = {}; 9687 } 9688 9689 // Process attributes if validation is enabled 9690 if (validate && !isInternalElement) { 9691 attributesRequired = elementRule.attributesRequired; 9692 attributesDefault = elementRule.attributesDefault; 9693 attributesForced = elementRule.attributesForced; 9694 anyAttributesRequired = elementRule.removeEmptyAttrs; 9695 9696 // Check if any attribute exists 9697 if (anyAttributesRequired && !attrList.length) { 9698 isValidElement = false; 9699 } 9700 9701 // Handle forced attributes 9702 if (attributesForced) { 9703 i = attributesForced.length; 9704 while (i--) { 9705 attr = attributesForced[i]; 9706 name = attr.name; 9707 attrValue = attr.value; 9708 9709 if (attrValue === '{$uid}') { 9710 attrValue = 'mce_' + idCount++; 9711 } 9712 9713 attrList.map[name] = attrValue; 9714 attrList.push({name: name, value: attrValue}); 9715 } 9716 } 9717 9718 // Handle default attributes 9719 if (attributesDefault) { 9720 i = attributesDefault.length; 9721 while (i--) { 9722 attr = attributesDefault[i]; 9723 name = attr.name; 9724 9725 if (!(name in attrList.map)) { 9726 attrValue = attr.value; 9727 9728 if (attrValue === '{$uid}') { 9729 attrValue = 'mce_' + idCount++; 9730 } 9731 9732 attrList.map[name] = attrValue; 9733 attrList.push({name: name, value: attrValue}); 9734 } 9735 } 9736 } 9737 9738 // Handle required attributes 9739 if (attributesRequired) { 9740 i = attributesRequired.length; 9741 while (i--) { 9742 if (attributesRequired[i] in attrList.map) { 9743 break; 9744 } 9745 } 9746 9747 // None of the required attributes where found 9748 if (i === -1) { 9749 isValidElement = false; 9750 } 9751 } 9752 9753 // Invalidate element if it's marked as bogus 9754 if (attrList.map['data-mce-bogus']) { 9755 isValidElement = false; 9756 } 9757 } 9758 9759 if (isValidElement) { 9760 self.start(value, attrList, isShortEnded); 9761 } 9762 } else { 9763 isValidElement = false; 9764 } 9765 9766 // Treat script, noscript and style a bit different since they may include code that looks like elements 9767 if ((endRegExp = specialElements[value])) { 9768 endRegExp.lastIndex = index = matches.index + matches[0].length; 9769 9770 if ((matches = endRegExp.exec(html))) { 9771 if (isValidElement) { 9772 text = html.substr(index, matches.index - index); 9773 } 9774 9775 index = matches.index + matches[0].length; 9776 } else { 9777 text = html.substr(index); 9778 index = html.length; 9779 } 9780 9781 if (isValidElement) { 9782 if (text.length > 0) { 9783 self.text(text, true); 9784 } 9785 9786 self.end(value); 9787 } 9788 9789 tokenRegExp.lastIndex = index; 9790 continue; 9791 } 9792 9793 // Push value on to stack 9794 if (!isShortEnded) { 9795 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) { 9796 stack.push({name: value, valid: isValidElement}); 9797 } else if (isValidElement) { 9798 self.end(value); 9799 } 9800 } 9801 } else if ((value = matches[1])) { // Comment 9802 // Padd comment value to avoid browsers from parsing invalid comments as HTML 9803 if (value.charAt(0) === '>') { 9804 value = ' ' + value; 9805 } 9806 9807 if (!settings.allow_conditional_comments && value.substr(0, 3) === '[if') { 9808 value = ' ' + value; 9809 } 9810 9811 self.comment(value); 9812 } else if ((value = matches[2])) { // CDATA 9813 self.cdata(value); 9814 } else if ((value = matches[3])) { // DOCTYPE 9815 self.doctype(value); 9816 } else if ((value = matches[4])) { // PI 9817 self.pi(value, matches[5]); 9818 } 9819 9820 index = matches.index + matches[0].length; 9821 } 9822 9823 // Text 9824 if (index < html.length) { 9825 self.text(decode(html.substr(index))); 9826 } 9827 9828 // Close any open elements 9829 for (i = stack.length - 1; i >= 0; i--) { 9830 value = stack[i]; 9831 9832 if (value.valid) { 9833 self.end(value.name); 9834 } 9835 } 9836 }; 9837 }; 9838}); 9839 9840// Included from: js/tinymce/classes/html/DomParser.js 9841 9842/** 9843 * DomParser.js 9844 * 9845 * Copyright, Moxiecode Systems AB 9846 * Released under LGPL License. 9847 * 9848 * License: http://www.tinymce.com/license 9849 * Contributing: http://www.tinymce.com/contributing 9850 */ 9851 9852/** 9853 * This class parses HTML code into a DOM like structure of nodes it will remove redundant whitespace and make 9854 * sure that the node tree is valid according to the specified schema. 9855 * So for example: <p>a<p>b</p>c</p> will become <p>a</p><p>b</p><p>c</p> 9856 * 9857 * @example 9858 * var parser = new tinymce.html.DomParser({validate: true}, schema); 9859 * var rootNode = parser.parse('<h1>content</h1>'); 9860 * 9861 * @class tinymce.html.DomParser 9862 * @version 3.4 9863 */ 9864define("tinymce/html/DomParser", [ 9865 "tinymce/html/Node", 9866 "tinymce/html/Schema", 9867 "tinymce/html/SaxParser", 9868 "tinymce/util/Tools" 9869], function(Node, Schema, SaxParser, Tools) { 9870 var makeMap = Tools.makeMap, each = Tools.each, explode = Tools.explode, extend = Tools.extend; 9871 9872 /** 9873 * Constructs a new DomParser instance. 9874 * 9875 * @constructor 9876 * @method DomParser 9877 * @param {Object}
9877 settings Name/value collection of settings. comment, cdata, text, start and end are callbacks. 9878 * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing. 9879 */ 9880 return function(settings, schema) { 9881 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {}; 9882 9883 settings = settings || {}; 9884 settings.validate = "validate" in settings ? settings.validate : true; 9885 settings.root_name = settings.root_name || 'body'; 9886 self.schema = schema = schema || new Schema(); 9887 9888 function fixInvalidChildren(nodes) { 9889 var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i; 9890 var nonEmptyElements, nonSplitableElements, textBlockElements, sibling, nextNode; 9891 9892 nonSplitableElements = makeMap('tr,td,th,tbody,thead,tfoot,table'); 9893 nonEmptyElements = schema.getNonEmptyElements(); 9894 textBlockElements = schema.getTextBlockElements(); 9895 9896 for (ni = 0; ni < nodes.length; ni++) { 9897 node = nodes[ni]; 9898 9899 // Already removed or fixed 9900 if (!node.parent || node.fixed) { 9901 continue; 9902 } 9903 9904 // If the invalid element is a text block and the text block is within a parent LI element 9905 // Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office 9906 if (textBlockElements[node.name] && node.parent.name == 'li') { 9907 // Move sibling text blocks after LI element 9908 sibling = node.next; 9909 while (sibling) { 9910 if (textBlockElements[sibling.name]) { 9911 sibling.name = 'li'; 9912 sibling.fixed = true; 9913 node.parent.insert(sibling, node.parent); 9914 } else { 9915 break; 9916 } 9917 9918 sibling = sibling.next; 9919 } 9920 9921 // Unwrap current text block 9922 node.unwrap(node); 9923 continue; 9924 } 9925 9926 // Get list of all parent nodes until we find a valid parent to stick the child into 9927 parents = [node]; 9928 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && 9929 !nonSplitableElements[parent.name]; parent = parent.parent) { 9930 parents.push(parent); 9931 } 9932 9933 // Found a suitable parent 9934 if (parent && parents.length > 1) { 9935 // Reverse the array since it makes looping easier 9936 parents.reverse(); 9937 9938 // Clone the related parent and insert that after the moved node 9939 newParent = currentNode = self.filterNode(parents[0].clone()); 9940 9941 // Start cloning and moving children on the left side of the target node 9942 for (i = 0; i < parents.length - 1; i++) { 9943 if (schema.isValidChild(currentNode.name, parents[i].name)) { 9944 tempNode = self.filterNode(parents[i].clone()); 9945 currentNode.append(tempNode); 9946 } else { 9947 tempNode = currentNode; 9948 } 9949 9950 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) { 9951 nextNode = childNode.next; 9952 tempNode.append(childNode); 9953 childNode = nextNode; 9954 } 9955 9956 currentNode = tempNode; 9957 } 9958 9959 if (!newParent.isEmpty(nonEmptyElements)) { 9960 parent.insert(newParent, parents[0], true); 9961 parent.insert(node, newParent); 9962 } else { 9963 parent.insert(node, parents[0], true); 9964 } 9965 9966 // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p> 9967 parent = parents[0]; 9968 if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') { 9969 parent.empty().remove(); 9970 } 9971 } else if (node.parent) { 9972 // If it's an LI try to find a UL/OL for it or wrap it 9973 if (node.name === 'li') { 9974 sibling = node.prev; 9975 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { 9976 sibling.append(node); 9977 continue; 9978 } 9979 9980 sibling = node.next; 9981 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { 9982 sibling.insert(node, sibling.firstChild, true); 9983 continue; 9984 } 9985 9986 node.wrap(self.filterNode(new Node('ul', 1))); 9987 continue; 9988 } 9989 9990 // Try wrapping the element in a DIV 9991 if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) { 9992 node.wrap(self.filterNode(new Node('div', 1))); 9993 } else { 9994 // We failed wrapping it, then remove or unwrap it 9995 if (node.name === 'style' || node.name === 'script') { 9996 node.empty().remove(); 9997 } else { 9998 node.unwrap(); 9999 } 10000 } 10001 } 10002 } 10003 } 10004 10005 /** 10006 * Runs the specified node though the element and attributes filters. 10007 * 10008 * @method filterNode 10009 * @param {tinymce.html.Node} Node the node to run filters on. 10010 * @return {tinymce.html.Node} The passed in node. 10011 */ 10012 self.filterNode = function(node) { 10013 var i, name, list; 10014 10015 // Run element filters 10016 if (name in nodeFilters) { 10017 list = matchedNodes[name]; 10018 10019 if (list) { 10020 list.push(node); 10021 } else { 10022 matchedNodes[name] = [node]; 10023 } 10024 } 10025 10026 // Run attribute filters 10027 i = attributeFilters.length; 10028 while (i--) { 10029 name = attributeFilters[i].name; 10030 10031 if (name in node.attributes.map) { 10032 list = matchedAttributes[name]; 10033 10034 if (list) { 10035 list.push(node); 10036 } else { 10037 matchedAttributes[name] = [node]; 10038 } 10039 } 10040 } 10041 10042 return node; 10043 }; 10044 10045 /**
10046 * Adds a node filter function to the parser, the parser will collect the specified nodes by name 10047 * and then execute the callback ones it has finished parsing the document. 10048 * 10049 * @example 10050 * parser.addNodeFilter('p,h1', function(nodes, name) { 10051 * for (var i = 0; i < nodes.length; i++) { 10052 * console.log(nodes[i].name); 10053 * } 10054 * }); 10055 * @method addNodeFilter 10056 * @method {String} name Comma separated list of nodes to collect. 10057 * @param {function} callback Callback function to execute once it has collected nodes. 10058 */ 10059 self.addNodeFilter = function(name, callback) { 10060 each(explode(name), function(name) { 10061 var list = nodeFilters[name]; 10062 10063 if (!list) { 10064 nodeFilters[name] = list = []; 10065 } 10066 10067 list.push(callback); 10068 }); 10069 }; 10070 10071 /** 10072 * Adds a attribute filter function to the parser, the parser will collect nodes that has the specified attributes 10073 * and then execute the callback ones it has finished parsing the document. 10074 * 10075 * @example 10076 * parser.addAttributeFilter('src,href', function(nodes, name) { 10077 * for (var i = 0; i < nodes.length; i++) { 10078 * console.log(nodes[i].name); 10079 * } 10080 * }); 10081 * @method addAttributeFilter 10082 * @method {String} name Comma separated list of nodes to collect. 10083 * @param {function} callback Callback function to execute once it has collected nodes. 10084 */ 10085 self.addAttributeFilter = function(name, callback) { 10086 each(explode(name), function(name) { 10087 var i; 10088 10089 for (i = 0; i < attributeFilters.length; i++) { 10090 if (attributeFilters[i].name === name) { 10091 attributeFilters[i].callbacks.push(callback); 10092 return; 10093 } 10094 } 10095 10096 attributeFilters.push({name: name, callbacks: [callback]}); 10097 }); 10098 }; 10099 10100 /** 10101 * Parses the specified HTML string into a DOM like node tree and returns the result. 10102 * 10103 * @example 10104 * var rootNode = new DomParser({...}).parse('<b>text</b>'); 10105 * @method parse 10106 * @param {String} html Html string to sax parse. 10107 * @param {Object} args Optional args object that gets passed to all filter functions. 10108 * @return {tinymce.html.Node} Root node containing the tree. 10109 */ 10110 self.parse = function(html, args) { 10111 var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate; 10112 var blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement; 10113 var endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements; 10114 var children, nonEmptyElements, rootBlockName; 10115 10116 args = args || {}; 10117 matchedNodes = {}; 10118 matchedAttributes = {}; 10119 blockElements = extend(makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements()); 10120 nonEmptyElements = schema.getNonEmptyElements(); 10121 children = schema.children; 10122 validate = settings.validate; 10123 rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block; 10124 10125 whiteSpaceElements = schema.getWhiteSpaceElements(); 10126 startWhiteSpaceRegExp = /^[ \t\r\n]+/; 10127 endWhiteSpaceRegExp = /[ \t\r\n]+$/; 10128 allWhiteSpaceRegExp = /[ \t\r\n]+/g; 10129 isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/; 10130 10131 function addRootBlocks() { 10132 var node = rootNode.firstChild, next, rootBlockNode; 10133 10134 // Removes whitespace at beginning and end of block so: 10135 // <p> x </p> -> <p>x</p> 10136 function trim(rootBlockNode) { 10137 if (rootBlockNode) { 10138 node = rootBlockNode.firstChild; 10139 if (node && node.type == 3) { 10140 node.value = node.value.replace(startWhiteSpaceRegExp, ''); 10141 } 10142 10143 node = rootBlockNode.lastChild; 10144 if (node && node.type == 3) { 10145 node.value = node.value.replace(endWhiteSpaceRegExp, ''); 10146 } 10147 } 10148 } 10149 10150 // Check if rootBlock is valid within rootNode for example if P is valid in H1 if H1 is the contentEditabe root 10151 if (!schema.isValidChild(rootNode.name, rootBlockName.toLowerCase())) { 10152 return; 10153 } 10154 10155 while (node) { 10156 next = node.next; 10157 10158 if (node.type == 3 || (node.type == 1 && node.name !== 'p' && 10159 !blockElements[node.name] && !node.attr('data-mce-type'))) { 10160 if (!rootBlockNode) { 10161 // Create a new root block element 10162 rootBlockNode = createNode(rootBlockName, 1); 10163 rootBlockNode.attr(settings.forced_root_block_attrs); 10164 rootNode.insert(rootBlockNode, node); 10165 rootBlockNode.append(node); 10166 } else { 10167 rootBlockNode.append(node); 10168 } 10169 } else { 10170 trim(rootBlockNode); 10171 rootBlockNode = null; 10172 } 10173 10174 node = next; 10175 } 10176 10177 trim(rootBlockNode); 10178 } 10179 10180 function createNode(name, type) { 10181 var node = new Node(name, type), list; 10182 10183 if (name in nodeFilters) { 10184 list = matchedNodes[name]; 10185 10186 if (list) { 10187 list.push(node); 10188 } else { 10189 matchedNodes[name] = [node]; 10190 } 10191 } 10192 10193 return node; 10194 } 10195 10196 function removeWhitespaceBefore(node) { 10197 var textNode, textVal, sibling; 10198 10199 for (textNode = node.prev; textNode && textNode.type === 3; ) { 10200 textVal = textNode.value.replace(endWhiteSpaceRegExp, ''); 10201 10202 if (textVal.length > 0) { 10203 textNode.value = textVal; 10204 textNode = textNode.prev; 10205 } else { 10206 sibling = textNode.prev; 10207 textNode.remove(); 10208 textNode = sibling; 10209 } 10210 } 10211 } 10212 10213 function cloneAndExcludeBlocks(input) { 10214 var name, output = {}; 10215 10216 for (name in input) { 10217 if (name !== 'li' && name != 'p') { 10218 output[name] = input[name]; 10219 } 10220 } 10221 10222 return output; 10223 } 10224 10225 parser = new SaxParser({ 10226 validate: validate, 10227 allow_script_urls: settings.allow_script_urls, 10228 allow_conditional_comments: settings.allow_conditional_comments, 10229 10230 // Exclude P and LI from DOM parsing since it's treated better by the DOM parser 10231 self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()), 10232 10233 cdata: function(text) { 10234 node.append(createNode('#cdata', 4)).value = text; 10235 }, 10236 10237 text: function(text, raw) { 10238 var textNode; 10239 10240 // Trim all redundant whitespace on non white space elements 10241 if (!isInWhiteSpacePreservedElement) { 10242 text = text.replace(allWhiteSpaceRegExp, ' '); 10243 10244 if (node.lastChild && blockElements[node.lastChild.name]) { 10245 text = text.replace(startWhiteSpaceRegExp, ''); 10246 } 10247 } 10248 10249 // Do we need to create the node 10250 if (text.length !== 0) { 10251 textNode = createNode('#text', 3); 10252 textNode.raw = !!raw; 10253 node.append(textNode).value = text; 10254 } 10255 }, 10256 10257 comment: function(text) { 10258 node.append(createNode('#comment', 8)).value = text; 10259 }, 10260 10261 pi: function(name, text) { 10262 node.append(createNode(name, 7)).value = text; 10263 removeWhitespaceBefore(node); 10264 }, 10265 10266 doctype: function(text) { 10267 var newNode; 10268 10269 newNode = node.append(createNode('#doctype', 10)); 10270 newNode.value = text; 10271 removeWhitespaceBefore(node); 10272 }, 10273 10274 start: function(name, attrs, empty) { 10275 var newNode, attrFiltersLen, elementRule, attrName, parent; 10276 10277 elementRule = validate ? schema.getElementRule(name) : {}; 10278 if (elementRule) {
10279 newNode = createNode(elementRule.outputName || name, 1); 10280 newNode.attributes = attrs; 10281 newNode.shortEnded = empty; 10282 10283 node.append(newNode); 10284 10285 // Check if node is valid child of the parent node is the child is 10286 // unknown we don't collect it since it's probably a custom element 10287 parent = children[node.name]; 10288 if (parent && children[newNode.name] && !parent[newNode.name]) { 10289 invalidChildren.push(newNode); 10290 } 10291 10292 attrFiltersLen = attributeFilters.length; 10293 while (attrFiltersLen--) { 10294 attrName = attributeFilters[attrFiltersLen].name; 10295 10296 if (attrName in attrs.map) { 10297 list = matchedAttributes[attrName]; 10298 10299 if (list) { 10300 list.push(newNode); 10301 } else { 10302 matchedAttributes[attrName] = [newNode]; 10303 } 10304 } 10305 } 10306 10307 // Trim whitespace before block 10308 if (blockElements[name]) { 10309 removeWhitespaceBefore(newNode); 10310 } 10311 10312 // Change current node if the element wasn't empty i.e not <br /> or <img /> 10313 if (!empty) { 10314 node = newNode; 10315 } 10316 10317 // Check if we are inside a whitespace preserved element 10318 if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { 10319 isInWhiteSpacePreservedElement = true; 10320 } 10321 } 10322 }, 10323 10324 end: function(name) { 10325 var textNode, elementRule, text, sibling, tempNode; 10326 10327 elementRule = validate ? schema.getElementRule(name) : {}; 10328 if (elementRule) { 10329 if (blockElements[name]) { 10330 if (!isInWhiteSpacePreservedElement) { 10331 // Trim whitespace of the first node in a block 10332 textNode = node.firstChild; 10333 if (textNode && textNode.type === 3) { 10334 text = textNode.value.replace(startWhiteSpaceRegExp, ''); 10335 10336 // Any characters left after trim or should we remove it 10337 if (text.length > 0) { 10338 textNode.value = text; 10339 textNode = textNode.next; 10340 } else { 10341 sibling = textNode.next; 10342 textNode.remove(); 10343 textNode = sibling; 10344 10345 // Remove any pure whitespace siblings 10346 while (textNode && textNode.type === 3) { 10347 text = textNode.value; 10348 sibling = textNode.next; 10349 10350 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { 10351 textNode.remove(); 10352 textNode = sibling; 10353 } 10354 10355 textNode = sibling; 10356 } 10357 } 10358 } 10359 10360 // Trim whitespace of the last node in a block 10361 textNode = node.lastChild; 10362 if (textNode && textNode.type === 3) { 10363 text = textNode.value.replace(endWhiteSpaceRegExp, ''); 10364 10365 // Any characters left after trim or should we remove it 10366 if (text.length > 0) { 10367 textNode.value = text; 10368 textNode = textNode.prev; 10369 } else { 10370 sibling = textNode.prev; 10371 textNode.remove(); 10372 textNode = sibling; 10373 10374 // Remove any pure whitespace siblings 10375 while (textNode && textNode.type === 3) { 10376 text = textNode.value; 10377 sibling = textNode.prev; 10378 10379 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { 10380 textNode.remove(); 10381 textNode = sibling; 10382 } 10383 10384 textNode = sibling; 10385 } 10386 } 10387 } 10388 } 10389 10390 // Trim start white space 10391 // Removed due to: #5424 10392 /*textNode = node.prev; 10393 if (textNode && textNode.type === 3) { 10394 text = textNode.value.replace(startWhiteSpaceRegExp, ''); 10395 10396 if (text.length > 0) 10397 textNode.value = text; 10398 else 10399 textNode.remove(); 10400 }*/ 10401 } 10402 10403 // Check if we exited a whitespace preserved element 10404 if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { 10405 isInWhiteSpacePreservedElement = false; 10406 } 10407 10408 // Handle empty nodes 10409 if (elementRule.removeEmpty || elementRule.paddEmpty) { 10410 if (node.isEmpty(nonEmptyElements)) { 10411 if (elementRule.paddEmpty) { 10412 node.empty().append(new Node('#text', '3')).value = '\u00a0'; 10413 } else { 10414 // Leave nodes that have a name like <a name="name"> 10415 if (!node.attributes.map.name && !node.attributes.map.id) { 10416 tempNode = node.parent; 10417 node.empty().remove(); 10418 node = tempNode; 10419 return; 10420 } 10421 } 10422 } 10423 } 10424 10425 node = node.parent; 10426 } 10427 } 10428 }, schema); 10429 10430 rootNode = node = new Node(args.context || settings.root_name, 11); 10431 10432 parser.parse(html); 10433 10434 // Fix invalid children or report invalid children in a contextual parsing 10435 if (validate && invalidChildren.length) { 10436 if (!args.context) { 10437 fixInvalidChildren(invalidChildren); 10438 } else { 10439 args.invalid = true; 10440 } 10441 } 10442 10443 // Wrap nodes in the root into block elements if the root is body 10444 if (rootBlockName && (rootNode.name == 'body' || args.isRootContent)) { 10445 addRootBlocks(); 10446 } 10447 10448 // Run filters only when the contents is valid 10449 if (!args.invalid) { 10450 // Run node filters 10451 for (name in matchedNodes) { 10452 list = nodeFilters[name]; 10453 nodes = matchedNodes[name]; 10454 10455 // Remove already removed children 10456 fi = nodes.length; 10457 while (fi--) { 10458 if (!nodes[fi].parent) { 10459 nodes.splice(fi, 1); 10460 } 10461 } 10462 10463 for (i = 0, l = list.length; i < l; i++) { 10464 list[i](nodes, name, args); 10465 } 10466 } 10467 10468 // Run attribute filters 10469 for (i = 0, l = attributeFilters.length; i < l; i++) { 10470 list = attributeFilters[i]; 10471 10472 if (list.name in matchedAttributes) { 10473 nodes = matchedAttributes[list.name]; 10474 10475 // Remove already removed children 10476 fi = nodes.length; 10477 while (fi--) { 10478 if (!nodes[fi].parent) { 10479 nodes.splice(fi, 1); 10480 } 10481 } 10482 10483 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) { 10484 list.callbacks[fi](nodes, list.name, args); 10485 } 10486 } 10487 } 10488 } 10489 10490 return rootNode; 10491 }; 10492 10493 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to 10494 // make it possible to place the caret inside empty blocks. This logic tries to remove 10495 // these elements and keep br elements that where intended to be there intact 10496 if (settings.remove_trailing_brs) { 10497 self.addNodeFilter('br', function(nodes) { 10498 var i, l = nodes.length, node, blockElements = extend({}, schema.getBlockElements()); 10499 var nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName; 10500 var elementRule, textNode; 10501 10502 // Remove brs from body element as well 10503 blockElements.body = 1; 10504 10505 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p> 10506 for (i = 0; i < l; i++) { 10507 node = nodes[i]; 10508 parent = node.parent; 10509 10510 if (blockElements[node.parent.name] && node === parent.lastChild) { 10511 // Loop all nodes to the left of the current node and check for other BR elements 10512 // excluding bookmarks since they are invisible 10513 prev = node.prev; 10514 while (prev) { 10515 prevName = prev.name; 10516 10517 // Ignore bookmarks 10518 if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') { 10519 // Found a non BR element 10520 if (prevName !== "br") { 10521 break; 10522 } 10523 10524 // Found another br it's a <br><br> structure then don't remove anything 10525 if (prevName === 'br') { 10526 node = null; 10527 break; 10528 } 10529 } 10530 10531 prev = prev.prev; 10532 } 10533 10534 if (node) { 10535 node.remove(); 10536 10537 // Is the parent to be considered empty after we removed the BR 10538 if (parent.isEmpty(nonEmptyElements)) { 10539 elementRule = schema.getElementRule(parent.name); 10540 10541 // Remove or padd the element depending on schema rule 10542 if (elementRule) { 10543 if (elementRule.removeEmpty) { 10544 parent.remove(); 10545 } else if (elementRule.paddEmpty) { 10546 parent.empty().append(new Node('#text', 3)).value = '\u00a0'; 10547 } 10548 } 10549 } 10550 } 10551 } else {
10552 // Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> 10553 // so they become <p><b><i> </i></b></p> 10554 lastParent = node; 10555 while (parent && parent.firstChild === lastParent && parent.lastChild === lastParent) { 10556 lastParent = parent; 10557 10558 if (blockElements[parent.name]) { 10559 break; 10560 } 10561 10562 parent = parent.parent; 10563 } 10564 10565 if (lastParent === parent) { 10566 textNode = new Node('#text', 3); 10567 textNode.value = '\u00a0'; 10568 node.replace(textNode); 10569 } 10570 } 10571 } 10572 }); 10573 } 10574 10575 // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included. 10576 if (!settings.allow_html_in_named_anchor) { 10577 self.addAttributeFilter('id,name', function(nodes) { 10578 var i = nodes.length, sibling, prevSibling, parent, node; 10579 10580 while (i--) { 10581 node = nodes[i]; 10582 if (node.name === 'a' && node.firstChild && !node.attr('href')) { 10583 parent = node.parent; 10584 10585 // Move children after current node 10586 sibling = node.lastChild; 10587 do { 10588 prevSibling = sibling.prev; 10589 parent.insert(sibling, node); 10590 sibling = prevSibling; 10591 } while (sibling); 10592 } 10593 } 10594 }); 10595 } 10596 }; 10597}); 10598 10599// Included from: js/tinymce/classes/html/Writer.js 10600 10601/** 10602 * Writer.js 10603 * 10604 * Copyright, Moxiecode Systems AB 10605 * Released under LGPL License. 10606 * 10607 * License: http://www.tinymce.com/license 10608 * Contributing: http://www.tinymce.com/contributing 10609 */ 10610 10611/** 10612 * This class is used to write HTML tags out it can be used with the Serializer or the SaxParser. 10613 * 10614 * @class tinymce.html.Writer 10615 * @example 10616 * var writer = new tinymce.html.Writer({indent: true}); 10617 * var parser = new tinymce.html.SaxParser(writer).parse('<p><br></p>'); 10618 * console.log(writer.getContent()); 10619 * 10620 * @class tinymce.html.Writer 10621 * @version 3.4 10622 */ 10623define("tinymce/html/Writer", [ 10624 "tinymce/html/Entities", 10625 "tinymce/util/Tools" 10626], function(Entities, Tools) { 10627 var makeMap = Tools.makeMap; 10628 10629 /** 10630 * Constructs a new Writer instance. 10631 * 10632 * @constructor 10633 * @method Writer 10634 * @param {Object} settings Name/value settings object. 10635 */ 10636 return function(settings) { 10637 var html = [], indent, indentBefore, indentAfter, encode, htmlOutput; 10638 10639 settings = settings || {}; 10640 indent = settings.indent; 10641 indentBefore = makeMap(settings.indent_before || ''); 10642 indentAfter = makeMap(settings.indent_after || ''); 10643 encode = Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities); 10644 htmlOutput = settings.element_format == "html"; 10645 10646 return { 10647 /** 10648 * Writes the a start element such as <p id="a">. 10649 * 10650 * @method start 10651 * @param {String} name Name of the element. 10652 * @param {Array} attrs Optional attribute array or undefined if it hasn't any. 10653 * @param {Boolean} empty Optional empty state if the tag should end like <br />. 10654 */ 10655 start: function(name, attrs, empty) { 10656 var i, l, attr, value; 10657 10658 if (indent && indentBefore[name] && html.length > 0) { 10659 value = html[html.length - 1]; 10660 10661 if (value.length > 0 && value !== '\n') { 10662 html.push('\n'); 10663 } 10664 } 10665 10666 html.push('<', name); 10667 10668 if (attrs) { 10669 for (i = 0, l = attrs.length; i < l; i++) { 10670 attr = attrs[i]; 10671 html.push(' ', attr.name, '="', encode(attr.value, true), '"'); 10672 } 10673 } 10674 10675 if (!empty || htmlOutput) { 10676 html[html.length] = '>'; 10677 } else { 10678 html[html.length] = ' />'; 10679 } 10680 10681 if (empty && indent && indentAfter[name] && html.length > 0) { 10682 value = html[html.length - 1]; 10683 10684 if (value.length > 0 && value !== '\n') { 10685 html.push('\n'); 10686 } 10687 } 10688 }, 10689 10690 /** 10691 * Writes the a end element such as </p>. 10692 * 10693 * @method end 10694 * @param {String} name Name of the element. 10695 */ 10696 end: function(name) { 10697 var value; 10698 10699 /*if (indent && indentBefore[name] && html.length > 0) { 10700 value = html[html.length - 1]; 10701 10702 if (value.length > 0 && value !== '\n') 10703 html.push('\n'); 10704 }*/ 10705 10706 html.push('</', name, '>'); 10707 10708 if (indent && indentAfter[name] && html.length > 0) { 10709 value = html[html.length - 1]; 10710 10711 if (value.length > 0 && value !== '\n') { 10712 html.push('\n'); 10713 } 10714 } 10715 }, 10716 10717 /** 10718 * Writes a text node. 10719 * 10720 * @method text 10721 * @param {String} text String to write out. 10722 * @param {Boolean} raw Optional raw state if true the contents wont get encoded. 10723 */ 10724 text: function(text, raw) { 10725 if (text.length > 0) { 10726 html[html.length] = raw ? text : encode(text); 10727 } 10728 }, 10729 10730 /** 10731 * Writes a cdata node such as <![CDATA[data]]>. 10732 * 10733 * @method cdata 10734 * @param {String} text String to write out inside the cdata. 10735 */ 10736 cdata: function(text) { 10737 html.push('<![CDATA[', text, ']]>'); 10738 }, 10739 10740 /** 10741 * Writes a comment node such as <!-- Comment -->. 10742 * 10743 * @method cdata 10744 * @param {String} text String to write out inside the comment. 10745 */ 10746 comment: function(text) { 10747 html.push('<!--', text, '-->'); 10748 }, 10749 10750 /** 10751 * Writes a PI node such as <?xml attr="value" ?>. 10752 * 10753 * @method pi 10754 * @param {String} name Name of the pi. 10755 * @param {String} text String to write out inside the pi. 10756 */ 10757 pi: function(name, text) { 10758 if (text) { 10759 html.push('<?', name, ' ', text, '?>'); 10760 } else { 10761 html.push('<?', name, '?>'); 10762 } 10763 10764 if (indent) { 10765 html.push('\n'); 10766 } 10767 }, 10768 10769 /** 10770 * Writes a doctype node such as <!DOCTYPE data>. 10771 * 10772 * @method doctype 10773 * @param {String} text String to write out inside the doctype. 10774 */ 10775 doctype: function(text) { 10776 html.push('<!DOCTYPE', text, '>', indent ? '\n' : ''); 10777 }, 10778 10779 /** 10780 * Resets the internal buffer if one wants to reuse the writer. 10781 * 10782 * @method reset 10783 */ 10784 reset: function() { 10785 html.length = 0; 10786 }, 10787 10788 /** 10789 * Returns the contents that got serialized. 10790 * 10791 * @method getContent 10792 * @return {String} HTML contents that got written down. 10793 */ 10794 getContent: function() { 10795 return html.join('').replace(/\n$/, ''); 10796 } 10797 }; 10798 }; 10799}); 10800 10801// Included from: js/tinymce/classes/html/Serializer.js 10802 10803/** 10804 * Serializer.js 10805 * 10806 * Copyright, Moxiecode Systems AB 10807 * Released under LGPL License. 10808 * 10809 * License: http://www.tinymce.com/license 10810 * Contributing: http://www.tinymce.com/contributing 10811 */ 10812 10813/** 10814 * This class is used to serialize down the DOM tree into a string using a Writer instance. 10815 * 10816 * 10817 * @example 10818 * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>')); 10819 * @class tinymce.html.Serializer 10820 * @version 3.4 10821 */ 10822define("tinymce/html/Serializer", [ 10823 "tinymce/html/Writer", 10824 "tinymce/html/Schema" 10825], function(Writer, Schema) { 10826 /** 10827 * Constructs a new Serializer instance. 10828 * 10829 * @constructor 10830 * @method Serializer 10831 * @param {Object} settings Name/value settings object. 10832 * @param {tinymce.html.Schema} schema Schema instance to use. 10833 */ 10834 return function(settings, schema) { 10835 var self = this, writer = new Writer(settings); 10836 10837 settings = settings || {};
10838 settings.validate = "validate" in settings ? settings.validate : true; 10839 10840 self.schema = schema = schema || new Schema(); 10841 self.writer = writer; 10842 10843 /** 10844 * Serializes the specified node into a string. 10845 * 10846 * @example 10847 * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>')); 10848 * @method serialize 10849 * @param {tinymce.html.Node} node Node instance to serialize. 10850 * @return {String} String with HTML based on DOM tree. 10851 */ 10852 self.serialize = function(node) { 10853 var handlers, validate; 10854 10855 validate = settings.validate; 10856 10857 handlers = { 10858 // #text 10859 3: function(node) { 10860 writer.text(node.value, node.raw); 10861 }, 10862 10863 // #comment 10864 8: function(node) { 10865 writer.comment(node.value); 10866 }, 10867 10868 // Processing instruction 10869 7: function(node) { 10870 writer.pi(node.name, node.value); 10871 }, 10872 10873 // Doctype 10874 10: function(node) { 10875 writer.doctype(node.value); 10876 }, 10877 10878 // CDATA 10879 4: function(node) { 10880 writer.cdata(node.value); 10881 }, 10882 10883 // Document fragment 10884 11: function(node) { 10885 if ((node = node.firstChild)) { 10886 do { 10887 walk(node); 10888 } while ((node = node.next)); 10889 } 10890 } 10891 }; 10892 10893 writer.reset(); 10894 10895 function walk(node) { 10896 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule; 10897 10898 if (!handler) { 10899 name = node.name; 10900 isEmpty = node.shortEnded; 10901 attrs = node.attributes; 10902 10903 // Sort attributes 10904 if (validate && attrs && attrs.length > 1) { 10905 sortedAttrs = []; 10906 sortedAttrs.map = {}; 10907 10908 elementRule = schema.getElementRule(node.name); 10909 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) { 10910 attrName = elementRule.attributesOrder[i]; 10911 10912 if (attrName in attrs.map) { 10913 attrValue = attrs.map[attrName]; 10914 sortedAttrs.map[attrName] = attrValue; 10915 sortedAttrs.push({name: attrName, value: attrValue}); 10916 } 10917 } 10918 10919 for (i = 0, l = attrs.length; i < l; i++) { 10920 attrName = attrs[i].name; 10921 10922 if (!(attrName in sortedAttrs.map)) { 10923 attrValue = attrs.map[attrName]; 10924 sortedAttrs.map[attrName] = attrValue; 10925 sortedAttrs.push({name: attrName, value: attrValue}); 10926 } 10927 } 10928 10929 attrs = sortedAttrs; 10930 } 10931 10932 writer.start(node.name, attrs, isEmpty); 10933 10934 if (!isEmpty) { 10935 if ((node = node.firstChild)) { 10936 do { 10937 walk(node); 10938 } while ((node = node.next)); 10939 } 10940 10941 writer.end(name); 10942 } 10943 } else { 10944 handler(node); 10945 } 10946 } 10947 10948 // Serialize element and treat all non elements as fragments 10949 if (node.type == 1 && !settings.inner) { 10950 walk(node); 10951 } else { 10952 handlers[11](node); 10953 } 10954 10955 return writer.getContent(); 10956 }; 10957 }; 10958}); 10959 10960// Included from: js/tinymce/classes/dom/Serializer.js 10961 10962/** 10963 * Serializer.js 10964 * 10965 * Copyright, Moxiecode Systems AB 10966 * Released under LGPL License. 10967 * 10968 * License: http://www.tinymce.com/license 10969 * Contributing: http://www.tinymce.com/contributing 10970 */ 10971 10972/** 10973 * This class is used to serialize DOM trees into a string. Consult the TinyMCE Wiki API for 10974 * more details and examples on how to use this class. 10975 * 10976 * @class tinymce.dom.Serializer 10977 */ 10978define("tinymce/dom/Serializer", [ 10979 "tinymce/dom/DOMUtils", 10980 "tinymce/html/DomParser", 10981 "tinymce/html/Entities", 10982 "tinymce/html/Serializer", 10983 "tinymce/html/Node", 10984 "tinymce/html/Schema", 10985 "tinymce/Env", 10986 "tinymce/util/Tools" 10987], function(DOMUtils, DomParser, Entities, Serializer, Node, Schema, Env, Tools) { 10988 var each = Tools.each, trim = Tools.trim; 10989 var DOM = DOMUtils.DOM; 10990 10991 /** 10992 * Constructs a new DOM serializer class. 10993 * 10994 * @constructor 10995 * @method Serializer 10996 * @param {Object} settings Serializer settings object. 10997 * @param {tinymce.Editor} editor Optional editor to bind events to and get schema/dom from. 10998 */ 10999 return function(settings, editor) { 11000 var dom, schema, htmlParser; 11001 11002 if (editor) { 11003 dom = editor.dom; 11004 schema = editor.schema; 11005 } 11006 11007 // Default DOM and Schema if they are undefined 11008 dom = dom || DOM; 11009 schema = schema || new Schema(settings);
11010 settings.entity_encoding = settings.entity_encoding || 'named'; 11011 settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true; 11012 11013 htmlParser = new DomParser(settings, schema); 11014 11015 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed 11016 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) { 11017 var i = nodes.length, node, value, internalName = 'data-mce-' + name; 11018 var urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef; 11019 11020 while (i--) { 11021 node = nodes[i]; 11022 11023 value = node.attributes.map[internalName]; 11024 if (value !== undef) { 11025 // Set external name to internal value and remove internal 11026 node.attr(name, value.length > 0 ? value : null); 11027 node.attr(internalName, null); 11028 } else { 11029 // No internal attribute found then convert the value we have in the DOM 11030 value = node.attributes.map[name]; 11031 11032 if (name === "style") { 11033 value = dom.serializeStyle(dom.parseStyle(value), node.name); 11034 } else if (urlConverter) { 11035 value = urlConverter.call(urlConverterScope, value, name, node.name); 11036 } 11037 11038 node.attr(name, value.length > 0 ? value : null); 11039 } 11040 } 11041 }); 11042 11043 // Remove internal classes mceItem<..> or mceSelected 11044 htmlParser.addAttributeFilter('class', function(nodes) { 11045 var i = nodes.length, node, value; 11046 11047 while (i--) { 11048 node = nodes[i]; 11049 value = node.attr('class').replace(/(?:^|\s)mce-item-\w+(?!\S)/g, ''); 11050 node.attr('class', value.length > 0 ? value : null); 11051 } 11052 }); 11053 11054 // Remove bookmark elements 11055 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) { 11056 var i = nodes.length, node; 11057 11058 while (i--) { 11059 node = nodes[i]; 11060 11061 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) { 11062 node.remove(); 11063 } 11064 } 11065 }); 11066 11067 // Remove expando attributes 11068 htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name) { 11069 var i = nodes.length; 11070 11071 while (i--) { 11072 nodes[i].attr(name, null); 11073 } 11074 }); 11075 11076 htmlParser.addNodeFilter('noscript', function(nodes) { 11077 var i = nodes.length, node; 11078 11079 while (i--) { 11080 node = nodes[i].firstChild; 11081 11082 if (node) { 11083 node.value = Entities.decode(node.value); 11084 } 11085 } 11086 }); 11087 11088 // Force script into CDATA sections and remove the mce- prefix also add comments around styles 11089 htmlParser.addNodeFilter('script,style', function(nodes, name) { 11090 var i = nodes.length, node, value; 11091 11092 function trim(value) { 11093 /*jshint maxlen:255 */ 11094 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n') 11095 .replace(/^[\r\n]*|[\r\n]*$/g, '') 11096 .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '') 11097 .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, ''); 11098 } 11099 11100 while (i--) { 11101 node = nodes[i]; 11102 value = node.firstChild ? node.firstChild.value : ''; 11103 11104 if (name === "script") { 11105 // Remove mce- prefix from script elements and remove default text/javascript mime type (HTML5) 11106 var type = (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''); 11107 node.attr('type', type === 'text/javascript' ? null : type); 11108 11109 if (value.length > 0) { 11110 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>'; 11111 } 11112 } else { 11113 if (value.length > 0) { 11114 node.firstChild.value = '<!--\n' + trim(value) + '\n-->'; 11115 } 11116 } 11117 } 11118 }); 11119 11120 // Convert comments to cdata and handle protected comments 11121 htmlParser.addNodeFilter('#comment', function(nodes) { 11122 var i = nodes.length, node; 11123 11124 while (i--) { 11125 node = nodes[i]; 11126 11127 if (node.value.indexOf('[CDATA[') === 0) { 11128 node.name = '#cdata'; 11129 node.type = 4; 11130 node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, ''); 11131 } else if (node.value.indexOf('mce:protected ') === 0) { 11132 node.name = "#text"; 11133 node.type = 3; 11134 node.raw = true; 11135 node.value = unescape(node.value).substr(14); 11136 } 11137 } 11138 }); 11139 11140 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) { 11141 var i = nodes.length, node; 11142 11143 while (i--) { 11144 node = nodes[i]; 11145 if (node.type === 7) { 11146 node.remove(); 11147 } else if (node.type === 1) { 11148 if (name === "input" && !("type" in node.attributes.map)) { 11149 node.attr('type', 'text'); 11150 } 11151 } 11152 } 11153 }); 11154 11155 // Fix list elements, TODO: Replace this later 11156 if (settings.fix_list_elements) { 11157 htmlParser.addNodeFilter('ul,ol', function(nodes) { 11158 var i = nodes.length, node, parentNode; 11159 11160 while (i--) { 11161 node = nodes[i]; 11162 parentNode = node.parent; 11163 11164 if (parentNode.name === 'ul' || parentNode.name === 'ol') { 11165 if (node.prev && node.prev.name === 'li') { 11166 node.prev.append(node); 11167 } 11168 } 11169 } 11170 }); 11171 } 11172 11173 // Remove internal data attributes 11174 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style,data-mce-selected', function(nodes, name) { 11175 var i = nodes.length; 11176 11177 while (i--) { 11178 nodes[i].attr(name, null); 11179 } 11180 }); 11181 11182 // Return public methods 11183 return { 11184 /** 11185 * Schema instance that was used to when the Serializer was constructed. 11186 * 11187 * @field {tinymce.html.Schema} schema 11188 */ 11189 schema: schema, 11190 11191 /**
11192 * Adds a node filter function to the parser used by the serializer, the parser will collect the specified nodes by name 11193 * and then execute the callback ones it has finished parsing the document. 11194 * 11195 * @example 11196 * parser.addNodeFilter('p,h1', function(nodes, name) { 11197 * for (var i = 0; i < nodes.length; i++) { 11198 * console.log(nodes[i].name); 11199 * } 11200 * }); 11201 * @method addNodeFilter 11202 * @method {String} name Comma separated list of nodes to collect. 11203 * @param {function} callback Callback function to execute once it has collected nodes. 11204 */ 11205 addNodeFilter: htmlParser.addNodeFilter, 11206 11207 /** 11208 * Adds a attribute filter function to the parser used by the serializer, the parser will 11209 * collect nodes that has the specified attributes 11210 * and then execute the callback ones it has finished parsing the document. 11211 * 11212 * @example 11213 * parser.addAttributeFilter('src,href', function(nodes, name) { 11214 * for (var i = 0; i < nodes.length; i++) { 11215 * console.log(nodes[i].name); 11216 * } 11217 * }); 11218 * @method addAttributeFilter 11219 * @method {String} name Comma separated list of nodes to collect. 11220 * @param {function} callback Callback function to execute once it has collected nodes. 11221 */ 11222 addAttributeFilter: htmlParser.addAttributeFilter, 11223 11224 /** 11225 * Serializes the specified browser DOM node into a HTML string. 11226 * 11227 * @method serialize 11228 * @param {DOMNode} node DOM node to serialize. 11229 * @param {Object} args Arguments option that gets passed to event handlers. 11230 */ 11231 serialize: function(node, args) { 11232 var self = this, impl, doc, oldDoc, htmlSerializer, content; 11233 11234 // Explorer won't clone contents of script and style and the 11235 // selected index of select elements are cleared on a clone operation. 11236 if (Env.ie && dom.select('script,style,select,map').length > 0) { 11237 content = node.innerHTML; 11238 node = node.cloneNode(false); 11239 dom.setHTML(node, content); 11240 } else { 11241 node = node.cloneNode(true); 11242 } 11243 11244 // Nodes needs to be attached to something in WebKit/Opera 11245 // This fix will make DOM ranges and make Sizzle happy! 11246 impl = node.ownerDocument.implementation; 11247 if (impl.createHTMLDocument) { 11248 // Create an empty HTML document 11249 doc = impl.createHTMLDocument(""); 11250 11251 // Add the element or it's children if it's a body element to the new document 11252 each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) { 11253 doc.body.appendChild(doc.importNode(node, true)); 11254 }); 11255 11256 // Grab first child or body element for serialization 11257 if (node.nodeName != 'BODY') { 11258 node = doc.body.firstChild; 11259 } else { 11260 node = doc.body; 11261 } 11262 11263 // set the new document in DOMUtils so createElement etc works 11264 oldDoc = dom.doc; 11265 dom.doc = doc; 11266 } 11267 11268 args = args || {}; 11269 args.format = args.format || 'html'; 11270 11271 // Don't wrap content if we want selected html 11272 if (args.selection) { 11273 args.forced_root_block = ''; 11274 } 11275 11276 // Pre process 11277 if (!args.no_events) { 11278 args.node = node; 11279 self.onPreProcess(args); 11280 } 11281 11282 // Setup serializer 11283 htmlSerializer = new Serializer(settings, schema); 11284 11285 // Parse and serialize HTML 11286 args.content = htmlSerializer.serialize( 11287 htmlParser.parse(trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args) 11288 ); 11289 11290 // Replace all BOM characters for now until we can find a better solution 11291 if (!args.cleanup) { 11292 args.content = args.content.replace(/\uFEFF/g, ''); 11293 } 11294 11295 // Post process 11296 if (!args.no_events) { 11297 self.onPostProcess(args); 11298 } 11299 11300 // Restore the old document if it was changed 11301 if (oldDoc) { 11302 dom.doc = oldDoc; 11303 } 11304 11305 args.node = null; 11306 11307 return args.content; 11308 }, 11309 11310 /** 11311 * Adds valid elements rules to the serializers schema instance this enables you to specify things 11312 * like what elements should be outputted and what attributes specific elements might have. 11313 * Consult the Wiki for more details on this format. 11314 * 11315 * @method addRules 11316 * @param {String} rules Valid elements rules string to add to schema. 11317 */ 11318 addRules: function(rules) { 11319 schema.addValidElements(rules); 11320 }, 11321 11322 /** 11323 * Sets the valid elements rules to the serializers schema instance this enables you to specify things 11324 * like what elements should be outputted and what attributes specific elements might have. 11325 * Consult the Wiki for more details on this format. 11326 * 11327 * @method setRules 11328 * @param {String} rules Valid elements rules string. 11329 */ 11330 setRules: function(rules) { 11331 schema.setValidElements(rules); 11332 }, 11333 11334 onPreProcess: function(args) { 11335 if (editor) {
11336 editor.fire('PreProcess', args); 11337 } 11338 }, 11339 11340 onPostProcess: function(args) { 11341 if (editor) { 11342 editor.fire('PostProcess', args); 11343 } 11344 } 11345 }; 11346 }; 11347}); 11348 11349// Included from: js/tinymce/classes/dom/TridentSelection.js 11350 11351/** 11352 * TridentSelection.js 11353 * 11354 * Copyright, Moxiecode Systems AB 11355 * Released under LGPL License. 11356 * 11357 * License: http://www.tinymce.com/license 11358 * Contributing: http://www.tinymce.com/contributing 11359 */ 11360 11361/** 11362 * Selection class for old explorer versions. This one fakes the 11363 * native selection object available on modern browsers. 11364 * 11365 * @class tinymce.dom.TridentSelection 11366 */ 11367define("tinymce/dom/TridentSelection", [], function() { 11368 function Selection(selection) { 11369 var self = this, dom = selection.dom, FALSE = false; 11370 11371 function getPosition(rng, start) { 11372 var checkRng, startIndex = 0, endIndex, inside, 11373 children, child, offset, index, position = -1, parent; 11374 11375 // Setup test range, collapse it and get the parent 11376 checkRng = rng.duplicate(); 11377 checkRng.collapse(start); 11378 parent = checkRng.parentElement(); 11379 11380 // Check if the selection is within the right document 11381 if (parent.ownerDocument !== selection.dom.doc) { 11382 return; 11383 } 11384 11385 // IE will report non editable elements as it's parent so look for an editable one 11386 while (parent.contentEditable === "false") { 11387 parent = parent.parentNode; 11388 } 11389 11390 // If parent doesn't have any children then return that we are inside the element 11391 if (!parent.hasChildNodes()) { 11392 return {node: parent, inside: 1}; 11393 } 11394 11395 // Setup node list and endIndex 11396 children = parent.children; 11397 endIndex = children.length - 1; 11398 11399 // Perform a binary search for the position 11400 while (startIndex <= endIndex) { 11401 index = Math.floor((startIndex + endIndex) / 2); 11402 11403 // Move selection to node and compare the ranges 11404 child = children[index]; 11405 checkRng.moveToElementText(child); 11406 position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng); 11407 11408 // Before/after or an exact match 11409 if (position > 0) { 11410 endIndex = index - 1; 11411 } else if (position < 0) { 11412 startIndex = index + 1; 11413 } else { 11414 return {node: child}; 11415 } 11416 } 11417 11418 // Check if child position is before or we didn't find a position 11419 if (position < 0) { 11420 // No element child was found use the parent element and the offset inside that 11421 if (!child) { 11422 checkRng.moveToElementText(parent); 11423 checkRng.collapse(true); 11424 child = parent; 11425 inside = true; 11426 } else { 11427 checkRng.collapse(false); 11428 } 11429 11430 // Walk character by character in text node until we hit the selected range endpoint, 11431 // hit the end of document or parent isn't the right one 11432 // We need to walk char by char since rng.text or rng.htmlText will trim line endings 11433 offset = 0; 11434 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { 11435 if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) { 11436 break; 11437 } 11438 11439 offset++; 11440 } 11441 } else { 11442 // Child position is after the selection endpoint 11443 checkRng.collapse(true); 11444 11445 // Walk character by character in text node until we hit the selected range endpoint, hit 11446 // the end of document or parent isn't the right one 11447 offset = 0; 11448 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { 11449 if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) { 11450 break; 11451 } 11452 11453 offset++; 11454 } 11455 } 11456 11457 return {node: child, position: position, offset: offset, inside: inside}; 11458 } 11459 11460 // Returns a W3C DOM compatible range object by using the IE Range API 11461 function getRange() { 11462 var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark; 11463 11464 // If selection is outside the current document just return an empty range 11465 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); 11466 if (element.ownerDocument != dom.doc) { 11467 return domRange; 11468 } 11469 11470 collapsed = selection.isCollapsed(); 11471 11472 // Handle control selection 11473 if (ieRange.item) { 11474 domRange.setStart(element.parentNode, dom.nodeIndex(element)); 11475 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); 11476 11477 return domRange; 11478 } 11479 11480 function findEndPoint(start) { 11481 var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue; 11482 11483 container = endPoint.node;
11484 offset = endPoint.offset; 11485 11486 if (endPoint.inside && !container.hasChildNodes()) { 11487 domRange[start ? 'setStart' : 'setEnd'](container, 0); 11488 return; 11489 } 11490 11491 if (offset === undef) { 11492 domRange[start ? 'setStartBefore' : 'setEndAfter'](container); 11493 return; 11494 } 11495 11496 if (endPoint.position < 0) { 11497 sibling = endPoint.inside ? container.firstChild : container.nextSibling; 11498 11499 if (!sibling) { 11500 domRange[start ? 'setStartAfter' : 'setEndAfter'](container); 11501 return; 11502 } 11503 11504 if (!offset) { 11505 if (sibling.nodeType == 3) { 11506 domRange[start ? 'setStart' : 'setEnd'](sibling, 0); 11507 } else { 11508 domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling); 11509 } 11510 11511 return; 11512 } 11513 11514 // Find the text node and offset 11515 while (sibling) { 11516 nodeValue = sibling.nodeValue; 11517 textNodeOffset += nodeValue.length; 11518 11519 // We are at or passed the position we where looking for 11520 if (textNodeOffset >= offset) { 11521 container = sibling; 11522 textNodeOffset -= offset; 11523 textNodeOffset = nodeValue.length - textNodeOffset; 11524 break; 11525 } 11526 11527 sibling = sibling.nextSibling; 11528 } 11529 } else { 11530 // Find the text node and offset 11531 sibling = container.previousSibling; 11532 11533 if (!sibling) { 11534 return domRange[start ? 'setStartBefore' : 'setEndBefore'](container); 11535 } 11536 11537 // If there isn't any text to loop then use the first position 11538 if (!offset) { 11539 if (container.nodeType == 3) { 11540 domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length); 11541 } else { 11542 domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling); 11543 } 11544 11545 return; 11546 } 11547 11548 while (sibling) { 11549 textNodeOffset += sibling.nodeValue.length; 11550 11551 // We are at or passed the position we where looking for 11552 if (textNodeOffset >= offset) { 11553 container = sibling; 11554 textNodeOffset -= offset; 11555 break; 11556 } 11557 11558 sibling = sibling.previousSibling; 11559 } 11560 } 11561 11562 domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset); 11563 } 11564 11565 try { 11566 // Find start point 11567 findEndPoint(true); 11568 11569 // Find end point if needed 11570 if (!collapsed) { 11571 findEndPoint(); 11572 } 11573 } catch (ex) { 11574 // IE has a nasty bug where text nodes might throw "invalid argument" when you 11575 // access the nodeValue or other properties of text nodes. This seems to happend when 11576 // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it. 11577 if (ex.number == -2147024809) { 11578 // Get the current selection 11579 bookmark = self.getBookmark(2); 11580 11581 // Get start element 11582 tmpRange = ieRange.duplicate(); 11583 tmpRange.collapse(true); 11584 element = tmpRange.parentElement(); 11585 11586 // Get end element 11587 if (!collapsed) { 11588 tmpRange = ieRange.duplicate(); 11589 tmpRange.collapse(false); 11590 element2 = tmpRange.parentElement(); 11591 element2.innerHTML = element2.innerHTML; 11592 } 11593 11594 // Remove the broken elements 11595 element.innerHTML = element.innerHTML; 11596 11597 // Restore the selection 11598 self.moveToBookmark(bookmark); 11599 11600 // Since the range has moved we need to re-get it 11601 ieRange = selection.getRng(); 11602 11603 // Find start point 11604 findEndPoint(true); 11605 11606 // Find end point if needed 11607 if (!collapsed) { 11608 findEndPoint(); 11609 } 11610 } else { 11611 throw ex; // Throw other errors 11612 } 11613 } 11614 11615 return domRange; 11616 } 11617 11618 this.getBookmark = function(type) { 11619 var rng = selection.getRng(), bookmark = {}; 11620 11621 function getIndexes(node) { 11622 var parent, root, children, i, indexes = []; 11623 11624 parent = node.parentNode; 11625 root = dom.getRoot().parentNode; 11626 11627 while (parent != root && parent.nodeType !== 9) { 11628 children = parent.children; 11629 11630 i = children.length; 11631 while (i--) { 11632 if (node === children[i]) { 11633 indexes.push(i); 11634 break; 11635 } 11636 } 11637 11638 node = parent; 11639 parent = parent.parentNode; 11640 } 11641 11642 return indexes; 11643 } 11644 11645 function getBookmarkEndPoint(start) { 11646 var position; 11647 11648 position = getPosition(rng, start); 11649 if (position) { 11650 return { 11651 position: position.position, 11652 offset: position.offset, 11653 indexes: getIndexes(position.node), 11654 inside: position.inside 11655 }; 11656 } 11657 } 11658 11659 // Non ubstructive bookmark 11660 if (type === 2) { 11661 // Handle text selection 11662 if (!rng.item) { 11663 bookmark.start = getBookmarkEndPoint(true); 11664 11665 if (!selection.isCollapsed()) { 11666 bookmark.end = getBookmarkEndPoint(); 11667 } 11668 } else { 11669 bookmark.start = {ctrl: true, indexes: getIndexes(rng.item(0))}; 11670 } 11671 } 11672 11673 return bookmark; 11674 }; 11675 11676 this.moveToBookmark = function(bookmark) { 11677 var rng, body = dom.doc.body; 11678 11679 function resolveIndexes(indexes) { 11680 var node, i, idx, children; 11681 11682 node = dom.getRoot(); 11683 for (i = indexes.length - 1; i >= 0; i--) { 11684 children = node.children; 11685 idx = indexes[i]; 11686 11687 if (idx <= children.length - 1) { 11688 node = children[idx]; 11689 } 11690 } 11691 11692 return node; 11693 } 11694 11695 function setBookmarkEndPoint(start) { 11696 var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef, offset; 11697 11698 if (endPoint) { 11699 moveLeft = endPoint.position > 0; 11700 11701 moveRng = body.createTextRange(); 11702 moveRng.moveToElementText(resolveIndexes(endPoint.indexes)); 11703 11704 offset = endPoint.offset; 11705 if (offset !== undef) { 11706 moveRng.collapse(endPoint.inside || moveLeft); 11707 moveRng.moveStart('character', moveLeft ? -offset : offset); 11708 } else { 11709 moveRng.collapse(start); 11710 } 11711 11712 rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng); 11713 11714 if (start) { 11715 rng.collapse(true); 11716 } 11717 } 11718 } 11719 11720 if (bookmark.start) { 11721 if (bookmark.start.ctrl) { 11722 rng = body.createControlRange(); 11723 rng.addElement(resolveIndexes(bookmark.start.indexes)); 11724 rng.select(); 11725 } else { 11726 rng = body.createTextRange(); 11727 setBookmarkEndPoint(true); 11728 setBookmarkEndPoint(); 11729 rng.select(); 11730 } 11731 } 11732 }; 11733 11734 this.addRange = function(rng) { 11735 var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, 11736 doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm; 11737 11738 function setEndPoint(start) { 11739 var container, offset, marker, tmpRng, nodes; 11740 11741 marker = dom.create('a'); 11742 container = start ? startContainer : endContainer; 11743 offset = start ? startOffset : endOffset; 11744 tmpRng = ieRng.duplicate(); 11745 11746 if (container == doc || container == doc.documentElement) { 11747 container = body; 11748 offset = 0; 11749 } 11750 11751 if (container.nodeType == 3) { 11752 container.parentNode.insertBefore(marker, container); 11753 tmpRng.moveToElementText(marker); 11754 tmpRng.moveStart('character', offset); 11755 dom.remove(marker); 11756 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); 11757 } else { 11758 nodes = container.childNodes; 11759 11760 if (nodes.length) { 11761 if (offset >= nodes.length) { 11762 dom.insertAfter(marker, nodes[nodes.length - 1]); 11763 } else { 11764 container.insertBefore(marker, nodes[offset]); 11765 } 11766 11767 tmpRng.moveToElementText(marker); 11768 } else if (container.canHaveHTML) { 11769 // Empty node selection for example <div>|</div> 11770 // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open 11771 container.innerHTML = '<span></span>'; 11772 marker = container.firstChild; 11773 tmpRng.moveToElementText(marker); 11774 tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason 11775 } 11776 11777 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); 11778 dom.remove(marker); 11779 } 11780 } 11781 11782 // Setup some shorter versions 11783 startContainer = rng.startContainer; 11784 startOffset = rng.startOffset; 11785 endContainer = rng.endContainer; 11786 endOffset = rng.endOffset; 11787 ieRng = body.createTextRange(); 11788 11789 // If single element selection then try making a control selection out of it 11790 if (startContainer == endContainer && startContainer.nodeType == 1) { 11791 // Trick to place the caret inside an empty block element like <p></p> 11792 if (startOffset == endOffset && !startContainer.hasChildNodes()) { 11793 if (startContainer.canHaveHTML) {
11794 // Check if previous sibling is an empty block if it is then we need to render it 11795 // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236 11796 // Example this: <p></p><p>|</p> would become this: <p>|</p><p></p> 11797 sibling = startContainer.previousSibling; 11798 if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) { 11799 sibling.innerHTML = ''; 11800 } else { 11801 sibling = null; 11802 } 11803 11804 startContainer.innerHTML = '<span></span><span></span>'; 11805 ieRng.moveToElementText(startContainer.lastChild); 11806 ieRng.select(); 11807 dom.doc.selection.clear(); 11808 startContainer.innerHTML = ''; 11809 11810 if (sibling) { 11811 sibling.innerHTML = ''; 11812 } 11813 return; 11814 } else { 11815 startOffset = dom.nodeIndex(startContainer); 11816 startContainer = startContainer.parentNode; 11817 } 11818 } 11819 11820 if (startOffset == endOffset - 1) { 11821 try { 11822 ctrlElm = startContainer.childNodes[startOffset]; 11823 ctrlRng = body.createControlRange(); 11824 ctrlRng.addElement(ctrlElm); 11825 ctrlRng.select(); 11826 11827 // Check if the range produced is on the correct element and is a control range 11828 // On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398 11829 nativeRng = selection.getRng(); 11830 if (nativeRng.item && ctrlElm === nativeRng.item(0)) { 11831 return; 11832 } 11833 } catch (ex) { 11834 // Ignore 11835 } 11836 } 11837 } 11838 11839 // Set start/end point of selection 11840 setEndPoint(true); 11841 setEndPoint(); 11842 11843 // Select the new range and scroll it into view 11844 ieRng.select(); 11845 }; 11846 11847 // Expose range method 11848 this.getRangeAt = getRange; 11849 } 11850 11851 return Selection; 11852}); 11853 11854// Included from: js/tinymce/classes/util/VK.js 11855 11856/** 11857 * VK.js 11858 * 11859 * Copyright, Moxiecode Systems AB 11860 * Released under LGPL License. 11861 * 11862 * License: http://www.tinymce.com/license 11863 * Contributing: http://www.tinymce.com/contributing 11864 */ 11865 11866/** 11867 * This file exposes a set of the common KeyCodes for use. Please grow it as needed. 11868 */ 11869define("tinymce/util/VK", [ 11870 "tinymce/Env" 11871], function(Env) { 11872 return { 11873 BACKSPACE: 8, 11874 DELETE: 46, 11875 DOWN: 40, 11876 ENTER: 13, 11877 LEFT: 37, 11878 RIGHT: 39, 11879 SPACEBAR: 32, 11880 TAB: 9, 11881 UP: 38, 11882 11883 modifierPressed: function(e) { 11884 return e.shiftKey || e.ctrlKey || e.altKey; 11885 }, 11886 11887 metaKeyPressed: function(e) { 11888 // Check if ctrl or meta key is pressed also check if alt is false for Polish users 11889 return (Env.mac ? e.metaKey : e.ctrlKey) && !e.altKey; 11890 } 11891 }; 11892}); 11893 11894// Included from: js/tinymce/classes/dom/ControlSelection.js 11895 11896/** 11897 * ControlSelection.js 11898 * 11899 * Copyright, Moxiecode Systems AB 11900 * Released under LGPL License. 11901 * 11902 * License: http://www.tinymce.com/license 11903 * Contributing: http://www.tinymce.com/contributing 11904 */ 11905 11906/** 11907 * This class handles control selection of elements. Controls are elements 11908 * that can be resized and needs to be selected as a whole. It adds custom resize handles 11909 * to all browser engines that support properly disabling the built in resize logic. 11910 * 11911 * @class tinymce.dom.ControlSelection 11912 */ 11913define("tinymce/dom/ControlSelection", [ 11914 "tinymce/util/VK", 11915 "tinymce/util/Tools", 11916 "tinymce/Env" 11917], function(VK, Tools, Env) { 11918 return function(selection, editor) { 11919 var dom = editor.dom, each = Tools.each; 11920 var selectedElm, selectedElmGhost, resizeHandles, selectedHandle, lastMouseDownEvent; 11921 var startX, startY, selectedElmX, selectedElmY, startW, startH, ratio, resizeStarted; 11922 var width, height, editableDoc = editor.getDoc(), rootDocument = document, isIE = Env.ie && Env.ie < 11; 11923 11924 // Details about each resize handle how to scale etc 11925 resizeHandles = { 11926 // Name: x multiplier, y multiplier, delta size x, delta size y 11927 n: [0.5, 0, 0, -1], 11928 e: [1, 0.5, 1, 0], 11929 s: [0.5, 1, 0, 1], 11930 w: [0, 0.5, -1, 0], 11931 nw: [0, 0, -1, -1], 11932 ne: [1, 0, 1, -1], 11933 se: [1, 1, 1, 1], 11934 sw: [0, 1, -1, 1] 11935 }; 11936 11937 // Add CSS for resize handles, cloned element and selected 11938 var rootClass = '.mce-content-body'; 11939 editor.contentStyles.push( 11940 rootClass + ' div.mce-resizehandle {' + 11941 'position: absolute;' + 11942 'border: 1px solid black;' + 11943 'background: #FFF;' + 11944 'width: 5px;' + 11945 'height: 5px;' + 11946 'z-index: 10000' + 11947 '}' + 11948 rootClass + ' .mce-resizehandle:hover {' + 11949 'background: #000' + 11950 '}' + 11951 rootClass + ' img[data-mce-selected], hr[data-mce-selected] {' + 11952 'outline: 1px solid black;' + 11953 'resize: none' + // Have been talks about implementing this in browsers 11954 '}' + 11955 rootClass + ' .mce-clonedresizable {' + 11956 'position: absolute;' + 11957 (Env.gecko ? '' : 'outline: 1px dashed black;') + // Gecko produces trails while resizing 11958 'opacity: .5;' + 11959 'filter: alpha(opacity=50);' + 11960 'z-index: 10000' + 11961 '}' 11962 ); 11963 11964 function isResizable(elm) { 11965 if (editor.settings.object_resizing === false) { 11966 return false;
11967 } 11968 11969 if (!/TABLE|IMG|DIV/.test(elm.nodeName)) { 11970 return false; 11971 } 11972 11973 if (elm.getAttribute('data-mce-resize') === 'false') { 11974 return false; 11975 } 11976 11977 return true; 11978 } 11979 11980 function resizeGhostElement(e) { 11981 var deltaX, deltaY; 11982 11983 // Calc new width/height 11984 deltaX = e.screenX - startX; 11985 deltaY = e.screenY - startY; 11986 11987 // Calc new size 11988 width = deltaX * selectedHandle[2] + startW; 11989 height = deltaY * selectedHandle[3] + startH; 11990 11991 // Never scale down lower than 5 pixels 11992 width = width < 5 ? 5 : width; 11993 height = height < 5 ? 5 : height; 11994 11995 // Constrain proportions when modifier key is pressed or if the nw, ne, sw, se corners are moved on an image 11996 if (VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0)) { 11997 width = Math.round(height / ratio); 11998 height = Math.round(width * ratio); 11999 } 12000 12001 // Update ghost size 12002 dom.setStyles(selectedElmGhost, { 12003 width: width, 12004 height: height 12005 }); 12006 12007 // Update ghost X position if needed 12008 if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) { 12009 dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width)); 12010 } 12011 12012 // Update ghost Y position if needed 12013 if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) { 12014 dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height)); 12015 } 12016 12017 if (!resizeStarted) { 12018 editor.fire('ObjectResizeStart', {target: selectedElm, width: startW, height: startH}); 12019 resizeStarted = true; 12020 } 12021 } 12022 12023 function endGhostResize() { 12024 resizeStarted = false; 12025 12026 function setSizeProp(name, value) { 12027 if (value) { 12028 // Resize by using style or attribute 12029 if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) { 12030 dom.setStyle(selectedElm, name, value); 12031 } else { 12032 dom.setAttrib(selectedElm, name, value); 12033 } 12034 } 12035 } 12036 12037 // Set width/height properties 12038 setSizeProp('width', width); 12039 setSizeProp('height', height); 12040 12041 dom.unbind(editableDoc, 'mousemove', resizeGhostElement); 12042 dom.unbind(editableDoc, 'mouseup', endGhostResize); 12043 12044 if (rootDocument != editableDoc) { 12045 dom.unbind(rootDocument, 'mousemove', resizeGhostElement); 12046 dom.unbind(rootDocument, 'mouseup', endGhostResize); 12047 } 12048 12049 // Remove ghost and update resize handle positions 12050 dom.remove(selectedElmGhost); 12051 12052 if (!isIE || selectedElm.nodeName == "TABLE") { 12053 showResizeRect(selectedElm); 12054 } 12055 12056 editor.fire('ObjectResized', {target: selectedElm, width: width, height: height}); 12057 editor.nodeChanged(); 12058 } 12059 12060 function showResizeRect(targetElm, mouseDownHandleName, mouseDownEvent) { 12061 var position, targetWidth, targetHeight, e, rect, offsetParent = editor.getBody(); 12062 12063 // Get position and size of target 12064 position = dom.getPos(targetElm, offsetParent); 12065 selectedElmX = position.x; 12066 selectedElmY = position.y; 12067 rect = targetElm.getBoundingClientRect(); // Fix for Gecko offsetHeight for table with caption 12068 targetWidth = rect.width || (rect.right - rect.left); 12069 targetHeight = rect.height || (rect.bottom - rect.top); 12070 12071 // Reset width/height if user selects a new image/table 12072 if (selectedElm != targetElm) { 12073 detachResizeStartListener(); 12074 selectedElm = targetElm; 12075 width = height = 0; 12076 } 12077 12078 // Makes it possible to disable resizing 12079 e = editor.fire('ObjectSelected', {target: targetElm}); 12080 12081 if (isResizable(targetElm) && !e.isDefaultPrevented()) { 12082 each(resizeHandles, function(handle, name) { 12083 var handleElm, handlerContainerElm; 12084 12085 function startDrag(e) { 12086 resizeStarted = true; 12087 12088 startX = e.screenX; 12089 startY = e.screenY; 12090 startW = selectedElm.clientWidth; 12091 startH = selectedElm.clientHeight; 12092 ratio = startH / startW; 12093 selectedHandle = handle; 12094 12095 selectedElmGhost = selectedElm.cloneNode(true); 12096 dom.addClass(selectedElmGhost, 'mce-clonedresizable'); 12097 selectedElmGhost.contentEditable = false; // Hides IE move layer cursor 12098 selectedElmGhost.unSelectabe = true; 12099 dom.setStyles(selectedElmGhost, { 12100 left: selectedElmX, 12101 top: selectedElmY, 12102 margin: 0 12103 }); 12104 12105 selectedElmGhost.removeAttribute('data-mce-selected'); 12106 editor.getBody().appendChild(selectedElmGhost); 12107 12108 dom.bind(editableDoc, 'mousemove', resizeGhostElement); 12109 dom.bind(editableDoc, 'mouseup', endGhostResize); 12110 12111 if (rootDocument != editableDoc) { 12112 dom.bind(rootDocument, 'mousemove', resizeGhostElement); 12113 dom.bind(rootDocument, 'mouseup', endGhostResize); 12114 } 12115 } 12116 12117 if (mouseDownHandleName) { 12118 // Drag started by IE native resizestart 12119 if (name == mouseDownHandleName) { 12120 startDrag(mouseDownEvent); 12121 } 12122 12123 return; 12124 } 12125 12126 // Get existing or render resize handle 12127 handleElm = dom.get('mceResizeHandle' + name); 12128 if (!handleElm) { 12129 handlerContainerElm = editor.getBody(); 12130 12131 handleElm = dom.add(handlerContainerElm, 'div', { 12132 id: 'mceResizeHandle' + name, 12133 'data-mce-bogus': true, 12134 'class': 'mce-resizehandle', 12135 contentEditable: false, // Hides IE move layer cursor 12136 unSelectabe: true, 12137 style: 'cursor:' + name + '-resize; margin:0; padding:0' 12138 }); 12139 12140 dom.bind(handleElm, 'mousedown', function(e) { 12141 e.preventDefault(); 12142 startDrag(e); 12143 }); 12144 } else { 12145 dom.show(handleElm); 12146 } 12147 12148 /* 12149 var halfHandleW = handleElm.offsetWidth / 2; 12150 var halfHandleH = handleElm.offsetHeight / 2; 12151 12152 // Position element 12153 dom.setStyles(handleElm, { 12154 left: Math.floor((targetWidth * handle[0] + selectedElmX) - halfHandleW + (handle[2] * halfHandleW)), 12155 top: Math.floor((targetHeight * handle[1] + selectedElmY) - halfHandleH + (handle[3] * halfHandleH)) 12156 }); 12157 */ 12158 12159 // Position element 12160 dom.setStyles(handleElm, { 12161 left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2), 12162 top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2) 12163 }); 12164 }); 12165 } else {
12166 hideResizeRect(); 12167 } 12168 12169 selectedElm.setAttribute('data-mce-selected', '1'); 12170 } 12171 12172 function hideResizeRect() { 12173 var name, handleElm; 12174 12175 if (selectedElm) { 12176 selectedElm.removeAttribute('data-mce-selected'); 12177 } 12178 12179 for (name in resizeHandles) { 12180 handleElm = dom.get('mceResizeHandle' + name); 12181 if (handleElm) { 12182 dom.unbind(handleElm); 12183 dom.remove(handleElm); 12184 } 12185 } 12186 } 12187 12188 function updateResizeRect(e) { 12189 var controlElm; 12190 12191 function isChildOrEqual(node, parent) { 12192 do { 12193 if (node === parent) { 12194 return true; 12195 } 12196 } while ((node = node.parentNode)); 12197 } 12198 12199 // Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v 12200 each(dom.select('img[data-mce-selected],hr[data-mce-selected]'), function(img) { 12201 img.removeAttribute('data-mce-selected'); 12202 }); 12203 12204 controlElm = e.type == 'mousedown' ? e.target : selection.getNode(); 12205 controlElm = dom.getParent(controlElm, isIE ? 'table' : 'table,img,hr'); 12206 12207 if (controlElm) { 12208 disableGeckoResize(); 12209 12210 if (isChildOrEqual(selection.getStart(), controlElm) && isChildOrEqual(selection.getEnd(), controlElm)) { 12211 if (!isIE || (controlElm != selection.getStart() && selection.getStart().nodeName !== 'IMG')) { 12212 showResizeRect(controlElm); 12213 return; 12214 } 12215 } 12216 } 12217 12218 hideResizeRect(); 12219 } 12220 12221 function attachEvent(elm, name, func) { 12222 if (elm && elm.attachEvent) { 12223 elm.attachEvent('on' + name, func); 12224 } 12225 } 12226 12227 function detachEvent(elm, name, func) { 12228 if (elm && elm.detachEvent) { 12229 elm.detachEvent('on' + name, func); 12230 } 12231 } 12232 12233 function resizeNativeStart(e) { 12234 var target = e.srcElement, pos, name, corner, cornerX, cornerY, relativeX, relativeY; 12235 12236 pos = target.getBoundingClientRect(); 12237 relativeX = lastMouseDownEvent.clientX - pos.left; 12238 relativeY = lastMouseDownEvent.clientY - pos.top; 12239 12240 // Figure out what corner we are draging on 12241 for (name in resizeHandles) { 12242 corner = resizeHandles[name]; 12243 12244 cornerX = target.offsetWidth * corner[0]; 12245 cornerY = target.offsetHeight * corner[1]; 12246 12247 if (Math.abs(cornerX - relativeX) < 8 && Math.abs(cornerY - relativeY) < 8) { 12248 selectedHandle = corner; 12249 break; 12250 } 12251 } 12252 12253 // Remove native selection and let the magic begin 12254 resizeStarted = true; 12255 editor.getDoc().selection.empty(); 12256 showResizeRect(target, name, lastMouseDownEvent); 12257 } 12258 12259 function nativeControlSelect(e) { 12260 var target = e.srcElement; 12261 12262 if (target != selectedElm) { 12263 detachResizeStartListener(); 12264 12265 if (target.id.indexOf('mceResizeHandle') === 0) { 12266 e.returnValue = false; 12267 return; 12268 } 12269 12270 if (target.nodeName == 'IMG' || target.nodeName == 'TABLE') { 12271 hideResizeRect(); 12272 selectedElm = target; 12273 attachEvent(target, 'resizestart', resizeNativeStart); 12274 } 12275 } 12276 } 12277 12278 function detachResizeStartListener() { 12279 detachEvent(selectedElm, 'resizestart', resizeNativeStart); 12280 } 12281 12282 function disableGeckoResize() { 12283 try { 12284 // Disable object resizing on Gecko 12285 editor.getDoc().execCommand('enableObjectResizing', false, false); 12286 } catch (ex) { 12287 // Ignore 12288 } 12289 } 12290 12291 function controlSelect(elm) { 12292 var ctrlRng; 12293 12294 if (!isIE) { 12295 return; 12296 } 12297 12298 ctrlRng = editableDoc.body.createControlRange(); 12299 12300 try { 12301 ctrlRng.addElement(elm); 12302 ctrlRng.select(); 12303 return true; 12304 } catch (ex) { 12305 // Ignore since the element can't be control selected for example a P tag 12306 } 12307 } 12308 12309 editor.on('init', function() { 12310 if (isIE) { 12311 // Hide the resize rect on resize and reselect the image
12312 editor.on('ObjectResized', function(e) { 12313 if (e.target.nodeName != 'TABLE') { 12314 hideResizeRect(); 12315 controlSelect(e.target); 12316 } 12317 }); 12318 12319 attachEvent(editor.getBody(), 'controlselect', nativeControlSelect); 12320 12321 editor.on('mousedown', function(e) { 12322 lastMouseDownEvent = e; 12323 }); 12324 } else { 12325 disableGeckoResize(); 12326 12327 if (Env.ie >= 11) { 12328 // TODO: Drag/drop doesn't work 12329 editor.on('mouseup', function(e) { 12330 var nodeName = e.target.nodeName; 12331 12332 if (/^(TABLE|IMG|HR)$/.test(nodeName)) { 12333 editor.selection.select(e.target, nodeName == 'TABLE'); 12334 editor.nodeChanged(); 12335 } 12336 }); 12337 12338 editor.dom.bind(editor.getBody(), 'mscontrolselect', function(e) { 12339 if (/^(TABLE|IMG|HR)$/.test(e.target.nodeName)) { 12340 e.preventDefault(); 12341 } 12342 }); 12343 } 12344 } 12345 12346 editor.on('nodechange mousedown mouseup ResizeEditor', updateResizeRect); 12347 12348 // Update resize rect while typing in a table 12349 editor.on('keydown keyup', function(e) { 12350 if (selectedElm && selectedElm.nodeName == "TABLE") { 12351 updateResizeRect(e); 12352 } 12353 }); 12354 12355 // Hide rect on focusout since it would float on top of windows otherwise 12356 //editor.on('focusout', hideResizeRect); 12357 }); 12358 12359 function destroy() { 12360 selectedElm = selectedElmGhost = null; 12361 12362 if (isIE) { 12363 detachResizeStartListener(); 12364 detachEvent(editor.getBody(), 'controlselect', nativeControlSelect); 12365 } 12366 } 12367 12368 return { 12369 controlSelect: controlSelect, 12370 destroy: destroy 12371 }; 12372 }; 12373}); 12374 12375// Included from: js/tinymce/classes/dom/Selection.js 12376 12377/** 12378 * Selection.js 12379 * 12380 * Copyright, Moxiecode Systems AB 12381 * Released under LGPL License. 12382 * 12383 * License: http://www.tinymce.com/license 12384 * Contributing: http://www.tinymce.com/contributing 12385 */ 12386 12387/** 12388 * This class handles text and control selection it's an crossbrowser utility class. 12389 * Consult the TinyMCE Wiki API for more details and examples on how to use this class. 12390 * 12391 * @class tinymce.dom.Selection 12392 * @example 12393 * // Getting the currently selected node for the active editor 12394 * alert(tinymce.activeEditor.selection.getNode().nodeName); 12395 */ 12396define("tinymce/dom/Selection", [ 12397 "tinymce/dom/TreeWalker", 12398 "tinymce/dom/TridentSelection", 12399 "tinymce/dom/ControlSelection", 12400 "tinymce/Env", 12401 "tinymce/util/Tools" 12402], function(TreeWalker, TridentSelection, ControlSelection, Env, Tools) { 12403 var each = Tools.each, grep = Tools.grep, trim = Tools.trim; 12404 var isIE = Env.ie, isOpera = Env.opera; 12405 12406 /** 12407 * Constructs a new selection instance. 12408 * 12409 * @constructor 12410 * @method Selection 12411 * @param {tinymce.dom.DOMUtils} dom DOMUtils object reference. 12412 * @param {Window} win Window to bind the selection object to. 12413 * @param {tinymce.dom.Serializer} serializer DOM serialization class to use for getContent. 12414 */ 12415 function Selection(dom, win, serializer, editor) { 12416 var self = this; 12417 12418 self.dom = dom; 12419 self.win = win; 12420 self.serializer = serializer; 12421 self.editor = editor; 12422 12423 self.controlSelection = new ControlSelection(self, editor); 12424 12425 // No W3C Range support 12426 if (!self.win.getSelection) { 12427 self.tridentSel = new TridentSelection(self); 12428 } 12429 } 12430 12431 Selection.prototype = { 12432 /** 12433 * Move the selection cursor range to the specified node and offset. 12434 * If there is no node specified it will move it to the first suitable location within the body. 12435 * 12436 * @method setCursorLocation 12437 * @param {Node} node Optional node to put the cursor in. 12438 * @param {Number} offset Optional offset from the start of the node to put the cursor at. 12439 */ 12440 setCursorLocation: function(node, offset) { 12441 var self = this, rng = self.dom.createRng(); 12442 12443 if (!node) { 12444 self._moveEndPoint(rng, self.editor.getBody(), true); 12445 self.setRng(rng); 12446 } else { 12447 rng.setStart(node, offset); 12448 rng.setEnd(node, offset); 12449 self.setRng(rng); 12450 self.collapse(false); 12451 } 12452 }, 12453 12454 /** 12455 * Returns the selected contents using the DOM serializer passed in to this class. 12456 * 12457 * @method getContent 12458 * @param {Object} s Optional settings class with for example output format text or html. 12459 * @return {String} Selected contents in for example HTML format. 12460 * @example 12461 * // Alerts the currently selected contents 12462 * alert(tinymce.activeEditor.selection.getContent()); 12463 * 12464 * // Alerts the currently selected contents as plain text 12465 * alert(tinymce.activeEditor.selection.getContent({format: 'text'})); 12466 */ 12467 getContent: function(args) { 12468 var self = this, rng = self.getRng(), tmpElm = self.dom.create("body"); 12469 var se = self.getSel(), whiteSpaceBefore, whiteSpaceAfter, fragment; 12470 12471 args = args || {}; 12472 whiteSpaceBefore = whiteSpaceAfter = ''; 12473 args.get = true; 12474 args.format = args.format || 'html'; 12475 args.selection = true; 12476 self.editor.fire('BeforeGetContent', args); 12477
12478 if (args.format == 'text') { 12479 return self.isCollapsed() ? '' : (rng.text || (se.toString ? se.toString() : '')); 12480 } 12481 12482 if (rng.cloneContents) { 12483 fragment = rng.cloneContents(); 12484 12485 if (fragment) { 12486 tmpElm.appendChild(fragment); 12487 } 12488 } else if (rng.item !== undefined || rng.htmlText !== undefined) { 12489 // IE will produce invalid markup if elements are present that 12490 // it doesn't understand like custom elements or HTML5 elements. 12491 // Adding a BR in front of the contents and then remoiving it seems to fix it though. 12492 tmpElm.innerHTML = '<br>' + (rng.item ? rng.item(0).outerHTML : rng.htmlText); 12493 tmpElm.removeChild(tmpElm.firstChild); 12494 } else { 12495 tmpElm.innerHTML = rng.toString(); 12496 } 12497 12498 // Keep whitespace before and after 12499 if (/^\s/.test(tmpElm.innerHTML)) { 12500 whiteSpaceBefore = ' '; 12501 } 12502 12503 if (/\s+$/.test(tmpElm.innerHTML)) { 12504 whiteSpaceAfter = ' '; 12505 } 12506 12507 args.getInner = true; 12508 12509 args.content = self.isCollapsed() ? '' : whiteSpaceBefore + self.serializer.serialize(tmpElm, args) + whiteSpaceAfter; 12510 self.editor.fire('GetContent', args); 12511 12512 return args.content; 12513 }, 12514 12515 /** 12516 * Sets the current selection to the specified content. If any contents is selected it will be replaced 12517 * with the contents passed in to this function. If there is no selection the contents will be inserted 12518 * where the caret is placed in the editor/page. 12519 * 12520 * @method setContent 12521 * @param {String} content HTML contents to set could also be other formats depending on settings. 12522 * @param {Object} args Optional settings object with for example data format. 12523 * @example 12524 * // Inserts some HTML contents at the current selection 12525 * tinymce.activeEditor.selection.setContent('<strong>Some contents</strong>'); 12526 */ 12527 setContent: function(content, args) { 12528 var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp; 12529 12530 args = args || {format: 'html'}; 12531 args.set = true; 12532 args.selection = true; 12533 content = args.content = content; 12534 12535 // Dispatch before set content event 12536 if (!args.no_events) { 12537 self.editor.fire('BeforeSetContent', args); 12538 } 12539 12540 content = args.content; 12541 12542 if (rng.insertNode) { 12543 // Make caret marker since insertNode places the caret in the beginning of text after insert 12544 content += '<span id="__caret">_</span>'; 12545 12546 // Delete and insert new node 12547 if (rng.startContainer == doc && rng.endContainer == doc) { 12548 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents 12549 doc.body.innerHTML = content; 12550 } else { 12551 rng.deleteContents(); 12552 12553 if (doc.body.childNodes.length === 0) { 12554 doc.body.innerHTML = content; 12555 } else { 12556 // createContextualFragment doesn't exists in IE 9 DOMRanges 12557 if (rng.createContextualFragment) { 12558 rng.insertNode(rng.createContextualFragment(content)); 12559 } else { 12560 // Fake createContextualFragment call in IE 9 12561 frag = doc.createDocumentFragment(); 12562 temp = doc.createElement('div'); 12563 12564 frag.appendChild(temp); 12565 temp.outerHTML = content; 12566 12567 rng.insertNode(frag); 12568 } 12569 } 12570 } 12571 12572 // Move to caret marker
12573 caretNode = self.dom.get('__caret'); 12574 12575 // Make sure we wrap it compleatly, Opera fails with a simple select call 12576 rng = doc.createRange(); 12577 rng.setStartBefore(caretNode); 12578 rng.setEndBefore(caretNode); 12579 self.setRng(rng); 12580 12581 // Remove the caret position 12582 self.dom.remove('__caret'); 12583 12584 try { 12585 self.setRng(rng); 12586 } catch (ex) { 12587 // Might fail on Opera for some odd reason 12588 } 12589 } else { 12590 if (rng.item) { 12591 // Delete content and get caret text selection 12592 doc.execCommand('Delete', false, null); 12593 rng = self.getRng(); 12594 } 12595 12596 // Explorer removes spaces from the beginning of pasted contents 12597 if (/^\s+/.test(content)) { 12598 rng.pasteHTML('<span id="__mce_tmp">_</span>' + content); 12599 self.dom.remove('__mce_tmp'); 12600 } else { 12601 rng.pasteHTML(content); 12602 } 12603 } 12604 12605 // Dispatch set content event 12606 if (!args.no_events) { 12607 self.editor.fire('SetContent', args); 12608 } 12609 }, 12610 12611 /** 12612 * Returns the start element of a selection range. If the start is in a text 12613 * node the parent element will be returned. 12614 * 12615 * @method getStart 12616 * @return {Element} Start element of selection range. 12617 */ 12618 getStart: function() { 12619 var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node; 12620 12621 if (rng.duplicate || rng.item) { 12622 // Control selection, return first item 12623 if (rng.item) { 12624 return rng.item(0); 12625 } 12626 12627 // Get start element 12628 checkRng = rng.duplicate(); 12629 checkRng.collapse(1); 12630 startElement = checkRng.parentElement(); 12631 if (startElement.ownerDocument !== self.dom.doc) { 12632 startElement = self.dom.getRoot(); 12633 } 12634 12635 // Check if range parent is inside the start element, then return the inner parent element 12636 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element 12637 parentElement = node = rng.parentElement(); 12638 while ((node = node.parentNode)) { 12639 if (node == startElement) { 12640 startElement = parentElement; 12641 break; 12642 } 12643 } 12644 12645 return startElement; 12646 } else { 12647 startElement = rng.startContainer; 12648 12649 if (startElement.nodeType == 1 && startElement.hasChildNodes()) { 12650 startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)]; 12651 } 12652 12653 if (startElement && startElement.nodeType == 3) { 12654 return startElement.parentNode; 12655 } 12656 12657 return startElement; 12658 } 12659 }, 12660 12661 /** 12662 * Returns the end element of a selection range. If the end is in a text 12663 * node the parent element will be returned. 12664 * 12665 * @method getEnd 12666 * @return {Element} End element of selection range. 12667 */ 12668 getEnd: function() { 12669 var self = this, rng = self.getRng(), endElement, endOffset; 12670 12671 if (rng.duplicate || rng.item) { 12672 if (rng.item) { 12673 return rng.item(0); 12674 } 12675 12676 rng = rng.duplicate(); 12677 rng.collapse(0); 12678 endElement = rng.parentElement(); 12679 if (endElement.ownerDocument !== self.dom.doc) { 12680 endElement = self.dom.getRoot(); 12681 } 12682 12683 if (endElement && endElement.nodeName == 'BODY') { 12684 return endElement.lastChild || endElement; 12685 } 12686 12687 return endElement; 12688 } else { 12689 endElement = rng.endContainer; 12690 endOffset = rng.endOffset; 12691 12692 if (endElement.nodeType == 1 && endElement.hasChildNodes()) { 12693 endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset]; 12694 } 12695 12696 if (endElement && endElement.nodeType == 3) { 12697 return endElement.parentNode; 12698 } 12699 12700 return endElement; 12701 } 12702 }, 12703 12704 /** 12705 * Returns a bookmark location for the current selection. This bookmark object 12706 * can then be used to restore the selection after some content modification to the document. 12707 * 12708 * @method getBookmark 12709 * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex. 12710 * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization. 12711 * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection. 12712 * @example 12713 * // Stores a bookmark of the current selection 12714 * var bm = tinymce.activeEditor.selection.getBookmark(); 12715 * 12716 * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content'); 12717 * 12718 * // Restore the selection bookmark 12719 * tinymce.activeEditor.selection.moveToBookmark(bm); 12720 */ 12721 getBookmark: function(type, normalized) { 12722 var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, chr = '', styles; 12723 12724 function findIndex(name, element) { 12725 var index = 0; 12726 12727 each(dom.select(name), function(node, i) { 12728 if (node == element) { 12729 index = i; 12730 } 12731 }); 12732 12733 return index; 12734 } 12735 12736 function normalizeTableCellSelection(rng) { 12737 function moveEndPoint(start) { 12738 var container, offset, childNodes, prefix = start ? 'start' : 'end'; 12739 12740 container = rng[prefix + 'Container']; 12741 offset = rng[prefix + 'Offset']; 12742 12743 if (container.nodeType == 1 && container.nodeName == "TR") { 12744 childNodes = container.childNodes; 12745 container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)]; 12746 if (container) { 12747 offset = start ? 0 : container.childNodes.length; 12748 rng['set' + (start ? 'Start' : 'End')](container, offset); 12749 } 12750 } 12751 } 12752 12753 moveEndPoint(true); 12754 moveEndPoint(); 12755 12756 return rng; 12757 } 12758 12759 function getLocation() { 12760 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {}; 12761 12762 function getPoint(rng, start) { 12763 var container = rng[start ? 'startContainer' : 'endContainer'], 12764 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; 12765 12766 if (container.nodeType == 3) { 12767 if (normalized) { 12768 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) { 12769 offset += node.nodeValue.length; 12770 } 12771 } 12772 12773 point.push(offset); 12774 } else { 12775 childNodes = container.childNodes; 12776 12777 if (offset >= childNodes.length && childNodes.length) { 12778 after = 1; 12779 offset = Math.max(0, childNodes.length - 1); 12780 } 12781 12782 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); 12783 } 12784 12785 for (; container && container != root; container = container.parentNode) { 12786 point.push(t.dom.nodeIndex(container, normalized)); 12787 } 12788 12789 return point; 12790 } 12791 12792 bookmark.start = getPoint(rng, true); 12793 12794 if (!t.isCollapsed()) { 12795 bookmark.end = getPoint(rng); 12796 } 12797 12798 return bookmark; 12799 } 12800 12801 if (type == 2) { 12802 element = t.getNode(); 12803 name = element.nodeName; 12804 12805 if (name == 'IMG') { 12806 return {name: name, index: findIndex(name, element)}; 12807 } 12808 12809 if (t.tridentSel) { 12810 return t.tridentSel.getBookmark(type); 12811 } 12812 12813 return getLocation(); 12814 } 12815 12816 // Handle simple range 12817 if (type) { 12818 return {rng: t.getRng()}; 12819 } 12820 12821 rng = t.getRng(); 12822 id = dom.uniqueId(); 12823 collapsed = t.isCollapsed(); 12824 styles = 'overflow:hidden;line-height:0px'; 12825 12826 // Explorer method 12827 if (rng.duplicate || rng.item) { 12828 // Text selection 12829 if (!rng.item) { 12830 rng2 = rng.duplicate(); 12831 12832 try { 12833 // Insert start marker 12834 rng.collapse(); 12835 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>'); 12836 12837 // Insert end marker 12838 if (!collapsed) { 12839 rng2.collapse(false); 12840 12841 // Detect the empty space after block elements in IE and move the 12842 // end back one character <p></p>] becomes <p>]</p> 12843 rng.moveToElementText(rng2.parentElement()); 12844 if (rng.compareEndPoints('StartToEnd', rng2) === 0) { 12845 rng2.move('character', -1); 12846 } 12847 12848 rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>'); 12849 } 12850 } catch (ex) { 12851 // IE might throw unspecified error so lets ignore it 12852 return null; 12853 } 12854 } else { 12855 // Control selection 12856 element = rng.item(0); 12857 name = element.nodeName; 12858 12859 return {name: name, index: findIndex(name, element)}; 12860 } 12861 } else { 12862 element = t.getNode(); 12863 name = element.nodeName; 12864 if (name == 'IMG') { 12865 return {name: name, index: findIndex(name, element)}; 12866 } 12867 12868 // W3C method 12869 rng2 = normalizeTableCellSelection(rng.cloneRange()); 12870 12871 // Insert end marker 12872 if (!collapsed) { 12873 rng2.collapse(false);
12874 rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr)); 12875 } 12876 12877 rng = normalizeTableCellSelection(rng); 12878 rng.collapse(true); 12879 rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr)); 12880 } 12881 12882 t.moveToBookmark({id: id, keep: 1}); 12883 12884 return {id: id}; 12885 }, 12886 12887 /** 12888 * Restores the selection to the specified bookmark. 12889 * 12890 * @method moveToBookmark 12891 * @param {Object} bookmark Bookmark to restore selection from. 12892 * @return {Boolean} true/false if it was successful or not. 12893 * @example 12894 * // Stores a bookmark of the current selection 12895 * var bm = tinymce.activeEditor.selection.getBookmark(); 12896 * 12897 * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content'); 12898 * 12899 * // Restore the selection bookmark 12900 * tinymce.activeEditor.selection.moveToBookmark(bm); 12901 */ 12902 moveToBookmark: function(bookmark) { 12903 var t = this, dom = t.dom, rng, root, startContainer, endContainer, startOffset, endOffset; 12904 12905 function setEndPoint(start) { 12906 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; 12907 12908 if (point) { 12909 offset = point[0]; 12910 12911 // Find container node 12912 for (node = root, i = point.length - 1; i >= 1; i--) { 12913 children = node.childNodes; 12914 12915 if (point[i] > children.length - 1) { 12916 return; 12917 } 12918 12919 node = children[point[i]]; 12920 } 12921 12922 // Move text offset to best suitable location 12923 if (node.nodeType === 3) { 12924 offset = Math.min(point[0], node.nodeValue.length); 12925 } 12926 12927 // Move element offset to best suitable location 12928 if (node.nodeType === 1) { 12929 offset = Math.min(point[0], node.childNodes.length); 12930 } 12931 12932 // Set offset within container node 12933 if (start) { 12934 rng.setStart(node, offset); 12935 } else { 12936 rng.setEnd(node, offset); 12937 } 12938 } 12939 12940 return true; 12941 } 12942 12943 function restoreEndPoint(suffix) { 12944 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; 12945 12946 if (marker) { 12947 node = marker.parentNode; 12948 12949 if (suffix == 'start') { 12950 if (!keep) { 12951 idx = dom.nodeIndex(marker); 12952 } else { 12953 node = marker.firstChild; 12954 idx = 1; 12955 } 12956 12957 startContainer = endContainer = node; 12958 startOffset = endOffset = idx; 12959 } else { 12960 if (!keep) { 12961 idx = dom.nodeIndex(marker); 12962 } else { 12963 node = marker.firstChild; 12964 idx = 1; 12965 } 12966 12967 endContainer = node; 12968 endOffset = idx; 12969 } 12970 12971 if (!keep) { 12972 prev = marker.previousSibling; 12973 next = marker.nextSibling; 12974 12975 // Remove all marker text nodes 12976 each(grep(marker.childNodes), function(node) { 12977 if (node.nodeType == 3) { 12978 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); 12979 } 12980 }); 12981 12982 // Remove marker but keep children if for example contents where inserted into the marker 12983 // Also remove duplicated instances of the marker for example by a 12984 // split operation or by WebKit auto split on paste feature 12985 while ((marker = dom.get(bookmark.id + '_' + suffix))) { 12986 dom.remove(marker, 1); 12987 } 12988 12989 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node 12990 // and we are sniffing since adding a lot of detection code for a browser with 3% of the market 12991 // isn't worth the effort. Sorry, Opera but it's just a fact 12992 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !isOpera) { 12993 idx = prev.nodeValue.length; 12994 prev.appendData(next.nodeValue); 12995 dom.remove(next); 12996 12997 if (suffix == 'start') { 12998 startContainer = endContainer = prev; 12999 startOffset = endOffset = idx; 13000 } else { 13001 endContainer = prev; 13002 endOffset = idx; 13003 } 13004 } 13005 } 13006 } 13007 } 13008 13009 function addBogus(node) { 13010 // Adds a bogus BR element for empty block elements 13011 if (dom.isBlock(node) && !node.innerHTML && !isIE) { 13012 node.innerHTML = '<br data-mce-bogus="1" />'; 13013 } 13014 13015 return node; 13016 } 13017 13018 if (bookmark) { 13019 if (bookmark.start) { 13020 rng = dom.createRng(); 13021 root = dom.getRoot(); 13022 13023 if (t.tridentSel) { 13024 return t.tridentSel.moveToBookmark(bookmark); 13025 } 13026 13027 if (setEndPoint(true) && setEndPoint()) { 13028 t.setRng(rng); 13029 } 13030 } else if (bookmark.id) { 13031 // Restore start/end points 13032 restoreEndPoint('start'); 13033 restoreEndPoint('end'); 13034 13035 if (startContainer) { 13036 rng = dom.createRng(); 13037 rng.setStart(addBogus(startContainer), startOffset); 13038 rng.setEnd(addBogus(endContainer), endOffset); 13039 t.setRng(rng); 13040 } 13041 } else if (bookmark.name) { 13042 t.select(dom.select(bookmark.name)[bookmark.index]); 13043 } else if (bookmark.rng) { 13044 t.setRng(bookmark.rng); 13045 } 13046 } 13047 }, 13048 13049 /** 13050 * Selects the specified element. This will place the start and end of the selection range around the element. 13051 * 13052 * @method select 13053 * @param {Element} node HMTL DOM element to select. 13054 * @param {Boolean} content Optional bool state if the contents should be selected or not on non IE browser. 13055 * @return {Element} Selected element the same element as the one that got passed in. 13056 * @example
13057 * // Select the first paragraph in the active editor 13058 * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]); 13059 */ 13060 select: function(node, content) { 13061 var self = this, dom = self.dom, rng = dom.createRng(), idx; 13062 13063 // Clear stored range set by FocusManager 13064 self.lastFocusBookmark = null; 13065 13066 if (node) { 13067 if (!content && self.controlSelection.controlSelect(node)) { 13068 return; 13069 } 13070 13071 idx = dom.nodeIndex(node); 13072 rng.setStart(node.parentNode, idx); 13073 rng.setEnd(node.parentNode, idx + 1); 13074 13075 // Find first/last text node or BR element 13076 if (content) { 13077 self._moveEndPoint(rng, node, true); 13078 self._moveEndPoint(rng, node); 13079 } 13080 13081 self.setRng(rng); 13082 } 13083 13084 return node; 13085 }, 13086 13087 /** 13088 * Returns true/false if the selection range is collapsed or not. Collapsed means if it's a caret or a larger selection. 13089 * 13090 * @method isCollapsed 13091 * @return {Boolean} true/false state if the selection range is collapsed or not. 13092 * Collapsed means if it's a caret or a larger selection. 13093 */ 13094 isCollapsed: function() { 13095 var self = this, rng = self.getRng(), sel = self.getSel(); 13096 13097 if (!rng || rng.item) { 13098 return false; 13099 } 13100 13101 if (rng.compareEndPoints) { 13102 return rng.compareEndPoints('StartToEnd', rng) === 0; 13103 } 13104 13105 return !sel || rng.collapsed; 13106 }, 13107 13108 /** 13109 * Collapse the selection to start or end of range. 13110 * 13111 * @method collapse 13112 * @param {Boolean} to_start Optional boolean state if to collapse to end or not. Defaults to start. 13113 */ 13114 collapse: function(to_start) { 13115 var self = this, rng = self.getRng(), node; 13116 13117 // Control range on IE 13118 if (rng.item) { 13119 node = rng.item(0); 13120 rng = self.win.document.body.createTextRange(); 13121 rng.moveToElementText(node); 13122 } 13123 13124 rng.collapse(!!to_start); 13125 self.setRng(rng); 13126 }, 13127 13128 /** 13129 * Returns the browsers internal selection object. 13130 * 13131 * @method getSel 13132 * @return {Selection} Internal browser selection object. 13133 */ 13134 getSel: function() { 13135 var win = this.win; 13136 13137 return win.getSelection ? win.getSelection() : win.document.selection; 13138 }, 13139 13140 /** 13141 * Returns the browsers internal range object. 13142 * 13143 * @method getRng 13144 * @param {Boolean} w3c Forces a compatible W3C range on IE. 13145 * @return {Range} Internal browser range object. 13146 * @see http://www.quirksmode.org/dom/range_intro.html 13147 * @see http://www.dotvoid.com/2001/03/using-the-range-object-in-mozilla/ 13148 */ 13149 getRng: function(w3c) { 13150 var self = this, selection, rng, elm, doc = self.win.document, ieRng; 13151 13152 // Use last rng passed from FocusManager if it's available this enables 13153 // calls to editor.selection.getStart() to work when caret focus is lost on IE 13154 if (!w3c && self.lastFocusBookmark) { 13155 var bookmark = self.lastFocusBookmark; 13156 13157 // Convert bookmark to range IE 11 fix 13158 if (bookmark.startContainer) { 13159 rng = doc.createRange(); 13160 rng.setStart(bookmark.startContainer, bookmark.startOffset); 13161 rng.setEnd(bookmark.endContainer, bookmark.endOffset); 13162 } else { 13163 rng = bookmark; 13164 } 13165 13166 return rng; 13167 } 13168 13169 // Found tridentSel object then we need to use that one 13170 if (w3c && self.tridentSel) { 13171 return self.tridentSel.getRangeAt(0); 13172 } 13173 13174 try { 13175 if ((selection = self.getSel())) { 13176 if (selection.rangeCount > 0) { 13177 rng = selection.getRangeAt(0); 13178 } else { 13179 rng = selection.createRange ? selection.createRange() : doc.createRange(); 13180 } 13181 } 13182 } catch (ex) { 13183 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe 13184 } 13185 13186 // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet 13187 if (isIE && rng && rng.setStart) { 13188 try { 13189 // IE will sometimes throw an exception here 13190 ieRng = doc.selection.createRange(); 13191 } catch (ex) { 13192 13193 } 13194 13195 if (ieRng && ieRng.item) { 13196 elm = ieRng.item(0); 13197 rng = doc.createRange(); 13198 rng.setStartBefore(elm); 13199 rng.setEndAfter(elm); 13200 } 13201 } 13202 13203 // No range found then create an empty one 13204 // This can occur when the editor is placed in a hidden container element on Gecko 13205 // Or on IE when there was an exception 13206 if (!rng) { 13207 rng = doc.createRange ? doc.createRange() : doc.body.createTextRange(); 13208 } 13209 13210 // If range is at start of document then move it to start of body 13211 if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) { 13212 elm = self.dom.getRoot(); 13213 rng.setStart(elm, 0); 13214 rng.setEnd(elm, 0); 13215 } 13216 13217 if (self.selectedRange && self.explicitRange) { 13218 if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && 13219 rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) { 13220 // Safari, Opera and Chrome only ever select text which causes the range to change. 13221 // This lets us use the originally set range if the selection hasn't been changed by the user. 13222 rng = self.explicitRange; 13223 } else { 13224 self.selectedRange = null; 13225 self.explicitRange = null; 13226 } 13227 } 13228 13229 return rng; 13230 }, 13231 13232 /** 13233 * Changes the selection to the specified DOM range. 13234 * 13235 * @method setRng 13236 * @param {Range} rng Range to select. 13237 */ 13238 setRng: function(rng, forward) { 13239 var self = this, sel; 13240 13241 // Is IE specific range 13242 if (rng.select) { 13243 try { 13244 rng.select(); 13245 } catch (ex) { 13246 // Needed for some odd IE bug #1843306 13247 } 13248 13249 return; 13250 } 13251 13252 if (!self.tridentSel) { 13253 sel = self.getSel(); 13254 13255 if (sel) { 13256 self.explicitRange = rng; 13257 13258 try { 13259 sel.removeAllRanges(); 13260 sel.addRange(rng); 13261 } catch (ex) { 13262 // IE might throw errors here if the editor is within a hidden container and selection is changed 13263 } 13264 13265 // Forward is set to false and we have an extend function 13266 if (forward === false && sel.extend) { 13267 sel.collapse(rng.endContainer, rng.endOffset); 13268 sel.extend(rng.startContainer, rng.startOffset); 13269 } 13270 13271 // adding range isn't always successful so we need to check range count otherwise an exception can occur 13272 self.selectedRange = sel.rangeCount > 0 ? sel.getRangeAt(0) : null; 13273 } 13274 } else { 13275 // Is W3C Range fake range on IE 13276 if (rng.cloneRange) { 13277 try { 13278 self.tridentSel.addRange(rng); 13279 return; 13280 } catch (ex) { 13281 //IE9 throws an error here if called before selection is placed in the editor
13282 } 13283 } 13284 } 13285 }, 13286 13287 /** 13288 * Sets the current selection to the specified DOM element. 13289 * 13290 * @method setNode 13291 * @param {Element} elm Element to set as the contents of the selection. 13292 * @return {Element} Returns the element that got passed in. 13293 * @example 13294 * // Inserts a DOM node at current selection/caret location 13295 * tinymce.activeEditor.selection.setNode(tinymce.activeEditor.dom.create('img', {src: 'some.gif', title: 'some title'})); 13296 */ 13297 setNode: function(elm) { 13298 var self = this; 13299 13300 self.setContent(self.dom.getOuterHTML(elm)); 13301 13302 return elm; 13303 }, 13304 13305 /** 13306 * Returns the currently selected element or the common ancestor element for both start and end of the selection. 13307 * 13308 * @method getNode 13309 * @return {Element} Currently selected element or common ancestor element. 13310 * @example 13311 * // Alerts the currently selected elements node name 13312 * alert(tinymce.activeEditor.selection.getNode().nodeName); 13313 */ 13314 getNode: function() { 13315 var self = this, rng = self.getRng(), elm; 13316 var startContainer = rng.startContainer, endContainer = rng.endContainer; 13317 var startOffset = rng.startOffset, endOffset = rng.endOffset, root = self.dom.getRoot(); 13318 13319 function skipEmptyTextNodes(node, forwards) { 13320 var orig = node; 13321 13322 while (node && node.nodeType === 3 && node.length === 0) { 13323 node = forwards ? node.nextSibling : node.previousSibling; 13324 } 13325 13326 return node || orig; 13327 } 13328 13329 // Range maybe lost after the editor is made visible again 13330 if (!rng) { 13331 return root; 13332 } 13333 13334 if (rng.setStart) { 13335 elm = rng.commonAncestorContainer; 13336 13337 // Handle selection a image or other control like element such as anchors 13338 if (!rng.collapsed) { 13339 if (startContainer == endContainer) { 13340 if (endOffset - startOffset < 2) { 13341 if (startContainer.hasChildNodes()) { 13342 elm = startContainer.childNodes[startOffset]; 13343 } 13344 } 13345 } 13346 13347 // If the anchor node is a element instead of a text node then return this element 13348 //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) 13349 // return sel.anchorNode.childNodes[sel.anchorOffset]; 13350 13351 // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent. 13352 // This happens when you double click an underlined word in FireFox. 13353 if (startContainer.nodeType === 3 && endContainer.nodeType === 3) { 13354 if (startContainer.length === startOffset) { 13355 startContainer = skipEmptyTextNodes(startContainer.nextSibling, true); 13356 } else { 13357 startContainer = startContainer.parentNode; 13358 } 13359 13360 if (endOffset === 0) { 13361 endContainer = skipEmptyTextNodes(endContainer.previousSibling, false); 13362 } else { 13363 endContainer = endContainer.parentNode; 13364 } 13365 13366 if (startContainer && startContainer === endContainer) { 13367 return startContainer; 13368 } 13369 } 13370 } 13371 13372 if (elm && elm.nodeType == 3) { 13373 return elm.parentNode; 13374 } 13375 13376 return elm; 13377 } 13378 13379 elm = rng.item ? rng.item(0) : rng.parentElement(); 13380 13381 // IE 7 might return elements outside the iframe 13382 if (elm.ownerDocument !== self.win.document) { 13383 elm = root; 13384 } 13385 13386 return elm; 13387 }, 13388 13389 getSelectedBlocks: function(startElm, endElm) { 13390 var self = this, dom = self.dom, node, root, selectedBlocks = []; 13391 13392 root = dom.getRoot(); 13393 startElm = dom.getParent(startElm || self.getStart(), dom.isBlock); 13394 endElm = dom.getParent(endElm || self.getEnd(), dom.isBlock); 13395 13396 if (startElm && startElm != root) { 13397 selectedBlocks.push(startElm); 13398 } 13399 13400 if (startElm && endElm && startElm != endElm) { 13401 node = startElm; 13402 13403 var walker = new TreeWalker(startElm, root); 13404 while ((node = walker.next()) && node != endElm) { 13405 if (dom.isBlock(node)) { 13406 selectedBlocks.push(node); 13407 } 13408 } 13409 } 13410 13411 if (endElm && startElm != endElm && endElm != root) { 13412 selectedBlocks.push(endElm); 13413 } 13414 13415 return selectedBlocks; 13416 }, 13417 13418 isForward: function(){ 13419 var dom = this.dom, sel = this.getSel(), anchorRange, focusRange; 13420 13421 // No support for selection direction then always return true 13422 if (!sel || !sel.anchorNode || !sel.focusNode) { 13423 return true; 13424 } 13425 13426 anchorRange = dom.createRng(); 13427 anchorRange.setStart(sel.anchorNode, sel.anchorOffset); 13428 anchorRange.collapse(true); 13429 13430 focusRange = dom.createRng(); 13431 focusRange.setStart(sel.focusNode, sel.focusOffset); 13432 focusRange.collapse(true); 13433 13434 return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0; 13435 }, 13436 13437 normalize: function() { 13438 var self = this, rng, normalized, collapsed; 13439 13440 function normalizeEndPoint(start) { 13441 var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName; 13442 var directionLeft; 13443 13444 function hasBrBeforeAfter(node, left) { 13445 var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body); 13446 13447 while ((node = walker[left ? 'prev' : 'next']())) { 13448 if (node.nodeName === "BR") { 13449 return true; 13450 } 13451 } 13452 } 13453 13454 function isPrevNode(node, name) { 13455 return node.previousSibling && node.previousSibling.nodeName == name; 13456 } 13457 13458 // Walks the dom left/right to find a suitable text node to move the endpoint into 13459 // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG 13460 function findTextNodeRelative(left, startNode) { 13461 var walker, lastInlineElement; 13462 13463 startNode = startNode || container; 13464 walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body); 13465 13466 // Walk left until we hit a text node we can move to or a block/br/img 13467 while ((node = walker[left ? 'prev' : 'next']())) { 13468 // Found text node that has a length 13469 if (node.nodeType === 3 && node.nodeValue.length > 0) { 13470 container = node; 13471 offset = left ? node.nodeValue.length : 0; 13472 normalized = true; 13473 return; 13474 } 13475 13476 // Break if we find a block or a BR/IMG/INPUT etc 13477 if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 13478 return; 13479 } 13480 13481 lastInlineElement = node; 13482 } 13483 13484 // Only fetch the last inline element when in caret mode for now 13485 if (collapsed && lastInlineElement) { 13486 container = lastInlineElement; 13487 normalized = true; 13488 offset = 0; 13489 } 13490 } 13491 13492 container = rng[(start ? 'start' : 'end') + 'Container']; 13493 offset = rng[(start ? 'start' : 'end') + 'Offset']; 13494 nonEmptyElementsMap = dom.schema.getNonEmptyElements(); 13495 directionLeft = start; 13496 13497 if (container.nodeType == 1 && offset > container.childNodes.length - 1) { 13498 directionLeft = false;
13499 } 13500 13501 // If the container is a document move it to the body element 13502 if (container.nodeType === 9) { 13503 container = dom.getRoot(); 13504 offset = 0; 13505 } 13506 13507 // If the container is body try move it into the closest text node or position 13508 if (container === body) { 13509 // If start is before/after a image, table etc 13510 if (directionLeft) { 13511 node = container.childNodes[offset > 0 ? offset - 1 : 0]; 13512 if (node) { 13513 nodeName = node.nodeName.toLowerCase(); 13514 if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") { 13515 return; 13516 } 13517 } 13518 } 13519 13520 // Resolve the index 13521 if (container.hasChildNodes()) { 13522 offset = Math.min(!directionLeft && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1); 13523 container = container.childNodes[offset]; 13524 offset = 0; 13525 13526 // Don't walk into elements that doesn't have any child nodes like a IMG 13527 if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) { 13528 // Walk the DOM to find a text node to place the caret at or a BR 13529 node = container; 13530 walker = new TreeWalker(container, body); 13531 13532 do { 13533 // Found a text node use that position 13534 if (node.nodeType === 3 && node.nodeValue.length > 0) { 13535 offset = directionLeft ? 0 : node.nodeValue.length; 13536 container = node; 13537 normalized = true; 13538 break; 13539 } 13540 13541 // Found a BR/IMG element that we can place the caret before 13542 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 13543 offset = dom.nodeIndex(node); 13544 container = node.parentNode; 13545 13546 // Put caret after image when moving the end point 13547 if (node.nodeName == "IMG" && !directionLeft) { 13548 offset++; 13549 } 13550 13551 normalized = true; 13552 break; 13553 } 13554 } while ((node = (directionLeft ? walker.next() : walker.prev()))); 13555 } 13556 } 13557 } 13558 13559 // Lean the caret to the left if possible 13560 if (collapsed) { 13561 // So this: <b>x</b><i>|x</i> 13562 // Becomes: <b>x|</b><i>x</i> 13563 // Seems that only gecko has issues with this 13564 if (container.nodeType === 3 && offset === 0) { 13565 findTextNodeRelative(true); 13566 } 13567 13568 // Lean left into empty inline elements when the caret is before a BR 13569 // So this: <i><b></b><i>|<br></i> 13570 // Becomes: <i><b>|</b><i><br></i> 13571 // Seems that only gecko has issues with this. 13572 // Special edge case for <p><a>x</a>|<br></p> since we don't want <p><a>x|</a><br></p> 13573 if (container.nodeType === 1) { 13574 node = container.childNodes[offset]; 13575 if(node && node.nodeName === 'BR' && !isPrevNode(node, 'A') && 13576 !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) { 13577 findTextNodeRelative(true, container.childNodes[offset]); 13578 } 13579 } 13580 } 13581 13582 // Lean the start of the selection right if possible 13583 // So this: x[<b>x]</b> 13584 // Becomes: x<b>[x]</b> 13585 if (directionLeft && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) { 13586 findTextNodeRelative(false); 13587 } 13588 13589 // Set endpoint if it was normalized 13590 if (normalized) { 13591 rng['set' + (start ? 'Start' : 'End')](container, offset); 13592 } 13593 } 13594 13595 // Normalize only on non IE browsers for now 13596 if (isIE) { 13597 return; 13598 } 13599 13600 rng = self.getRng(); 13601 collapsed = rng.collapsed; 13602 13603 // Normalize the end points 13604 normalizeEndPoint(true); 13605 13606 if (!collapsed) { 13607 normalizeEndPoint(); 13608 } 13609 13610 // Set the selection if it was normalized 13611 if (normalized) { 13612 // If it was collapsed then make sure it still is 13613 if (collapsed) { 13614 rng.collapse(true); 13615 } 13616 13617 //console.log(self.dom.dumpRng(rng)); 13618 self.setRng(rng, self.isForward()); 13619 } 13620 }, 13621 13622 /** 13623 * Executes callback of the current selection matches the specified selector or not and passes the state and args to the callback. 13624 * 13625 * @method selectorChanged 13626 * @param {String} selector CSS selector to check for. 13627 * @param {function} callback Callback with state and args when the selector is matches or not. 13628 */ 13629 selectorChanged: function(selector, callback) { 13630 var self = this, currentSelectors; 13631 13632 if (!self.selectorChangedData) { 13633 self.selectorChangedData = {}; 13634 currentSelectors = {}; 13635 13636 self.editor.on('NodeChange', function(e) { 13637 var node = e.element, dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {}; 13638 13639 // Check for new matching selectors 13640 each(self.selectorChangedData, function(callbacks, selector) { 13641 each(parents, function(node) { 13642 if (dom.is(node, selector)) { 13643 if (!currentSelectors[selector]) { 13644 // Execute callbacks 13645 each(callbacks, function(callback) { 13646 callback(true, {node: node, selector: selector, parents: parents}); 13647 }); 13648 13649 currentSelectors[selector] = callbacks; 13650 } 13651 13652 matchedSelectors[selector] = callbacks; 13653 return false;
13654 } 13655 }); 13656 }); 13657 13658 // Check if current selectors still match 13659 each(currentSelectors, function(callbacks, selector) { 13660 if (!matchedSelectors[selector]) { 13661 delete currentSelectors[selector]; 13662 13663 each(callbacks, function(callback) { 13664 callback(false, {node: node, selector: selector, parents: parents}); 13665 }); 13666 } 13667 }); 13668 }); 13669 } 13670 13671 // Add selector listeners 13672 if (!self.selectorChangedData[selector]) { 13673 self.selectorChangedData[selector] = []; 13674 } 13675 13676 self.selectorChangedData[selector].push(callback); 13677 13678 return self; 13679 }, 13680 13681 getScrollContainer: function() { 13682 var scrollContainer, node = this.dom.getRoot(); 13683 13684 while (node && node.nodeName != 'BODY') { 13685 if (node.scrollHeight > node.clientHeight) { 13686 scrollContainer = node; 13687 break; 13688 } 13689 13690 node = node.parentNode; 13691 } 13692 13693 return scrollContainer; 13694 }, 13695 13696 scrollIntoView: function(elm) { 13697 var y, viewPort, self = this, dom = self.dom, root = dom.getRoot(), viewPortY, viewPortH; 13698 13699 function getPos(elm) { 13700 var x = 0, y = 0; 13701 13702 var offsetParent = elm; 13703 while (offsetParent && offsetParent.nodeType) { 13704 x += offsetParent.offsetLeft || 0; 13705 y += offsetParent.offsetTop || 0; 13706 offsetParent = offsetParent.offsetParent; 13707 } 13708 13709 return {x: x, y: y}; 13710 } 13711 13712 if (root.nodeName != 'BODY') { 13713 var scrollContainer = self.getScrollContainer(); 13714 if (scrollContainer) { 13715 y = getPos(elm).y - getPos(scrollContainer).y; 13716 viewPortH = scrollContainer.clientHeight; 13717 viewPortY = scrollContainer.scrollTop; 13718 if (y < viewPortY || y + 25 > viewPortY + viewPortH) { 13719 scrollContainer.scrollTop = y < viewPortY ? y : y - viewPortH + 25; 13720 } 13721 13722 return; 13723 } 13724 } 13725 13726 viewPort = dom.getViewPort(self.editor.getWin()); 13727 y = dom.getPos(elm).y; 13728 viewPortY = viewPort.y; 13729 viewPortH = viewPort.h; 13730 if (y < viewPort.y || y + 25 > viewPortY + viewPortH) { 13731 self.editor.getWin().scrollTo(0, y < viewPortY ? y : y - viewPortH + 25); 13732 } 13733 }, 13734 13735 _moveEndPoint: function(rng, node, start) { 13736 var root = node, walker = new TreeWalker(node, root); 13737 var nonEmptyElementsMap = this.dom.schema.getNonEmptyElements(); 13738 13739 do { 13740 // Text node 13741 if (node.nodeType == 3 && trim(node.nodeValue).length !== 0) { 13742 if (start) { 13743 rng.setStart(node, 0); 13744 } else { 13745 rng.setEnd(node, node.nodeValue.length); 13746 } 13747 13748 return; 13749 } 13750 13751 // BR/IMG/INPUT elements 13752 if (nonEmptyElementsMap[node.nodeName]) { 13753 if (start) { 13754 rng.setStartBefore(node); 13755 } else { 13756 if (node.nodeName == 'BR') { 13757 rng.setEndBefore(node); 13758 } else { 13759 rng.setEndAfter(node); 13760 } 13761 } 13762 13763 return; 13764 } 13765 13766 // Found empty text block old IE can place the selection inside those 13767 if (Env.ie && Env.ie < 11 && this.dom.isBlock(node) && this.dom.isEmpty(node)) { 13768 if (start) { 13769 rng.setStart(node, 0); 13770 } else { 13771 rng.setEnd(node, 0); 13772 } 13773 13774 return; 13775 } 13776 } while ((node = (start ? walker.next() : walker.prev()))); 13777 13778 // Failed to find any text node or other suitable location then move to the root of body 13779 if (root.nodeName == 'BODY') { 13780 if (start) { 13781 rng.setStart(root, 0); 13782 } else { 13783 rng.setEnd(root, root.childNodes.length); 13784 } 13785 } 13786 }, 13787 13788 destroy: function() { 13789 this.win = null; 13790 this.controlSelection.destroy(); 13791 } 13792 }; 13793 13794 return Selection; 13795}); 13796 13797// Included from: js/tinymce/classes/dom/RangeUtils.js 13798 13799/** 13800 * Range.js 13801 * 13802 * Copyright, Moxiecode Systems AB 13803 * Released under LGPL License. 13804 * 13805 * License: http://www.tinymce.com/license 13806 * Contributing: http://www.tinymce.com/contributing 13807 */ 13808 13809/** 13810 * RangeUtils 13811 * 13812 * @class tinymce.dom.RangeUtils 13813 * @private 13814 */ 13815define("tinymce/dom/RangeUtils", [ 13816 "tinymce/util/Tools" 13817], function(Tools) { 13818 var each = Tools.each; 13819 13820 function RangeUtils(dom) { 13821 /** 13822 * Walks the specified range like object and executes the callback for each sibling collection it finds. 13823 * 13824 * @method walk 13825 * @param {Object} rng Range like object. 13826 * @param {function} callback Callback function to execute for each sibling collection. 13827 */ 13828 this.walk = function(rng, callback) { 13829 var startContainer = rng.startContainer, 13830 startOffset = rng.startOffset, 13831 endContainer = rng.endContainer, 13832 endOffset = rng.endOffset, 13833 ancestor, startPoint, 13834 endPoint, node, parent, siblings, nodes; 13835 13836 // Handle table cell selection the table plugin enables 13837 // you to fake select table cells and perform formatting actions on them
13838 nodes = dom.select('td.mce-item-selected,th.mce-item-selected'); 13839 if (nodes.length > 0) { 13840 each(nodes, function(node) { 13841 callback([node]); 13842 }); 13843 13844 return; 13845 } 13846 13847 /** 13848 * Excludes start/end text node if they are out side the range 13849 * 13850 * @private 13851 * @param {Array} nodes Nodes to exclude items from. 13852 * @return {Array} Array with nodes excluding the start/end container if needed. 13853 */ 13854 function exclude(nodes) { 13855 var node; 13856 13857 // First node is excluded 13858 node = nodes[0]; 13859 if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) { 13860 nodes.splice(0, 1); 13861 } 13862 13863 // Last node is excluded 13864 node = nodes[nodes.length - 1]; 13865 if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) { 13866 nodes.splice(nodes.length - 1, 1); 13867 } 13868 13869 return nodes; 13870 } 13871 13872 /** 13873 * Collects siblings 13874 * 13875 * @private 13876 * @param {Node} node Node to collect siblings from. 13877 * @param {String} name Name of the sibling to check for. 13878 * @return {Array} Array of collected siblings. 13879 */ 13880 function collectSiblings(node, name, end_node) { 13881 var siblings = []; 13882 13883 for (; node && node != end_node; node = node[name]) { 13884 siblings.push(node); 13885 } 13886 13887 return siblings; 13888 } 13889 13890 /** 13891 * Find an end point this is the node just before the common ancestor root. 13892 * 13893 * @private 13894 * @param {Node} node Node to start at. 13895 * @param {Node} root Root/ancestor element to stop just before. 13896 * @return {Node} Node just before the root element. 13897 */ 13898 function findEndPoint(node, root) { 13899 do { 13900 if (node.parentNode == root) { 13901 return node; 13902 } 13903 13904 node = node.parentNode; 13905 } while(node); 13906 } 13907
13908 function walkBoundary(start_node, end_node, next) { 13909 var siblingName = next ? 'nextSibling' : 'previousSibling'; 13910 13911 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) { 13912 parent = node.parentNode; 13913 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName); 13914 13915 if (siblings.length) { 13916 if (!next) { 13917 siblings.reverse(); 13918 } 13919 13920 callback(exclude(siblings)); 13921 } 13922 } 13923 } 13924 13925 // If index based start position then resolve it 13926 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { 13927 startContainer = startContainer.childNodes[startOffset]; 13928 } 13929 13930 // If index based end position then resolve it 13931 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { 13932 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)]; 13933 } 13934 13935 // Same container 13936 if (startContainer == endContainer) { 13937 return callback(exclude([startContainer])); 13938 } 13939 13940 // Find common ancestor and end points 13941 ancestor = dom.findCommonAncestor(startContainer, endContainer); 13942 13943 // Process left side 13944 for (node = startContainer; node; node = node.parentNode) { 13945 if (node === endContainer) { 13946 return walkBoundary(startContainer, ancestor, true); 13947 } 13948 13949 if (node === ancestor) { 13950 break; 13951 } 13952 } 13953 13954 // Process right side 13955 for (node = endContainer; node; node = node.parentNode) { 13956 if (node === startContainer) { 13957 return walkBoundary(endContainer, ancestor); 13958 } 13959 13960 if (node === ancestor) { 13961 break; 13962 } 13963 } 13964 13965 // Find start/end point 13966 startPoint = findEndPoint(startContainer, ancestor) || startContainer; 13967 endPoint = findEndPoint(endContainer, ancestor) || endContainer; 13968 13969 // Walk left leaf 13970 walkBoundary(startContainer, startPoint, true); 13971 13972 // Walk the middle from start to end point 13973 siblings = collectSiblings( 13974 startPoint == startContainer ? startPoint : startPoint.nextSibling, 13975 'nextSibling', 13976 endPoint == endContainer ? endPoint.nextSibling : endPoint 13977 ); 13978 13979 if (siblings.length) { 13980 callback(exclude(siblings)); 13981 } 13982 13983 // Walk right leaf 13984 walkBoundary(endContainer, endPoint); 13985 }; 13986 13987 /** 13988 * Splits the specified range at it's start/end points. 13989 * 13990 * @private 13991 * @param {Range/RangeObject} rng Range to split. 13992 * @return {Object} Range position object. 13993 */ 13994 this.split = function(rng) { 13995 var startContainer = rng.startContainer, 13996 startOffset = rng.startOffset, 13997 endContainer = rng.endContainer, 13998 endOffset = rng.endOffset; 13999 14000 function splitText(node, offset) { 14001 return node.splitText(offset); 14002 } 14003 14004 // Handle single text node 14005 if (startContainer == endContainer && startContainer.nodeType == 3) { 14006 if (startOffset > 0 && startOffset < startContainer.nodeValue.length) { 14007 endContainer = splitText(startContainer, startOffset); 14008 startContainer = endContainer.previousSibling; 14009 14010 if (endOffset > startOffset) { 14011 endOffset = endOffset - startOffset; 14012 startContainer = endContainer = splitText(endContainer, endOffset).previousSibling; 14013 endOffset = endContainer.nodeValue.length; 14014 startOffset = 0; 14015 } else { 14016 endOffset = 0; 14017 } 14018 } 14019 } else { 14020 // Split startContainer text node if needed 14021 if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) { 14022 startContainer = splitText(startContainer, startOffset); 14023 startOffset = 0; 14024 } 14025 14026 // Split endContainer text node if needed 14027 if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) { 14028 endContainer = splitText(endContainer, endOffset).previousSibling; 14029 endOffset = endContainer.nodeValue.length; 14030 } 14031 } 14032 14033 return { 14034 startContainer: startContainer, 14035 startOffset: startOffset, 14036 endContainer: endContainer, 14037 endOffset: endOffset 14038 }; 14039 }; 14040 } 14041 14042 /** 14043 * Compares two ranges and checks if they are equal. 14044 * 14045 * @static 14046 * @method compareRanges 14047 * @param {DOMRange} rng1 First range to compare. 14048 * @param {DOMRange} rng2 First range to compare. 14049 * @return {Boolean} true/false if the ranges are equal. 14050 */ 14051 RangeUtils.compareRanges = function(rng1, rng2) { 14052 if (rng1 && rng2) { 14053 // Compare native IE ranges 14054 if (rng1.item || rng1.duplicate) { 14055 // Both are control ranges and the selected element matches 14056 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) { 14057 return true; 14058 } 14059 14060 // Both are text ranges and the range matches 14061 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) { 14062 return true; 14063 } 14064 } else { 14065 // Compare w3c ranges 14066 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset; 14067 } 14068 } 14069 14070 return false;
14071 }; 14072 14073 return RangeUtils; 14074}); 14075 14076// Included from: js/tinymce/classes/Formatter.js 14077 14078/** 14079 * Formatter.js 14080 * 14081 * Copyright, Moxiecode Systems AB 14082 * Released under LGPL License. 14083 * 14084 * License: http://www.tinymce.com/license 14085 * Contributing: http://www.tinymce.com/contributing 14086 */ 14087 14088/** 14089 * Text formatter engine class. This class is used to apply formats like bold, italic, font size 14090 * etc to the current selection or specific nodes. This engine was build to replace the browsers 14091 * default formatting logic for execCommand due to it's inconsistent and buggy behavior. 14092 * 14093 * @class tinymce.Formatter 14094 * @example 14095 * tinymce.activeEditor.formatter.register('mycustomformat', { 14096 * inline: 'span', 14097 * styles: {color: '#ff0000'} 14098 * }); 14099 * 14100 * tinymce.activeEditor.formatter.apply('mycustomformat'); 14101 */ 14102define("tinymce/Formatter", [ 14103 "tinymce/dom/TreeWalker", 14104 "tinymce/dom/RangeUtils", 14105 "tinymce/util/Tools" 14106], function(TreeWalker, RangeUtils, Tools) { 14107 /** 14108 * Constructs a new formatter instance. 14109 * 14110 * @constructor Formatter 14111 * @param {tinymce.Editor} ed Editor instance to construct the formatter engine to. 14112 */ 14113 return function(ed) { 14114 var formats = {}, 14115 dom = ed.dom, 14116 selection = ed.selection, 14117 rangeUtils = new RangeUtils(dom), 14118 isValid = ed.schema.isValidChild, 14119 isBlock = dom.isBlock, 14120 forcedRootBlock = ed.settings.forced_root_block, 14121 nodeIndex = dom.nodeIndex, 14122 INVISIBLE_CHAR = '\uFEFF', 14123 MCE_ATTR_RE = /^(src|href|style)$/, 14124 FALSE = false, 14125 TRUE = true, 14126 formatChangeData, 14127 undef, 14128 getContentEditable = dom.getContentEditable, 14129 disableCaretContainer, 14130 markCaretContainersBogus; 14131 14132 var each = Tools.each, 14133 grep = Tools.grep, 14134 walk = Tools.walk, 14135 extend = Tools.extend; 14136 14137 function isTextBlock(name) { 14138 if (name.nodeType) { 14139 name = name.nodeName; 14140 } 14141 14142 return !!ed.schema.getTextBlockElements()[name.toLowerCase()]; 14143 } 14144 14145 function getParents(node, selector) { 14146 return dom.getParents(node, selector, dom.getRoot()); 14147 } 14148 14149 function isCaretNode(node) { 14150 return node.nodeType === 1 && node.id === '_mce_caret'; 14151 } 14152 14153 function defaultFormats() { 14154 register({ 14155 alignleft: [ 14156 {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'left'}, defaultBlock: 'div'}, 14157 {selector: 'img,table', collapsed: false, styles: {'float': 'left'}} 14158 ], 14159 14160 aligncenter: [ 14161 {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'center'}, defaultBlock: 'div'}, 14162 {selector: 'img', collapsed: false, styles: {display: 'block', marginLeft: 'auto', marginRight: 'auto'}}, 14163 {selector: 'table', collapsed: false, styles: {marginLeft: 'auto', marginRight: 'auto'}} 14164 ], 14165 14166 alignright: [ 14167 {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'right'}, defaultBlock: 'div'}, 14168 {selector: 'img,table', collapsed: false, styles: {'float': 'right'}} 14169 ], 14170 14171 alignjustify: [ 14172 {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'justify'}, defaultBlock: 'div'} 14173 ], 14174 14175 bold: [ 14176 {inline: 'strong', remove: 'all'}, 14177 {inline: 'span', styles: {fontWeight: 'bold'}}, 14178 {inline: 'b', remove: 'all'} 14179 ], 14180 14181 italic: [ 14182 {inline: 'em', remove: 'all'}, 14183 {inline: 'span', styles: {fontStyle: 'italic'}}, 14184 {inline: 'i', remove: 'all'} 14185 ], 14186 14187 underline: [ 14188 {inline: 'span', styles: {textDecoration: 'underline'}, exact: true}, 14189 {inline: 'u', remove: 'all'} 14190 ], 14191 14192 strikethrough: [ 14193 {inline: 'span', styles: {textDecoration: 'line-through'}, exact: true}, 14194 {inline: 'strike', remove: 'all'} 14195 ], 14196 14197 forecolor: {inline: 'span', styles: {color: '%value'}, wrap_links: false}, 14198 hilitecolor: {inline: 'span', styles: {backgroundColor: '%value'}, wrap_links: false}, 14199 fontname: {inline: 'span', styles: {fontFamily: '%value'}}, 14200 fontsize: {inline: 'span', styles: {fontSize: '%value'}}, 14201 fontsize_class: {inline: 'span', attributes: {'class': '%value'}}, 14202 blockquote: {block: 'blockquote', wrapper: 1, remove: 'all'}, 14203 subscript: {inline: 'sub'}, 14204 superscript: {inline: 'sup'}, 14205 code: {inline: 'code'}, 14206 14207 link: {inline: 'a', selector: 'a', remove: 'all', split: true, deep: true, 14208 onmatch: function() { 14209 return true; 14210 }, 14211 14212 onformat: function(elm, fmt, vars) { 14213 each(vars, function(value, key) { 14214 dom.setAttrib(elm, key, value); 14215 }); 14216 } 14217 }, 14218 14219 removeformat: [ 14220 { 14221 selector: 'b,strong,em,i,font,u,strike,sub,sup', 14222 remove: 'all', 14223 split: true, 14224 expand: false, 14225 block_expand: true, 14226 deep: true 14227 }, 14228 {selector: 'span', attributes: ['style', 'class'], remove: 'empty', split: true, expand: false, deep: true}, 14229 {selector: '*', attributes: ['style', 'class'], split: false, expand: false, deep: true} 14230 ] 14231 }); 14232 14233 // Register default block formats 14234 each('p h1 h2 h3 h4 h5 h6 div address pre div dt dd samp'.split(/\s/), function(name) { 14235 register(name, {block: name, remove: 'all'}); 14236 }); 14237 14238 // Register user defined formats 14239 register(ed.settings.formats); 14240 } 14241 14242 function addKeyboardShortcuts() { 14243 // Add some inline shortcuts 14244 ed.addShortcut('ctrl+b', 'bold_desc', 'Bold'); 14245 ed.addShortcut('ctrl+i', 'italic_desc', 'Italic'); 14246 ed.addShortcut('ctrl+u', 'underline_desc', 'Underline'); 14247 14248 // BlockFormat shortcuts keys 14249 for (var i = 1; i <= 6; i++) { 14250 ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]); 14251 } 14252 14253 ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']); 14254 ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']); 14255 ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']); 14256 } 14257 14258 // Public functions 14259 14260 /**
14261 * Returns the format by name or all formats if no name is specified. 14262 * 14263 * @method get 14264 * @param {String} name Optional name to retrive by. 14265 * @return {Array/Object} Array/Object with all registred formats or a specific format. 14266 */ 14267 function get(name) { 14268 return name ? formats[name] : formats; 14269 } 14270 14271 /** 14272 * Registers a specific format by name. 14273 * 14274 * @method register 14275 * @param {Object/String} name Name of the format for example "bold". 14276 * @param {Object/Array} format Optional format object or array of format variants 14277 * can only be omitted if the first arg is an object. 14278 */ 14279 function register(name, format) { 14280 if (name) { 14281 if (typeof(name) !== 'string') { 14282 each(name, function(format, name) { 14283 register(name, format); 14284 }); 14285 } else { 14286 // Force format into array and add it to internal collection 14287 format = format.length ? format : [format]; 14288 14289 each(format, function(format) { 14290 // Set deep to false by default on selector formats this to avoid removing 14291 // alignment on images inside paragraphs when alignment is changed on paragraphs 14292 if (format.deep === undef) { 14293 format.deep = !format.selector; 14294 } 14295 14296 // Default to true 14297 if (format.split === undef) { 14298 format.split = !format.selector || format.inline; 14299 } 14300 14301 // Default to true 14302 if (format.remove === undef && format.selector && !format.inline) { 14303 format.remove = 'none'; 14304 } 14305 14306 // Mark format as a mixed format inline + block level 14307 if (format.selector && format.inline) { 14308 format.mixed = true; 14309 format.block_expand = true; 14310 } 14311 14312 // Split classes if needed 14313 if (typeof(format.classes) === 'string') { 14314 format.classes = format.classes.split(/\s+/); 14315 } 14316 }); 14317 14318 formats[name] = format; 14319 } 14320 } 14321 } 14322 14323 function getTextDecoration(node) { 14324 var decoration; 14325 14326 ed.dom.getParent(node, function(n) { 14327 decoration = ed.dom.getStyle(n, 'text-decoration'); 14328 return decoration && decoration !== 'none'; 14329 }); 14330 14331 return decoration; 14332 } 14333 14334 function processUnderlineAndColor(node) { 14335 var textDecoration; 14336 if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) { 14337 textDecoration = getTextDecoration(node.parentNode); 14338 if (ed.dom.getStyle(node, 'color') && textDecoration) { 14339 ed.dom.setStyle(node, 'text-decoration', textDecoration); 14340 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) { 14341 ed.dom.setStyle(node, 'text-decoration', null); 14342 } 14343 } 14344 } 14345 14346 /** 14347 * Applies the specified format to the current selection or specified node. 14348 * 14349 * @method apply 14350 * @param {String} name Name of format to apply. 14351 * @param {Object} vars Optional list of variables to replace within format before applying it. 14352 * @param {Node} node Optional node to apply the format to defaults to current selection. 14353 */ 14354 function apply(name, vars, node) { 14355 var formatList = get(name), format = formatList[0], bookmark, rng, isCollapsed = !node && selection.isCollapsed(); 14356 14357 function setElementFormat(elm, fmt) { 14358 fmt = fmt || format; 14359 14360 if (elm) { 14361 if (fmt.onformat) { 14362 fmt.onformat(elm, fmt, vars, node); 14363 } 14364 14365 each(fmt.styles, function(value, name) { 14366 dom.setStyle(elm, name, replaceVars(value, vars)); 14367 }); 14368 14369 each(fmt.attributes, function(value, name) { 14370 dom.setAttrib(elm, name, replaceVars(value, vars)); 14371 }); 14372 14373 each(fmt.classes, function(value) { 14374 value = replaceVars(value, vars); 14375 14376 if (!dom.hasClass(elm, value)) { 14377 dom.addClass(elm, value); 14378 } 14379 }); 14380 } 14381 } 14382 14383 function adjustSelectionToVisibleSelection() { 14384 function findSelectionEnd(start, end) { 14385 var walker = new TreeWalker(end); 14386 for (node = walker.current(); node; node = walker.prev()) { 14387 if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') { 14388 return node; 14389 } 14390 } 14391 } 14392 14393 // Adjust selection so that a end container with a end offset of zero is not included in the selection 14394 // as this isn't visible to the user. 14395 var rng = ed.selection.getRng(); 14396 var start = rng.startContainer; 14397 var end = rng.endContainer; 14398 14399 if (start != end && rng.endOffset === 0) { 14400 var newEnd = findSelectionEnd(start, end); 14401 var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length; 14402 14403 rng.setEnd(newEnd, endOffset); 14404 } 14405 14406 return rng; 14407 } 14408 14409 function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){ 14410 var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm; 14411 14412 // find the index of the first child list. 14413 each(node.childNodes, function(n, index) { 14414 if (n.nodeName === "UL" || n.nodeName === "OL") { 14415 listIndex = index; 14416 list = n; 14417 return false;
14418 } 14419 }); 14420 14421 // get the index of the bookmarks 14422 each(node.childNodes, function(n, index) { 14423 if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") { 14424 if (n.id == bookmark.id + "_start") { 14425 startIndex = index; 14426 } else if (n.id == bookmark.id + "_end") { 14427 endIndex = index; 14428 } 14429 } 14430 }); 14431 14432 // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally 14433 if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) { 14434 each(grep(node.childNodes), process); 14435 return 0; 14436 } else { 14437 currentWrapElm = dom.clone(wrapElm, FALSE); 14438 14439 // create a list of the nodes on the same side of the list as the selection 14440 each(grep(node.childNodes), function(n, index) { 14441 if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) { 14442 nodes.push(n); 14443 n.parentNode.removeChild(n); 14444 } 14445 }); 14446 14447 // insert the wrapping element either before or after the list. 14448 if (startIndex < listIndex) { 14449 node.insertBefore(currentWrapElm, list); 14450 } else if (startIndex > listIndex) { 14451 node.insertBefore(currentWrapElm, list.nextSibling); 14452 } 14453 14454 // add the new nodes to the list. 14455 newWrappers.push(currentWrapElm); 14456 14457 each(nodes, function(node) { 14458 currentWrapElm.appendChild(node); 14459 }); 14460 14461 return currentWrapElm; 14462 } 14463 } 14464 14465 function applyRngStyle(rng, bookmark, node_specific) { 14466 var newWrappers = [], wrapName, wrapElm, contentEditable = true; 14467 14468 // Setup wrapper element 14469 wrapName = format.inline || format.block; 14470 wrapElm = dom.create(wrapName); 14471 setElementFormat(wrapElm); 14472 14473 rangeUtils.walk(rng, function(nodes) { 14474 var currentWrapElm; 14475 14476 /** 14477 * Process a list of nodes wrap them. 14478 */ 14479 function process(node) { 14480 var nodeName, parentName, found, hasContentEditableState, lastContentEditable; 14481 14482 lastContentEditable = contentEditable; 14483 nodeName = node.nodeName.toLowerCase(); 14484 parentName = node.parentNode.nodeName.toLowerCase(); 14485 14486 // Node has a contentEditable value 14487 if (node.nodeType === 1 && getContentEditable(node)) { 14488 lastContentEditable = contentEditable; 14489 contentEditable = getContentEditable(node) === "true"; 14490 hasContentEditableState = true; // We don't want to wrap the container only it's children 14491 } 14492 14493 // Stop wrapping on br elements 14494 if (isEq(nodeName, 'br')) { 14495 currentWrapElm = 0; 14496 14497 // Remove any br elements when we wrap things 14498 if (format.block) { 14499 dom.remove(node); 14500 } 14501 14502 return; 14503 } 14504 14505 // If node is wrapper type 14506 if (format.wrapper && matchNode(node, name, vars)) { 14507 currentWrapElm = 0; 14508 return; 14509 } 14510 14511 // Can we rename the block 14512 // TODO: Break this if up, too complex 14513 if (contentEditable && !hasContentEditableState && format.block && 14514 !format.wrapper && isTextBlock(nodeName) && isValid(parentName, wrapName)) { 14515 node = dom.rename(node, wrapName); 14516 setElementFormat(node); 14517 newWrappers.push(node); 14518 currentWrapElm = 0; 14519 return; 14520 } 14521 14522 // Handle selector patterns 14523 if (format.selector) { 14524 // Look for matching formats 14525 each(formatList, function(format) { 14526 // Check collapsed state if it exists 14527 if ('collapsed' in format && format.collapsed !== isCollapsed) { 14528 return; 14529 } 14530 14531 if (dom.is(node, format.selector) && !isCaretNode(node)) { 14532 setElementFormat(node, format); 14533 found = true; 14534 } 14535 }); 14536 14537 // Continue processing if a selector match wasn't found and a inline element is defined 14538 if (!format.inline || found) { 14539 currentWrapElm = 0; 14540 return; 14541 } 14542 } 14543 14544 // Is it valid to wrap this item 14545 // TODO: Break this if up, too complex 14546 if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) && 14547 !(!node_specific && node.nodeType === 3 && 14548 node.nodeValue.length === 1 && 14549 node.nodeValue.charCodeAt(0) === 65279) && 14550 !isCaretNode(node) && 14551 (!format.inline || !isBlock(node))) { 14552 // Start wrapping 14553 if (!currentWrapElm) { 14554 // Wrap the node 14555 currentWrapElm = dom.clone(wrapElm, FALSE); 14556 node.parentNode.insertBefore(currentWrapElm, node); 14557 newWrappers.push(currentWrapElm); 14558 } 14559 14560 currentWrapElm.appendChild(node); 14561 } else if (nodeName == 'li' && bookmark) { 14562 // Start wrapping - if we are in a list node and have a bookmark, then 14563 // we will always begin by wrapping in a new element. 14564 currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process); 14565 } else { 14566 // Start a new wrapper for possible children 14567 currentWrapElm = 0; 14568 14569 each(grep(node.childNodes), process); 14570 14571 if (hasContentEditableState) {
14572 contentEditable = lastContentEditable; // Restore last contentEditable state from stack 14573 } 14574 14575 // End the last wrapper 14576 currentWrapElm = 0; 14577 } 14578 } 14579 14580 // Process siblings from range 14581 each(nodes, process); 14582 }); 14583 14584 // Wrap links inside as well, for example color inside a link when the wrapper is around the link 14585 if (format.wrap_links === false) { 14586 each(newWrappers, function(node) { 14587 function process(node) { 14588 var i, currentWrapElm, children; 14589 14590 if (node.nodeName === 'A') { 14591 currentWrapElm = dom.clone(wrapElm, FALSE); 14592 newWrappers.push(currentWrapElm); 14593 14594 children = grep(node.childNodes); 14595 for (i = 0; i < children.length; i++) { 14596 currentWrapElm.appendChild(children[i]); 14597 } 14598 14599 node.appendChild(currentWrapElm); 14600 } 14601 14602 each(grep(node.childNodes), process); 14603 } 14604 14605 process(node); 14606 }); 14607 } 14608 14609 // Cleanup 14610 each(newWrappers, function(node) { 14611 var childCount; 14612 14613 function getChildCount(node) { 14614 var count = 0; 14615 14616 each(node.childNodes, function(node) { 14617 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) { 14618 count++; 14619 } 14620 }); 14621 14622 return count; 14623 } 14624 14625 function mergeStyles(node) { 14626 var child, clone; 14627 14628 each(node.childNodes, function(node) { 14629 if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) { 14630 child = node; 14631 return FALSE; // break loop 14632 } 14633 }); 14634 14635 // If child was found and of the same type as the current node 14636 if (child && matchName(child, format)) { 14637 clone = dom.clone(child, FALSE); 14638 setElementFormat(clone); 14639 14640 dom.replace(clone, node, TRUE); 14641 dom.remove(child, 1); 14642 } 14643 14644 return clone || node; 14645 } 14646 14647 childCount = getChildCount(node); 14648 14649 // Remove empty nodes but only if there is multiple wrappers and they are not block 14650 // elements so never remove single <h1></h1> since that would remove the 14651 // currrent empty block element where the caret is at 14652 if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) { 14653 dom.remove(node, 1); 14654 return; 14655 } 14656 14657 if (format.inline || format.wrapper) { 14658 // Merges the current node with it's children of similar type to reduce the number of elements 14659 if (!format.exact && childCount === 1) { 14660 node = mergeStyles(node); 14661 } 14662 14663 // Remove/merge children 14664 each(formatList, function(format) { 14665 // Merge all children of similar type will move styles from child to parent 14666 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span> 14667 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span> 14668 each(dom.select(format.inline, node), function(child) { 14669 var parent; 14670 14671 // When wrap_links is set to false we don't want 14672 // to remove the format on children within links 14673 if (format.wrap_links === false) { 14674 parent = child.parentNode; 14675 14676 do { 14677 if (parent.nodeName === 'A') { 14678 return; 14679 } 14680 } while ((parent = parent.parentNode)); 14681 } 14682 14683 removeFormat(format, vars, child, format.exact ? child : null); 14684 }); 14685 }); 14686 14687 // Remove child if direct parent is of same type 14688 if (matchNode(node.parentNode, name, vars)) { 14689 dom.remove(node, 1); 14690 node = 0; 14691 return TRUE; 14692 } 14693 14694 // Look for parent with similar style format 14695 if (format.merge_with_parents) { 14696 dom.getParent(node.parentNode, function(parent) { 14697 if (matchNode(parent, name, vars)) { 14698 dom.remove(node, 1); 14699 node = 0; 14700 return TRUE; 14701 } 14702 }); 14703 } 14704 14705 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b> 14706 if (node && format.merge_siblings !== false) { 14707 node = mergeSiblings(getNonWhiteSpaceSibling(node), node); 14708 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE)); 14709 } 14710 } 14711 }); 14712 } 14713 14714 if (format) { 14715 if (node) { 14716 if (node.nodeType) { 14717 rng = dom.createRng(); 14718 rng.setStartBefore(node); 14719 rng.setEndAfter(node); 14720 applyRngStyle(expandRng(rng, formatList), null, true); 14721 } else { 14722 applyRngStyle(node, null, true); 14723 } 14724 } else {
14725 if (!isCollapsed || !format.inline || dom.select('td.mce-item-selected,th.mce-item-selected').length) { 14726 // Obtain selection node before selection is unselected by applyRngStyle() 14727 var curSelNode = ed.selection.getNode(); 14728 14729 // If the formats have a default block and we can't find a parent block then 14730 // start wrapping it with a DIV this is for forced_root_blocks: false 14731 // It's kind of a hack but people should be using the default block type P since all desktop editors work that way 14732 if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) { 14733 apply(formatList[0].defaultBlock); 14734 } 14735 14736 // Apply formatting to selection 14737 ed.selection.setRng(adjustSelectionToVisibleSelection()); 14738 bookmark = selection.getBookmark(); 14739 applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark); 14740 14741 // Colored nodes should be underlined so that the color of the underline matches the text color. 14742 if (format.styles && (format.styles.color || format.styles.textDecoration)) { 14743 walk(curSelNode, processUnderlineAndColor, 'childNodes'); 14744 processUnderlineAndColor(curSelNode); 14745 } 14746 14747 selection.moveToBookmark(bookmark); 14748 moveStart(selection.getRng(TRUE)); 14749 ed.nodeChanged(); 14750 } else { 14751 performCaretAction('apply', name, vars); 14752 } 14753 } 14754 } 14755 } 14756 14757 /** 14758 * Removes the specified format from the current selection or specified node. 14759 * 14760 * @method remove 14761 * @param {String} name Name of format to remove. 14762 * @param {Object} vars Optional list of variables to replace within format before removing it. 14763 * @param {Node/Range} node Optional node or DOM range to remove the format from defaults to current selection. 14764 */ 14765 function remove(name, vars, node) { 14766 var formatList = get(name), format = formatList[0], bookmark, rng, contentEditable = true; 14767 14768 // Merges the styles for each node 14769 function process(node) { 14770 var children, i, l, lastContentEditable, hasContentEditableState; 14771 14772 // Node has a contentEditable value 14773 if (node.nodeType === 1 && getContentEditable(node)) { 14774 lastContentEditable = contentEditable; 14775 contentEditable = getContentEditable(node) === "true"; 14776 hasContentEditableState = true; // We don't want to wrap the container only it's children 14777 } 14778 14779 // Grab the children first since the nodelist might be changed 14780 children = grep(node.childNodes); 14781 14782 // Process current node 14783 if (contentEditable && !hasContentEditableState) { 14784 for (i = 0, l = formatList.length; i < l; i++) { 14785 if (removeFormat(formatList[i], vars, node, node)) { 14786 break; 14787 } 14788 } 14789 } 14790 14791 // Process the children 14792 if (format.deep) { 14793 if (children.length) { 14794 for (i = 0, l = children.length; i < l; i++) { 14795 process(children[i]); 14796 } 14797 14798 if (hasContentEditableState) { 14799 contentEditable = lastContentEditable; // Restore last contentEditable state from stack 14800 } 14801 } 14802 } 14803 } 14804 14805 function findFormatRoot(container) { 14806 var formatRoot; 14807 14808 // Find format root 14809 each(getParents(container.parentNode).reverse(), function(parent) { 14810 var format; 14811 14812 // Find format root element 14813 if (!formatRoot && parent.id != '_start' && parent.id != '_end') { 14814 // Is the node matching the format we are looking for 14815 format = matchNode(parent, name, vars); 14816 if (format && format.split !== false) { 14817 formatRoot = parent; 14818 } 14819 } 14820 }); 14821 14822 return formatRoot; 14823 } 14824 14825 function wrapAndSplit(format_root, container, target, split) { 14826 var parent, clone, lastClone, firstClone, i, formatRootParent; 14827 14828 // Format root found then clone formats and split it 14829 if (format_root) { 14830 formatRootParent = format_root.parentNode; 14831 14832 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) { 14833 clone = dom.clone(parent, FALSE); 14834 14835 for (i = 0; i < formatList.length; i++) { 14836 if (removeFormat(formatList[i], vars, clone, clone)) { 14837 clone = 0; 14838 break; 14839 } 14840 } 14841 14842 // Build wrapper node 14843 if (clone) { 14844 if (lastClone) { 14845 clone.appendChild(lastClone); 14846 } 14847 14848 if (!firstClone) { 14849 firstClone = clone; 14850 } 14851 14852 lastClone = clone; 14853 } 14854 } 14855 14856 // Never split block elements if the format is mixed 14857 if (split && (!format.mixed || !isBlock(format_root))) { 14858 container = dom.split(format_root, container); 14859 } 14860 14861 // Wrap container in cloned formats 14862 if (lastClone) { 14863 target.parentNode.insertBefore(lastClone, target); 14864 firstClone.appendChild(target); 14865 } 14866 } 14867 14868 return container; 14869 } 14870 14871 function splitToFormatRoot(container) { 14872 return wrapAndSplit(findFormatRoot(container), container, container, true); 14873 } 14874 14875 function unwrap(start) { 14876 var node = dom.get(start ? '_start' : '_end'), 14877 out = node[start ? 'firstChild' : 'lastChild']; 14878 14879 // If the end is placed within the start the result will be removed 14880 // So this checks if the out node is a bookmark node if it is it 14881 // checks for another more suitable node 14882 if (isBookmarkNode(out)) { 14883 out = out[start ? 'firstChild' : 'lastChild']; 14884 } 14885 14886 dom.remove(node, true); 14887 14888 return out; 14889 } 14890 14891 function removeRngStyle(rng) { 14892 var startContainer, endContainer; 14893 var commonAncestorContainer = rng.commonAncestorContainer; 14894 14895 rng = expandRng(rng, formatList, TRUE); 14896 14897 if (format.split) { 14898 startContainer = getContainer(rng, TRUE); 14899 endContainer = getContainer(rng); 14900 14901 if (startContainer != endContainer) { 14902 // WebKit will render the table incorrectly if we wrap a TH or TD in a SPAN 14903 // so let's see if we can use the first child instead 14904 // This will happen if you triple click a table cell and use remove formatting 14905 if (/^(TR|TH|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) { 14906 if (startContainer.nodeName == "TR") { 14907 startContainer = startContainer.firstChild.firstChild || startContainer; 14908 } else { 14909 startContainer = startContainer.firstChild || startContainer; 14910 } 14911 } 14912 14913 // Try to adjust endContainer as well if cells on the same row were selected - bug #6410 14914 if (commonAncestorContainer && 14915 /^T(HEAD|BODY|FOOT|R)$/.test(commonAncestorContainer.nodeName) && 14916 /^(TH|TD)$/.test(endContainer.nodeName) && endContainer.firstChild) { 14917 endContainer = endContainer.firstChild || endContainer; 14918 } 14919 14920 // Wrap start/end nodes in span element since these might be cloned/moved 14921 startContainer = wrap(startContainer, 'span', {id: '_start', 'data-mce-type': 'bookmark'});
14922 endContainer = wrap(endContainer, 'span', {id: '_end', 'data-mce-type': 'bookmark'}); 14923 14924 // Split start/end 14925 splitToFormatRoot(startContainer); 14926 splitToFormatRoot(endContainer); 14927 14928 // Unwrap start/end to get real elements again 14929 startContainer = unwrap(TRUE); 14930 endContainer = unwrap(); 14931 } else { 14932 startContainer = endContainer = splitToFormatRoot(startContainer); 14933 } 14934 14935 // Update range positions since they might have changed after the split operations 14936 rng.startContainer = startContainer.parentNode; 14937 rng.startOffset = nodeIndex(startContainer); 14938 rng.endContainer = endContainer.parentNode; 14939 rng.endOffset = nodeIndex(endContainer) + 1; 14940 } 14941 14942 // Remove items between start/end 14943 rangeUtils.walk(rng, function(nodes) { 14944 each(nodes, function(node) { 14945 process(node); 14946 14947 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined. 14948 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && 14949 node.parentNode && getTextDecoration(node.parentNode) === 'underline') { 14950 removeFormat({ 14951 'deep': false, 14952 'exact': true, 14953 'inline': 'span', 14954 'styles': { 14955 'textDecoration': 'underline' 14956 } 14957 }, null, node); 14958 } 14959 }); 14960 }); 14961 } 14962 14963 // Handle node 14964 if (node) { 14965 if (node.nodeType) { 14966 rng = dom.createRng(); 14967 rng.setStartBefore(node); 14968 rng.setEndAfter(node); 14969 removeRngStyle(rng); 14970 } else { 14971 removeRngStyle(node); 14972 } 14973 14974 return; 14975 } 14976 14977 if (!selection.isCollapsed() || !format.inline || dom.select('td.mce
14977-item-selected,th.mce-item-selected').length) { 14978 bookmark = selection.getBookmark(); 14979 removeRngStyle(selection.getRng(TRUE)); 14980 selection.moveToBookmark(bookmark); 14981 14982 // Check if start element still has formatting then we are at: "<b>text|</b>text" 14983 // and need to move the start into the next text node 14984 if (format.inline && match(name, vars, selection.getStart())) { 14985 moveStart(selection.getRng(true)); 14986 } 14987 14988 ed.nodeChanged(); 14989 } else { 14990 performCaretAction('remove', name, vars); 14991 } 14992 } 14993 14994 /** 14995 * Toggles the specified format on/off. 14996 * 14997 * @method toggle 14998 * @param {String} name Name of format to apply/remove. 14999 * @param {Object} vars Optional list of variables to replace within format before applying/removing it. 15000 * @param {Node} node Optional node to apply the format to or remove from. Defaults to current selection. 15001 */ 15002 function toggle(name, vars, node) { 15003 var fmt = get(name); 15004 15005 if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) { 15006 remove(name, vars, node); 15007 } else { 15008 apply(name, vars, node); 15009 } 15010 } 15011 15012 /** 15013 * Return true/false if the specified node has the specified format. 15014 * 15015 * @method matchNode 15016 * @param {Node} node Node to check the format on. 15017 * @param {String} name Format name to check. 15018 * @param {Object} vars Optional list of variables to replace before checking it. 15019 * @param {Boolean} similar Match format that has similar properties. 15020 * @return {Object} Returns the format object it matches or undefined if it doesn't match. 15021 */ 15022 function matchNode(node, name, vars, similar) { 15023 var formatList = get(name), format, i, classes; 15024 15025 function matchItems(node, format, item_name) { 15026 var key, value, items = format[item_name], i; 15027 15028 // Custom match 15029 if (format.onmatch) { 15030 return format.onmatch(node, format, item_name); 15031 } 15032 15033 // Check all items 15034 if (items) { 15035 // Non indexed object 15036 if (items.length === undef) { 15037 for (key in items) { 15038 if (items.hasOwnProperty(key)) { 15039 if (item_name === 'attributes') { 15040 value = dom.getAttrib(node, key); 15041 } else { 15042 value = getStyle(node, key); 15043 } 15044 15045 if (similar && !value && !format.exact) { 15046 return; 15047 } 15048 15049 if ((!similar || format.exact) && !isEq(value, normalizeStyleValue(replaceVars(items[key], vars), key))) { 15050 return; 15051 } 15052 } 15053 } 15054 } else { 15055 // Only one match needed for indexed arrays 15056 for (i = 0; i < items.length; i++) { 15057 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) { 15058 return format; 15059 } 15060 } 15061 } 15062 } 15063 15064 return format; 15065 } 15066 15067 if (formatList && node) { 15068 // Check each format in list 15069 for (i = 0; i < formatList.length; i++) { 15070 format = formatList[i]; 15071 15072 // Name name, attributes, styles and classes 15073 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) { 15074 // Match classes 15075 if ((classes = format.classes)) { 15076 for (i = 0; i < classes.length; i++) { 15077 if (!dom.hasClass(node, classes[i])) { 15078 return; 15079 } 15080 } 15081 } 15082 15083 return format; 15084 } 15085 } 15086 } 15087 } 15088 15089 /** 15090 * Matches the current selection or specified node against the specified format name. 15091 * 15092 * @method match 15093 * @param {String} name Name of format to match. 15094 * @param {Object} vars Optional list of variables to replace before checking it. 15095 * @param {Node} node Optional node to check. 15096 * @return {boolean} true/false if the specified selection/node matches the format. 15097 */ 15098 function match(name, vars, node) { 15099 var startNode; 15100 15101 function matchParents(node) { 15102 var root = dom.getRoot(); 15103 15104 if (node === root) { 15105 return false; 15106 } 15107 15108 // Find first node with similar format settings 15109 node = dom.getParent(node, function(node) { 15110 return node.parentNode === root || !!matchNode(node, name, vars, true); 15111 }); 15112 15113 // Do an exact check on the similar format element 15114 return matchNode(node, name, vars); 15115 } 15116
15117 // Check specified node 15118 if (node) { 15119 return matchParents(node); 15120 } 15121 15122 // Check selected node 15123 node = selection.getNode(); 15124 if (matchParents(node)) { 15125 return TRUE; 15126 } 15127 15128 // Check start node if it's different 15129 startNode = selection.getStart(); 15130 if (startNode != node) { 15131 if (matchParents(startNode)) { 15132 return TRUE; 15133 } 15134 } 15135 15136 return FALSE; 15137 } 15138 15139 /** 15140 * Matches the current selection against the array of formats and returns a new array with matching formats. 15141 * 15142 * @method matchAll 15143 * @param {Array} names Name of format to match. 15144 * @param {Object} vars Optional list of variables to replace before checking it. 15145 * @return {Array} Array with matched formats. 15146 */ 15147 function matchAll(names, vars) { 15148 var startElement, matchedFormatNames = [], checkedMap = {}; 15149 15150 // Check start of selection for formats 15151 startElement = selection.getStart(); 15152 dom.getParent(startElement, function(node) { 15153 var i, name; 15154 15155 for (i = 0; i < names.length; i++) { 15156 name = names[i]; 15157 15158 if (!checkedMap[name] && matchNode(node, name, vars)) { 15159 checkedMap[name] = true; 15160 matchedFormatNames.push(name); 15161 } 15162 } 15163 }, dom.getRoot()); 15164 15165 return matchedFormatNames; 15166 } 15167 15168 /** 15169 * Returns true/false if the specified format can be applied to the current selection or not. It 15170 * will currently only check the state for selector formats, it returns true on all other format types. 15171 * 15172 * @method canApply 15173 * @param {String} name Name of format to check. 15174 * @return {boolean} true/false if the specified format can be applied to the current selection/node. 15175 */ 15176 function canApply(name) { 15177 var formatList = get(name), startNode, parents, i, x, selector; 15178 15179 if (formatList) { 15180 startNode = selection.getStart(); 15181 parents = getParents(startNode); 15182 15183 for (x = formatList.length - 1; x >= 0; x--) { 15184 selector = formatList[x].selector; 15185 15186 // Format is not selector based then always return TRUE 15187 // Is it has a defaultBlock then it's likely it can be applied for example align on a non block element line 15188 if (!selector || formatList[x].defaultBlock) { 15189 return TRUE; 15190 } 15191 15192 for (i = parents.length - 1; i >= 0; i--) { 15193 if (dom.is(parents[i], selector)) { 15194 return TRUE; 15195 } 15196 } 15197 } 15198 } 15199 15200 return FALSE; 15201 } 15202 15203 /** 15204 * Executes the specified callback when the current selection matches the formats or not. 15205 * 15206 * @method formatChanged 15207 * @param {String} formats Comma separated list of formats to check for. 15208 * @param {function} callback Callback with state and args when the format is changed/toggled on/off. 15209 * @param {Boolean} similar True/false state if the match should handle similar or exact formats. 15210 */ 15211 function formatChanged(formats, callback, similar) { 15212 var currentFormats; 15213 15214 // Setup format node change logic 15215 if (!formatChangeData) { 15216 formatChangeData = {}; 15217 currentFormats = {}; 15218 15219 ed.on('NodeChange', function(e) { 15220 var parents = getParents(e.element), matchedFormats = {}; 15221 15222 // Check for new formats 15223 each(formatChangeData, function(callbacks, format) { 15224 each(parents, function(node) { 15225 if (matchNode(node, format, {}, callbacks.similar)) { 15226 if (!currentFormats[format]) { 15227 // Execute callbacks 15228 each(callbacks, function(callback) { 15229 callback(true, {node: node, format: format, parents: parents}); 15230 }); 15231 15232 currentFormats[format] = callbacks; 15233 } 15234 15235 matchedFormats[format] = callbacks; 15236 return false; 15237 } 15238 }); 15239 }); 15240 15241 // Check if current formats still match 15242 each(currentFormats, function(callbacks, format) { 15243 if (!matchedFormats[format]) { 15244 delete currentFormats[format]; 15245 15246 each(callbacks, function(callback) { 15247 callback(false, {node: e.element, format: format, parents: parents}); 15248 }); 15249 } 15250 }); 15251 }); 15252 } 15253 15254 // Add format listeners 15255 each(formats.split(','), function(format) { 15256 if (!formatChangeData[format]) { 15257 formatChangeData[format] = []; 15258 formatChangeData[format].similar = similar; 15259 } 15260 15261 formatChangeData[format].push(callback); 15262 }); 15263 15264 return this; 15265 } 15266 15267 // Expose to public 15268 extend(this, { 15269 get: get, 15270 register: register, 15271 apply: apply, 15272 remove: remove, 15273 toggle: toggle, 15274 match: match, 15275 matchAll: matchAll, 15276 matchNode: matchNode, 15277 canApply: canApply, 15278 formatChanged: formatChanged 15279 }); 15280 15281 // Initialize 15282 defaultFormats(); 15283 addKeyboardShortcuts(); 15284 ed.on('BeforeGetContent', function() { 15285 if (markCaretContainersBogus) { 15286 markCaretContainersBogus(); 15287 } 15288 }); 15289 ed.on('mouseup keydown', function(e) { 15290 if (disableCaretContainer) { 15291 disableCaretContainer(e); 15292 } 15293 }); 15294 15295 // Private functions 15296 15297 /** 15298 * Checks if the specified nodes name matches the format inline/block or selector. 15299 * 15300 * @private 15301 * @param {Node} node Node to match against the specified format. 15302 * @param {Object} format Format object o match with. 15303 * @return {boolean} true/false if the format matches. 15304 */ 15305 function matchName(node, format) { 15306 // Check for inline match 15307 if (isEq(node, format.inline)) { 15308 return TRUE; 15309 } 15310 15311 // Check for block match 15312 if (isEq(node, format.block)) { 15313 return TRUE; 15314 } 15315 15316 // Check for selector match 15317 if (format.selector) { 15318 return node.nodeType == 1 && dom.is(node, format.selector); 15319 } 15320 } 15321 15322 /** 15323 * Compares two string/nodes regardless of their case. 15324 * 15325 * @private 15326 * @param {String/Node} Node or string to compare. 15327 * @param {String/Node} Node or string to compare. 15328 * @return {boolean} True/false if they match. 15329 */ 15330 function isEq(str1, str2) { 15331 str1 = str1 || ''; 15332 str2 = str2 || ''; 15333 15334 str1 = '' + (str1.nodeName || str1); 15335 str2 = '' + (str2.nodeName || str2); 15336 15337 return str1.toLowerCase() == str2.toLowerCase(); 15338 } 15339 15340 /** 15341 * Returns the style by name on the specified node. This method modifies the style 15342 * contents to make it more easy to match. This will resolve a few browser issues. 15343 * 15344 * @private 15345 * @param {Node} node to get style from. 15346 * @param {String} name Style name to get. 15347 * @return {String} Style item value. 15348 */ 15349 function getStyle(node, name) { 15350 return normalizeStyleValue(dom.getStyle(node, name), name); 15351 } 15352 15353 /** 15354 * Normalize style value by name. This method modifies the style contents 15355 * to make it more easy to match. This will resolve a few browser issues. 15356 * 15357 * @private 15358 * @param {Node} node to get style from. 15359 * @param {String} name Style name to get. 15360 * @return {String} Style item value. 15361 */ 15362 function normalizeStyleValue(value, name) { 15363 // Force the format to hex 15364 if (name == 'color' || name == 'backgroundColor') { 15365 value = dom.toHex(value); 15366 } 15367 15368 // Opera will return bold as 700 15369 if (name == 'fontWeight' && value == 700) { 15370 value = 'bold'; 15371 } 15372
15373 // Normalize fontFamily so "'Font name', Font" becomes: "Font name,Font" 15374 if (name == 'fontFamily') { 15375 value = value.replace(/[\'\"]/g, '').replace(/,\s+/g, ','); 15376 } 15377 15378 return '' + value; 15379 } 15380 15381 /** 15382 * Replaces variables in the value. The variable format is %var. 15383 * 15384 * @private 15385 * @param {String} value Value to replace variables in. 15386 * @param {Object} vars Name/value array with variables to replace. 15387 * @return {String} New value with replaced variables. 15388 */ 15389 function replaceVars(value, vars) { 15390 if (typeof(value) != "string") { 15391 value = value(vars); 15392 } else if (vars) { 15393 value = value.replace(/%(\w+)/g, function(str, name) { 15394 return vars[name] || str; 15395 }); 15396 } 15397 15398 return value; 15399 } 15400 15401 function isWhiteSpaceNode(node) { 15402 return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue); 15403 } 15404 15405 function wrap(node, name, attrs) { 15406 var wrapper = dom.create(name, attrs); 15407 15408 node.parentNode.insertBefore(wrapper, node); 15409 wrapper.appendChild(node); 15410 15411 return wrapper; 15412 } 15413 15414 /** 15415 * Expands the specified range like object to depending on format. 15416 * 15417 * For example on block formats it will move the start/end position 15418 * to the beginning of the current block. 15419 * 15420 * @private 15421 * @param {Object} rng Range like object. 15422 * @param {Array} formats Array with formats to expand by. 15423 * @return {Object} Expanded range like object. 15424 */ 15425 function expandRng(rng, format, remove) { 15426 var lastIdx, leaf, endPoint, 15427 startContainer = rng.startContainer, 15428 startOffset = rng.startOffset, 15429 endContainer = rng.endContainer, 15430 endOffset = rng.endOffset; 15431 15432 // This function walks up the tree if there is no siblings before/after the node 15433 function findParentContainer(start) { 15434 var container, parent, sibling, siblingName, root; 15435 15436 container = parent = start ? startContainer : endContainer; 15437 siblingName = start ? 'previousSibling' : 'nextSibling'; 15438 root = dom.getRoot(); 15439 15440 function isBogusBr(node) { 15441 return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling; 15442 } 15443 15444 // If it's a text node and the offset is inside the text 15445 if (container.nodeType == 3 && !isWhiteSpaceNode(container)) { 15446 if (start ? startOffset > 0 : endOffset < container.nodeValue.length) { 15447 return container; 15448 } 15449 } 15450 15451 for (;;) { 15452 // Stop expanding on block elements 15453 if (!format[0].block_expand && isBlock(parent)) { 15454 return parent; 15455 } 15456 15457 // Walk left/right 15458 for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) { 15459 if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) { 15460 return parent; 15461 } 15462 } 15463 15464 // Check if we can move up are we at root level or body level 15465 if (parent.parentNode == root) { 15466 container = parent; 15467 break; 15468 } 15469 15470 parent = parent.parentNode; 15471 } 15472 15473 return container; 15474 } 15475 15476 // This function walks down the tree to find the leaf at the selection. 15477 // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node. 15478 function findLeaf(node, offset) { 15479 if (offset === undef) { 15480 offset = node.nodeType === 3 ? node.length : node.childNodes.length; 15481 } 15482 15483 while (node && node.hasChildNodes()) { 15484 node = node.childNodes[offset]; 15485 if (node) { 15486 offset = node.nodeType === 3 ? node.length : node.childNodes.length; 15487 } 15488 } 15489 return { node: node, offset: offset }; 15490 } 15491 15492 // If index based start position then resolve it 15493 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { 15494 lastIdx = startContainer.childNodes.length - 1; 15495 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset]; 15496 15497 if (startContainer.nodeType == 3) { 15498 startOffset = 0; 15499 } 15500 } 15501 15502 // If index based end position then resolve it 15503 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { 15504 lastIdx = endContainer.childNodes.length - 1; 15505 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1]; 15506 15507 if (endContainer.nodeType == 3) { 15508 endOffset = endContainer.nodeValue.length; 15509 } 15510 } 15511 15512 // Expands the node to the closes contentEditable false element if it exists 15513 function findParentContentEditable(node) { 15514 var parent = node; 15515 15516 while (parent) { 15517 if (parent.nodeType === 1 && getContentEditable(parent)) {
15518 return getContentEditable(parent) === "false" ? parent : node; 15519 } 15520 15521 parent = parent.parentNode; 15522 } 15523 15524 return node; 15525 } 15526 15527 function findWordEndPoint(container, offset, start) { 15528 var walker, node, pos, lastTextNode; 15529 15530 function findSpace(node, offset) { 15531 var pos, pos2, str = node.nodeValue; 15532 15533 if (typeof(offset) == "undefined") { 15534 offset = start ? str.length : 0; 15535 } 15536 15537 if (start) { 15538 pos = str.lastIndexOf(' ', offset); 15539 pos2 = str.lastIndexOf('\u00a0', offset); 15540 pos = pos > pos2 ? pos : pos2; 15541 15542 // Include the space on remove to avoid tag soup 15543 if (pos !== -1 && !remove) { 15544 pos++; 15545 } 15546 } else { 15547 pos = str.indexOf(' ', offset); 15548 pos2 = str.indexOf('\u00a0', offset); 15549 pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2; 15550 } 15551 15552 return pos; 15553 } 15554 15555 if (container.nodeType === 3) { 15556 pos = findSpace(container, offset); 15557 15558 if (pos !== -1) { 15559 return {container: container, offset: pos}; 15560 } 15561 15562 lastTextNode = container; 15563 } 15564 15565 // Walk the nodes inside the block 15566 walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody()); 15567 while ((node = walker[start ? 'prev' : 'next']())) { 15568 if (node.nodeType === 3) { 15569 lastTextNode = node; 15570 pos = findSpace(node); 15571 15572 if (pos !== -1) { 15573 return {container: node, offset: pos}; 15574 } 15575 } else if (isBlock(node)) { 15576 break; 15577 } 15578 } 15579 15580 if (lastTextNode) { 15581 if (start) { 15582 offset = 0; 15583 } else { 15584 offset = lastTextNode.length; 15585 } 15586 15587 return {container: lastTextNode, offset: offset}; 15588 } 15589 } 15590 15591 function findSelectorEndPoint(container, sibling_name) { 15592 var parents, i, y, curFormat; 15593 15594 if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name]) { 15595 container = container[sibling_name]; 15596 } 15597 15598 parents = getParents(container); 15599 for (i = 0; i < parents.length; i++) { 15600 for (y = 0; y < format.length; y++) { 15601 curFormat = format[y]; 15602 15603 // If collapsed state is set then skip formats that doesn't match that 15604 if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) { 15605 continue; 15606 } 15607 15608 if (dom.is(parents[i], curFormat.selector)) { 15609 return parents[i]; 15610 } 15611 } 15612 } 15613 15614 return container; 15615 } 15616 15617 function findBlockEndPoint(container, sibling_name) { 15618 var node, root = dom.getRoot(); 15619 15620 // Expand to block of similar type 15621 if (!format[0].wrapper) { 15622 node = dom.getParent(container, format[0].block, root); 15623 } 15624 15625 // Expand to first wrappable block element or any block element 15626 if (!node) { 15627 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, function(node) { 15628 // Fixes #6183 where it would expand to editable parent element in inline mode 15629 return node != root && isTextBlock(node); 15630 }); 15631 } 15632 15633 // Exclude inner lists from wrapping 15634 if (node && format[0].wrapper) { 15635 node = getParents(node, 'ul,ol').reverse()[0] || node; 15636 } 15637 15638 // Didn't find a block element look for first/last wrappable element 15639 if (!node) { 15640 node = container; 15641 15642 while (node[sibling_name] && !isBlock(node[sibling_name])) { 15643 node = node[sibling_name]; 15644 15645 // Break on BR but include it will be removed later on 15646 // we can't remove it now since we need to check if it can be wrapped 15647 if (isEq(node, 'br')) { 15648 break; 15649 } 15650 } 15651 } 15652 15653 return node || container; 15654 } 15655 15656 // Expand to closest contentEditable element 15657 startContainer = findParentContentEditable(startContainer); 15658 endContainer = findParentContentEditable(endContainer); 15659 15660 // Exclude bookmark nodes if possible 15661 if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) { 15662 startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode; 15663 startContainer = startContainer.nextSibling || startContainer; 15664 15665 if (startContainer.nodeType == 3) { 15666 startOffset = 0; 15667 } 15668 } 15669 15670 if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) { 15671 endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode; 15672 endContainer = endContainer.previousSibling || endContainer; 15673 15674 if (endContainer.nodeType == 3) { 15675 endOffset = endContainer.length; 15676 } 15677 } 15678 15679 if (format[0].inline) { 15680 if (rng.collapsed) { 15681 // Expand left to closest word boundary 15682 endPoint = findWordEndPoint(startContainer, startOffset, true); 15683 if (endPoint) { 15684 startContainer = endPoint.container; 15685 startOffset = endPoint.offset; 15686 } 15687 15688 // Expand right to closest word boundary 15689 endPoint = findWordEndPoint(endContainer, endOffset); 15690 if (endPoint) { 15691 endContainer = endPoint.container; 15692 endOffset = endPoint.offset; 15693 } 15694 } 15695 15696 // Avoid applying formatting to a trailing space. 15697 leaf = findLeaf(endContainer, endOffset); 15698 if (leaf.node) { 15699 while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) { 15700 leaf = findLeaf(leaf.node.previousSibling); 15701 } 15702 15703 if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 && 15704 leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') { 15705 15706 if (leaf.offset > 1) { 15707 endContainer = leaf.node;
15708 endContainer.splitText(leaf.offset - 1); 15709 } 15710 } 15711 } 15712 } 15713 15714 // Move start/end point up the tree if the leaves are sharp and if we are in different containers 15715 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>! 15716 // This will reduce the number of wrapper elements that needs to be created 15717 // Move start point up the tree 15718 if (format[0].inline || format[0].block_expand) { 15719 if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) { 15720 startContainer = findParentContainer(true); 15721 } 15722 15723 if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) { 15724 endContainer = findParentContainer(); 15725 } 15726 } 15727 15728 // Expand start/end container to matching selector 15729 if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { 15730 // Find new startContainer/endContainer if there is better one 15731 startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); 15732 endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); 15733 } 15734 15735 // Expand start/end container to matching block element or text node 15736 if (format[0].block || format[0].selector) { 15737 // Find new startContainer/endContainer if there is better one 15738 startContainer = findBlockEndPoint(startContainer, 'previousSibling'); 15739 endContainer = findBlockEndPoint(endContainer, 'nextSibling'); 15740 15741 // Non block element then try to expand up the leaf 15742 if (format[0].block) { 15743 if (!isBlock(startContainer)) { 15744 startContainer = findParentContainer(true); 15745 } 15746 15747 if (!isBlock(endContainer)) { 15748 endContainer = findParentContainer(); 15749 } 15750 } 15751 } 15752 15753 // Setup index for startContainer 15754 if (startContainer.nodeType == 1) { 15755 startOffset = nodeIndex(startContainer); 15756 startContainer = startContainer.parentNode; 15757 } 15758 15759 // Setup index for endContainer 15760 if (endContainer.nodeType == 1) { 15761 endOffset = nodeIndex(endContainer) + 1; 15762 endContainer = endContainer.parentNode; 15763 } 15764 15765 // Return new range like object 15766 return { 15767 startContainer: startContainer, 15768 startOffset: startOffset, 15769 endContainer: endContainer, 15770 endOffset: endOffset 15771 }; 15772 } 15773 15774 /** 15775 * Removes the specified format for the specified node. It will also remove the node if it doesn't have 15776 * any attributes if the format specifies it to do so. 15777 * 15778 * @private 15779 * @param {Object} format Format object with items to remove from node. 15780 * @param {Object} vars Name/value object with variables to apply to format. 15781 * @param {Node} node Node to remove the format styles on. 15782 * @param {Node} compare_node Optional compare node, if specified the styles will be compared to that node. 15783 * @return {Boolean} True/false if the node was removed or not. 15784 */ 15785 function removeFormat(format, vars, node, compare_node) { 15786 var i, attrs, stylesModified; 15787 15788 // Check if node matches format 15789 if (!matchName(node, format)) { 15790 return FALSE; 15791 } 15792 15793 // Should we compare with format attribs and styles 15794 if (format.remove != 'all') { 15795 // Remove styles 15796 each(format.styles, function(value, name) { 15797 value = normalizeStyleValue(replaceVars(value, vars), name); 15798 15799 // Indexed array 15800 if (typeof(name) === 'number') { 15801 name = value; 15802 compare_node = 0; 15803 } 15804 15805 if (!compare_node || isEq(getStyle(compare_node, name), value)) { 15806 dom.setStyle(node, name, ''); 15807 } 15808 15809 stylesModified = 1; 15810 }); 15811 15812 // Remove style attribute if it's empty 15813 if (stylesModified && dom.getAttrib(node, 'style') === '') { 15814 node.removeAttribute('style'); 15815 node.removeAttribute('data-mce-style'); 15816 } 15817 15818 // Remove attributes 15819 each(format.attributes, function(value, name) { 15820 var valueOut; 15821 15822 value = replaceVars(value, vars); 15823 15824 // Indexed array 15825 if (typeof(name) === 'number') { 15826 name = value; 15827 compare_node = 0; 15828 } 15829 15830 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) { 15831 // Keep internal classes 15832 if (name == 'class') { 15833 value = dom.getAttrib(node, name); 15834 if (value) { 15835 // Build new class value where everything is removed except the internal prefixed classes 15836 valueOut = ''; 15837 each(value.split(/\s+/), function(cls) { 15838 if (/mce\w+/.test(cls)) { 15839 valueOut += (valueOut ? ' ' : '') + cls; 15840 } 15841 }); 15842
15843 // We got some internal classes left 15844 if (valueOut) { 15845 dom.setAttrib(node, name, valueOut); 15846 return; 15847 } 15848 } 15849 } 15850 15851 // IE6 has a bug where the attribute doesn't get removed correctly 15852 if (name == "class") { 15853 node.removeAttribute('className'); 15854 } 15855 15856 // Remove mce prefixed attributes 15857 if (MCE_ATTR_RE.test(name)) { 15858 node.removeAttribute('data-mce-' + name); 15859 } 15860 15861 node.removeAttribute(name); 15862 } 15863 }); 15864 15865 // Remove classes 15866 each(format.classes, function(value) { 15867 value = replaceVars(value, vars); 15868 15869 if (!compare_node || dom.hasClass(compare_node, value)) { 15870 dom.removeClass(node, value); 15871 } 15872 }); 15873 15874 // Check for non internal attributes 15875 attrs = dom.getAttribs(node); 15876 for (i = 0; i < attrs.length; i++) { 15877 if (attrs[i].nodeName.indexOf('_') !== 0) { 15878 return FALSE; 15879 } 15880 } 15881 } 15882 15883 // Remove the inline child if it's empty for example <b> or <span> 15884 if (format.remove != 'none') { 15885 removeNode(node, format); 15886 return TRUE; 15887 } 15888 } 15889 15890 /** 15891 * Removes the node and wrap it's children in paragraphs before doing so or 15892 * appends BR elements to the beginning/end of the block element if forcedRootBlocks is disabled. 15893 * 15894 * If the div in the node below gets removed: 15895 * text<div>text</div>text 15896 * 15897 * Output becomes: 15898 * text<div><br />text<br /></div>text 15899 * 15900 * So when the div is removed the result is: 15901 * text<br />text<br />text 15902 * 15903 * @private 15904 * @param {Node} node Node to remove + apply BR/P elements to. 15905 * @param {Object} format Format rule. 15906 * @return {Node} Input node. 15907 */ 15908 function removeNode(node, format) { 15909 var parentNode = node.parentNode, rootBlockElm; 15910 15911 function find(node, next, inc) { 15912 node = getNonWhiteSpaceSibling(node, next, inc); 15913 15914 return !node || (node.nodeName == 'BR' || isBlock(node)); 15915 } 15916 15917 if (format.block) { 15918 if (!forcedRootBlock) { 15919 // Append BR elements if needed before we remove the block 15920 if (isBlock(node) && !isBlock(parentNode)) { 15921 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) { 15922 node.insertBefore(dom.create('br'), node.firstChild); 15923 } 15924 15925 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) { 15926 node.appendChild(dom.create('br')); 15927 } 15928 } 15929 } else { 15930 // Wrap the block in a forcedRootBlock if we are at the root of document 15931 if (parentNode == dom.getRoot()) { 15932 if (!format.list_block || !isEq(node, format.list_block)) { 15933 each(grep(node.childNodes), function(node) { 15934 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) { 15935 if (!rootBlockElm) { 15936 rootBlockElm = wrap(node, forcedRootBlock); 15937 dom.setAttribs(rootBlockElm, ed.settings.forced_root_block_attrs); 15938 } else { 15939 rootBlockElm.appendChild(node); 15940 } 15941 } else { 15942 rootBlockElm = 0; 15943 } 15944 }); 15945 } 15946 } 15947 } 15948 } 15949 15950 // Never remove nodes that isn't the specified inline element if a selector is specified too 15951 if (format.selector && format.inline && !isEq(format.inline, node)) { 15952 return; 15953 } 15954 15955 dom.remove(node, 1); 15956 } 15957 15958 /** 15959 * Returns the next/previous non whitespace node. 15960 * 15961 * @private 15962 * @param {Node} node Node to start at. 15963 * @param {boolean} next (Optional) Include next or previous node defaults to previous. 15964 * @param {boolean} inc (Optional) Include the current node in checking. Defaults to false. 15965 * @return {Node} Next or previous node or undefined if it wasn't found. 15966 */ 15967 function getNonWhiteSpaceSibling(node, next, inc) { 15968 if (node) { 15969 next = next ? 'nextSibling' : 'previousSibling'; 15970 15971 for (node = inc ? node : node[next]; node; node = node[next]) { 15972 if (node.nodeType == 1 || !isWhiteSpaceNode(node)) { 15973 return node; 15974 } 15975 } 15976 } 15977 } 15978 15979 /** 15980 * Checks if the specified node is a bookmark node or not. 15981 * 15982 * @private 15983 * @param {Node} node Node to check if it's a bookmark node or not. 15984 * @return {Boolean} true/false if the node is a bookmark node. 15985 */ 15986 function isBookmarkNode(node) { 15987 return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark'; 15988 } 15989 15990 /** 15991 * Merges the next/previous sibling element if they match. 15992 * 15993 * @private 15994 * @param {Node} prev Previous node to compare/merge. 15995 * @param {Node} next Next node to compare/merge. 15996 * @return {Node} Next node if we didn't merge and prev node if we did. 15997 */ 15998 function mergeSiblings(prev, next) { 15999 var sibling, tmpSibling; 16000 16001 /** 16002 * Compares two nodes and checks if it's attributes and styles matches. 16003 * This doesn't compare classes as items since their order is significant. 16004 * 16005 * @private 16006 * @param {Node} node1 First node to compare with. 16007 * @param {Node} node2 Second node to compare with. 16008 * @return {boolean} True/false if the nodes are the same or not. 16009 */ 16010 function compareElements(node1, node2) { 16011 // Not the same name 16012 if (node1.nodeName != node2.nodeName) { 16013 return FALSE; 16014 } 16015 16016 /** 16017 * Returns all the nodes attributes excluding internal ones, styles and classes. 16018 * 16019 * @private 16020 * @param {Node} node Node to get attributes from. 16021 * @return {Object} Name/value object with attributes and attribute values. 16022 */ 16023 function getAttribs(node) { 16024 var attribs = {}; 16025 16026 each(dom.getAttribs(node), function(attr) { 16027 var name = attr.nodeName.toLowerCase(); 16028 16029 // Don't compare internal attributes or style 16030 if (name.indexOf('_') !== 0 && name !== 'style') { 16031 attribs[name] = dom.getAttrib(node, name); 16032 } 16033 }); 16034 16035 return attribs; 16036 } 16037 16038 /** 16039 * Compares two objects checks if it's key + value exists in the other one. 16040 * 16041 * @private 16042 * @param {Object} obj1 First object to compare. 16043 * @param {Object} obj2 Second object to compare. 16044 * @return {boolean} True/false if the objects matches or not. 16045 */ 16046 function compareObjects(obj1, obj2) { 16047 var value, name; 16048 16049 for (name in obj1) { 16050 // Obj1 has item obj2 doesn't have 16051 if (obj1.hasOwnProperty(name)) { 16052 value = obj2[name]; 16053 16054 // Obj2 doesn't have obj1 item 16055 if (value === undef) { 16056 return FALSE; 16057 } 16058 16059 // Obj2 item has a different value 16060 if (obj1[name] != value) { 16061 return FALSE; 16062 } 16063 16064 // Delete similar value 16065 delete obj2[name]; 16066 } 16067 } 16068 16069 // Check if obj 2 has something obj 1 doesn't have 16070 for (name in obj2) { 16071 // Obj2 has item obj1 doesn't have 16072 if (obj2.hasOwnProperty(name)) { 16073 return FALSE; 16074 } 16075 } 16076 16077 return TRUE; 16078 } 16079 16080 // Attribs are not the same 16081 if (!compareObjects(getAttribs(node1), getAttribs(node2))) { 16082 return FALSE; 16083 } 16084 16085 // Styles are not the same 16086 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) { 16087 return FALSE; 16088 } 16089 16090 return TRUE; 16091 } 16092 16093 function findElementSibling(node, sibling_name) { 16094 for (sibling = node; sibling; sibling = sibling[sibling_name]) { 16095 if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) { 16096 return node; 16097 } 16098 16099 if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) { 16100 return sibling; 16101 } 16102 } 16103 16104 return node; 16105 } 16106 16107 // Check if next/prev exists and that they are elements 16108 if (prev && next) { 16109 // If previous sibling is empty then jump over it 16110 prev = findElementSibling(prev, 'previousSibling'); 16111 next = findElementSibling(next, 'nextSibling'); 16112 16113 // Compare next and previous nodes 16114 if (compareElements(prev, next)) { 16115 // Append nodes between 16116 for (sibling = prev.nextSibling; sibling && sibling != next;) { 16117 tmpSibling = sibling; 16118 sibling = sibling.nextSibling; 16119 prev.appendChild(tmpSibling); 16120 } 16121 16122 // Remove next node 16123 dom.remove(next); 16124 16125 // Move children into prev node 16126 each(grep(next.childNodes), function(node) { 16127 prev.appendChild(node); 16128 }); 16129 16130 return prev; 16131 } 16132 } 16133 16134 return next; 16135 } 16136 16137 function getContainer(rng, start) { 16138 var container, offset, lastIdx; 16139 16140 container = rng[start ? 'startContainer' : 'endContainer']; 16141 offset = rng[start ? 'startOffset' : 'endOffset']; 16142 16143 if (container.nodeType == 1) { 16144 lastIdx = container.childNodes.length - 1; 16145 16146 if (!start && offset) { 16147 offset--; 16148 } 16149 16150 container = container.childNodes[offset > lastIdx ? lastIdx : offset]; 16151 } 16152 16153 // If start text node is excluded then walk to the next node 16154 if (container.nodeType === 3 && start && offset >= container.nodeValue.length) { 16155 container = new TreeWalker(container, ed.getBody()).next() || container; 16156 } 16157 16158 // If end text node is excluded then walk to the previous node 16159 if (container.nodeType === 3 && !start && offset === 0) { 16160 container = new TreeWalker(container, ed.getBody()).prev() || container; 16161 } 16162 16163 return container; 16164 } 16165 16166 function performCaretAction(type, name, vars) { 16167 var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug; 16168 16169 // Creates a caret container bogus element 16170 function createCaretContainer(fill) { 16171 var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''}); 16172 16173 if (fill) { 16174 caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR)); 16175 } 16176 16177 return caretContainer; 16178 } 16179 16180 function isCaretContainerEmpty(node, nodes) { 16181 while (node) { 16182 if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) { 16183 return false;
16184 } 16185 16186 // Collect nodes 16187 if (nodes && node.nodeType === 1) { 16188 nodes.push(node); 16189 } 16190 16191 node = node.firstChild; 16192 } 16193 16194 return true; 16195 } 16196 16197 // Returns any parent caret container element 16198 function getParentCaretContainer(node) { 16199 while (node) { 16200 if (node.id === caretContainerId) { 16201 return node; 16202 } 16203 16204 node = node.parentNode; 16205 } 16206 } 16207 16208 // Finds the first text node in the specified node 16209 function findFirstTextNode(node) { 16210 var walker; 16211 16212 if (node) { 16213 walker = new TreeWalker(node, node); 16214 16215 for (node = walker.current(); node; node = walker.next()) { 16216 if (node.nodeType === 3) { 16217 return node; 16218 } 16219 } 16220 } 16221 } 16222 16223 // Removes the caret container for the specified node or all on the current document 16224 function removeCaretContainer(node, move_caret) { 16225 var child, rng; 16226 16227 if (!node) { 16228 node = getParentCaretContainer(selection.getStart()); 16229 16230 if (!node) { 16231 while ((node = dom.get(caretContainerId))) { 16232 removeCaretContainer(node, false); 16233 } 16234 } 16235 } else { 16236 rng = selection.getRng(true); 16237 16238 if (isCaretContainerEmpty(node)) { 16239 if (move_caret !== false) { 16240 rng.setStartBefore(node); 16241 rng.setEndBefore(node); 16242 } 16243 16244 dom.remove(node); 16245 } else { 16246 child = findFirstTextNode(node); 16247 16248 if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) { 16249 child = child.deleteData(0, 1); 16250 } 16251 16252 dom.remove(node, 1); 16253 } 16254 16255 selection.setRng(rng); 16256 } 16257 } 16258 16259 // Applies formatting to the caret postion 16260 function applyCaretFormat() { 16261 var rng, caretContainer, textNode, offset, bookmark, container, text; 16262 16263 rng = selection.getRng(true); 16264 offset = rng.startOffset; 16265 container = rng.startContainer; 16266 text = container.nodeValue; 16267 16268 caretContainer = getParentCaretContainer(selection.getStart()); 16269 if (caretContainer) { 16270 textNode = findFirstTextNode(caretContainer); 16271 } 16272 16273 // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character 16274 if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) { 16275 // Get bookmark of caret position 16276 bookmark = selection.getBookmark(); 16277 16278 // Collapse bookmark range (WebKit) 16279 rng.collapse(true); 16280 16281 // Expand the range to the closest word and split it at those points 16282 rng = expandRng(rng, get(name)); 16283 rng = rangeUtils.split(rng); 16284 16285 // Apply the format to the range 16286 apply(name, vars, rng); 16287 16288 // Move selection back to caret position 16289 selection.moveToBookmark(bookmark); 16290 } else { 16291 if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) { 16292 caretContainer = createCaretContainer(true); 16293 textNode = caretContainer.firstChild; 16294 16295 rng.insertNode(caretContainer); 16296 offset = 1; 16297 16298 apply(name, vars, caretContainer); 16299 } else { 16300 apply(name, vars, caretContainer); 16301 } 16302 16303 // Move selection to text node 16304 selection.setCursorLocation(textNode, offset); 16305 } 16306 } 16307 16308 function removeCaretFormat() { 16309 var rng = selection.getRng(true), container, offset, bookmark, 16310 hasContentAfter, node, formatNode, parents = [], i, caretContainer; 16311 16312 container = rng.startContainer; 16313 offset = rng.startOffset; 16314 node = container; 16315 16316 if (container.nodeType == 3) { 16317 if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) { 16318 hasContentAfter = true; 16319 } 16320 16321 node = node.parentNode; 16322 } 16323 16324 while (node) { 16325 if (matchNode(node, name, vars)) { 16326 formatNode = node; 16327 break; 16328 } 16329 16330 if (node.nextSibling) { 16331 hasContentAfter = true; 16332 } 16333 16334 parents.push(node); 16335 node = node.parentNode; 16336 } 16337 16338 // Node doesn't have the specified format 16339 if (!formatNode) { 16340 return; 16341 } 16342 16343 // Is there contents after the caret then remove the format on the element 16344 if (hasContentAfter) { 16345 // Get bookmark of caret position 16346 bookmark = selection.getBookmark(); 16347 16348 // Collapse bookmark range (WebKit) 16349 rng.collapse(true); 16350 16351 // Expand the range to the closest word and split it at those points 16352 rng = expandRng(rng, get(name), true); 16353 rng = rangeUtils.split(rng); 16354 16355 // Remove the format from the range 16356 remove(name, vars, rng); 16357 16358 // Move selection back to caret position 16359 selection.moveToBookmark(bookmark); 16360 } else { 16361 caretContainer = createCaretContainer(); 16362 16363 node = caretContainer; 16364 for (i = parents.length - 1; i >= 0; i--) { 16365 node.appendChild(dom.clone(parents[i], false)); 16366 node = node.firstChild; 16367 } 16368 16369 // Insert invisible character into inner most format element 16370 node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR)); 16371 node = node.firstChild; 16372 16373 var block = dom.getParent(formatNode, isTextBlock); 16374 16375 if (block && dom.isEmpty(block)) { 16376 // Replace formatNode with caretContainer when removing format from empty block like <p><b>|</b></p> 16377 formatNode.parentNode.replaceChild(caretContainer, formatNode); 16378 } else { 16379 // Insert caret container after the formated node 16380 dom.insertAfter(caretContainer, formatNode); 16381 } 16382 16383 // Move selection to text node 16384 selection.setCursorLocation(node, 1); 16385 16386 // If the formatNode is empty, we can remove it safely. 16387 if (dom.isEmpty(formatNode)) { 16388 dom.remove(formatNode); 16389 } 16390 } 16391 } 16392 16393 // Checks if the parent caret container node isn't empty if that is the case it 16394 // will remove the bogus state on all children that isn't empty 16395 function unmarkBogusCaretParents() { 16396 var caretContainer; 16397 16398 caretContainer = getParentCaretContainer(selection.getStart()); 16399 if (caretContainer && !dom.isEmpty(caretContainer)) { 16400 walk(caretContainer, function(node) { 16401 if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) { 16402 dom.setAttrib(node, 'data-mce-bogus', null); 16403 } 16404 }, 'childNodes'); 16405 } 16406 } 16407 16408 // Only bind the caret events once 16409 if (!ed._hasCaretEvents) { 16410 // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements 16411 markCaretContainersBogus = function() { 16412 var nodes = [], i; 16413 16414 if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) { 16415 // Mark children 16416 i = nodes.length; 16417 while (i--) { 16418 dom.setAttrib(nodes[i], 'data-mce-bogus', '1'); 16419 } 16420 } 16421 }; 16422 16423 disableCaretContainer = function(e) { 16424 var keyCode = e.keyCode; 16425 16426 removeCaretContainer(); 16427 16428 // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys 16429 if (keyCode == 8 || keyCode == 37 || keyCode == 39) { 16430 removeCaretContainer(getParentCaretContainer(selection.getStart())); 16431 } 16432 16433 unmarkBogusCaretParents(); 16434 }; 16435 16436 // Remove bogus state if they got filled by contents using editor.selection.setContent 16437 ed.on('SetContent', function(e) { 16438 if (e.selection) { 16439 unmarkBogusCaretParents(); 16440 } 16441 }); 16442 ed._hasCaretEvents = true; 16443 } 16444 16445 // Do apply or remove caret format 16446 if (type == "apply") { 16447 applyCaretFormat(); 16448 } else { 16449 removeCaretFormat(); 16450 } 16451 } 16452 16453 /** 16454 * Moves the start to the first suitable text node. 16455 */ 16456 function moveStart(rng) { 16457 var container = rng.startContainer, 16458 offset = rng.startOffset, isAtEndOfText, 16459 walker, node, nodes, tmpNode; 16460 16461 // Convert text node into index if possible 16462 if (container.nodeType == 3 && offset >= container.nodeValue.length) { 16463 // Get the parent container location and walk from there 16464 offset = nodeIndex(container); 16465 container = container.parentNode; 16466 isAtEndOfText = true; 16467 } 16468 16469 // Move startContainer/startOffset in to a suitable node 16470 if (container.nodeType == 1) { 16471 nodes = container.childNodes; 16472 container = nodes[Math.min(offset, nodes.length - 1)]; 16473 walker = new TreeWalker(container, dom.getParent(container, dom.isBlock)); 16474 16475 // If offset is at end of the parent node walk to the next one 16476 if (offset >
16476 nodes.length - 1 || isAtEndOfText) { 16477 walker.next(); 16478 } 16479 16480 for (node = walker.current(); node; node = walker.next()) { 16481 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { 16482 // IE has a "neat" feature where it moves the start node into the closest element 16483 // we can avoid this by inserting an element before it and then remove it after we set the selection 16484 tmpNode = dom.create('a', null, INVISIBLE_CHAR); 16485 node.parentNode.insertBefore(tmpNode, node); 16486 16487 // Set selection and remove tmpNode 16488 rng.setStart(node, 0); 16489 selection.setRng(rng); 16490 dom.remove(tmpNode); 16491 16492 return; 16493 } 16494 } 16495 } 16496 } 16497 }; 16498}); 16499 16500// Included from: js/tinymce/classes/UndoManager.js 16501 16502/** 16503 * UndoManager.js 16504 * 16505 * Copyright, Moxiecode Systems AB 16506 * Released under LGPL License. 16507 * 16508 * License: http://www.tinymce.com/license 16509 * Contributing: http://www.tinymce.com/contributing 16510 */ 16511 16512/** 16513 * 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. 16514 * 16515 * @class tinymce.UndoManager 16516 */ 16517define("tinymce/UndoManager", [ 16518 "tinymce/Env", 16519 "tinymce/util/Tools" 16520], function(Env, Tools) { 16521 var trim = Tools.trim, trimContentRegExp; 16522 16523 trimContentRegExp = new RegExp([ 16524 '<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\\/span>', // Trim bogus spans like caret containers 16525 '<div[^>]+data-mce-bogus[^>]+><\\/div>', // Trim bogus divs like resize handles 16526 '\\s?data-mce-selected="[^"]+"' // Trim temporaty data-mce prefixed attributes like data-mce-selected 16527 ].join('|'), 'gi'); 16528 16529 return function(editor) { 16530 var self, index = 0, data = [], beforeBookmark, isFirstTypedCharacter, lock; 16531 16532 // Returns a trimmed version of the current editor contents 16533 function getContent() { 16534 return trim(editor.getContent({format: 'raw', no_events: 1}).replace(trimContentRegExp, '')); 16535 } 16536 16537 function addNonTypingUndoLevel() { 16538 self.typing = false; 16539 self.add(); 16540 } 16541 16542 // Add initial undo level when the editor is initialized 16543 editor.on('init', function() { 16544 self.add(); 16545 }); 16546 16547 // Get position before an execCommand is processed 16548 editor.on('BeforeExecCommand', function(e) { 16549 var cmd = e.command; 16550 16551 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') { 16552 self.beforeChange(); 16553 } 16554 }); 16555 16556 // Add undo level after an execCommand call was made 16557 editor.on('ExecCommand', function(e) { 16558 var cmd = e.command; 16559 16560 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') { 16561 self.add(); 16562 } 16563 }); 16564
16565 editor.on('ObjectResizeStart', function() { 16566 self.beforeChange(); 16567 }); 16568 16569 editor.on('SaveContent ObjectResized', addNonTypingUndoLevel); 16570 editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel); 16571 editor.dom.bind(editor.getBody(), 'focusout', function() { 16572 if (!editor.removed && self.typing) { 16573 addNonTypingUndoLevel(); 16574 } 16575 }); 16576 16577 editor.on('KeyUp', function(e) { 16578 var keyCode = e.keyCode; 16579 16580 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) { 16581 addNonTypingUndoLevel(); 16582 editor.nodeChanged(); 16583 } 16584 16585 if (keyCode == 46 || keyCode == 8 || (Env.mac && (keyCode == 91 || keyCode == 93))) { 16586 editor.nodeChanged(); 16587 } 16588 16589 // Fire a TypingUndo event on the first character entered 16590 if (isFirstTypedCharacter && self.typing) { 16591 // Make the it dirty if the content was changed after typing the first character 16592 if (!editor.isDirty()) { 16593 editor.isNotDirty = !data[0] || getContent() == data[0].content; 16594 16595 // Fire initial change event 16596 if (!editor.isNotDirty) { 16597 editor.fire('change', {level: data[0], lastLevel: null}); 16598 } 16599 } 16600 16601 editor.fire('TypingUndo'); 16602 isFirstTypedCharacter = false; 16603 editor.nodeChanged(); 16604 } 16605 }); 16606 16607 editor.on('KeyDown', function(e) { 16608 var keyCode = e.keyCode; 16609 16610 // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter 16611 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) { 16612 if (self.typing) { 16613 addNonTypingUndoLevel(); 16614 } 16615 16616 return; 16617 } 16618 16619 // If key isn't shift,ctrl,alt,capslock,metakey 16620 if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) { 16621 self.beforeChange(); 16622 self.typing = true; 16623 self.add(); 16624 isFirstTypedCharacter = true; 16625 } 16626 }); 16627 16628 editor.on('MouseDown', function() { 16629 if (self.typing) { 16630 addNonTypingUndoLevel(); 16631 } 16632 }); 16633 16634 // Add keyboard shortcuts for undo/redo keys 16635 editor.addShortcut('ctrl+z', '', 'Undo'); 16636 editor.addShortcut('ctrl+y,ctrl+shift+z', '', 'Redo'); 16637 16638 editor.on('AddUndo Undo Redo ClearUndos MouseUp', function(e) { 16639 if (!e.isDefaultPrevented()) { 16640 editor.nodeChanged(); 16641 } 16642 }); 16643 16644 self = { 16645 // Explose for debugging reasons 16646 data: data, 16647 16648 /** 16649 * State if the user is currently typing or not. This will add a typing operation into one undo 16650 * level instead of one new level for each keystroke. 16651 * 16652 * @field {Boolean} typing 16653 */ 16654 typing: false, 16655 16656 /** 16657 * Stores away a bookmark to be used when performing an undo action so that the selection is before 16658 * the change has been made. 16659 * 16660 * @method beforeChange 16661 */ 16662 beforeChange: function() { 16663 if (!lock) { 16664 beforeBookmark = editor.selection.getBookmark(2, true); 16665 } 16666 }, 16667 16668 /** 16669 * Adds a new undo level/snapshot to the undo list. 16670 * 16671 * @method add 16672 * @param {Object} l Optional undo level object to add. 16673 * @return {Object} Undo level that got added or null it a level wasn't needed. 16674 */ 16675 add: function(level) { 16676 var i, settings = editor.settings, lastLevel; 16677 16678 level = level || {}; 16679 level.content = getContent(); 16680 16681 if (lock || editor.fire('BeforeAddUndo', {level: level}).isDefaultPrevented()) { 16682 return null; 16683 } 16684 16685 // Add undo level if needed 16686 lastLevel = data[index]; 16687 if (lastLevel && lastLevel.content == level.content) { 16688 return null; 16689 } 16690 16691 // Set before bookmark on previous level 16692 if (data[index]) { 16693 data[index].beforeBookmark = beforeBookmark; 16694 } 16695 16696 // Time to compress 16697 if (settings.custom_undo_redo_levels) { 16698 if (data.length > settings.custom_undo_redo_levels) { 16699 for (i = 0; i < data.length - 1; i++) { 16700 data[i] = data[i + 1]; 16701 } 16702 16703 data.length--; 16704 index = data.length; 16705 } 16706 } 16707 16708 // Get a non intrusive normalized bookmark 16709 level.bookmark = editor.selection.getBookmark(2, true); 16710 16711 // Crop array if needed 16712 if (index < data.length - 1) { 16713 data.length = index + 1; 16714 } 16715 16716 data.push(level); 16717 index = data.length - 1; 16718 16719 var args = {level: level, lastLevel: lastLevel}; 16720 16721 editor.fire('AddUndo', args); 16722 16723 if (index > 0) { 16724 editor.fire('change', args); 16725 editor.isNotDirty = false;
16726 } 16727 16728 return level; 16729 }, 16730 16731 /** 16732 * Undoes the last action. 16733 * 16734 * @method undo 16735 * @return {Object} Undo level or null if no undo was performed. 16736 */ 16737 undo: function() { 16738 var level; 16739 16740 if (self.typing) { 16741 self.add(); 16742 self.typing = false; 16743 } 16744 16745 if (index > 0) { 16746 level = data[--index]; 16747 16748 // Undo to first index then set dirty state to false 16749 if (index === 0) { 16750 editor.isNotDirty = true; 16751 } 16752 16753 editor.setContent(level.content, {format: 'raw'}); 16754 editor.selection.moveToBookmark(level.beforeBookmark); 16755 16756 editor.fire('undo', {level: level}); 16757 } 16758 16759 return level; 16760 }, 16761 16762 /** 16763 * Redoes the last action. 16764 * 16765 * @method redo 16766 * @return {Object} Redo level or null if no redo was performed. 16767 */ 16768 redo: function() { 16769 var level; 16770 16771 if (index < data.length - 1) { 16772 level = data[++index]; 16773 16774 editor.setContent(level.content, {format: 'raw'}); 16775 editor.selection.moveToBookmark(level.bookmark); 16776 16777 editor.fire('redo', {level: level}); 16778 } 16779 16780 return level; 16781 }, 16782 16783 /** 16784 * Removes all undo levels. 16785 * 16786 * @method clear 16787 */ 16788 clear: function() { 16789 data = []; 16790 index = 0; 16791 self.typing = false; 16792 editor.fire('ClearUndos'); 16793 }, 16794 16795 /** 16796 * Returns true/false if the undo manager has any undo levels. 16797 * 16798 * @method hasUndo 16799 * @return {Boolean} true/false if the undo manager has any undo levels. 16800 */ 16801 hasUndo: function() { 16802 // Has undo levels or typing and content isn't the same as the initial level 16803 return index > 0 || (self.typing && data[0] && getContent() != data[0].content); 16804 }, 16805 16806 /** 16807 * Returns true/false if the undo manager has any redo levels. 16808 * 16809 * @method hasRedo 16810 * @return {Boolean} true/false if the undo manager has any redo levels. 16811 */ 16812 hasRedo: function() { 16813 return index < data.length - 1 && !this.typing; 16814 }, 16815 16816 /** 16817 * Executes the specified function in an undo transation. The selection 16818 * before the modification will be stored to the undo stack and if the DOM changes 16819 * it will add a new undo level. Any methods within the transation that adds undo levels will 16820 * be ignored. So a transation can include calls to execCommand or editor.insertContent. 16821 * 16822 * @method transact 16823 * @param {function} callback Function to execute dom manipulation logic in. 16824 */ 16825 transact: function(callback) { 16826 self.beforeChange(); 16827 16828 lock = true; 16829 callback(); 16830 lock = false; 16831 16832 self.add(); 16833 } 16834 }; 16835 16836 return self; 16837 }; 16838}); 16839 16840// Included from: js/tinymce/classes/EnterKey.js 16841 16842/** 16843 * EnterKey.js 16844 * 16845 * Copyright, Moxiecode Systems AB 16846 * Released under LGPL License. 16847 * 16848 * License: http://www.tinymce.com/license 16849 * Contributing: http://www.tinymce.com/contributing 16850 */ 16851 16852/** 16853 * Contains logic for handling the enter key to split/generate block elements. 16854 */ 16855define("tinymce/EnterKey", [ 16856 "tinymce/dom/TreeWalker", 16857 "tinymce/Env" 16858], function(TreeWalker, Env) { 16859 var isIE = Env.ie && Env.ie < 11; 16860 16861 return function(editor) { 16862 var dom = editor.dom, selection = editor.selection, settings = editor.settings; 16863 var undoManager = editor.undoManager, schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements(); 16864 16865 function handleEnterKey(evt) { 16866 var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey, 16867 newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer; 16868 16869 // Returns true if the block can be split into two blocks or not 16870 function canSplitBlock(node) { 16871 return node && 16872 dom.isBlock(node) && 16873 !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) && 16874 !/^(fixed|absolute)/i.test(node.style.position) && 16875 dom.getContentEditable(node) !== "true"; 16876 } 16877 16878 // Renders empty block on IE 16879 function renderBlockOnIE(block) { 16880 var oldRng; 16881 16882 if (dom.isBlock(block)) { 16883 oldRng = selection.getRng(); 16884 block.appendChild(dom.create('span', null, '\u00a0')); 16885 selection.select(block); 16886 block.lastChild.outerHTML = ''; 16887 selection.setRng(oldRng); 16888 } 16889 } 16890 16891 // Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>
16891x</p> 16892 function trimInlineElementsOnLeftSideOfBlock(block) { 16893 var node = block, firstChilds = [], i; 16894 16895 // Find inner most first child ex: <p><i><b>*</b></i></p> 16896 while ((node = node.firstChild)) { 16897 if (dom.isBlock(node)) { 16898 return; 16899 } 16900 16901 if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 16902 firstChilds.push(node); 16903 } 16904 } 16905 16906 i = firstChilds.length; 16907 while (i--) { 16908 node = firstChilds[i]; 16909 if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) { 16910 dom.remove(node); 16911 } else { 16912 // Remove <a> </a> see #5381 16913 if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') { 16914 dom.remove(node); 16915 } 16916 } 16917 } 16918 } 16919 16920 // Moves the caret to a suitable position within the root for example in the first non 16921 // pure whitespace text node or before an image 16922 function moveToCaretPosition(root) { 16923 var walker, node, rng, lastNode = root, tempElm; 16924 16925 function firstNonWhiteSpaceNodeSibling(node) { 16926 while (node) { 16927 if (node.nodeType == 1 || (node.nodeType == 3 && node.data && /[\r\n\s]/.test(node.data))) { 16928 return node; 16929 } 16930 16931 node = node.nextSibling; 16932 } 16933 } 16934 16935 // Old IE versions doesn't properly render blocks with br elements in them 16936 // For example <p><br></p> wont be rendered correctly in a contentEditable area 16937 // until you remove the br producing <p></p> 16938 if (Env.ie && Env.ie < 9 && parentBlock && parentBlock.firstChild) { 16939 if (parentBlock.firstChild == parentBlock.lastChild && parentBlock.firstChild.tagName == 'BR') { 16940 dom.remove(parentBlock.firstChild); 16941 } 16942 } 16943 16944 if (root.nodeName == 'LI') { 16945 var firstChild = firstNonWhiteSpaceNodeSibling(root.firstChild); 16946 16947 if (firstChild && /^(UL|OL)$/.test(firstChild.nodeName)) { 16948 root.insertBefore(dom.doc.createTextNode('\u00a0'), root.firstChild); 16949 } 16950 } 16951 16952 rng = dom.createRng(); 16953 16954 if (root.hasChildNodes()) { 16955 walker = new TreeWalker(root, root); 16956 16957 while ((node = walker.current())) { 16958 if (node.nodeType == 3) { 16959 rng.setStart(node, 0); 16960 rng.setEnd(node, 0); 16961 break; 16962 } 16963 16964 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 16965 rng.setStartBefore(node); 16966 rng.setEndBefore(node); 16967 break; 16968 } 16969 16970 lastNode = node; 16971 node = walker.next(); 16972 } 16973 16974 if (!node) { 16975 rng.setStart(lastNode, 0); 16976 rng.setEnd(lastNode, 0); 16977 } 16978 } else { 16979 if (root.nodeName == 'BR') { 16980 if (root.nextSibling && dom.isBlock(root.nextSibling)) { 16981 // Trick on older IE versions to render the caret before the BR between two lists 16982 if (!documentMode || documentMode < 9) { 16983 tempElm = dom.create('br'); 16984 root.parentNode.insertBefore(tempElm, root); 16985 } 16986 16987 rng.setStartBefore(root); 16988 rng.setEndBefore(root); 16989 } else { 16990 rng.setStartAfter(root); 16991 rng.setEndAfter(root); 16992 } 16993 } else { 16994 rng.setStart(root, 0); 16995 rng.setEnd(root, 0); 16996 } 16997 } 16998 16999 selection.setRng(rng); 17000 17001 // Remove tempElm created for old IE:s 17002 dom.remove(tempElm); 17003 selection.scrollIntoView(root); 17004 } 17005 17006 function setForcedBlockAttrs(node) { 17007 var forcedRootBlockName = settings.forced_root_block; 17008 17009 if (forcedRootBlockName && forcedRootBlockName.toLowerCase() === node.tagName.toLowerCase()) { 17010 dom.setAttribs(node, settings.forced_root_block_attrs); 17011 } 17012 } 17013 17014 // Creates a new block element by cloning the current one or creating a new one if the name is specified 17015 // This function will also copy any text formatting from the parent block and add it to the new one 17016 function createNewBlock(name) { 17017 var node = container, block, clonedNode, caretNode; 17018 17019 if (name || parentBlockName == "TABLE") { 17020 block = dom.create(name || newBlockName); 17021 setForcedBlockAttrs(block); 17022 } else { 17023 block = parentBlock.cloneNode(false); 17024 } 17025 17026 caretNode = block; 17027 17028 // Clone any parent styles 17029 if (settings.keep_styles !== false) { 17030 do { 17031 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) { 17032 // Never clone a caret containers 17033 if (node.id == '_mce_caret') { 17034 continue; 17035 } 17036 17037 clonedNode = node.cloneNode(false); 17038 dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique 17039 17040 if (block.hasChildNodes()) { 17041 clonedNode.appendChild(block.firstChild); 17042 block.appendChild(clonedNode); 17043 } else {
17044 caretNode = clonedNode; 17045 block.appendChild(clonedNode); 17046 } 17047 } 17048 } while ((node = node.parentNode)); 17049 } 17050 17051 // BR is needed in empty blocks on non IE browsers 17052 if (!isIE) { 17053 caretNode.innerHTML = '<br data-mce-bogus="1">'; 17054 } 17055 17056 return block; 17057 } 17058 17059 // Returns true/false if the caret is at the start/end of the parent block element 17060 function isCaretAtStartOrEndOfBlock(start) { 17061 var walker, node, name; 17062 17063 // Caret is in the middle of a text node like "a|b" 17064 if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) { 17065 return false; 17066 } 17067 17068 // If after the last element in block node edge case for #5091 17069 if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) { 17070 return true; 17071 } 17072 17073 // If the caret if before the first element in parentBlock 17074 if (start && container.nodeType == 1 && container == parentBlock.firstChild) { 17075 return true; 17076 } 17077 17078 // Caret can be before/after a table 17079 if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) { 17080 return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start); 17081 } 17082 17083 // Walk the DOM and look for text nodes or non empty elements 17084 walker = new TreeWalker(container, parentBlock); 17085 17086 // If caret is in beginning or end of a text block then jump to the next/previous node 17087 if (container.nodeType == 3) { 17088 if (start && offset === 0) { 17089 walker.prev(); 17090 } else if (!start && offset == container.nodeValue.length) { 17091 walker.next(); 17092 } 17093 } 17094 17095 while ((node = walker.current())) { 17096 if (node.nodeType === 1) { 17097 // Ignore bogus elements 17098 if (!node.getAttribute('data-mce-bogus')) { 17099 // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p> 17100 name = node.nodeName.toLowerCase(); 17101 if (nonEmptyElementsMap[name] && name !== 'br') { 17102 return false; 17103 } 17104 } 17105 } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) { 17106 return false; 17107 } 17108 17109 if (start) { 17110 walker.prev(); 17111 } else { 17112 walker.next(); 17113 } 17114 } 17115 17116 return true; 17117 } 17118 17119 // Wraps any text nodes or inline elements in the specified forced root block name 17120 function wrapSelfAndSiblingsInDefaultBlock(container, offset) { 17121 var newBlock, parentBlock, startNode, node, next, rootBlockName, blockName = newBlockName || 'P'; 17122 17123 // Not in a block element or in a table cell or caption 17124 parentBlock = dom.getParent(container, dom.isBlock); 17125 rootBlockName = editor.getBody().nodeName.toLowerCase(); 17126 if (!parentBlock || !canSplitBlock(parentBlock)) { 17127 parentBlock = parentBlock || editableRoot; 17128 17129 if (!parentBlock.hasChildNodes()) { 17130 newBlock = dom.create(blockName); 17131 setForcedBlockAttrs(newBlock); 17132 parentBlock.appendChild(newBlock); 17133 rng.setStart(newBlock, 0); 17134 rng.setEnd(newBlock, 0); 17135 return newBlock; 17136 } 17137 17138 // Find parent that is the first child of parentBlock 17139 node = container; 17140 while (node.parentNode != parentBlock) { 17141 node = node.parentNode; 17142 } 17143 17144 // Loop left to find start node start wrapping at 17145 while (node && !dom.isBlock(node)) { 17146 startNode = node; 17147 node = node.previousSibling; 17148 } 17149 17150 if (startNode && schema.isValidChild(rootBlockName, blockName.toLowerCase())) { 17151 newBlock = dom.create(blockName); 17152 setForcedBlockAttrs(newBlock); 17153 startNode.parentNode.insertBefore(newBlock, startNode); 17154 17155 // Start wrapping until we hit a block 17156 node = startNode; 17157 while (node && !dom.isBlock(node)) { 17158 next = node.nextSibling; 17159 newBlock.appendChild(node); 17160 node = next; 17161 } 17162 17163 // Restore range to it's past location 17164 rng.setStart(container, offset); 17165 rng.setEnd(container, offset); 17166 } 17167 } 17168 17169 return container; 17170 } 17171 17172 // Inserts a block or br before/after or in the middle of a split list of the LI is empty 17173 function handleEmptyListItem() { 17174 function isFirstOrLastLi(first) { 17175 var node = containerBlock[first ? 'firstChild' : 'lastChild']; 17176 17177 // Find first/last element since there might be whitespace there 17178 while (node) { 17179 if (node.nodeType == 1) { 17180 break; 17181 } 17182 17183 node = node[first ? 'nextSibling' : 'previousSibling']; 17184 } 17185 17186 return node === parentBlock; 17187 } 17188 17189 function getContainerBlock() { 17190 var containerBlockParent = containerBlock.parentNode; 17191 17192 if (containerBlockParent.nodeName == 'LI') { 17193 return containerBlockParent; 17194 } 17195 17196 return containerBlock; 17197 } 17198 17199 // Check if we are in an nested list 17200 var containerBlockParentName = containerBlock.parentNode.nodeName; 17201 if (/^(OL|UL|LI)$/.test(containerBlockParentName)) { 17202 newBlockName = 'LI'; 17203 } 17204 17205 newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR'); 17206 17207 if (isFirstOrLastLi(true) && isFirstOrLastLi()) { 17208 if (containerBlockParentName == 'LI') { 17209 // Nested list is inside a LI 17210 dom.insertAfter(newBlock, getContainerBlock()); 17211 } else { 17212 // Is first and last list item then replace the OL/UL with a text block 17213 dom.replace(newBlock, containerBlock); 17214 } 17215 } else if (isFirstOrLastLi(true)) { 17216 if (containerBlockParentName == 'LI') { 17217 // List nested in an LI then move the list to a new sibling LI 17218 dom.insertAfter(newBlock, getContainerBlock()); 17219 newBlock.appendChild(dom.doc.createTextNode(' ')); // Needed for IE so the caret can be placed 17220 newBlock.appendChild(containerBlock); 17221 } else { 17222 // First LI in list then remove LI and add text block before list 17223 containerBlock.parentNode.insertBefore(newBlock, containerBlock); 17224 } 17225 } else if (isFirstOrLastLi()) { 17226 // Last LI in list then remove LI and add text block after list 17227 dom.insertAfter(newBlock, getContainerBlock()); 17228 renderBlockOnIE(newBlock); 17229 } else {
17230 // Middle LI in list the split the list and insert a text block in the middle 17231 // Extract after fragment and insert it after the current block 17232 containerBlock = getContainerBlock(); 17233 tmpRng = rng.cloneRange(); 17234 tmpRng.setStartAfter(parentBlock); 17235 tmpRng.setEndAfter(containerBlock); 17236 fragment = tmpRng.extractContents(); 17237 17238 if (newBlockName == 'LI' && fragment.firstChild.nodeName == 'LI') { 17239 newBlock = fragment.firstChild; 17240 dom.insertAfter(fragment, containerBlock); 17241 } else { 17242 dom.insertAfter(fragment, containerBlock); 17243 dom.insertAfter(newBlock, containerBlock); 17244 } 17245 } 17246 17247 dom.remove(parentBlock); 17248 moveToCaretPosition(newBlock); 17249 undoManager.add(); 17250 } 17251 17252 // Walks the parent block to the right and look for BR elements 17253 function hasRightSideContent() { 17254 var walker = new TreeWalker(container, parentBlock), node; 17255 17256 while ((node = walker.next())) { 17257 if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) { 17258 return true; 17259 } 17260 } 17261 } 17262 17263 // Inserts a BR element if the forced_root_block option is set to false or empty string 17264 function insertBr() { 17265 var brElm, extraBr, marker; 17266 17267 if (container && container.nodeType == 3 && offset >= container.nodeValue.length) { 17268 // Insert extra BR element at the end block elements 17269 if (!isIE && !hasRightSideContent()) { 17270 brElm = dom.create('br'); 17271 rng.insertNode(brElm); 17272 rng.setStartAfter(brElm); 17273 rng.setEndAfter(brElm); 17274 extraBr = true; 17275 } 17276 } 17277 17278 brElm = dom.create('br'); 17279 rng.insertNode(brElm); 17280 17281 // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it 17282 if (isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) { 17283 brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm); 17284 } 17285 17286 // Insert temp marker and scroll to that 17287 marker = dom.create('span', {}, ' '); 17288 brElm.parentNode.insertBefore(marker, brElm); 17289 selection.scrollIntoView(marker); 17290 dom.remove(marker); 17291 17292 if (!extraBr) { 17293 rng.setStartAfter(brElm); 17294 rng.setEndAfter(brElm); 17295 } else { 17296 rng.setStartBefore(brElm); 17297 rng.setEndBefore(brElm); 17298 } 17299 17300 selection.setRng(rng); 17301 undoManager.add(); 17302 } 17303 17304 // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element 17305 function trimLeadingLineBreaks(node) { 17306 do { 17307 if (node.nodeType === 3) { 17308 node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, ''); 17309 } 17310 17311 node = node.firstChild; 17312 } while (node); 17313 } 17314 17315 function getEditableRoot(node) { 17316 var root = dom.getRoot(), parent, editableRoot; 17317 17318 // Get all parents until we hit a non editable parent or the root 17319 parent = node; 17320 while (parent !== root && dom.getContentEditable(parent) !== "false") { 17321 if (dom.getContentEditable(parent) === "true") { 17322 editableRoot = parent; 17323 } 17324 17325 parent = parent.parentNode; 17326 } 17327 17328 return parent !== root ? editableRoot : root; 17329 } 17330 17331 // Adds a BR at the end of blocks that only contains an IMG or INPUT since 17332 // these might be floated and then they won't expand the block 17333 function addBrToBlockIfNeeded(block) { 17334 var lastChild; 17335 17336 // IE will render the blocks correctly other browsers needs a BR 17337 if (!isIE) { 17338 block.normalize(); // Remove empty text nodes that got left behind by the extract 17339 17340 // Check if the block is empty or contains a floated last child 17341 lastChild = block.lastChild; 17342 if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) { 17343 dom.add(block, 'br'); 17344 } 17345 } 17346 } 17347 17348 // Delete any selected contents 17349 if (!rng.collapsed) { 17350 editor.execCommand('Delete'); 17351 return; 17352 } 17353 17354 // Event is blocked by some other handler for example the lists plugin 17355 if (evt.isDefaultPrevented()) { 17356 return; 17357 } 17358 17359 // Setup range items and newBlockName 17360 container = rng.startContainer; 17361 offset = rng.startOffset; 17362 newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block; 17363 newBlockName = newBlockName ? newBlockName.toUpperCase() : ''; 17364 documentMode = dom.doc.documentMode; 17365 shiftKey = evt.shiftKey; 17366 17367 // Resolve node index 17368 if (container.nodeType == 1 && container.hasChildNodes()) { 17369 isAfterLastNodeInContainer = offset > container.childNodes.length - 1; 17370 container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container; 17371 if (isAfterLastNodeInContainer && container.nodeType == 3) { 17372 offset = container.nodeValue.length; 17373 } else { 17374 offset = 0; 17375 } 17376 } 17377 17378 // Get editable root node normaly the body element but sometimes a div or span 17379 editableRoot = getEditableRoot(container); 17380 17381 // If there is no editable root then enter is done inside a contentEditable false element 17382 if (!editableRoot) { 17383 return; 17384 } 17385 17386 undoManager.beforeChange(); 17387 17388 // If editable root isn't block nor the root of the editor
17389 if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) { 17390 if (!newBlockName || shiftKey) { 17391 insertBr(); 17392 } 17393 17394 return; 17395 } 17396 17397 // Wrap the current node and it's sibling in a default block if it's needed. 17398 // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td> 17399 // This won't happen if root blocks are disabled or the shiftKey is pressed 17400 if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) { 17401 container = wrapSelfAndSiblingsInDefaultBlock(container, offset); 17402 } 17403 17404 // Find parent block and setup empty block paddings 17405 parentBlock = dom.getParent(container, dom.isBlock); 17406 containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null; 17407 17408 // Setup block names 17409 parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 17410 containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 17411 17412 // Enter inside block contained within a LI then split or insert before/after LI 17413 if (containerBlockName == 'LI' && !evt.ctrlKey) { 17414 parentBlock = containerBlock; 17415 parentBlockName = containerBlockName; 17416 } 17417 17418 // Handle enter in LI 17419 if (parentBlockName == 'LI') { 17420 if (!newBlockName && shiftKey) { 17421 insertBr(); 17422 return; 17423 } 17424 17425 // Handle enter inside an empty list item 17426 if (dom.isEmpty(parentBlock)) { 17427 handleEmptyListItem(); 17428 return; 17429 } 17430 } 17431 17432 // Don't split PRE tags but insert a BR instead easier when writing code samples etc 17433 if (parentBlockName == 'PRE' && settings.br_in_pre !== false) { 17434 if (!shiftKey) { 17435 insertBr(); 17436 return; 17437 } 17438 } else { 17439 // If no root block is configured then insert a BR by default or if the shiftKey is pressed 17440 if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) { 17441 insertBr(); 17442 return; 17443 } 17444 } 17445 17446 // If parent block is root then never insert new blocks 17447 if (newBlockName && parentBlock === editor.getBody()) { 17448 return; 17449 } 17450 17451 // Default block name if it's not configured 17452 newBlockName = newBlockName || 'P'; 17453 17454 // Insert new block before/after the parent block depending on caret location 17455 if (isCaretAtStartOrEndOfBlock()) { 17456 // 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 17457 if (/^(H[1-6]|PRE|FIGURE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') { 17458 newBlock = createNewBlock(newBlockName); 17459 } else { 17460 newBlock = createNewBlock(); 17461 } 17462 17463 // Split the current container block element if enter is pressed inside an empty inner block element 17464 if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) { 17465 // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P 17466 newBlock = dom.split(containerBlock, parentBlock); 17467 } else { 17468 dom.insertAfter(newBlock, parentBlock); 17469 } 17470 17471 moveToCaretPosition(newBlock); 17472 } else if (isCaretAtStartOrEndOfBlock(true)) { 17473 // Insert new block before 17474 newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock); 17475 renderBlockOnIE(newBlock); 17476 moveToCaretPosition(parentBlock); 17477 } else { 17478 // Extract after fragment and insert it after the current block 17479 tmpRng = rng.cloneRange(); 17480 tmpRng.setEndAfter(parentBlock); 17481 fragment = tmpRng.extractContents(); 17482 trimLeadingLineBreaks(fragment); 17483 newBlock = fragment.firstChild; 17484 dom.insertAfter(fragment, parentBlock); 17485 trimInlineElementsOnLeftSideOfBlock(newBlock); 17486 addBrToBlockIfNeeded(parentBlock); 17487 moveToCaretPosition(newBlock); 17488 } 17489 17490 dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique 17491 17492 // Allow custom handling of new blocks 17493 editor.fire('NewBlock', { newBlock: newBlock }); 17494 17495 undoManager.add(); 17496 } 17497 17498 editor.on('keydown', function(evt) { 17499 if (evt.keyCode == 13) { 17500 if (handleEnterKey(evt) !== false) { 17501 evt.preventDefault(); 17502 } 17503 } 17504 }); 17505 }; 17506}); 17507 17508// Included from: js/tinymce/classes/ForceBlocks.js 17509 17510/** 17511 * ForceBlocks.js 17512 * 17513 * Copyright, Moxiecode Systems AB 17514 * Released under LGPL License. 17515 * 17516 * License: http://www.tinymce.com/license 17517 * Contributing: http://www.tinymce.com/contributing 17518 */ 17519 17520define("tinymce/ForceBlocks", [], function() { 17521 return function(editor) { 17522 var settings = editor.settings, dom = editor.dom, selection = editor.selection; 17523 var schema = editor.schema, blockElements = schema.getBlockElements(); 17524 17525 function addRootBlocks() { 17526 var node = selection.getStart(), rootNode = editor.getBody(), rng; 17527 var startContainer, startOffset, endContainer, endOffset, rootBlockNode; 17528 var tempNode, offset = -0xFFFFFF, wrapped, restoreSelection; 17529 var tmpRng, rootNodeName, forcedRootBlock; 17530 17531 forcedRootBlock = settings.forced_root_block; 17532 17533 if (!node || node.nodeType !== 1 || !forcedRootBlock) { 17534 return; 17535 } 17536 17537 // Check if node is wrapped in block 17538 while (node && node != rootNode) { 17539 if (blockElements[node.nodeName]) { 17540 return; 17541 } 17542 17543 node = node.parentNode; 17544 } 17545 17546 // Get current selection 17547 rng = selection.getRng(); 17548 if (rng.setStart) { 17549 startContainer = rng.startContainer; 17550 startOffset = rng.startOffset; 17551 endContainer = rng.endContainer; 17552 endOffset = rng.endOffset; 17553 17554 try { 17555 restoreSelection = editor.getDoc().activeElement === rootNode; 17556 } catch (ex) { 17557 // IE throws unspecified error here sometimes 17558 } 17559 } else { 17560 // Force control range into text range 17561 if (rng.item) { 17562 node = rng.item(0); 17563 rng = editor.getDoc().body.createTextRange(); 17564 rng.moveToElementText(node); 17565 } 17566 17567 restoreSelection = rng.parentElement().ownerDocument === editor.getDoc(); 17568 tmpRng = rng.duplicate(); 17569 tmpRng.collapse(true); 17570 startOffset = tmpRng.move('character', offset) * -1; 17571 17572 if (!tmpRng.collapsed) { 17573 tmpRng = rng.duplicate(); 17574 tmpRng.collapse(false); 17575 endOffset = (tmpRng.move('character', offset) * -1) - startOffset; 17576 } 17577 } 17578 17579 // Wrap non block elements and text nodes 17580 node = rootNode.firstChild; 17581 rootNodeName = rootNode.nodeName.toLowerCase(); 17582 while (node) { 17583 // TODO: Break this up, too complex 17584 if (((node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName]))) && 17585 schema.isValidChild(rootNodeName, forcedRootBlock.toLowerCase())) { 17586 // Remove empty text nodes 17587 if (node.nodeType === 3 && node.nodeValue.length === 0) { 17588 tempNode = node; 17589 node = node.nextSibling; 17590 dom.remove(tempNode); 17591 continue; 17592 } 17593 17594 if (!rootBlockNode) { 17595 rootBlockNode = dom.create(forcedRootBlock, editor.settings.forced_root_block_attrs); 17596 node.parentNode.insertBefore(rootBlockNode, node); 17597 wrapped = true; 17598 } 17599 17600 tempNode = node; 17601 node = node.nextSibling; 17602 rootBlockNode.appendChild(tempNode); 17603 } else { 17604 rootBlockNode = null; 17605 node = node.nextSibling; 17606 } 17607 } 17608 17609 if (wrapped && restoreSelection) { 17610 if (rng.setStart) { 17611 rng.setStart(startContainer, startOffset); 17612 rng.setEnd(endContainer, endOffset); 17613 selection.setRng(rng); 17614 } else { 17615 // Only select if the previous selection was inside the document to prevent auto focus in quirks mode 17616 try { 17617 rng = editor.getDoc().body.createTextRange(); 17618 rng.moveToElementText(rootNode); 17619 rng.collapse(true); 17620 rng.moveStart('character', startOffset); 17621 17622 if (endOffset > 0) { 17623 rng.moveEnd('character', endOffset); 17624 } 17625 17626 rng.select(); 17627 } catch (ex) { 17628 // Ignore 17629 } 17630 } 17631 17632 editor.nodeChanged(); 17633 } 17634 } 17635 17636 // Force root blocks 17637 if (settings.forced_root_block) { 17638 editor.on('NodeChange', addRootBlocks); 17639 } 17640 }; 17641}); 17642 17643// Included from: js/tinymce/classes/EditorCommands.js 17644 17645/** 17646 * EditorCommands.js 17647 * 17648 * Copyright, Moxiecode Systems AB 17649 * Released under LGPL License. 17650 * 17651 * License: http://www.tinymce.com/license 17652 * Contributing: http://www.tinymce.com/contributing 17653 */ 17654 17655/** 17656 * This class enables you to add custom editor commands and it contains 17657 * overrides for native browser commands to address various bugs and issues. 17658 * 17659 * @class tinymce.EditorCommands 17660 */ 17661define("tinymce/EditorCommands", [ 17662 "tinymce/html/Serializer", 17663 "tinymce/Env", 17664 "tinymce/util/Tools" 17665], function(Serializer, Env, Tools) {
17666 // Added for compression purposes 17667 var each = Tools.each, extend = Tools.extend; 17668 var map = Tools.map, inArray = Tools.inArray, explode = Tools.explode; 17669 var isGecko = Env.gecko, isIE = Env.ie; 17670 var TRUE = true, FALSE = false; 17671 17672 return function(editor) { 17673 var dom = editor.dom, 17674 selection = editor.selection, 17675 commands = {state: {}, exec: {}, value: {}}, 17676 settings = editor.settings, 17677 formatter = editor.formatter, 17678 bookmark; 17679 17680 /** 17681 * Executes the specified command. 17682 * 17683 * @method execCommand 17684 * @param {String} command Command to execute. 17685 * @param {Boolean} ui Optional user interface state. 17686 * @param {Object} value Optional value for command. 17687 * @return {Boolean} true/false if the command was found or not. 17688 */ 17689 function execCommand(command, ui, value) { 17690 var func; 17691 17692 command = command.toLowerCase(); 17693 if ((func = commands.exec[command])) { 17694 func(command, ui, value); 17695 return TRUE; 17696 } 17697 17698 return FALSE; 17699 } 17700 17701 /** 17702 * Queries the current state for a command for example if the current selection is "bold". 17703 * 17704 * @method queryCommandState 17705 * @param {String} command Command to check the state of. 17706 * @return {Boolean/Number} true/false if the selected contents is bold or not, -1 if it's not found. 17707 */ 17708 function queryCommandState(command) { 17709 var func; 17710 17711 command = command.toLowerCase(); 17712 if ((func = commands.state[command])) { 17713 return func(command); 17714 } 17715 17716 return -1; 17717 } 17718 17719 /** 17720 * Queries the command value for example the current fontsize. 17721 * 17722 * @method queryCommandValue 17723 * @param {String} command Command to check the value of. 17724 * @return {Object} Command value of false if it's not found. 17725 */ 17726 function queryCommandValue(command) { 17727 var func; 17728 17729 command = command.toLowerCase(); 17730 if ((func = commands.value[command])) { 17731 return func(command); 17732 } 17733 17734 return FALSE; 17735 } 17736 17737 /** 17738 * Adds commands to the command collection. 17739 * 17740 * @method addCommands 17741 * @param {Object} command_list Name/value collection with commands to add, the names can also be c
17741omma separated. 17742 * @param {String} type Optional type to add, defaults to exec. Can be value or state as well. 17743 */ 17744 function addCommands(command_list, type) { 17745 type = type || 'exec'; 17746 17747 each(command_list, function(callback, command) { 17748 each(command.toLowerCase().split(','), function(command) { 17749 commands[type][command] = callback; 17750 }); 17751 }); 17752 } 17753 17754 // Expose public methods 17755 extend(this, { 17756 execCommand: execCommand, 17757 queryCommandState: queryCommandState, 17758 queryCommandValue: queryCommandValue, 17759 addCommands: addCommands 17760 }); 17761 17762 // Private methods 17763 17764 function execNativeCommand(command, ui, value) { 17765 if (ui === undefined) { 17766 ui = FALSE; 17767 } 17768 17769 if (value === undefined) { 17770 value = null; 17771 } 17772 17773 return editor.getDoc().execCommand(command, ui, value); 17774 } 17775 17776 function isFormatMatch(name) { 17777 return formatter.match(name); 17778 } 17779 17780 function toggleFormat(name, value) { 17781 formatter.toggle(name, value ? {value: value} : undefined); 17782 editor.nodeChanged(); 17783 } 17784 17785 function storeSelection(type) { 17786 bookmark = selection.getBookmark(type); 17787 } 17788 17789 function restoreSelection() { 17790 selection.moveToBookmark(bookmark); 17791 } 17792 17793 // Add execCommand overrides 17794 addCommands({ 17795 // Ignore these, added for compatibility 17796 'mceResetDesignMode,mceBeginUndoLevel': function() {}, 17797 17798 // Add undo manager logic 17799 'mceEndUndoLevel,mceAddUndoLevel': function() { 17800 editor.undoManager.add(); 17801 }, 17802 17803 'Cut,Copy,Paste': function(command) { 17804 var doc = editor.getDoc(), failed; 17805 17806 // Try executing the native command 17807 try { 17808 execNativeCommand(command); 17809 } catch (ex) { 17810 // Command failed 17811 failed = TRUE; 17812 } 17813 17814 // Present alert message about clipboard access not being available 17815 if (failed || !doc.queryCommandSupported(command)) { 17816 var msg = editor.translate( 17817 "Your browser doesn't support direct access to the clipboard. " + 17818 "Please use the Ctrl+X/C/V keyboard shortcuts instead." 17819 ); 17820 17821 if (Env.mac) { 17822 msg = msg.replace(/Ctrl\+/g, '\u2318+'); 17823 } 17824 17825 editor.windowManager.alert(msg); 17826 } 17827 }, 17828 17829 // Override unlink command 17830 unlink: function() { 17831 if (selection.isCollapsed()) { 17832 var elm = selection.getNode(); 17833 if (elm.tagName == 'A') { 17834 editor.dom.remove(elm, true); 17835 } 17836 17837 return; 17838 } 17839 17840 formatter.remove("link"); 17841 }, 17842 17843 // Override justify commands to use the text formatter engine 17844 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) { 17845 var align = command.substring(7); 17846 17847 if (align == 'full') {
17848 align = 'justify'; 17849 } 17850 17851 // Remove all other alignments first 17852 each('left,center,right,justify'.split(','), function(name) { 17853 if (align != name) { 17854 formatter.remove('align' + name); 17855 } 17856 }); 17857 17858 toggleFormat('align' + align); 17859 execCommand('mceRepaint'); 17860 }, 17861 17862 // Override list commands to fix WebKit bug 17863 'InsertUnorderedList,InsertOrderedList': function(command) { 17864 var listElm, listParent; 17865 17866 execNativeCommand(command); 17867 17868 // WebKit produces lists within block elements so we need to split them 17869 // we will replace the native list creation logic to custom logic later on 17870 // TODO: Remove this when the list creation logic is removed 17871 listElm = dom.getParent(selection.getNode(), 'ol,ul'); 17872 if (listElm) { 17873 listParent = listElm.parentNode; 17874 17875 // If list is within a text block then split that block 17876 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) { 17877 storeSelection(); 17878 dom.split(listParent, listElm); 17879 restoreSelection(); 17880 } 17881 } 17882 }, 17883 17884 // Override commands to use the text formatter engine 17885 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) { 17886 toggleFormat(command); 17887 }, 17888 17889 // Override commands to use the text formatter engine 17890 'ForeColor,HiliteColor,FontName': function(command, ui, value) { 17891 toggleFormat(command, value); 17892 }, 17893 17894 FontSize: function(command, ui, value) { 17895 var fontClasses, fontSizes; 17896 17897 // Convert font size 1-7 to styles 17898 if (value >= 1 && value <= 7) { 17899 fontSizes = explode(settings.font_size_style_values); 17900 fontClasses = explode(settings.font_size_classes); 17901 17902 if (fontClasses) { 17903 value = fontClasses[value - 1] || value; 17904 } else { 17905 value = fontSizes[value - 1] || value; 17906 } 17907 } 17908 17909 toggleFormat(command, value); 17910 }, 17911 17912 RemoveFormat: function(command) { 17913 formatter.remove(command); 17914 }, 17915 17916 mceBlockQuote: function() { 17917 toggleFormat('blockquote'); 17918 }, 17919 17920 FormatBlock: function(command, ui, value) { 17921 return toggleFormat(value || 'p'); 17922 }, 17923 17924 mceCleanup: function() { 17925 var bookmark = selection.getBookmark(); 17926 17927 editor.setContent(editor.getContent({cleanup: TRUE}), {cleanup: TRUE}); 17928 17929 selection.moveToBookmark(bookmark); 17930 }, 17931 17932 mceRemoveNode: function(command, ui, value) { 17933 var node = value || selection.getNode(); 17934 17935 // Make sure that the body node isn't removed 17936 if (node != editor.getBody()) { 17937 storeSelection(); 17938 editor.dom.remove(node, TRUE); 17939 restoreSelection(); 17940 } 17941 }, 17942 17943 mceSelectNodeDepth: function(command, ui, value) { 17944 var counter = 0; 17945 17946 dom.getParent(selection.getNode(), function(node) { 17947 if (node.nodeType == 1 && counter++ == value) { 17948 selection.select(node); 17949 return FALSE; 17950 } 17951 }, editor.getBody()); 17952 }, 17953 17954 mceSelectNode: function(command, ui, value) { 17955 selection.select(value); 17956 }, 17957 17958 mceInsertContent: function(command, ui, value) { 17959 var parser, serializer, parentNode, rootNode, fragment, args; 17960 var marker, rng, node, node2, bookmarkHtml; 17961 17962 function trimOrPaddLeftRight(html) { 17963 var rng, container, offset; 17964 17965 rng = selection.getRng(true); 17966 container = rng.startContainer; 17967 offset = rng.startOffset; 17968 17969 function hasSiblingText(siblingName) { 17970 return container[siblingName] && container[siblingName].nodeType == 3; 17971 } 17972 17973 if (container.nodeType == 3) { 17974 if (offset > 0) { 17975 html = html.replace(/^ /, ' '); 17976 } else if (!hasSiblingText('previousSibling')) { 17977 html = html.replace(/^ /, ' '); 17978 } 17979 17980 if (offset < container.length) { 17981 html = html.replace(/ (<br>|)$/, ' '); 17982 } else if (!hasSiblingText('nextSibling')) { 17983 html = html.replace(/( | )(<br>|)$/, ' '); 17984 } 17985 } 17986 17987 return html; 17988 } 17989 17990 // Check for whitespace before/after value 17991 if (/^ | $/.test(value)) { 17992 value = trimOrPaddLeftRight(value); 17993 } 17994 17995 // Setup parser and serializer 17996 parser = editor.parser; 17997 serializer = new Serializer({}, editor.schema); 17998 bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">
17998ÈB;</span>'; 17999 18000 // Run beforeSetContent handlers on the HTML to be inserted 18001 args = {content: value, format: 'html', selection: true}; 18002 editor.fire('BeforeSetContent', args); 18003 value = args.content; 18004 18005 // Add caret at end of contents if it's missing 18006 if (value.indexOf('{$caret}') == -1) { 18007 value += '{$caret}'; 18008 } 18009 18010 // Replace the caret marker with a span bookmark element 18011 value = value.replace(/\{\$caret\}/, bookmarkHtml); 18012 18013 // If selection is at <body>|<p></p> then move it into <body><p>|</p> 18014 rng = selection.getRng(); 18015 var caretElement = rng.startContainer || (rng.parentElement ? rng.parentElement() : null); 18016 var body = editor.getBody(); 18017 if (caretElement === body && selection.isCollapsed()) { 18018 if (dom.isBlock(body.firstChild) && dom.isEmpty(body.firstChild)) { 18019 rng = dom.createRng(); 18020 rng.setStart(body.firstChild, 0); 18021 rng.setEnd(body.firstChild, 0); 18022 selection.setRng(rng); 18023 } 18024 } 18025 18026 // Insert node maker where we will insert the new HTML and get it's parent 18027 if (!selection.isCollapsed()) { 18028 editor.getDoc().execCommand('Delete', false, null); 18029 } 18030 18031 parentNode = selection.getNode(); 18032 18033 // Parse the fragment within the context of the parent node 18034 var parserArgs = {context: parentNode.nodeName.toLowerCase()}; 18035 fragment = parser.parse(value, parserArgs); 18036 18037 // Move the caret to a more suitable location 18038 node = fragment.lastChild; 18039 if (node.attr('id') == 'mce_marker') { 18040 marker = node; 18041 18042 for (node = node.prev; node; node = node.walk(true)) { 18043 if (node.type == 3 || !dom.isBlock(node.name)) { 18044 node.parent.insert(marker, node, node.name === 'br'); 18045 break; 18046 } 18047 } 18048 } 18049 18050 // If parser says valid we can insert the contents into that parent 18051 if (!parserArgs.invalid) { 18052 value = serializer.serialize(fragment); 18053 18054 // Check if parent is empty or only has one BR element then set the innerHTML of that parent 18055 node = parentNode.firstChild; 18056 node2 = parentNode.lastChild; 18057 if (!node || (node === node2 && node.nodeName === 'BR')) { 18058 dom.setHTML(parentNode, value); 18059 } else { 18060 selection.setContent(value); 18061 } 18062 } else {
18063 // If the fragment was invalid within that context then we need 18064 // to parse and process the parent it's inserted into 18065 18066 // Insert bookmark node and get the parent 18067 selection.setContent(bookmarkHtml); 18068 parentNode = selection.getNode(); 18069 rootNode = editor.getBody(); 18070 18071 // Opera will return the document node when selection is in root 18072 if (parentNode.nodeType == 9) { 18073 parentNode = node = rootNode; 18074 } else { 18075 node = parentNode; 18076 } 18077 18078 // Find the ancestor just before the root element 18079 while (node !== rootNode) { 18080 parentNode = node; 18081 node = node.parentNode; 18082 } 18083 18084 // Get the outer/inner HTML depending on if we are in the root and parser and serialize that 18085 value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode); 18086 value = serializer.serialize( 18087 parser.parse( 18088 // Need to replace by using a function since $ in the contents would otherwise be a problem 18089 value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() { 18090 return serializer.serialize(fragment); 18091 }) 18092 ) 18093 ); 18094 18095 // Set the inner/outer HTML depending on if we are in the root or not 18096 if (parentNode == rootNode) { 18097 dom.setHTML(rootNode, value); 18098 } else { 18099 dom.setOuterHTML(parentNode, value); 18100 } 18101 } 18102 18103 marker = dom.get('mce_marker'); 18104 selection.scrollIntoView(marker); 18105 18106 // Move selection before marker and remove it 18107 rng = dom.createRng(); 18108 18109 // If previous sibling is a text node set the selection to the end of that node 18110 node = marker.previousSibling; 18111 if (node && node.nodeType == 3) { 18112 rng.setStart(node, node.nodeValue.length); 18113 18114 // TODO: Why can't we normalize on IE 18115 if (!isIE) { 18116 node2 = marker.nextSibling; 18117 if (node2 && node2.nodeType == 3) { 18118 node.appendData(node2.data); 18119 node2.parentNode.removeChild(node2); 18120 } 18121 } 18122 } else { 18123 // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node 18124 rng.setStartBefore(marker); 18125 rng.setEndBefore(marker); 18126 } 18127 18128 // Remove the marker node and set the new range 18129 dom.remove(marker); 18130 selection.setRng(rng); 18131 18132 // Dispatch after event and add any visual elements needed 18133 editor.fire('SetContent', args); 18134 editor.addVisual(); 18135 }, 18136 18137 mceInsertRawHTML: function(command, ui, value) { 18138 selection.setContent('tiny_mce_marker'); 18139 editor.setContent( 18140 editor.getContent().replace(/tiny_mce_marker/g, function() { 18141 return value; 18142 }) 18143 ); 18144 }, 18145 18146 mceToggleFormat: function(command, ui, value) { 18147 toggleFormat(value); 18148 }, 18149 18150 mceSetContent: function(command, ui, value) { 18151 editor.setContent(value); 18152 }, 18153 18154 'Indent,Outdent': function(command) { 18155 var intentValue, indentUnit, value; 18156 18157 // Setup indent level 18158 intentValue = settings.indentation; 18159 indentUnit = /[a-z%]+$/i.exec(intentValue); 18160 intentValue = parseInt(intentValue, 10); 18161 18162 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { 18163 // If forced_root_blocks is set to false we don't have a block to indent so lets create a div 18164 if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) { 18165 formatter.apply('div'); 18166 } 18167 18168 each(selection.getSelectedBlocks(), function(element) { 18169 var indentStyleName; 18170 18171 if (element.nodeName != "LI") { 18172 indentStyleName = dom.getStyle(element, 'direction', true) == 'rtl' ? 'paddingRight' : 'paddingLeft'; 18173 18174 if (command == 'outdent') { 18175 value = Math.max(0, parseInt(element.style[indentStyleName] || 0, 10) - intentValue); 18176 dom.setStyle(element, indentStyleName, value ? value + indentUnit : ''); 18177 } else { 18178 value = (parseInt(element.style[indentStyleName] || 0, 10) + intentValue) + indentUnit; 18179 dom.setStyle(element, indentStyleName, value); 18180 } 18181 } 18182 }); 18183 } else { 18184 execNativeCommand(command); 18185 } 18186 }, 18187 18188 mceRepaint: function() { 18189 if (isGecko) { 18190 try { 18191 storeSelection(TRUE); 18192 18193 if (selection.getSel()) { 18194 selection.getSel().selectAllChildren(editor.getBody()); 18195 } 18196 18197 selection.collapse(TRUE); 18198 restoreSelection(); 18199 } catch (ex) { 18200 // Ignore 18201 } 18202 } 18203 }, 18204 18205 InsertHorizontalRule: function() { 18206 editor.execCommand('mceInsertContent', false, '<hr />'); 18207 }, 18208 18209 mceToggleVisualAid: function() { 18210 editor.hasVisual = !editor.hasVisual; 18211 editor.addVisual(); 18212 }, 18213 18214 mceReplaceContent: function(command, ui, value) { 18215 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format: 'text'}))); 18216 }, 18217 18218 mceInsertLink: function(command, ui, value) { 18219 var anchor; 18220 18221 if (typeof(value) == 'string') { 18222 value = {href: value}; 18223 } 18224 18225 anchor = dom.getParent(selection.getNode(), 'a'); 18226 18227 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here. 18228 value.href = value.href.replace(' ', '%20'); 18229 18230 // Remove existing links if there could be child links or that the href isn't specified 18231 if (!anchor || !value.href) { 18232 formatter.remove('link'); 18233 } 18234 18235 // Apply new link to selection 18236 if (value.href) { 18237 formatter.apply('link', value, anchor); 18238 } 18239 }, 18240 18241 selectAll: function() { 18242 var root = dom.getRoot(), rng; 18243 18244 if (selection.getRng().setStart) { 18245 rng = dom.createRng(); 18246 rng.setStart(root, 0); 18247 rng.setEnd(root, root.childNodes.length); 18248 selection.setRng(rng); 18249 } else {
18250 // IE will render it's own root level block elements and sometimes 18251 // even put font elements in them when the user starts typing. So we need to 18252 // move the selection to a more suitable element from this: 18253 // <body>|<p></p></body> to this: <body><p>|</p></body> 18254 rng = selection.getRng(); 18255 if (!rng.item) { 18256 rng.moveToElementText(root); 18257 rng.select(); 18258 } 18259 } 18260 }, 18261 18262 "delete": function() { 18263 execNativeCommand("Delete"); 18264 18265 // Check if body is empty after the delete call if so then set the contents 18266 // to an empty string and move the caret to any block produced by that operation 18267 // this fixes the issue with root blocks not being properly produced after a delete call on IE 18268 var body = editor.getBody(); 18269 18270 if (dom.isEmpty(body)) { 18271 editor.setContent(''); 18272 18273 if (body.firstChild && dom.isBlock(body.firstChild)) { 18274 editor.selection.setCursorLocation(body.firstChild, 0); 18275 } else { 18276 editor.selection.setCursorLocation(body, 0); 18277 } 18278 } 18279 }, 18280 18281 mceNewDocument: function() { 18282 editor.setContent(''); 18283 } 18284 }); 18285 18286 // Add queryCommandState overrides 18287 addCommands({ 18288 // Override justify commands 18289 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) { 18290 var name = 'align' + command.substring(7); 18291 var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks(); 18292 var matches = map(nodes, function(node) { 18293 return !!formatter.matchNode(node, name); 18294 }); 18295 return inArray(matches, TRUE) !== -1; 18296 }, 18297 18298 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) { 18299 return isFormatMatch(command); 18300 }, 18301 18302 mceBlockQuote: function() { 18303 return isFormatMatch('blockquote'); 18304 }, 18305 18306 Outdent: function() { 18307 var node; 18308 18309 if (settings.inline_styles) { 18310 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) { 18311 return TRUE; 18312 } 18313 18314 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) { 18315 return TRUE; 18316 } 18317 } 18318 18319 return ( 18320 queryCommandState('InsertUnorderedList') || 18321 queryCommandState('InsertOrderedList') || 18322 (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')) 18323 ); 18324 }, 18325 18326 'InsertUnorderedList,InsertOrderedList': function(command) { 18327 var list = dom.getParent(selection.getNode(), 'ul,ol'); 18328 18329 return list && 18330 ( 18331 command === 'insertunorderedlist' && list.tagName === 'UL' || 18332 command === 'insertorderedlist' && list.tagName === 'OL' 18333 ); 18334 } 18335 }, 'state'); 18336 18337 // Add queryCommandValue overrides 18338 addCommands({ 18339 'FontSize,FontName': function(command) { 18340 var value = 0, parent; 18341 18342 if ((parent = dom.getParent(selection.getNode(), 'span'))) { 18343 if (command == 'fontsize') { 18344 value = parent.style.fontSize; 18345 } else { 18346 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); 18347 } 18348 } 18349 18350 return value; 18351 } 18352 }, 'value'); 18353 18354 // Add undo manager logic 18355 addCommands({ 18356 Undo: function() { 18357 editor.undoManager.undo(); 18358 }, 18359 18360 Redo: function() { 18361 editor.undoManager.redo(); 18362 } 18363 }); 18364 }; 18365}); 18366 18367// Included from: js/tinymce/classes/util/URI.js 18368 18369/** 18370 * URI.js 18371 * 18372 * Copyright, Moxiecode Systems AB 18373 * Released under LGPL License. 18374 * 18375 * License: http://www.tinymce.com/license 18376 * Contributing: http://www.tinymce.com/contributing 18377 */ 18378 18379/** 18380 * This class handles parsing, modification and serialization of URI/URL strings. 18381 * @class tinymce.util.URI 18382 */ 18383define("tinymce/util/URI", [ 18384 "tinymce/util/Tools" 18385], function(Tools) { 18386 var each = Tools.each, trim = Tools.trim; 18387 18388 /** 18389 * Constructs a new URI instance. 18390 * 18391 * @constructor 18392 * @method URI 18393 * @param {String} url URI string to parse. 18394 * @param {Object} settings Optional settings object. 18395 */ 18396 function URI(url, settings) { 18397 var self = this, baseUri, base_url; 18398 18399 // Trim whitespace 18400 url = trim(url); 18401 18402 // Default settings 18403 settings = self.settings = settings || {}; 18404 18405 // Strange app protocol that isn't http/https or local anchor 18406 // For example: mailto,skype,tel etc. 18407 if (/^([\w\-]+):([^\/]{2})/i.test(url) || /^\s*#/.test(url)) { 18408 self.source = url; 18409 return; 18410 } 18411 18412 var isProtocolRelative = url.indexOf('//') === 0; 18413
18414 // Absolute path with no host, fake host and protocol 18415 if (url.indexOf('/') === 0 && !isProtocolRelative) { 18416 url = (settings.base_uri ? settings.base_uri.protocol || 'http' : 'http') + '://mce_host' + url; 18417 } 18418 18419 // Relative path http:// or protocol relative //path 18420 if (!/^[\w\-]*:?\/\//.test(url)) { 18421 base_url = settings.base_uri ? settings.base_uri.path : new URI(location.href).directory; 18422 if (settings.base_uri.protocol === "") { 18423 url = '//mce_host' + self.toAbsPath(base_url, url); 18424 } else { 18425 url = ((settings.base_uri && settings.base_uri.protocol) || 'http') + '://mce_host' + self.toAbsPath(base_url, url); 18426 } 18427 } 18428 18429 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri) 18430 url = url.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something 18431 18432 /*jshint maxlen: 255 */ 18433 url = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(url); 18434 18435 each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) { 18436 var part = url[i]; 18437 18438 // Zope 3 workaround, they use @@something 18439 if (part) { 18440 part = part.replace(/\(mce_at\)/g, '@@'); 18441 } 18442 18443 self[v] = part; 18444 }); 18445 18446 baseUri = settings.base_uri; 18447 if (baseUri) { 18448 if (!self.protocol) { 18449 self.protocol = baseUri.protocol; 18450 } 18451 18452 if (!self.userInfo) { 18453 self.userInfo = baseUri.userInfo; 18454 } 18455 18456 if (!self.port && self.host === 'mce_host') { 18457 self.port = baseUri.port; 18458 } 18459 18460 if (!self.host || self.host === 'mce_host') { 18461 self.host = baseUri.host; 18462 } 18463 18464 self.source = ''; 18465 } 18466 18467 if (isProtocolRelative) { 18468 self.protocol = ''; 18469 } 18470 18471 //t.path = t.path || '/'; 18472 } 18473 18474 URI.prototype = { 18475 /** 18476 * Sets the internal path part of the URI. 18477 * 18478 * @method setPath 18479 * @param {string} path Path string to set. 18480 */ 18481 setPath: function(path) { 18482 var self = this; 18483 18484 path = /^(.*?)\/?(\w+)?$/.exec(path); 18485 18486 // Update path parts 18487 self.path = path[0]; 18488 self.directory = path[1]; 18489 self.file = path[2]; 18490 18491 // Rebuild source 18492 self.source = ''; 18493 self.getURI(); 18494 }, 18495 18496 /** 18497 * Converts the specified URI into a relative URI based on the current URI instance location. 18498 * 18499 * @method toRelative 18500 * @param {String} uri URI to convert into a relative path/URI. 18501 * @return {String} Relative URI from the point specified in the current URI instance. 18502 * @example 18503 * // Converts an absolute URL to an relative URL url will be somedir/somefile.htm 18504 * var url = new tinymce.util.URI('http://www.site.com/dir/').toRelative('http://www.site.com/dir/somedir/somefile.htm'); 18505 */ 18506 toRelative: function(uri) { 18507 var self = this, output; 18508 18509 if (uri === "./") { 18510 return uri; 18511 } 18512 18513 uri = new URI(uri, {base_uri: self}); 18514 18515 // Not on same domain/port or protocol 18516 if ((uri.host != 'mce_host' && self.host != uri.host && uri.host) || self.port != uri.port || 18517 (self.protocol != uri.protocol && uri.protocol !== "")) { 18518 return uri.getURI(); 18519 } 18520 18521 var tu = self.getURI(), uu = uri.getURI(); 18522 18523 // Allow usage of the base_uri when relative_urls = true 18524 if (tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) { 18525 return tu; 18526 } 18527 18528 output = self.toRelPath(self.path, uri.path); 18529 18530 // Add query 18531 if (uri.query) { 18532 output += '?' + uri.query; 18533 } 18534 18535 // Add anchor 18536 if (uri.anchor) { 18537 output += '#' + uri.anchor; 18538 } 18539 18540 return output; 18541 }, 18542 18543 /** 18544 * Converts the specified URI into a absolute URI based on the current URI instance location. 18545 * 18546 * @method toAbsolute 18547 * @param {String} uri URI to convert into a relative path/URI. 18548 * @param {Boolean} noHost No host and protocol prefix. 18549 * @return {String} Absolute URI from the point specified in the current URI instance. 18550 * @example 18551 * // Converts an relative URL to an absolute URL url will be http://www.site.com/dir/somedir/somefile.htm 18552 * var url = new tinymce.util.URI('http://www.site.com/dir/').toAbsolute('somedir/somefile.htm'); 18553 */ 18554 toAbsolute: function(uri, noHost) { 18555 uri = new URI(uri, {base_uri: this}); 18556 18557 return uri.getURI(this.host == uri.host && this.protocol == uri.protocol ? noHost : 0); 18558 }, 18559 18560 /** 18561 * Converts a absolute path into a relative path. 18562 * 18563 * @method toRelPath 18564 * @param {String} base Base point to convert the path from. 18565 * @param {String} path Absolute path to convert into a relative path. 18566 */ 18567 toRelPath: function(base, path) { 18568 var items, breakPoint = 0, out = '', i, l; 18569 18570 // Split the paths 18571 base = base.substring(0, base.lastIndexOf('/')); 18572 base = base.split('/'); 18573 items = path.split('/'); 18574 18575 if (base.length >= items.length) { 18576 for (i = 0, l = base.length; i < l; i++) { 18577 if (i >= items.length || base[i] != items[i]) { 18578 breakPoint = i + 1; 18579 break; 18580 } 18581 } 18582 } 18583 18584 if (base.length < items.length) { 18585 for (i = 0, l = items.length; i < l; i++) { 18586 if (i >= base.length || base[i] != items[i]) { 18587 breakPoint = i + 1; 18588 break; 18589 } 18590 } 18591 } 18592 18593 if (breakPoint === 1) { 18594 return path; 18595 } 18596 18597 for (i = 0, l = base.length - (breakPoint - 1); i < l; i++) { 18598 out += "../"; 18599 } 18600 18601 for (i = breakPoint - 1, l = items.length; i < l; i++) { 18602 if (i != breakPoint - 1) { 18603 out += "/" + items[i]; 18604 } else { 18605 out += items[i]; 18606 } 18607 } 18608 18609 return out; 18610 }, 18611 18612 /** 18613 * Converts a relative path into a absolute path. 18614 * 18615 * @method toAbsPath 18616 * @param {String} base Base point to convert the path from. 18617 * @param {String} path Relative path to convert into an absolute path. 18618 */ 18619 toAbsPath: function(base, path) { 18620 var i, nb = 0, o = [], tr, outPath; 18621 18622 // Split paths 18623 tr = /\/$/.test(path) ? '/' : ''; 18624 base = base.split('/'); 18625 path = path.split('/'); 18626 18627 // Remove empty chunks 18628 each(base, function(k) { 18629 if (k) { 18630 o.push(k); 18631 } 18632 }); 18633 18634 base = o; 18635 18636 // Merge relURLParts chunks 18637 for (i = path.length - 1, o = []; i >= 0; i--) { 18638 // Ignore empty or . 18639 if (path[i].length === 0 || path[i] === ".") { 18640 continue; 18641 } 18642 18643 // Is parent 18644 if (path[i] === '..') { 18645 nb++; 18646 continue; 18647 } 18648 18649 // Move up 18650 if (nb > 0) { 18651 nb--; 18652 continue; 18653 } 18654 18655 o.push(path[i]); 18656 } 18657 18658 i = base.length - nb; 18659 18660 // If /a/b/c or / 18661 if (i <= 0) { 18662 outPath = o.reverse().join('/'); 18663 } else { 18664 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/'); 18665 } 18666 18667 // Add front / if it's needed 18668 if (outPath.indexOf('/') !== 0) { 18669 outPath = '/' + outPath; 18670 } 18671 18672 // Add traling / if it's needed 18673 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) { 18674 outPath += tr; 18675 } 18676 18677 return outPath; 18678 }, 18679 18680 /** 18681 * Returns the full URI of the internal structure. 18682 * 18683 * @method getURI 18684 * @param {Boolean} noProtoHost Optional no host and protocol part. Defaults to false. 18685 */ 18686 getURI: function(noProtoHost) { 18687 var s, self = this; 18688 18689 // Rebuild source 18690 if (!self.source || noProtoHost) { 18691 s = ''; 18692 18693 if (!noProtoHost) { 18694 if (self.protocol) { 18695 s += self.protocol + '://'; 18696 } else { 18697 s += '//'; 18698 } 18699 18700 if (self.userInfo) { 18701 s += self.userInfo + '@'; 18702 } 18703 18704 if (self.host) { 18705 s += self.host; 18706 } 18707 18708 if (self.port) { 18709 s += ':' + self.port; 18710 } 18711 } 18712 18713 if (self.path) { 18714 s += self.path; 18715 } 18716 18717 if (self.query) { 18718 s += '?' + self.query; 18719 } 18720 18721 if (self.anchor) { 18722 s += '#' + self.anchor; 18723 } 18724 18725 self.source = s; 18726 } 18727 18728 return self.source; 18729 } 18730 }; 18731 18732 return URI; 18733}); 18734 18735// Included from: js/tinymce/classes/util/Class.js 18736 18737/** 18738 * Class.js 18739 * 18740 * Copyright 2003-2012, Moxiecode Systems AB, All rights reserved. 18741 */ 18742 18743/** 18744 * This utilitiy class is used for easier inheritage. 18745 * 18746 * Features: 18747 * * Exposed super functions: this._super(); 18748 * * Mixins 18749 * * Dummy functions 18750 * * Property functions: var value = object.value(); and object.value(newValue); 18751 * * Static functions 18752 * * Defaults settings 18753 */ 18754define("tinymce/util/Class", [ 18755 "tinymce/util/Tools" 18756], function(Tools) { 18757 var each = Tools.each, extend = Tools.extend; 18758 18759 var extendClass, initializing; 18760 18761 function Class() { 18762 } 18763 18764 // Provides classical inheritance, based on code made by John Resig 18765 Class.extend = extendClass = function(prop) { 18766 var Self = this, _super = Self.prototype, prototype, name, member; 18767 18768 // The dummy class constructor 18769 function Class() { 18770 var i, mixins, mixin, self; 18771 18772 // All construction is actually done in the init method 18773 if (!initializing) { 18774 self = this; 18775 18776 // Run class constuctor 18777 if (self.init) {
18778 self.init.apply(self, arguments); 18779 } 18780 18781 // Run mixin constructors 18782 mixins = self.Mixins; 18783 if (mixins) { 18784 i = mixins.length; 18785 while (i--) { 18786 mixin = mixins[i]; 18787 if (mixin.init) { 18788 mixin.init.apply(self, arguments); 18789 } 18790 } 18791 } 18792 } 18793 } 18794 18795 // Dummy function, needs to be extended in order to provide functionality 18796 function dummy() { 18797 return this; 18798 } 18799 18800 // Creates a overloaded method for the class 18801 // this enables you to use this._super(); to call the super function 18802 function createMethod(name, fn) { 18803 return function(){ 18804 var self = this, tmp = self._super, ret; 18805 18806 self._super = _super[name]; 18807 ret = fn.apply(self, arguments); 18808 self._super = tmp; 18809 18810 return ret; 18811 }; 18812 } 18813 18814 // Instantiate a base class (but only create the instance, 18815 // don't run the init constructor) 18816 initializing = true; 18817 prototype = new Self(); 18818 initializing = false; 18819 18820 // Add mixins 18821 if (prop.Mixins) { 18822 each(prop.Mixins, function(mixin) { 18823 mixin = mixin; 18824 18825 for (var name in mixin) { 18826 if (name !== "init") { 18827 prop[name] = mixin[name]; 18828 } 18829 } 18830 }); 18831 18832 if (_super.Mixins) { 18833 prop.Mixins = _super.Mixins.concat(prop.Mixins); 18834 } 18835 } 18836 18837 // Generate dummy methods 18838 if (prop.Methods) { 18839 each(prop.Methods.split(','), function(name) { 18840 prop[name] = dummy; 18841 }); 18842 } 18843 18844 // Generate property methods 18845 if (prop.Properties) { 18846 each(prop.Properties.split(','), function(name) { 18847 var fieldName = '_' + name; 18848 18849 prop[name] = function(value) { 18850 var self = this, undef; 18851 18852 // Set value 18853 if (value !== undef) { 18854 self[fieldName] = value; 18855 18856 return self; 18857 } 18858 18859 // Get value 18860 return self[fieldName]; 18861 }; 18862 }); 18863 } 18864 18865 // Static functions 18866 if (prop.Statics) { 18867 each(prop.Statics, function(func, name) { 18868 Class[name] = func; 18869 }); 18870 } 18871 18872 // Default settings 18873 if (prop.Defaults && _super.Defaults) { 18874 prop.Defaults = extend({}, _super.Defaults, prop.Defaults); 18875 } 18876 18877 // Copy the properties over onto the new prototype 18878 for (name in prop) { 18879 member = prop[name]; 18880 18881 if (typeof member == "function" && _super[name]) { 18882 prototype[name] = createMethod(name, member); 18883 } else { 18884 prototype[name] = member; 18885 } 18886 } 18887 18888 // Populate our constructed prototype object 18889 Class.prototype = prototype; 18890 18891 // Enforce the constructor to be what we expect 18892 Class.constructor = Class; 18893 18894 // And make this class extendible 18895 Class.extend = extendClass; 18896 18897 return Class; 18898 }; 18899 18900 return Class; 18901}); 18902 18903// Included from: js/tinymce/classes/ui/Selector.js 18904 18905/** 18906 * Selector.js 18907 * 18908 * Copyright, Moxiecode Systems AB 18909 * Released under LGPL License. 18910 * 18911 * License: http://www.tinymce.com/license 18912 * Contributing: http://www.tinymce.com/contributing 18913 */ 18914 18915/** 18916 * Selector engine, enables you to select controls by using CSS like expressions. 18917 * We currently only support basic CSS expressions to reduce the size of the core 18918 * and the ones we support should be enough for most cases. 18919 * 18920 * @example 18921 * Supported expressions: 18922 * element 18923 * element#name 18924 * element.class 18925 * element[attr] 18926 * element[attr*=value] 18927 * element[attr~=value] 18928 * element[attr!=value] 18929 * element[attr^=value] 18930 * element[attr$=value] 18931 * element:<state> 18932 * element:not(<expression>) 18933 * element:first 18934 * element:last 18935 * element:odd 18936 * element:even 18937 * element element 18938 * element > element 18939 * 18940 * @class tinymce.ui.Selector 18941 */ 18942define("tinymce/ui/Selector", [ 18943 "tinymce/util/Class" 18944], function(Class) { 18945 "use strict"; 18946 18947 /** 18948 * Produces an array with a unique set of objects. It will not compare the values 18949 * but the references of the objects. 18950 * 18951 * @private 18952 * @method unqiue 18953 * @param {Array} array Array to make into an array with unique items. 18954 * @return {Array} Array with unique items. 18955 */ 18956 function unique(array) { 18957 var uniqueItems = [], i = array.length, item; 18958 18959 while (i--) { 18960 item = array[i]; 18961 18962 if (!item.__checked) { 18963 uniqueItems.push(item); 18964 item.__checked = 1; 18965 } 18966 } 18967 18968 i = uniqueItems.length; 18969 while (i--) { 18970 delete uniqueItems[i].__checked; 18971 } 18972 18973 return uniqueItems; 18974 } 18975 18976 var expression = /^([\w\\*]+)?(?:#([\w\\]+))?(?:\.([\w\\\.]+))?(?:\[\@?([\w\\]+)([\^\$\*!~]?=)([\w\\]+)\])?(?:\:(.+))?/i; 18977 18978 /*jshint maxlen:255 */ 18979 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, 18980 whiteSpace = /^\s*|\s*$/g, 18981 Collection; 18982 18983 var Selector = Class.extend({ 18984 /** 18985 * Constructs a new Selector instance. 18986 * 18987 * @constructor 18988 * @method init 18989 * @param {String} selector CSS like selector expression. 18990 */ 18991 init: function(selector) { 18992 var match = this.match; 18993 18994 function compileNameFilter(name) { 18995 if (name) { 18996 name = name.toLowerCase(); 18997 18998 return function(item) { 18999 return name === '*' || item.type === name; 19000 }; 19001 } 19002 } 19003 19004 function compileIdFilter(id) { 19005 if (id) { 19006 return function(item) { 19007 return item._name === id; 19008 }; 19009 } 19010 } 19011 19012 function compileClassesFilter(classes) { 19013 if (classes) { 19014 classes = classes.split('.'); 19015 19016 return function(item) { 19017 var i = classes.length; 19018 19019 while (i--) {
19020 if (!item.hasClass(classes[i])) { 19021 return false; 19022 } 19023 } 19024 19025 return true; 19026 }; 19027 } 19028 } 19029 19030 function compileAttrFilter(name, cmp, check) { 19031 if (name) { 19032 return function(item) { 19033 var value = item[name] ? item[name]() : ''; 19034 19035 return !cmp ? !!check : 19036 cmp === "=" ? value === check : 19037 cmp === "*=" ? value.indexOf(check) >= 0 : 19038 cmp === "~=" ? (" " + value + " ").indexOf(" " + check + " ") >= 0 : 19039 cmp === "!=" ? value != check : 19040 cmp === "^=" ? value.indexOf(check) === 0 : 19041 cmp === "$=" ? value.substr(value.length - check.length) === check : 19042 false; 19043 }; 19044 } 19045 } 19046 19047 function compilePsuedoFilter(name) { 19048 var notSelectors; 19049 19050 if (name) { 19051 name = /(?:not\((.+)\))|(.+)/i.exec(name); 19052 19053 if (!name[1]) { 19054 name = name[2]; 19055 19056 return function(item, index, length) { 19057 return name === 'first' ? index === 0 : 19058 name === 'last' ? index === length - 1 : 19059 name === 'even' ? index % 2 === 0 : 19060 name === 'odd' ? index % 2 === 1 : 19061 item[name] ? item[name]() : 19062 false; 19063 }; 19064 } else { 19065 // Compile not expression 19066 notSelectors = parseChunks(name[1], []); 19067 19068 return function(item) { 19069 return !match(item, notSelectors); 19070 }; 19071 } 19072 } 19073 } 19074 19075 function compile(selector, filters, direct) { 19076 var parts; 19077 19078 function add(filter) { 19079 if (filter) { 19080 filters.push(filter); 19081 } 19082 } 19083 19084 // Parse expression into parts 19085 parts = expression.exec(selector.replace(whiteSpace, '')); 19086 19087 add(compileNameFilter(parts[1])); 19088 add(compileIdFilter(parts[2])); 19089 add(compileClassesFilter(parts[3])); 19090 add(compileAttrFilter(parts[4], parts[5], parts[6])); 19091 add(compilePsuedoFilter(parts[7])); 19092 19093 // Mark the filter with psuedo for performance 19094 filters.psuedo = !!parts[7]; 19095 filters.direct = direct; 19096 19097 return filters; 19098 } 19099 19100 // Parser logic based on Sizzle by John Resig 19101 function parseChunks(selector, selectors) { 19102 var parts = [], extra, matches, i; 19103 19104 do { 19105 chunker.exec(""); 19106 matches = chunker.exec(selector); 19107 19108 if (matches) { 19109 selector = matches[3]; 19110 parts.push(matches[1]); 19111 19112 if (matches[2]) { 19113 extra = matches[3]; 19114 break; 19115 } 19116 } 19117 } while (matches); 19118 19119 if (extra) { 19120 parseChunks(extra, selectors); 19121 } 19122 19123 selector = []; 19124 for (i = 0; i < parts.length; i++) { 19125 if (parts[i] != '>') { 19126 selector.push(compile(parts[i], [], parts[i - 1] === '>')); 19127 } 19128 } 19129 19130 selectors.push(selector); 19131 19132 return selectors; 19133 } 19134 19135 this._selectors = parseChunks(selector, []); 19136 }, 19137 19138 /** 19139 * Returns true/false if the selector matches the specified control. 19140 * 19141 * @method match 19142 * @param {tinymce.ui.Control} control Control to match agains the selector. 19143 * @param {Array} selectors Optional array of selectors, mostly used internally. 19144 * @return {Boolean} true/false state if the control matches or not. 19145 */ 19146 match: function(control, selectors) { 19147 var i, l, si, sl, selector, fi, fl, filters, index, length, siblings, count, item; 19148 19149 selectors = selectors || this._selectors; 19150 for (i = 0, l = selectors.length; i < l; i++) { 19151 selector = selectors[i]; 19152 sl = selector.length; 19153 item = control; 19154 count = 0; 19155 19156 for (si = sl - 1; si >= 0; si--) { 19157 filters = selector[si]; 19158 19159 while (item) { 19160 // Find the index and length since a psuedo filter like :first needs it 19161 if (filters.psuedo) { 19162 siblings = item.parent().items(); 19163 index = length = siblings.length; 19164 while (index--) { 19165 if (siblings[index] === item) { 19166 break; 19167 } 19168 } 19169 } 19170 19171 for (fi = 0, fl = filters.length; fi < fl; fi++) { 19172 if (!filters[fi](item, index, length)) { 19173 fi = fl + 1; 19174 break; 19175 } 19176 } 19177 19178 if (fi === fl) { 19179 count++; 19180 break; 19181 } else { 19182 // If it didn't match the right most expression then 19183 // break since it's no point looking at the parents 19184 if (si === sl - 1) { 19185 break; 19186 } 19187 } 19188 19189 item = item.parent(); 19190 } 19191 } 19192 19193 // If we found all selectors then return true otherwise continue looking 19194 if (count === sl) { 19195 return true; 19196 } 19197 } 19198 19199 return false;
19200 }, 19201 19202 /** 19203 * Returns a tinymce.ui.Collection with matches of the specified selector inside the specified container. 19204 * 19205 * @method find 19206 * @param {tinymce.ui.Control} container Container to look for items in. 19207 * @return {tinymce.ui.Collection} Collection with matched elements. 19208 */ 19209 find: function(container) { 19210 var matches = [], i, l, selectors = this._selectors; 19211 19212 function collect(items, selector, index) { 19213 var i, l, fi, fl, item, filters = selector[index]; 19214 19215 for (i = 0, l = items.length; i < l; i++) { 19216 item = items[i]; 19217 19218 // Run each filter agains the item 19219 for (fi = 0, fl = filters.length; fi < fl; fi++) { 19220 if (!filters[fi](item, i, l)) { 19221 fi = fl + 1; 19222 break; 19223 } 19224 } 19225 19226 // All filters matched the item 19227 if (fi === fl) { 19228 // Matched item is on the last expression like: panel toolbar [button] 19229 if (index == selector.length - 1) { 19230 matches.push(item); 19231 } else { 19232 // Collect next expression type 19233 if (item.items) { 19234 collect(item.items(), selector, index + 1); 19235 } 19236 } 19237 } else if (filters.direct) { 19238 return; 19239 } 19240 19241 // Collect child items 19242 if (item.items) { 19243 collect(item.items(), selector, index); 19244 } 19245 } 19246 } 19247 19248 if (container.items) { 19249 for (i = 0, l = selectors.length; i < l; i++) { 19250 collect(container.items(), selectors[i], 0); 19251 } 19252 19253 // Unique the matches if needed 19254 if (l > 1) { 19255 matches = unique(matches); 19256 } 19257 } 19258 19259 // Fix for circular reference 19260 if (!Collection) { 19261 // TODO: Fix me! 19262 Collection = Selector.Collection; 19263 } 19264 19265 return new Collection(matches); 19266 } 19267 }); 19268 19269 return Selector; 19270}); 19271 19272// Included from: js/tinymce/classes/ui/Collection.js 19273 19274/** 19275 * Collection.js 19276 * 19277 * Copyright, Moxiecode Systems AB 19278 * Released under LGPL License. 19279 * 19280 * License: http://www.tinymce.com/license 19281 * Contributing: http://www.tinymce.com/contributing 19282 */ 19283 19284/** 19285 * Control collection, this class contains control instances and it enables you to 19286 * perform actions on all the contained items. This is very similar to how jQuery works. 19287 * 19288 * @example 19289 * someCollection.show().disabled(true); 19290 * 19291 * @class tinymce.ui.Collection 19292 */ 19293define("tinymce/ui/Collection", [ 19294 "tinymce/util/Tools", 19295 "tinymce/ui/Selector", 19296 "tinymce/util/Class" 19297], function(Tools, Selector, Class) { 19298 "use strict"; 19299 19300 var Collection, proto, push = Array.prototype.push, slice = Array.prototype.slice; 19301 19302 proto = { 19303 /** 19304 * Current number of contained control instances. 19305 * 19306 * @field length 19307 * @type Number 19308 */ 19309 length: 0, 19310 19311 /** 19312 * Constructor for the collection. 19313 * 19314 * @constructor 19315 * @method init 19316 * @param {Array} items Optional array with items to add. 19317 */ 19318 init: function(items) { 19319 if (items) { 19320 this.add(items); 19321 } 19322 }, 19323 19324 /** 19325 * Adds new items to the control collection. 19326 * 19327 * @method add 19328 * @param {Array} items Array if items to add to collection. 19329 * @return {tinymce.ui.Collection} Current collection instance. 19330 */ 19331 add: function(items) { 19332 var self = this; 19333 19334 // Force single item into array 19335 if (!Tools.isArray(items)) { 19336 if (items instanceof Collection) { 19337 self.add(items.toArray()); 19338 } else { 19339 push.call(self, items); 19340 } 19341 } else { 19342 push.apply(self, items); 19343 } 19344 19345 return self; 19346 }, 19347 19348 /** 19349 * Sets the contents of the collection. This will remove any existing items 19350 * and replace them with the ones specified in the input array. 19351 * 19352 * @method set 19353 * @param {Array} items Array with items to set into the Collection. 19354 * @return {tinymce.ui.Collection} Collection instance. 19355 */ 19356 set: function(items) { 19357 var self = this, len = self.length, i; 19358 19359 self.length = 0; 19360 self.add(items); 19361 19362 // Remove old entries 19363 for (i = self.length; i < len; i++) { 19364 delete self[i]; 19365 } 19366 19367 return self; 19368 }, 19369 19370 /** 19371 * Filters the collection item based on the specified selector expression or selector function. 19372 * 19373 * @method filter 19374 * @param {String} selector Selector expression to filter items by. 19375 * @return {tinymce.ui.Collection} Collection containing the filtered items. 19376 */ 19377 filter: function(selector) { 19378 var self = this, i, l, matches = [], item, match; 19379 19380 // Compile string into selector expression 19381 if (typeof(selector) === "string") { 19382 selector = new Selector(selector); 19383 19384 match = function(item) { 19385 return selector.match(item); 19386 }; 19387 } else { 19388 // Use selector as matching function 19389 match = selector; 19390 } 19391 19392 for (i = 0, l = self.length; i < l; i++) { 19393 item = self[i]; 19394 19395 if (match(item)) { 19396 matches.push(item); 19397 } 19398 } 19399 19400 return new Collection(matches); 19401 }, 19402 19403 /** 19404 * Slices the items within the collection. 19405 * 19406 * @method slice 19407 * @param {Number} index Index to slice at. 19408 * @param {Number} len Optional length to slice. 19409 * @return {tinymce.ui.Collection} Current collection. 19410 */ 19411 slice: function() { 19412 return new Collection(slice.apply(this, arguments)); 19413 }, 19414 19415 /** 19416 * Makes the current collection equal to the specified index. 19417 * 19418 * @method eq 19419 * @param {Number} index Index of the item to set the collection to. 19420 * @return {tinymce.ui.Collection} Current collection. 19421 */ 19422 eq: function(index) { 19423 return index === -1 ? this.slice(index) : this.slice(index, +index + 1); 19424 }, 19425 19426 /** 19427 * Executes the specified callback on each item in collection. 19428 * 19429 * @method each 19430 * @param {function} callback Callback to execute for each item in collection. 19431 * @return {tinymce.ui.Collection} Current collection instance. 19432 */ 19433 each: function(callback) { 19434 Tools.each(this, callback); 19435 19436 return this; 19437 }, 19438 19439 /** 19440 * Returns an JavaScript array object of the contents inside the collection. 19441 * 19442 * @method toArray 19443 * @return {Array} Array with all items from collection. 19444 */ 19445 toArray: function() { 19446 return Tools.toArray(this); 19447 }, 19448 19449 /** 19450 * Finds the index of the specified control or return -1 if it isn't in the collection. 19451 * 19452 * @method indexOf 19453 * @param {Control} ctrl Control instance to look for. 19454 * @return {Number} Index of the specified control or -1. 19455 */ 19456 indexOf: function(ctrl) { 19457 var self = this, i = self.length; 19458 19459 while (i--) { 19460 if (self[i] === ctrl) { 19461 break; 19462 } 19463 } 19464 19465 return i; 19466 }, 19467 19468 /** 19469 * Returns a new collection of the contents in reverse order. 19470 * 19471 * @method reverse 19472 * @return {tinymce.ui.Collection} Collection instance with reversed items. 19473 */ 19474 reverse: function() { 19475 return new Collection(Tools.toArray(this).reverse()); 19476 }, 19477 19478 /** 19479 * Returns true/false if the class exists or not. 19480 * 19481 * @method hasClass 19482 * @param {String} cls Class to check for. 19483 * @return {Boolean} true/false state if the class exists or not. 19484 */ 19485 hasClass: function(cls) { 19486 return this[0] ? this[0].hasClass(cls) : false;
19487 }, 19488 19489 /** 19490 * Sets/gets the specific property on the items in the collection. The same as executing control.<property>(<value>); 19491 * 19492 * @method prop 19493 * @param {String} name Property name to get/set. 19494 * @param {Object} value Optional object value to set. 19495 * @return {tinymce.ui.Collection} Current collection instance or value of the first item on a get operation. 19496 */ 19497 prop: function(name, value) { 19498 var self = this, undef, item; 19499 19500 if (value !== undef) { 19501 self.each(function(item) { 19502 if (item[name]) { 19503 item[name](value); 19504 } 19505 }); 19506 19507 return self; 19508 } 19509 19510 item = self[0]; 19511 19512 if (item && item[name]) { 19513 return item[name](); 19514 } 19515 }, 19516 19517 /** 19518 * Executes the specific function name with optional arguments an all items in collection if it exists. 19519 * 19520 * @example collection.exec("myMethod", arg1, arg2, arg3); 19521 * @method exec 19522 * @param {String} name Name of the function to execute. 19523 * @param {Object} ... Multiple arguments to pass to each function. 19524 * @return {tinymce.ui.Collection} Current collection. 19525 */ 19526 exec: function(name) { 19527 var self = this, args = Tools.toArray(arguments).slice(1); 19528 19529 self.each(function(item) { 19530 if (item[name]) { 19531 item[name].apply(item, args); 19532 } 19533 }); 19534 19535 return self; 19536 }, 19537 19538 /** 19539 * Remove all items from collection and DOM. 19540 * 19541 * @method remove 19542 * @return {tinymce.ui.Collection} Current collection. 19543 */ 19544 remove: function() { 19545 var i = this.length; 19546 19547 while (i--) { 19548 this[i].remove(); 19549 } 19550 19551 return this; 19552 } 19553 19554 /** 19555 * Fires the specified event by name and arguments on the control. This will execute all 19556 * bound event handlers. 19557 * 19558 * @method fire 19559 * @param {String} name Name of the event to fire. 19560 * @param {Object} args Optional arguments to pass to the event. 19561 * @return {tinymce.ui.Collection} Current collection instance. 19562 */ 19563 // fire: function(event, args) {}, -- Generated by code below 19564 19565 /** 19566 * Binds a callback to the specified event. This event can both be 19567 * native browser events like "click" or custom ones like PostRender. 19568 * 19569 * The callback function will have two parameters the first one being the control that received the event 19570 * the second one will be the event object either the browsers native event object or a custom JS object. 19571 * 19572 * @method on 19573 * @param {String} name Name of the event to bind. For example "click". 19574 * @param {String/function} callback Callback function to execute ones the event occurs. 19575 * @return {tinymce.ui.Collection} Current collection instance. 19576 */ 19577 // on: function(name, callback) {}, -- Generated by code below 19578 19579 /** 19580 * Unbinds the specified event and optionally a specific callback. If you omit the name 19581 * parameter all event handlers will be removed. If you omit the callback all event handles 19582 * by the specified name will be removed. 19583 * 19584 * @method off 19585 * @param {String} name Optional name for the event to unbind. 19586 * @param {function} callback Optional callback function to unbind. 19587 * @return {tinymce.ui.Collection} Current collection instance. 19588 */ 19589 // off: function(name, callback) {}, -- Generated by code below 19590 19591 /** 19592 * Shows the items in the current collection. 19593 * 19594 * @method show 19595 * @return {tinymce.ui.Collection} Current collection instance. 19596 */ 19597 // show: function() {}, -- Generated by code below 19598 19599 /** 19600 * Hides the items in the current collection. 19601 * 19602 * @method hide 19603 * @return {tinymce.ui.Collection} Current collection instance. 19604 */ 19605 // hide: function() {}, -- Generated by code below 19606 19607 /** 19608 * Sets/gets the text contents of the items in the current collection. 19609 * 19610 * @method text 19611 * @return {tinymce.ui.Collection} Current collection instance or text value of the first item on a get operation. 19612 */ 19613 // text: function(value) {}, -- Generated by code below 19614 19615 /** 19616 * Sets/gets the name contents of the items in the current collection. 19617 * 19618 * @method name 19619 * @return {tinymce.ui.Collection} Current collection instance or name value of the first item on a get operation. 19620 */ 19621 // name: function(value) {}, -- Generated by code below 19622 19623 /** 19624 * Sets/gets the disabled state on the items in the current collection. 19625 * 19626 * @method disabled 19627 * @return {tinymce.ui.Collection} Current collection instance or disabled state of the first item on a get operation. 19628 */ 19629 // disabled: function(state) {}, -- Generated by code below 19630 19631 /** 19632 * Sets/gets the active state on the items in the current collection. 19633 * 19634 * @method active 19635 * @return {tinymce.ui.Collection} Current collection instance or active state of the first item on a get operation. 19636 */ 19637 // active: function(state) {}, -- Generated by code below 19638 19639 /** 19640 * Sets/gets the selected state on the items in the current collection. 19641 * 19642 * @method selected 19643 * @return {tinymce.ui.Collection} Current collection instance or selected state of the first item on a get operation. 19644 */ 19645 // selected: function(state) {}, -- Generated by code below 19646 19647 /** 19648 * Sets/gets the selected state on the items in the current collection. 19649 * 19650 * @method visible 19651 * @return {tinymce.ui.Collection} Current collection instance or visible state of the first item on a get operation. 19652 */ 19653 // visible: function(state) {}, -- Generated by code below 19654 19655 /** 19656 * Adds a class to all items in the collection. 19657 * 19658 * @method addClass 19659 * @param {String} cls Class to add to each item. 19660 * @return {tinymce.ui.Collection} Current collection instance. 19661 */ 19662 // addClass: function(cls) {}, -- Generated by code below 19663 19664 /** 19665 * Removes the specified class from all items in collection. 19666 * 19667 * @method removeClass 19668 * @param {String} cls Class to remove from each item. 19669 * @return {tinymce.ui.Collection} Current collection instance. 19670 */ 19671 // removeClass: function(cls) {}, -- Generated by code below 19672 }; 19673 19674 // Extend tinymce.ui.Collection prototype with some generated control specific methods 19675 Tools.each('fire on off show hide addClass removeClass append prepend before after reflow'.split(' '), function(name) { 19676 proto[name] = function() { 19677 var args = Tools.toArray(arguments); 19678 19679 this.each(function(ctrl) { 19680 if (name in ctrl) { 19681 ctrl[name].apply(ctrl, args); 19682 } 19683 }); 19684 19685 return this; 19686 }; 19687 }); 19688 19689 // Extend tinymce.ui.Collection prototype with some property methods 19690 Tools.each('text name disabled active selected checked visible parent value data'.split(' '), function(name) { 19691 proto[name] = function(value) { 19692 return this.prop(name, value); 19693 }; 19694 }); 19695 19696 // Create class based on the new prototype 19697 Collection = Class.extend(proto); 19698 19699 // Stick Collection into Selector to prevent circual references 19700 Selector.Collection = Collection; 19701 19702 return Collection; 19703}); 19704 19705// Included from: js/tinymce/classes/ui/DomUtils.js 19706 19707/** 19708 * DOMUtils.js 19709 * 19710 * Copyright, Moxiecode Systems AB 19711 * Released under LGPL License. 19712 * 19713 * License: http://www.tinymce.com/license 19714 * Contributing: http://www.tinymce.com/contributing 19715 */ 19716
19717define("tinymce/ui/DomUtils", [ 19718 "tinymce/util/Tools", 19719 "tinymce/dom/DOMUtils" 19720], function(Tools, DOMUtils) { 19721 "use strict"; 19722 19723 return { 19724 id: function() { 19725 return DOMUtils.DOM.uniqueId(); 19726 }, 19727 19728 createFragment: function(html) { 19729 return DOMUtils.DOM.createFragment(html); 19730 }, 19731 19732 getWindowSize: function() { 19733 return DOMUtils.DOM.getViewPort(); 19734 }, 19735 19736 getSize: function(elm) { 19737 var width, height; 19738 19739 if (elm.getBoundingClientRect) { 19740 var rect = elm.getBoundingClientRect(); 19741 19742 width = Math.max(rect.width || (rect.right - rect.left), elm.offsetWidth); 19743 height = Math.max(rect.height || (rect.bottom - rect.bottom), elm.offsetHeight); 19744 } else { 19745 width = elm.offsetWidth; 19746 height = elm.offsetHeight; 19747 } 19748 19749 return {width: width, height: height}; 19750 }, 19751 19752 getPos: function(elm, root) { 19753 return DOMUtils.DOM.getPos(elm, root); 19754 }, 19755 19756 getViewPort: function(win) { 19757 return DOMUtils.DOM.getViewPort(win); 19758 }, 19759 19760 get: function(id) { 19761 return document.getElementById(id); 19762 }, 19763 19764 addClass : function(elm, cls) { 19765 return DOMUtils.DOM.addClass(elm, cls); 19766 }, 19767 19768 removeClass : function(elm, cls) { 19769 return DOMUtils.DOM.removeClass(elm, cls); 19770 }, 19771 19772 hasClass : function(elm, cls) { 19773 return DOMUtils.DOM.hasClass(elm, cls); 19774 }, 19775 19776 toggleClass: function(elm, cls, state) { 19777 return DOMUtils.DOM.toggleClass(elm, cls, state); 19778 }, 19779 19780 css: function(elm, name, value) { 19781 return DOMUtils.DOM.setStyle(elm, name, value); 19782 }, 19783 19784 on: function(target, name, callback, scope) { 19785 return DOMUtils.DOM.bind(target, name, callback, scope); 19786 }, 19787 19788 off: function(target, name, callback) { 19789 return DOMUtils.DOM.unbind(target, name, callback); 19790 }, 19791 19792 fire: function(target, name, args) { 19793 return DOMUtils.DOM.fire(target, name, args); 19794 }, 19795 19796 innerHtml: function(elm, html) { 19797 // Workaround for <div> in <p> bug on IE 8 #6178 19798 DOMUtils.DOM.setHTML(elm, html); 19799 } 19800 }; 19801}); 19802 19803// Included from: js/tinymce/classes/ui/Control.js 19804 19805/** 19806 * Control.js 19807 * 19808 * Copyright, Moxiecode Systems AB 19809 * Released under LGPL License. 19810 * 19811 * License: http://www.tinymce.com/license 19812 * Contributing: http://www.tinymce.com/contributing 19813 */ 19814 19815/** 19816 * This is the base class for all controls and containers. All UI control instances inherit 19817 * from this one as it has the base logic needed by all of them. 19818 * 19819 * @class tinymce.ui.Control 19820 */ 19821define("tinymce/ui/Control", [ 19822 "tinymce/util/Class", 19823 "tinymce/util/Tools", 19824 "tinymce/ui/Collection", 19825 "tinymce/ui/DomUtils" 19826], function(Class, Tools, Collection, DomUtils) { 19827 "use strict"; 19828 19829 var nativeEvents = Tools.makeMap("focusin focusout scroll click dblclick mousedown mouseup mousemove mouseover" + 19830 " mouseout mouseenter mouseleave wheel keydown keypress keyup contextmenu", " "); 19831 19832 var elementIdCache = {}; 19833 var hasMouseWheelEventSupport = "onmousewheel" in document; 19834 var hasWheelEventSupport = false; 19835 19836 var Control = Class.extend({ 19837 Statics: { 19838 controlIdLookup: {}, 19839 elementIdCache: elementIdCache 19840 }, 19841 19842 isRtl: function() { 19843 return Control.rtl; 19844 }, 19845 19846 /** 19847 * Class/id prefix to use for all controls. 19848 * 19849 * @final 19850 * @field {String} classPrefix 19851 */ 19852 classPrefix: "mce-", 19853 19854 /** 19855 * Constructs a new control instance with the specified settings. 19856 * 19857 * @constructor 19858 * @param {Object} settings Name/value object with settings. 19859 * @setting {String} style Style CSS properties to add. 19860 * @setting {String} border Border box values example: 1 1 1 1 19861 * @setting {String} padding Padding box values example: 1 1 1 1 19862 * @setting {String} margin Margin box values example: 1 1 1 1 19863 * @setting {Number} minWidth Minimal width for the control. 19864 * @setting {Number} minHeight Minimal height for the control. 19865 * @setting {String} classes Space separated list of classes to add. 19866 * @setting {String} role WAI-ARIA role to use for control. 19867 * @setting {Boolean} hidden Is the control hidden by default. 19868 * @setting {Boolean} disabled Is the control disabled by default. 19869 * @setting {String} name Name of the control instance. 19870 */ 19871 init: function(settings) { 19872 var self = this, classes, i; 19873 19874 self.settings = settings = Tools.extend({}, self.Defaults, settings); 19875 19876 // Initial states 19877 self._id = DomUtils.id(); 19878 self._text = self._name = ''; 19879 self._width = self._height = 0; 19880 self._aria = {role: settings.role}; 19881 19882 // Setup classes 19883 classes = settings.classes; 19884 if (classes) { 19885 classes = classes.split(' '); 19886 classes.map = {}; 19887 i = classes.length; 19888 while (i--) { 19889 classes.map[classes[i]] = true; 19890 } 19891 } 19892 19893 self._classes = classes || []; 19894 self.visible(true); 19895 19896 // Set some properties 19897 Tools.each('title text width height name classes visible disabled active value'.split(' '), function(name) { 19898 var value = settings[name], undef; 19899 19900 if (value !== undef) { 19901 self[name](value); 19902 } else if (self['_' + name] === undef) { 19903 self['_' + name] = false;
19904 } 19905 }); 19906 19907 self.on('click', function() { 19908 if (self.disabled()) { 19909 return false; 19910 } 19911 }); 19912 19913 // TODO: Is this needed duplicate code see above? 19914 if (settings.classes) { 19915 Tools.each(settings.classes.split(' '), function(cls) { 19916 self.addClass(cls); 19917 }); 19918 } 19919 19920 /** 19921 * Name/value object with settings for the current control. 19922 * 19923 * @field {Object} settings 19924 */ 19925 self.settings = settings; 19926 19927 self._borderBox = self.parseBox(settings.border); 19928 self._paddingBox = self.parseBox(settings.padding); 19929 self._marginBox = self.parseBox(settings.margin); 19930 19931 if (settings.hidden) { 19932 self.hide(); 19933 } 19934 }, 19935 19936 // Will generate getter/setter methods for these properties 19937 Properties: 'parent,title,text,width,height,disabled,active,name,value', 19938 19939 // Will generate empty dummy functions for these 19940 Methods: 'renderHtml', 19941 19942 /** 19943 * Returns the root element to render controls into. 19944 * 19945 * @method getContainerElm 19946 * @return {Element} HTML DOM element to render into. 19947 */ 19948 getContainerElm: function() { 19949 return document.body; 19950 }, 19951 19952 /** 19953 * Returns a control instance for the current DOM element. 19954 * 19955 * @method getParentCtrl 19956 * @param {Element} elm HTML dom element to get parent control from. 19957 * @return {tinymce.ui.Control} Control instance or undefined. 19958 */ 19959 getParentCtrl: function(elm) { 19960 var ctrl; 19961 19962 while (elm) { 19963 ctrl = Control.controlIdLookup[elm.id]; 19964 if (ctrl) { 19965 break; 19966 } 19967 19968 elm = elm.parentNode; 19969 } 19970 19971 return ctrl; 19972 }, 19973 19974 /** 19975 * Parses the specified box value. A box value contains 1-4 properties in clockwise order. 19976 * 19977 * @method parseBox 19978 * @param {String/Number} value Box value "0 1 2 3" or "0" etc. 19979 * @return {Object} Object with top/right/bottom/left properties. 19980 * @private 19981 */ 19982 parseBox: function(value) { 19983 var len, radix = 10; 19984 19985 if (!value) { 19986 return; 19987 } 19988 19989 if (typeof(value) === "number") { 19990 value = value || 0; 19991 19992 return { 19993 top: value, 19994 left: value, 19995 bottom: value, 19996 right: value 19997 }; 19998 } 19999 20000 value = value.split(' '); 20001 len = value.length; 20002 20003 if (len === 1) { 20004 value[1] = value[2] = value[3] = value[0]; 20005 } else if (len === 2) { 20006 value[2] = value[0]; 20007 value[3] = value[1]; 20008 } else if (len === 3) { 20009 value[3] = value[1]; 20010 } 20011 20012 return { 20013 top: parseInt(value[0], radix) || 0, 20014 right: parseInt(value[1], radix) || 0, 20015 bottom: parseInt(value[2], radix) || 0, 20016 left: parseInt(value[3], radix) || 0 20017 }; 20018 }, 20019 20020 borderBox: function() { 20021 return this._borderBox; 20022 }, 20023 20024 paddingBox: function() { 20025 return this._paddingBox; 20026 }, 20027 20028 marginBox: function() { 20029 return this._marginBox; 20030 }, 20031 20032 measureBox: function(elm, prefix) { 20033 function getStyle(name) { 20034 var defaultView = document.defaultView; 20035 20036 if (defaultView) { 20037 // Remove camelcase 20038 name = name.replace(/[A-Z]/g, function(a) { 20039 return '-' + a; 20040 }); 20041 20042 return defaultView.getComputedStyle(elm, null).getPropertyValue(name); 20043 } 20044 20045 return elm.currentStyle[name]; 20046 } 20047 20048 function getSide(name) { 20049 var val = parseFloat(getStyle(name), 10); 20050 20051 return isNaN(val) ? 0 : val; 20052 } 20053 20054 return { 20055 top: getSide(prefix + "TopWidth"), 20056 right: getSide(prefix + "RightWidth"), 20057 bottom: getSide(prefix + "BottomWidth"), 20058 left: getSide(prefix + "LeftWidth") 20059 }; 20060 }, 20061 20062 /** 20063 * Initializes the current controls layout rect. 20064 * This will be executed by the layout managers to determine the 20065 * default minWidth/minHeight etc. 20066 * 20067 * @method initLayoutRect 20068 * @return {Object} Layout rect instance. 20069 */ 20070 initLayoutRect: function() { 20071 var self = this, settings = self.settings, borderBox, layoutRect; 20072 var elm = self.getEl(), width, height, minWidth, minHeight, autoResize; 20073 var startMinWidth, startMinHeight, initialSize; 20074 20075 // Measure the current element 20076 borderBox = self._borderBox = self._borderBox || self.measureBox(elm, 'border'); 20077 self._paddingBox = self._paddingBox || self.measureBox(elm, 'padding'); 20078 self._marginBox = self._marginBox || self.measureBox(elm, 'margin'); 20079 initialSize = DomUtils.getSize(elm); 20080 20081 // Setup minWidth/minHeight and width/height 20082 startMinWidth = settings.minWidth; 20083 startMinHeight = settings.minHeight; 20084 minWidth = startMinWidth || initialSize.width; 20085 minHeight = startMinHeight || initialSize.height; 20086 width = settings.width; 20087 height = settings.height; 20088 autoResize = settings.autoResize; 20089 autoResize = typeof(autoResize) != "undefined" ? autoResize : !width && !height; 20090 20091 width = width || minWidth; 20092 height = height || minHeight; 20093 20094 var deltaW = borderBox.left + borderBox.right; 20095 var deltaH = borderBox.top + borderBox.bottom; 20096 20097 var maxW = settings.maxWidth || 0xFFFF; 20098 var maxH = settings.maxHeight || 0xFFFF; 20099 20100 // Setup initial layout rect
20101 self._layoutRect = layoutRect = { 20102 x: settings.x || 0, 20103 y: settings.y || 0, 20104 w: width, 20105 h: height, 20106 deltaW: deltaW, 20107 deltaH: deltaH, 20108 contentW: width - deltaW, 20109 contentH: height - deltaH, 20110 innerW: width - deltaW, 20111 innerH: height - deltaH, 20112 startMinWidth: startMinWidth || 0, 20113 startMinHeight: startMinHeight || 0, 20114 minW: Math.min(minWidth, maxW), 20115 minH: Math.min(minHeight, maxH), 20116 maxW: maxW, 20117 maxH: maxH, 20118 autoResize: autoResize, 20119 scrollW: 0 20120 }; 20121 20122 self._lastLayoutRect = {}; 20123 20124 return layoutRect; 20125 }, 20126 20127 /** 20128 * Getter/setter for the current layout rect. 20129 * 20130 * @method layoutRect 20131 * @param {Object} [newRect] Optional new layout rect. 20132 * @return {tinymce.ui.Control/Object} Current control or rect object. 20133 */ 20134 layoutRect: function(newRect) { 20135 var self = this, curRect = self._layoutRect, lastLayoutRect, size, deltaWidth, deltaHeight, undef, repaintControls; 20136 20137 // Initialize default layout rect 20138 if (!curRect) { 20139 curRect = self.initLayoutRect(); 20140 } 20141 20142 // Set new rect values 20143 if (newRect) { 20144 // Calc deltas between inner and outer sizes 20145 deltaWidth = curRect.deltaW; 20146 deltaHeight = curRect.deltaH; 20147 20148 // Set x position 20149 if (newRect.x !== undef) { 20150 curRect.x = newRect.x; 20151 } 20152 20153 // Set y position 20154 if (newRect.y !== undef) { 20155 curRect.y = newRect.y; 20156 } 20157 20158 // Set minW 20159 if (newRect.minW !== undef) { 20160 curRect.minW = newRect.minW; 20161 } 20162 20163 // Set minH 20164 if (newRect.minH !== undef) { 20165 curRect.minH = newRect.minH; 20166 } 20167 20168 // Set new width and calculate inner width 20169 size = newRect.w; 20170 if (size !== undef) { 20171 size = size < curRect.minW ? curRect.minW : size; 20172 size = size > curRect.maxW ? curRect.maxW : size; 20173 curRect.w = size; 20174 curRect.innerW = size - deltaWidth; 20175 } 20176 20177 // Set new height and calculate inner height 20178 size = newRect.h; 20179 if (size !== undef) { 20180 size = size < curRect.minH ? curRect.minH : size; 20181 size = size > curRect.maxH ? curRect.maxH : size; 20182 curRect.h = size; 20183 curRect.innerH = size - deltaHeight; 20184 } 20185 20186 // Set new inner width and calculate width 20187 size = newRect.innerW; 20188 if (size !== undef) { 20189 size = size < curRect.minW - deltaWidth ? curRect.minW - deltaWidth : size; 20190 size = size > curRect.maxW - deltaWidth ? curRect.maxW - deltaWidth : size; 20191 curRect.innerW = size; 20192 curRect.w = size + deltaWidth; 20193 } 20194 20195 // Set new height and calculate inner height 20196 size = newRect.innerH; 20197 if (size !== undef) { 20198 size = size < curRect.minH - deltaHeight ? curRect.minH - deltaHeight : size; 20199 size = size > curRect.maxH - deltaHeight ? curRect.maxH - deltaHeight : size; 20200 curRect.innerH = size; 20201 curRect.h = size + deltaHeight; 20202 } 20203 20204 // Set new contentW 20205 if (newRect.contentW !== undef) { 20206 curRect.contentW = newRect.contentW; 20207 } 20208 20209 // Set new contentH 20210 if (newRect.contentH !== undef) { 20211 curRect.contentH = newRect.contentH; 20212 } 20213 20214 // Compare last layout rect with the current one to see if we need to repaint or not 20215 lastLayoutRect = self._lastLayoutRect; 20216 if (lastLayoutRect.x !== curRect.x || lastLayoutRect.y !== curRect.y || 20217 lastLayoutRect.w !== curRect.w || lastLayoutRect.h !== curRect.h) { 20218 repaintControls = Control.repaintControls; 20219 20220 if (repaintControls) { 20221 if (repaintControls.map && !repaintControls.map[self._id]) { 20222 repaintControls.push(self); 20223 repaintControls.map[self._id] = true; 20224 } 20225 } 20226 20227 lastLayoutRect.x = curRect.x; 20228 lastLayoutRect.y = curRect.y; 20229 lastLayoutRect.w = curRect.w; 20230 lastLayoutRect.h = curRect.h; 20231 } 20232 20233 return self; 20234 } 20235 20236 return curRect; 20237 }, 20238 20239 /** 20240 * Repaints the control after a layout operation. 20241 * 20242 * @method repaint 20243 */ 20244 repaint: function() { 20245 var self = this, style, bodyStyle, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect, round; 20246 20247 // Use Math.round on all values on IE < 9 20248 round = !document.createRange ? Math.round : function(value) { 20249 return value; 20250 }; 20251 20252 style = self.getEl().style; 20253 rect = self._layoutRect; 20254 lastRepaintRect = self._lastRepaintRect || {}; 20255 20256 borderBox = self._borderBox; 20257 borderW = borderBox.left + borderBox.right; 20258 borderH = borderBox.top + borderBox.bottom; 20259 20260 if (rect.x !== lastRepaintRect.x) { 20261 style.left = round(rect.x) + 'px'; 20262 lastRepaintRect.x = rect.x; 20263 } 20264 20265 if (rect.y !== lastRepaintRect.y) { 20266 style.top = round(rect.y) + 'px'; 20267 lastRepaintRect.y = rect.y; 20268 } 20269 20270 if (rect.w !== lastRepaintRect.w) { 20271 style.width = round(rect.w - borderW) + 'px'; 20272 lastRepaintRect.w = rect.w; 20273 } 20274 20275 if (rect.h !== lastRepaintRect.h) { 20276 style.height = round(rect.h - borderH) + 'px'; 20277 lastRepaintRect.h = rect.h; 20278 } 20279 20280 // Update body if needed 20281 if (self._hasBody && rect.innerW !== lastRepaintRect.innerW) { 20282 bodyStyle = self.getEl('body').style; 20283 bodyStyle.width = round(rect.innerW) + 'px'; 20284 lastRepaintRect.innerW = rect.innerW; 20285 } 20286 20287 if (self._hasBody && rect.innerH !== lastRepaintRect.innerH) { 20288 bodyStyle = bodyStyle || self.getEl('body').style; 20289 bodyStyle.height = round(rect.innerH) + 'px'; 20290 lastRepaintRect.innerH = rect.innerH; 20291 } 20292 20293 self._lastRepaintRect = lastRepaintRect; 20294 self.fire('repaint', {}, false); 20295 }, 20296 20297 /** 20298 * Binds a callback to the specified event. This event can both be 20299 * native browser events like "click" or custom ones like PostRender. 20300 * 20301 * The callback function will be passed a DOM event like object that enables yout do stop propagation. 20302 * 20303 * @method on 20304 * @param {String} name Name of the event to bind. For example "click". 20305 * @param {String/function} callback Callback function to execute ones the event occurs. 20306 * @return {tinymce.ui.Control} Current control object. 20307 */ 20308 on: function(name, callback) { 20309 var self = this, bindings, handlers, names, i; 20310
20311 function resolveCallbackName(name) { 20312 var callback, scope; 20313 20314 return function(e) { 20315 if (!callback) { 20316 self.parents().each(function(ctrl) { 20317 var callbacks = ctrl.settings.callbacks; 20318 20319 if (callbacks && (callback = callbacks[name])) { 20320 scope = ctrl; 20321 return false; 20322 } 20323 }); 20324 } 20325 20326 return callback.call(scope, e); 20327 }; 20328 } 20329 20330 if (callback) { 20331 if (typeof(callback) == 'string') { 20332 callback = resolveCallbackName(callback); 20333 } 20334 20335 names = name.toLowerCase().split(' '); 20336 i = names.length; 20337 while (i--) { 20338 name = names[i]; 20339 20340 bindings = self._bindings; 20341 if (!bindings) { 20342 bindings = self._bindings = {}; 20343 } 20344 20345 handlers = bindings[name]; 20346 if (!handlers) { 20347 handlers = bindings[name] = []; 20348 } 20349 20350 handlers.push(callback); 20351 20352 if (nativeEvents[name]) { 20353 if (!self._nativeEvents) { 20354 self._nativeEvents = {name: true}; 20355 } else { 20356 self._nativeEvents[name] = true; 20357 } 20358 20359 if (self._rendered) { 20360 self.bindPendingEvents(); 20361 } 20362 } 20363 } 20364 } 20365 20366 return self; 20367 }, 20368 20369 /** 20370 * Unbinds the specified event and optionally a specific callback. If you omit the name 20371 * parameter all event handlers will be removed. If you omit the callback all event handles 20372 * by the specified name will be removed. 20373 * 20374 * @method off 20375 * @param {String} [name] Name for the event to unbind. 20376 * @param {function} [callback] Callback function to unbind. 20377 * @return {mxex.ui.Control} Current control object. 20378 */ 20379 off: function(name, callback) { 20380 var self = this, i, bindings = self._bindings, handlers, bindingName, names, hi; 20381 20382 if (bindings) { 20383 if (name) { 20384 names = name.toLowerCase().split(' '); 20385 i = names.length; 20386 while (i--) { 20387 name = names[i]; 20388 handlers = bindings[name]; 20389 20390 // Unbind all handlers 20391 if (!name) { 20392 for (bindingName in bindings) { 20393 bindings[bindingName].length = 0; 20394 } 20395 20396 return self; 20397 } 20398 20399 if (handlers) { 20400 // Unbind all by name 20401 if (!callback) { 20402 handlers.length = 0; 20403 } else { 20404 // Unbind specific ones 20405 hi = handlers.length; 20406 while (hi--) { 20407 if (handlers[hi] === callback) { 20408 handlers.splice(hi, 1); 20409 } 20410 } 20411 } 20412 } 20413 } 20414 } else { 20415 self._bindings = []; 20416 } 20417 } 20418 20419 return self; 20420 }, 20421 20422 /** 20423 * Fires the specified event by name and arguments on the control. This will execute all 20424 * bound event handlers. 20425 * 20426 * @method fire 20427 * @param {String} name Name of the event to fire. 20428 * @param {Object} [args] Arguments to pass to the event. 20429 * @param {Boolean} [bubble] Value to control bubbeling. Defaults to true. 20430 * @return {Object} Current arguments object. 20431 */ 20432 fire: function(name, args, bubble) { 20433 var self = this, i, l, handlers, parentCtrl; 20434 20435 name = name.toLowerCase(); 20436 20437 // Dummy function that gets replaced on the delegation state functions 20438 function returnFalse() { 20439 return false; 20440 } 20441 20442 // Dummy function that gets replaced on the delegation state functions 20443 function returnTrue() { 20444 return true; 20445 } 20446 20447 // Setup empty object if args is omited 20448 args = args || {}; 20449 20450 // Stick type into event object 20451 if (!args.type) { 20452 args.type = name; 20453 } 20454 20455 // Stick control into event 20456 if (!args.control) { 20457 args.control = self; 20458 } 20459 20460 // Add event delegation methods if they are missing 20461 if (!args.preventDefault) { 20462 // Add preventDefault method 20463 args.preventDefault = function() { 20464 args.isDefaultPrevented = returnTrue; 20465 }; 20466 20467 // Add stopPropagation 20468 args.stopPropagation = function() { 20469 args.isPropagationStopped = returnTrue; 20470 }; 20471 20472 // Add stopImmediatePropagation 20473 args.stopImmediatePropagation = function() { 20474 args.isImmediatePropagationStopped = returnTrue; 20475 }; 20476 20477 // Add event delegation states 20478 args.isDefaultPrevented = returnFalse; 20479 args.isPropagationStopped = returnFalse; 20480 args.isImmediatePropagationStopped = returnFalse; 20481 } 20482 20483 if (self._bindings) { 20484 handlers = self._bindings[name]; 20485 20486 if (handlers) { 20487 for (i = 0, l = handlers.length; i < l; i++) { 20488 // Execute callback and break if the callback returns a fal
20488se 20489 if (!args.isImmediatePropagationStopped() && handlers[i].call(self, args) === false) { 20490 break; 20491 } 20492 } 20493 } 20494 } 20495 20496 // Bubble event up to parent controls 20497 if (bubble !== false) { 20498 parentCtrl = self.parent(); 20499 while (parentCtrl && !args.isPropagationStopped()) { 20500 parentCtrl.fire(name, args, false); 20501 parentCtrl = parentCtrl.parent(); 20502 } 20503 } 20504 20505 return args; 20506 }, 20507 20508 /** 20509 * Returns true/false if the specified event has any listeners. 20510 * 20511 * @method hasEventListeners 20512 * @param {String} name Name of the event to check for. 20513 * @return {Boolean} True/false state if the event has listeners. 20514 */ 20515 hasEventListeners: function(name) { 20516 return name in this._bindings; 20517 }, 20518 20519 /** 20520 * Returns a control collection with all parent controls. 20521 * 20522 * @method parents 20523 * @param {String} selector Optional selector expression to find parents. 20524 * @return {tinymce.ui.Collection} Collection with all parent controls. 20525 */ 20526 parents: function(selector) { 20527 var ctrl = this, parents = new Collection(); 20528 20529 // Add each parent to collection 20530 for (ctrl = ctrl.parent(); ctrl; ctrl = ctrl.parent()) { 20531 parents.add(ctrl); 20532 } 20533 20534 // Filter away everything that doesn't match the selector 20535 if (selector) { 20536 parents = parents.filter(selector); 20537 } 20538 20539 return parents; 20540 }, 20541 20542 /** 20543 * Returns the control next to the current control. 20544 * 20545 * @method next 20546 * @return {tinymce.ui.Control} Next control instance. 20547 */ 20548 next: function() { 20549 var parentControls = this.parent().items(); 20550 20551 return parentControls[parentControls.indexOf(this) + 1]; 20552 }, 20553 20554 /** 20555 * Returns the control previous to the current control. 20556 * 20557 * @method prev 20558 * @return {tinymce.ui.Control} Previous control instance. 20559 */ 20560 prev: function() { 20561 var parentControls = this.parent().items(); 20562 20563 return parentControls[parentControls.indexOf(this) - 1]; 20564 }, 20565 20566 /** 20567 * Find the common ancestor for two control instances. 20568 * 20569 * @method findCommonAncestor 20570 * @param {tinymce.ui.Control} ctrl1 First control. 20571 * @param {tinymce.ui.Control} ctrl2 Second control. 20572 * @return {tinymce.ui.Control} Ancestor control instance. 20573 */ 20574 findCommonAncestor: function(ctrl1, ctrl2) { 20575 var parentCtrl; 20576 20577 while (ctrl1) { 20578 parentCtrl = ctrl2; 20579 20580 while (parentCtrl && ctrl1 != parentCtrl) { 20581 parentCtrl = parentCtrl.parent(); 20582 } 20583 20584 if (ctrl1 == parentCtrl) { 20585 break; 20586 } 20587 20588 ctrl1 = ctrl1.parent(); 20589 } 20590 20591 return ctrl1; 20592 }, 20593 20594 /** 20595 * Returns true/false if the specific control has the specific class. 20596 * 20597 * @method hasClass 20598 * @param {String} cls Class to check for. 20599 * @param {String} [group] Sub element group name. 20600 * @return {Boolean} True/false if the control has the specified class. 20601 */ 20602 hasClass: function(cls, group) { 20603 var classes = this._classes[group || 'control']; 20604 20605 cls = this.classPrefix + cls; 20606 20607 return classes && !!classes.map[cls]; 20608 }, 20609 20610 /** 20611 * Adds the specified class to the control 20612 * 20613 * @method addClass 20614 * @param {String} cls Class to check for. 20615 * @param {String} [group] Sub element group name. 20616 * @return {tinymce.ui.Control} Current control object. 20617 */ 20618 addClass: function(cls, group) { 20619 var self = this, classes, elm; 20620 20621 cls = this.classPrefix + cls; 20622 classes = self._classes[group || 'control']; 20623 20624 if (!classes) { 20625 classes = []; 20626 classes.map = {}; 20627 self._classes[group || 'control'] = classes; 20628 } 20629 20630 if (!classes.map[cls]) { 20631 classes.map[cls] = cls; 20632 classes.push(cls); 20633 20634 if (self._rendered) { 20635 elm = self.getEl(group); 20636 20637 if (elm) { 20638 elm.className = classes.join(' '); 20639 } 20640 } 20641 } 20642 20643 return self; 20644 }, 20645 20646 /** 20647 * Removes the specified class from the control. 20648 * 20649 * @method removeClass 20650 * @param {String} cls Class to remove. 20651 * @param {String} [group] Sub element group name. 20652 * @return {tinymce.ui.Control} Current control object. 20653 */ 20654 removeClass: function(cls, group) { 20655 var self = this, classes, i, elm; 20656 20657 cls = this.classPrefix + cls; 20658 classes = self._classes[group || 'control']; 20659 if (classes && classes.map[cls]) { 20660 delete classes.map[cls]; 20661 20662 i = classes.length; 20663 while (i--) { 20664 if (classes[i] === cls) { 20665 classes.splice(i, 1); 20666 } 20667 } 20668 } 20669 20670 if (self._rendered) { 20671 elm = self.getEl(group); 20672 20673 if (elm) { 20674 elm.className = classes.join(' '); 20675 } 20676 } 20677 20678 return self; 20679 }, 20680 20681 /** 20682 * Toggles the specified class on the control. 20683 * 20684 * @method toggleClass 20685 * @param {String} cls Class to remove. 20686 * @param {Boolean} state True/false state to add/remove class. 20687 * @param {String} [group] Sub element group name. 20688 * @return {tinymce.ui.Control} Current control object. 20689 */ 20690 toggleClass: function(cls, state, group) { 20691 var self = this; 20692 20693 if (state) { 20694 self.addClass(cls, group); 20695 } else { 20696 self.removeClass(cls, group); 20697 } 20698 20699 return self; 20700 }, 20701 20702 /** 20703 * Returns the class string for the specified group name. 20704 * 20705 * @method classes 20706 * @param {String} [group] Group to get clases by. 20707 * @return {String} Classes for the specified group. 20708 */ 20709 classes: function(group) { 20710 var classes = this._classes[group || 'control']; 20711 20712 return classes ? classes.join(' ') : ''; 20713 }, 20714 20715 /** 20716 * Sets the inner HTML of the control element. 20717 * 20718 * @method innerHtml 20719 * @param {String} html Html string to set as inner html. 20720 * @return {tinymce.ui.Control} Current control object. 20721 */ 20722 innerHtml: function(html) { 20723 DomUtils.innerHtml(this.getEl(), html); 20724 return this; 20725 }, 20726 20727 /** 20728 * Returns the control DOM element or sub element. 20729 * 20730 * @method getEl 20731 * @param {String} [suffix] Suffix to get element by. 20732 * @param {Boolean} [dropCache] True if the cache for the element should be dropped. 20733 * @return {Element} HTML DOM element for the current control or it's children. 20734 */ 20735 getEl: function(suffix, dropCache) { 20736 var elm, id = suffix ? this._id + '-' + suffix : this._id; 20737 20738 elm = elementIdCache[id] = (dropCache === true ? null : elementIdCache[id]) || DomUtils.get(id); 20739 20740 return elm; 20741 }, 20742 20743 /** 20744 * Sets/gets the visible for the control. 20745 * 20746 * @method visible 20747 * @param {Boolean} state Value to set to control. 20748 * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get. 20749 */ 20750 visible: function(state) { 20751 var self = this, parentCtrl; 20752 20753 if (typeof(state) !== "undefined") { 20754 if (self._visible !== state) { 20755 if (self._rendered) { 20756 self.getEl().style.display = state ? '' : 'none'; 20757 } 20758 20759 self._visible = state; 20760 20761 // Parent container needs to reflow 20762 parentCtrl = self.parent(); 20763 if (parentCtrl) { 20764 parentCtrl._lastRect = null; 20765 } 20766 20767 self.fire(state ? 'show' : 'hide'); 20768 } 20769 20770 return self; 20771 } 20772 20773 return self._visible; 20774 }, 20775 20776 /** 20777 * Sets the visible state to true. 20778 * 20779 * @method show 20780 * @return {tinymce.ui.Control} Current control instance. 20781 */ 20782 show: function() { 20783 return this.visible(true); 20784 }, 20785 20786 /** 20787 * Sets the visible state to false. 20788 * 20789 * @method hide 20790 * @return {tinymce.ui.Control} Current control instance. 20791 */ 20792 hide: function() { 20793 return this.visible(false); 20794 }, 20795 20796 /** 20797 * Focuses the current control. 20798 *
20799 * @method focus 20800 * @return {tinymce.ui.Control} Current control instance. 20801 */ 20802 focus: function() { 20803 try { 20804 this.getEl().focus(); 20805 } catch (ex) { 20806 // Ignore IE error 20807 } 20808 20809 return this; 20810 }, 20811 20812 /** 20813 * Blurs the current control. 20814 * 20815 * @method blur 20816 * @return {tinymce.ui.Control} Current control instance. 20817 */ 20818 blur: function() { 20819 this.getEl().blur(); 20820 20821 return this; 20822 }, 20823 20824 /** 20825 * Sets the specified aria property. 20826 * 20827 * @method aria 20828 * @param {String} name Name of the aria property to set. 20829 * @param {String} value Value of the aria property. 20830 * @return {tinymce.ui.Control} Current control instance. 20831 */ 20832 aria: function(name, value) { 20833 var self = this, elm = self.getEl(); 20834 20835 if (typeof(value) === "undefined") { 20836 return self._aria[name]; 20837 } else { 20838 self._aria[name] = value; 20839 } 20840 20841 if (self._rendered) { 20842 if (name == 'label') { 20843 elm.setAttribute('aria-labeledby', self._id); 20844 } 20845 20846 elm.setAttribute(name == 'role' ? name : 'aria-' + name, value); 20847 } 20848 20849 return self; 20850 }, 20851 20852 /** 20853 * Encodes the specified string with HTML entities. It will also 20854 * translate the string to different languages. 20855 * 20856 * @method encode 20857 * @param {String/Object/Array} text Text to entity encode. 20858 * @param {Boolean} [translate=true] False if the contents shouldn't be translated. 20859 * @return {String} Encoded and possible traslated string. 20860 */ 20861 encode: function(text, translate) { 20862 if (translate !== false && Control.translate) { 20863 text = Control.translate(text); 20864 } 20865 20866 return (text || '').replace(/[&<>"]/g, function(match) { 20867 return '&#' + match.charCodeAt(0) + ';'; 20868 }); 20869 }, 20870 20871 /** 20872 * Adds items before the current control. 20873 * 20874 * @method before 20875 * @param {Array/tinymce.ui.Collection} items Array of items to prepend before this control. 20876 * @return {tinymce.ui.Control} Current control instance. 20877 */ 20878 before: function(items) { 20879 var self = this, parent = self.parent(); 20880 20881 if (parent) { 20882 parent.insert(items, parent.items().indexOf(self), true); 20883 } 20884 20885 return self; 20886 }, 20887 20888 /** 20889 * Adds items after the current control. 20890 * 20891 * @method after 20892 * @param {Array/tinymce.ui.Collection} items Array of items to append after this control. 20893 * @return {tinymce.ui.Control} Current control instance. 20894 */ 20895 after: function(items) { 20896 var self = this, parent = self.parent(); 20897 20898 if (parent) { 20899 parent.insert(items, parent.items().indexOf(self)); 20900 } 20901 20902 return self; 20903 }, 20904 20905 /** 20906 * Removes the current control from DOM and from UI collections. 20907 * 20908 * @method remove 20909 * @return {tinymce.ui.Control} Current control instance. 20910 */ 20911 remove: function() { 20912 var self = this, elm = self.getEl(), parent = self.parent(), newItems, i; 20913 20914 if (self.items) { 20915 var controls = self.items().toArray(); 20916 i = controls.length; 20917 while (i--) { 20918 controls[i].remove(); 20919 } 20920 } 20921 20922 if (parent && parent.items) { 20923 newItems = []; 20924 20925 parent.items().each(function(item) { 20926 if (item !== self) { 20927 newItems.push(item); 20928 } 20929 }); 20930 20931 parent.items().set(newItems); 20932 parent._lastRect = null; 20933 } 20934 20935 if (self._eventsRoot && self._eventsRoot == self) { 20936 DomUtils.off(elm); 20937 } 20938 20939 delete Control.controlIdLookup[self._id]; 20940 delete elementIdCache[self._id]; 20941 20942 if (elm && elm.parentNode) { 20943 var nodes = elm.getElementsByTagName('*'); 20944 20945 i = nodes.length; 20946 while (i--) { 20947 delete elementIdCache[nodes[i].id]; 20948 } 20949 20950 elm.parentNode.removeChild(elm); 20951 } 20952 20953 return self; 20954 }, 20955 20956 /** 20957 * Renders the control before the specified element. 20958 * 20959 * @method renderBefore 20960 * @param {Element} elm Element to render before. 20961 * @return {tinymce.ui.Control} Current control instance. 20962 */ 20963 renderBefore: function(elm) { 20964 var self = this; 20965 20966 elm.parentNode.insertBefore(DomUtils.createFragment(self.renderHtml()), elm); 20967 self.postRender(); 20968 20969 return self; 20970 }, 20971 20972 /** 20973 * Renders the control to the specified element. 20974 * 20975 * @method renderBefore 20976 * @param {Element} elm Element to render to. 20977 * @return {tinymce.ui.Control} Current control instance. 20978 */ 20979 renderTo: function(elm) { 20980 var self = this; 20981 20982 elm = elm || self.getContainerElm(); 20983 elm.appendChild(DomUtils.createFragment(self.renderHtml())); 20984 self.postRender(); 20985 20986 return self; 20987 }, 20988 20989 /** 20990 * Post render method. Called after the control has been rendered to the target. 20991 * 20992 * @method postRender 20993 * @return {tinymce.ui.Control} Current control instance. 20994 */ 20995 postRender: function() { 20996 var self = this, settings = self.settings, elm, box, parent, name, parentEventsRoot; 20997 20998 // Bind on<event> settings 20999 for (name in settings) { 21000 if (name.indexOf("on") === 0) { 21001 self.on(name.substr(2), settings[name]); 21002 } 21003 } 21004 21005 if (self._eventsRoot) { 21006 for (parent = self.parent(); !parentEventsRoot && parent; parent = parent.parent()) { 21007 parentEventsRoot = parent._eventsRoot; 21008 } 21009 21010 if (parentEventsRoot) { 21011 for (name in parentEventsRoot._nativeEvents) { 21012 self._nativeEvents[name] = true; 21013 } 21014 } 21015 } 21016 21017 self.bindPendingEvents(); 21018 21019 if (settings.style) { 21020 elm = self.getEl(); 21021 if (elm) { 21022 elm.setAttribute('style', settings.style); 21023 elm.style.cssText = settings.style; 21024 } 21025 } 21026 21027 if (!self._visible) { 21028 DomUtils.css(self.getEl(), 'display', 'none'); 21029 } 21030 21031 if (self.settings.border) { 21032 box = self.borderBox(); 21033 DomUtils.css(self.getEl(), { 21034 'border-top-width': box.top, 21035 'border-right-width': box.right, 21036 'border-bottom-width': box.bottom, 21037 'border-left-width': box.left 21038 }); 21039 } 21040 21041 // Add instance to lookup 21042 Control.controlIdLookup[self._id] = self; 21043 21044 for (var key in self._aria) { 21045 self.aria(key, self._aria[key]); 21046 } 21047 21048 self.fire('postrender', {}, false); 21049 }, 21050 21051 /** 21052 * Scrolls the current control into view. 21053 * 21054 * @method scrollIntoView 21055 * @param {String} align Alignment in view top|center|bottom. 21056 * @return {tinymce.ui.Control} Current control instance. 21057 */ 21058 scrollIntoView: function(align) { 21059 function getOffset(elm, rootElm) { 21060 var x, y, parent = elm; 21061 21062 x = y = 0; 21063 while (parent && parent != rootElm && parent.nodeType) { 21064 x += parent.offsetLeft || 0; 21065 y += parent.offsetTop || 0; 21066 parent = parent.offsetParent; 21067 } 21068 21069 return {x: x, y: y}; 21070 } 21071 21072 var elm = this.getEl(), parentElm = elm.parentNode; 21073 var x, y, width, height, parentWidth, parentHeight; 21074 var pos = getOffset(elm, parentElm); 21075 21076 x = pos.x; 21077 y = pos.y; 21078 width = elm.offsetWidth; 21079 height = elm.offsetHeight; 21080 parentWidth = parentElm.clientWidth; 21081 parentHeight = parentElm.clientHeight; 21082
21083 if (align == "end") { 21084 x -= parentWidth - width; 21085 y -= parentHeight - height; 21086 } else if (align == "center") { 21087 x -= (parentWidth / 2) - (width / 2); 21088 y -= (parentHeight / 2) - (height / 2); 21089 } 21090 21091 parentElm.scrollLeft = x; 21092 parentElm.scrollTop = y; 21093 21094 return this; 21095 }, 21096 21097 /** 21098 * Binds pending DOM events. 21099 * 21100 * @private 21101 */ 21102 bindPendingEvents: function() { 21103 var self = this, i, l, parents, eventRootCtrl, nativeEvents, name; 21104 21105 function delegate(e) { 21106 var control = self.getParentCtrl(e.target); 21107 21108 if (control) { 21109 control.fire(e.type, e); 21110 } 21111 } 21112 21113 function mouseLeaveHandler() { 21114 var ctrl = eventRootCtrl._lastHoverCtrl; 21115 21116 if (ctrl) { 21117 ctrl.fire("mouseleave", {target: ctrl.getEl()}); 21118 21119 ctrl.parents().each(function(ctrl) { 21120 ctrl.fire("mouseleave", {target: ctrl.getEl()}); 21121 }); 21122 21123 eventRootCtrl._lastHoverCtrl = null; 21124 } 21125 } 21126 21127 function mouseEnterHandler(e) { 21128 var ctrl = self.getParentCtrl(e.target), lastCtrl = eventRootCtrl._lastHoverCtrl, idx = 0, i, parents, lastParents; 21129 21130 // Over on a new control 21131 if (ctrl !== lastCtrl) { 21132 eventRootCtrl._lastHoverCtrl = ctrl; 21133 21134 parents = ctrl.parents().toArray().reverse(); 21135 parents.push(ctrl); 21136 21137 if (lastCtrl) { 21138 lastParents = lastCtrl.parents().toArray().reverse(); 21139 lastParents.push(lastCtrl); 21140 21141 for (idx = 0; idx < lastParents.length; idx++) { 21142 if (parents[idx] !== lastParents[idx]) { 21143 break; 21144 } 21145 } 21146 21147 for (i = lastParents.length - 1; i >= idx; i--) { 21148 lastCtrl = lastParents[i]; 21149 lastCtrl.fire("mouseleave", { 21150 target : lastCtrl.getEl() 21151 }); 21152 } 21153 } 21154 21155 for (i = idx; i < parents.length; i++) { 21156 ctrl = parents[i]; 21157 ctrl.fire("mouseenter", { 21158 target : ctrl.getEl() 21159 }); 21160 } 21161 } 21162 } 21163 21164 function fixWheelEvent(e) { 21165 e.preventDefault(); 21166 21167 if (e.type == "mousewheel") { 21168 e.deltaY = - 1/40 * e.wheelDelta; 21169 21170 if (e.wheelDeltaX) { 21171 e.deltaX = -1/40 * e.wheelDeltaX; 21172 } 21173 } else { 21174 e.deltaX = 0; 21175 e.deltaY = e.detail; 21176 } 21177 21178 e = self.fire("wheel", e); 21179 } 21180 21181 self._rendered = true; 21182 21183 nativeEvents = self._nativeEvents; 21184 if (nativeEvents) { 21185 // Find event root element if it exists 21186 parents = self.parents().toArray(); 21187 parents.unshift(self); 21188 for (i = 0, l = parents.length; !eventRootCtrl && i < l; i++) { 21189 eventRootCtrl = parents[i]._eventsRoot; 21190 } 21191 21192 // Event root wasn't found the use the root control 21193 if (!eventRootCtrl) { 21194 eventRootCtrl = parents[parents.length - 1] || self; 21195 } 21196 21197 // Set the eventsRoot property on children that didn't have it 21198 self._eventsRoot = eventRootCtrl; 21199 for (l = i, i = 0; i < l; i++) { 21200 parents[i]._eventsRoot = eventRootCtrl; 21201 } 21202 21203 // Bind native event delegates 21204 for (name in nativeEvents) { 21205 if (!nativeEvents) { 21206 return false; 21207 } 21208 21209 if (name === "wheel" && !hasWheelEventSupport) { 21210 if (hasMouseWheelEventSupport) { 21211 DomUtils.on(self.getEl(), "mousewheel", fixWheelEvent); 21212 } else { 21213 DomUtils.on(self.getEl(), "DOMMouseScroll", fixWheelEvent); 21214 } 21215 21216 continue; 21217 } 21218 21219 // Special treatment for mousenter/mouseleave since these doesn't bubble 21220 if (name === "mouseenter" || name === "mouseleave") { 21221 // Fake mousenter/mouseleave 21222 if (!eventRootCtrl._hasMouseEnter) { 21223 DomUtils.on(eventRootCtrl.getEl(), "mouseleave", mouseLeaveHandler); 21224 DomUtils.on(eventRootCtrl.getEl(), "mouseover", mouseEnterHandler); 21225 eventRootCtrl._hasMouseEnter = 1; 21226 } 21227 } else if (!eventRootCtrl[name]) { 21228 DomUtils.on(eventRootCtrl.getEl(), name, delegate); 21229 eventRootCtrl[name] = true; 21230 } 21231 21232 // Remove the event once it's bound 21233 nativeEvents[name] = false; 21234 } 21235 } 21236 }, 21237 21238 /** 21239 * Reflows the current control and it's parents. 21240 * This should be used after you for example append children to the current control so 21241 * that the layout managers know that they need to reposition everything. 21242 * 21243 * @example 21244 * container.append({type: 'button', text: 'My button'}).reflow(); 21245 * 21246 * @method reflow 21247 * @return {tinymce.ui.Control} Current control instance. 21248 */ 21249 reflow: function() { 21250 this.repaint(); 21251 21252 return this; 21253 } 21254 21255 /** 21256 * Sets/gets the parent container for the control. 21257 * 21258 * @method parent 21259 * @param {tinymce.ui.Container} parent Optional parent to set. 21260 * @return {tinymce.ui.Control} Parent control or the current control on a set action. 21261 */ 21262 // parent: function(parent) {} -- Generated 21263 21264 /** 21265 * Sets/gets the text for the control. 21266 * 21267 * @method text 21268 * @param {String} value Value to set to control. 21269 * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get. 21270 */ 21271 // text: function(value) {} -- Generated 21272 21273 /** 21274 * Sets/gets the width for the control. 21275 * 21276 * @method width 21277 * @param {Number} value Value to set to control. 21278 * @return {Number/tinymce.ui.Control} Current control on a set operation or current value on a get. 21279 */ 21280 // width: function(value) {} -- Generated 21281 21282 /** 21283 * Sets/gets the height for the control. 21284 * 21285 * @method height 21286 * @param {Number} value Value to set to control. 21287 * @return {Number/tinymce.ui.Control} Current control on a set operation or current value on a get. 21288 */ 21289 // height: function(value) {} -- Generated 21290 21291 /** 21292 * Sets/gets the disabled state on the control. 21293 * 21294 * @method disabled 21295 * @param {Boolean} state Value to set to control. 21296 * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get. 21297 */ 21298 // disabled: function(state) {} -- Generated 21299 21300 /** 21301 * Sets/gets the active for the control. 21302 * 21303 * @method active 21304 * @param {Boolean} state Value to set to control. 21305 * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get. 21306 */ 21307 // active: function(state) {} -- Generated 21308 21309 /** 21310 * Sets/gets the name for the control. 21311 *
21312 * @method name 21313 * @param {String} value Value to set to control. 21314 * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get. 21315 */ 21316 // name: function(value) {} -- Generated 21317 21318 /** 21319 * Sets/gets the title for the control. 21320 * 21321 * @method title 21322 * @param {String} value Value to set to control. 21323 * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get. 21324 */ 21325 // title: function(value) {} -- Generated 21326 }); 21327 21328 return Control; 21329}); 21330 21331// Included from: js/tinymce/classes/ui/Factory.js 21332 21333/** 21334 * Factory.js 21335 * 21336 * Copyright, Moxiecode Systems AB 21337 * Released under LGPL License. 21338 * 21339 * License: http://www.tinymce.com/license 21340 * Contributing: http://www.tinymce.com/contributing 21341 */ 21342 21343/*global tinymce:true */ 21344 21345/** 21346 * This class is a factory for control instances. This enables you 21347 * to create instances of controls without having to require the UI controls directly. 21348 * 21349 * It also allow you to override or add new control types. 21350 * 21351 * @class tinymce.ui.Factory 21352 */ 21353define("tinymce/ui/Factory", [], function() { 21354 "use strict"; 21355 21356 var types = {}, namespaceInit; 21357 21358 return { 21359 /** 21360 * Adds a new control instance type to the factory. 21361 * 21362 * @method add 21363 * @param {String} type Type name for example "button". 21364 * @param {function} typeClass Class type function. 21365 */ 21366 add: function(type, typeClass) { 21367 types[type.toLowerCase()] = typeClass; 21368 }, 21369 21370 /** 21371 * Returns true/false if the specified type exists or not. 21372 * 21373 * @method has 21374 * @param {String} type Type to look for. 21375 * @return {Boolean} true/false if the control by name exists. 21376 */ 21377 has: function(type) { 21378 return !!types[type.toLowerCase()]; 21379 }, 21380 21381 /** 21382 * Creates a new control instance based on the settings provided. The instance created will be 21383 * based on the specified type property it can also create whole structures of components out of 21384 * the specified JSON object. 21385 * 21386 * @example 21387 * tinymce.ui.Factory.create({ 21388 * type: 'button', 21389 * text: 'Hello world!' 21390 * }); 21391 * 21392 * @method create 21393 * @param {Object/String} settings Name/Value object with items used to create the type. 21394 * @return {tinymce.ui.Control} Control instance based on the specified type. 21395 */ 21396 create: function(type, settings) { 21397 var ControlType, name, namespace; 21398 21399 // Build type lookup 21400 if (!namespaceInit) { 21401 namespace = tinymce.ui; 21402 21403 for (name in namespace) { 21404 types[name.toLowerCase()] = namespace[name]; 21405 } 21406 21407 namespaceInit = true; 21408 } 21409 21410 // If string is specified then use it as the type 21411 if (typeof(type) == 'string') { 21412 settings = settings || {}; 21413 settings.type = type; 21414 } else { 21415 settings = type; 21416 type = settings.type; 21417 } 21418 21419 // Find control type 21420 type = type.toLowerCase(); 21421 ControlType = types[type]; 21422 21423 // #if debug 21424 21425 if (!ControlType) { 21426 throw new Error("Could not find control by type: " + type); 21427 } 21428 21429 // #endif 21430 21431 ControlType = new ControlType(settings); 21432 ControlType.type = type; // Set the type on the instance, this will be used by the Selector engine 21433 21434 return ControlType; 21435 } 21436 }; 21437}); 21438 21439// Included from: js/tinymce/classes/ui/Container.js 21440 21441/** 21442 * Container.js 21443 * 21444 * Copyright, Moxiecode Systems AB 21445 * Released under LGPL License. 21446 * 21447 * License: http://www.tinymce.com/license 21448 * Contributing: http://www.tinymce.com/contributing 21449 */ 21450 21451/** 21452 * Container control. This is extended by all controls that can have 21453 * children such as panels etc. You can also use this class directly as an 21454 * generic container instance. The container doesn't have any specific role or style. 21455 * 21456 * @-x-less Container.less 21457 * @class tinymce.ui.Container 21458 * @extends tinymce.ui.Control 21459 */ 21460define("tinymce/ui/Container", [ 21461 "tinymce/ui/Control", 21462 "tinymce/ui/Collection", 21463 "tinymce/ui/Selector", 21464 "tinymce/ui/Factory", 21465 "tinymce/util/Tools", 21466 "tinymce/ui/DomUtils" 21467], function(Control, Collection, Selector, Factory, Tools, DomUtils) { 21468 "use strict"; 21469 21470 var selectorCache = {}; 21471 21472 return Control.extend({ 21473 layout: '', 21474 innerClass: 'container-inner', 21475 21476 /** 21477 * Constructs a new control instance with the specified settings. 21478 * 21479 * @constructor 21480 * @param {Object} settings Name/value object with settings. 21481 * @setting {Array} items Items to add to container in JSON format or control instances.
21482 * @setting {String} layout Layout manager by name to use. 21483 * @setting {Object} defaults Default settings to apply to all items. 21484 */ 21485 init: function(settings) { 21486 var self = this; 21487 21488 self._super(settings); 21489 settings = self.settings; 21490 self._fixed = settings.fixed; 21491 self._items = new Collection(); 21492 21493 if (self.isRtl()) { 21494 self.addClass('rtl'); 21495 } 21496 21497 self.addClass('container'); 21498 self.addClass('container-body', 'body'); 21499 21500 if (settings.containerCls) { 21501 self.addClass(settings.containerCls); 21502 } 21503 21504 self._layout = Factory.create((settings.layout || self.layout) + 'layout'); 21505 21506 if (self.settings.items) { 21507 self.add(self.settings.items); 21508 } 21509 21510 // TODO: Fix this! 21511 self._hasBody = true; 21512 }, 21513 21514 /** 21515 * Returns a collection of child items that the container currently have. 21516 * 21517 * @method items 21518 * @return {tinymce.ui.Collection} Control collection direct child controls. 21519 */ 21520 items: function() { 21521 return this._items; 21522 }, 21523 21524 /** 21525 * Find child controls by selector. 21526 * 21527 * @method find 21528 * @param {String} selector Selector CSS pattern to find children by. 21529 * @return {tinymce.ui.Collection} Control collection with child controls. 21530 */ 21531 find: function(selector) { 21532 selector = selectorCache[selector] = selectorCache[selector] || new Selector(selector); 21533 21534 return selector.find(this); 21535 }, 21536 21537 /** 21538 * Adds one or many items to the current container. This will create instances of 21539 * the object representations if needed. 21540 * 21541 * @method add 21542 * @param {Array/Object/tinymce.ui.Control} items Array or item that will be added to the container. 21543 * @return {tinymce.ui.Collection} Current collection control. 21544 */ 21545 add: function(items) { 21546 var self = this; 21547 21548 self.items().add(self.create(items)).parent(self); 21549 21550 return self; 21551 }, 21552 21553 /** 21554 * Focuses the current container instance. This will look 21555 * for the first control in the container and focus that. 21556 * 21557 * @method focus 21558 * @return {tinymce.ui.Collection} Current instance. 21559 */ 21560 focus: function() { 21561 var self = this; 21562 21563 if (self.keyNav) { 21564 self.keyNav.focusFirst(); 21565 } else { 21566 self._super(); 21567 } 21568 21569 return self; 21570 }, 21571 21572 /** 21573 * Replaces the specified child control with a new control. 21574 * 21575 * @method replace 21576 * @param {tinymce.ui.Control} oldItem Old item to be replaced. 21577 * @param {tinymce.ui.Control} newItem New item to be inserted. 21578 */ 21579 replace: function(oldItem, newItem) { 21580 var ctrlElm, items = this.items(), i = items.length; 21581 21582 // Replace the item in collection 21583 while (i--) { 21584 if (items[i] === oldItem) { 21585 items[i] = newItem; 21586 break; 21587 } 21588 } 21589 21590 if (i >= 0) { 21591 // Remove new item from DOM 21592 ctrlElm = newItem.getEl(); 21593 if (ctrlElm) { 21594 ctrlElm.parentNode.removeChild(ctrlElm); 21595 } 21596 21597 // Remove old item from DOM 21598 ctrlElm = oldItem.getEl(); 21599 if (ctrlElm) { 21600 ctrlElm.parentNode.removeChild(ctrlElm); 21601 } 21602 } 21603 21604 // Adopt the item 21605 newItem.parent(this); 21606 }, 21607 21608 /** 21609 * Creates the specified items. If any of the items is plain JSON style objects 21610 * it will convert these into real tinymce.ui.Control instances. 21611 * 21612 * @method create 21613 * @param {Array} items Array of items to convert into control instances. 21614 * @return {Array} Array with control instances. 21615 */ 21616 create: function(items) { 21617 var self = this, settings, ctrlItems = []; 21618 21619 // Non array structure, then force it into an array 21620 if (!Tools.isArray(items)) { 21621 items = [items]; 21622 } 21623 21624 // Add default type to each child control 21625 Tools.each(items, function(item) { 21626 if (item) { 21627 // Construct item if needed 21628 if (!(item instanceof Control)) { 21629 // Name only then convert it to an object 21630 if (typeof(item) == "string") { 21631 item = {type: item}; 21632 } 21633 21634 // Create control instance based on input settings and default settings 21635 settings = Tools.extend({}, self.settings.defaults, item); 21636 item.type = settings.type = settings.type || item.type || self.settings.defaultType || 21637 (settings.defaults ? settings.defaults.type : null);
21638 item = Factory.create(settings); 21639 } 21640 21641 ctrlItems.push(item); 21642 } 21643 }); 21644 21645 return ctrlItems; 21646 }, 21647 21648 /** 21649 * Renders new control instances. 21650 * 21651 * @private 21652 */ 21653 renderNew: function() { 21654 var self = this; 21655 21656 // Render any new items 21657 self.items().each(function(ctrl, index) { 21658 var containerElm, fragment; 21659 21660 ctrl.parent(self); 21661 21662 if (!ctrl._rendered) { 21663 containerElm = self.getEl('body'); 21664 fragment = DomUtils.createFragment(ctrl.renderHtml()); 21665 21666 // Insert or append the item 21667 if (containerElm.hasChildNodes() && index <= containerElm.childNodes.length - 1) { 21668 containerElm.insertBefore(fragment, containerElm.childNodes[index]); 21669 } else { 21670 containerElm.appendChild(fragment); 21671 } 21672 21673 ctrl.postRender(); 21674 } 21675 }); 21676 21677 self._layout.applyClasses(self); 21678 self._lastRect = null; 21679 21680 return self; 21681 }, 21682 21683 /** 21684 * Appends new instances to the current container. 21685 * 21686 * @method append 21687 * @param {Array/tinymce.ui.Collection} items Array if controls to append. 21688 * @return {tinymce.ui.Container} Current container instance. 21689 */ 21690 append: function(items) { 21691 return this.add(items).renderNew(); 21692 }, 21693 21694 /** 21695 * Prepends new instances to the current container. 21696 * 21697 * @method prepend 21698 * @param {Array/tinymce.ui.Collection} items Array if controls to prepend. 21699 * @return {tinymce.ui.Container} Current container instance. 21700 */ 21701 prepend: function(items) { 21702 var self = this; 21703 21704 self.items().set(self.create(items).concat(self.items().toArray())); 21705 21706 return self.renderNew(); 21707 }, 21708 21709 /** 21710 * Inserts an control at a specific index. 21711 * 21712 * @method insert 21713 * @param {Array/tinymce.ui.Collection} items Array if controls to insert. 21714 * @param {Number} index Index to insert controls at. 21715 * @param {Boolean} [before=false] Inserts controls before the index. 21716 */ 21717 insert: function(items, index, before) { 21718 var self = this, curItems, beforeItems, afterItems; 21719 21720 items = self.create(items); 21721 curItems = self.items(); 21722 21723 if (!before && index < curItems.length - 1) { 21724 index += 1; 21725 } 21726 21727 if (index >= 0 && index < curItems.length) { 21728 beforeItems = curItems.slice(0, index).toArray(); 21729 afterItems = curItems.slice(index).toArray(); 21730 curItems.set(beforeItems.concat(items, afterItems)); 21731 } 21732 21733 return self.renderNew(); 21734 }, 21735 21736 /** 21737 * Populates the form fields from the specified JSON data object. 21738 * 21739 * Control items in the form that matches the data will have it's value set. 21740 * 21741 * @method fromJSON 21742 * @param {Object} data JSON data object to set control values by. 21743 * @return {tinymce.ui.Container} Current form instance. 21744 */ 21745 fromJSON: function(data) { 21746 var self = this; 21747 21748 for (var name in data) { 21749 self.find('#' + name).value(data[name]); 21750 } 21751 21752 return self; 21753 }, 21754 21755 /** 21756 * Serializes the form into a JSON object by getting all items 21757 * that has a name and a value. 21758 * 21759 * @method toJSON 21760 * @return {Object} JSON object with form data. 21761 */ 21762 toJSON: function() { 21763 var self = this, data = {}; 21764 21765 self.find('*').each(function(ctrl) { 21766 var name = ctrl.name(), value = ctrl.value(); 21767 21768 if (name && typeof(value) != "undefined") { 21769 data[name] = value; 21770 } 21771 }); 21772 21773 return data; 21774 }, 21775 21776 preRender: function() { 21777 }, 21778 21779 /** 21780 * Renders the control as a HTML string. 21781 * 21782 * @method renderHtml 21783 * @return {String} HTML representing the control. 21784 */ 21785 renderHtml: function() { 21786 var self = this, layout = self._layout; 21787 21788 self.preRender(); 21789 layout.preRender(self); 21790 21791 return ( 21792 '<div id="' + self._id + '" class="' + self.classes() + '" role="' + this.settings.role + '">' + 21793 '<div id="' + self._id + '-body" class="' + self.classes('body') + '">'+ 21794 (self.settings.html || '') + layout.renderHtml(self) + 21795 '</div>' + 21796 '</div>' 21797 ); 21798 }, 21799 21800 /** 21801 * Post render method. Called after the control has been rendered to the target. 21802 * 21803 * @method postRender 21804 * @return {tinymce.ui.Container} Current combobox instance. 21805 */ 21806 postRender: function() { 21807 var self = this, box; 21808 21809 self.items().exec('postRender'); 21810 self._super(); 21811 21812 self._layout.postRender(self); 21813 self._rendered = true; 21814 21815 if (self.settings.style) { 21816 DomUtils.css(self.getEl(), self.settings.style); 21817 } 21818 21819 if (self.settings.border) { 21820 box = self.borderBox(); 21821 DomUtils.css(self.getEl(), { 21822 'border-top-width': box.top, 21823 'border-right-width': box.right, 21824 'border-bottom-width': box.bottom, 21825 'border-left-width': box.left 21826 }); 21827 } 21828 21829 return self; 21830 }, 21831 21832 /** 21833 * Initializes the current controls layout rect. 21834 * This will be executed by the layout managers to determine the 21835 * default minWidth/minHeight etc. 21836 * 21837 * @method initLayoutRect 21838 * @return {Object} Layout rect instance. 21839 */ 21840 initLayoutRect: function() { 21841 var self = this, layoutRect = self._super(); 21842 21843 // Recalc container size by asking layout manager 21844 self._layout.recalc(self); 21845 21846 return layoutRect; 21847 }, 21848 21849 /** 21850 * Recalculates the positions of the controls in the current container. 21851 * This is invoked by the reflow method and shouldn't be called directly. 21852 * 21853 * @method recalc 21854 */ 21855 recalc: function() { 21856 var self = this, rect = self._layoutRect, lastRect = self._lastRect; 21857 21858 if (!lastRect || lastRect.w != rect.w || lastRect.h != rect.h) { 21859 self._layout.recalc(self); 21860 rect = self.layoutRect(); 21861 self._lastRect = {x: rect.x, y: rect.y, w: rect.w, h: rect.h}; 21862 return true; 21863 } 21864 }, 21865 21866 /** 21867 * Reflows the current container and it's children and possible parents. 21868 * This should be used after you for example append children to the current control so 21869 * that the layout managers know that they need to reposition everything. 21870 * 21871 * @example 21872 * container.append({type: 'button', text: 'My button'}).reflow(); 21873 * 21874 * @method reflow 21875 * @return {tinymce.ui.Container} Current container instance. 21876 */ 21877 reflow: function() { 21878 var i, items; 21879 21880 if (this.visible()) { 21881 Control.repaintControls = []; 21882 Control.repaintControls.map = {}; 21883 21884 items = this.recalc(); 21885 i = Control.repaintControls.length; 21886 21887 while (i--) { 21888 Control.repaintControls[i].repaint(); 21889 } 21890 21891 // TODO: Fix me! 21892 if (this.settings.layout !== "flow" && this.settings.layout !== "stack") { 21893 this.repaint(); 21894 } 21895 21896 Control.repaintControls = []; 21897 } 21898 21899 return this; 21900 } 21901 }); 21902}); 21903 21904// Included from: js/tinymce/classes/ui/DragHelper.js 21905 21906/** 21907 * DragHelper.js 21908 * 21909 * Copyright, Moxiecode Systems AB 21910 * Released under LGPL License. 21911 * 21912 * License: http://www.tinymce.com/license 21913 * Contributing: http://www.tinymce.com/contributing 21914 */ 21915 21916/** 21917 * Drag/drop helper class. 21918 * 21919 * @example 21920 * var dragHelper = new tinymce.ui.DragHelper('mydiv', { 21921 * start: function(evt) { 21922 * }, 21923 * 21924 * drag: function(evt) { 21925 * }, 21926 * 21927 * end: function(evt) { 21928 * } 21929 * }); 21930 * 21931 * @class tinymce.ui.DragHelper 21932 */
21933define("tinymce/ui/DragHelper", [ 21934 "tinymce/ui/DomUtils" 21935], function(DomUtils) { 21936 "use strict"; 21937 21938 function getDocumentSize() { 21939 var doc = document, documentElement, body, scrollWidth, clientWidth; 21940 var offsetWidth, scrollHeight, clientHeight, offsetHeight, max = Math.max; 21941 21942 documentElement = doc.documentElement; 21943 body = doc.body; 21944 21945 scrollWidth = max(documentElement.scrollWidth, body.scrollWidth); 21946 clientWidth = max(documentElement.clientWidth, body.clientWidth); 21947 offsetWidth = max(documentElement.offsetWidth, body.offsetWidth); 21948 21949 scrollHeight = max(documentElement.scrollHeight, body.scrollHeight); 21950 clientHeight = max(documentElement.clientHeight, body.clientHeight); 21951 offsetHeight = max(documentElement.offsetHeight, body.offsetHeight); 21952 21953 return { 21954 width: scrollWidth < offsetWidth ? clientWidth : scrollWidth, 21955 height: scrollHeight < offsetHeight ? clientHeight : scrollHeight 21956 }; 21957 } 21958 21959 return function(id, settings) { 21960 var eventOverlayElm, doc = document, downButton, start, stop, drag, startX, startY; 21961 21962 settings = settings || {}; 21963 21964 function getHandleElm() { 21965 return doc.getElementById(settings.handle || id); 21966 } 21967 21968 start = function(e) { 21969 var docSize = getDocumentSize(), handleElm, cursor; 21970 21971 e.preventDefault(); 21972 downButton = e.button; 21973 handleElm = getHandleElm(); 21974 startX = e.screenX; 21975 startY = e.screenY; 21976 21977 // Grab cursor from handle 21978 if (window.getComputedStyle) { 21979 cursor = window.getComputedStyle(handleElm, null).getPropertyValue("cursor"); 21980 } else { 21981 cursor = handleElm.runtimeStyle.cursor; 21982 } 21983 21984 // Create event overlay and add it to document 21985 eventOverlayElm = doc.createElement('div'); 21986 DomUtils.css(eventOverlayElm, { 21987 position: "absolute", 21988 top: 0, left: 0, 21989 width: docSize.width, 21990 height: docSize.height, 21991 zIndex: 0x7FFFFFFF, 21992 opacity: 0.0001, 21993 background: 'red', 21994 cursor: cursor 21995 }); 21996 21997 doc.body.appendChild(eventOverlayElm); 21998 21999 // Bind mouse events 22000 DomUtils.on(doc, 'mousemove', drag); 22001 DomUtils.on(doc, 'mouseup', stop); 22002 22003 // Begin drag 22004 settings.start(e); 22005 }; 22006 22007 drag = function(e) { 22008 if (e.button !== downButton) { 22009 return stop(e); 22010 } 22011 22012 e.deltaX = e.screenX - startX; 22013 e.deltaY = e.screenY - startY; 22014 22015 e.preventDefault(); 22016 settings.drag(e); 22017 }; 22018 22019 stop = function(e) { 22020 DomUtils.off(doc, 'mousemove', drag); 22021 DomUtils.off(doc, 'mouseup', stop); 22022 22023 eventOverlayElm.parentNode.removeChild(eventOverlayElm); 22024 22025 if (settings.stop) { 22026 settings.stop(e); 22027 } 22028 }; 22029 22030 /** 22031 * Destroys the drag/drop helper instance. 22032 * 22033 * @method destroy 22034 */ 22035 this.destroy = function() { 22036 DomUtils.off(getHandleElm()); 22037 }; 22038 22039 DomUtils.on(getHandleElm(), 'mousedown', start); 22040 }; 22041}); 22042 22043// Included from: js/tinymce/classes/ui/Scrollable.js 22044 22045/** 22046 * Scrollable.js 22047 * 22048 * Copyright, Moxiecode Systems AB 22049 * Released under LGPL License. 22050 * 22051 * License: http://www.tinymce.com/license 22052 * Contributing: http://www.tinymce.com/contributing 22053 */ 22054 22055/** 22056 * This mixin makes controls scrollable using custom scrollbars. 22057 * 22058 * @-x-less Scrollable.less 22059 * @mixin tinymce.ui.Scrollable 22060 */ 22061define("tinymce/ui/Scrollable", [ 22062 "tinymce/ui/DomUtils", 22063 "tinymce/ui/DragHelper" 22064], function(DomUtils, DragHelper) { 22065 "use strict"; 22066 22067 return { 22068 init: function() { 22069 var self = this; 22070 self.on('repaint', self.renderScroll); 22071 }, 22072 22073 renderScroll: function() { 22074 var self = this, margin = 2; 22075 22076 function repaintScroll() { 22077 var hasScrollH, hasScrollV, bodyElm; 22078 22079 function repaintAxis(axisName, posName, sizeName, contentSizeName, hasScroll, ax) { 22080 var containerElm, scrollBarElm, scrollThumbElm; 22081 var containerSize, scrollSize, ratio, rect; 22082 var posNameLower, sizeNameLower; 22083 22084 scrollBarElm = self.getEl('scroll' + axisName); 22085 if (scrollBarElm) { 22086 posNameLower = posName.toLowerCase(); 22087 sizeNameLower = sizeName.toLowerCase(); 22088 22089 if (self.getEl('absend')) { 22090 DomUtils.css(self.getEl('absend'), posNameLower, self.layoutRect()[contentSizeName] - 1); 22091 } 22092 22093 if (!hasScroll) { 22094 DomUtils.css(scrollBarElm, 'display', 'none'); 22095 return; 22096 } 22097 22098 DomUtils.css(scrollBarElm, 'display', 'block'); 22099 containerElm = self.getEl('body'); 22100 scrollThumbElm = self.getEl('scroll' + axisName + "t"); 22101 containerSize = containerElm["client" + sizeName] - (margin * 2); 22102 containerSize -= hasScrollH && hasScrollV ? scrollBarElm["client" + ax] : 0; 22103 scrollSize = containerElm["scroll" + sizeName]; 22104 ratio = containerSize / scrollSize; 22105 22106 rect = {}; 22107 rect[posNameLower] = containerElm["offset" + posName] + margin; 22108 rect[sizeNameLower] = containerSize; 22109 DomUtils.css(scrollBarElm, rect); 22110 22111 rect = {}; 22112 rect[posNameLower] = containerElm["scroll" + posName] * ratio; 22113 rect[sizeNameLower] = containerSize * ratio; 22114 DomUtils.css(scrollThumbElm, rect); 22115 } 22116 } 22117 22118 bodyElm = self.getEl('body'); 22119 hasScrollH = bodyElm.scrollWidth > bodyElm.clientWidth; 22120 hasScrollV = bodyElm.scrollHeight > bodyElm.clientHeight; 22121 22122 repaintAxis("h", "Left", "Width", "contentW", hasScrollH, "Height"); 22123 repaintAxis("v", "Top", "Height", "contentH", hasScrollV, "Width"); 22124 } 22125 22126 function addScroll() { 22127 function addScrollAxis(axisName, posName, sizeName, deltaPosName, ax) { 22128 var scrollStart, axisId = self._id + '-scroll' + axisName, prefix = self.classPrefix; 22129 22130 self.getEl().appendChild(DomUtils.createFragment( 22131 '<div id="' + axisId + '" class="' + prefix + 'scrollbar ' + prefix + 'scrollbar-' + axisName + '">' + 22132 '<div id="' + axisId + 't" class="' + prefix + 'scrollbar-thumb"></div>' + 22133 '</div>' 22134 )); 22135 22136 self.draghelper = new DragHelper(axisId + 't', { 22137 start: function() { 22138 scrollStart = self.getEl('body')["scroll" + posName]; 22139 DomUtils.addClass(DomUtils.get(axisId), prefix + 'active'); 22140 }, 22141 22142 drag: function(e) { 22143 var ratio, hasScrollH, hasScrollV, containerSize, layoutRect = self.layoutRect(); 22144 22145 hasScrollH = layoutRect.contentW > layoutRect.innerW; 22146 hasScrollV = layoutRect.contentH > layoutRect.innerH; 22147 containerSize = self.getEl('body')["client" + sizeName] - (margin * 2); 22148 containerSize -= hasScrollH && hasScrollV ? self.getEl('scroll' + axisName)["client" + ax] : 0; 22149 22150 ratio = containerSize / self.getEl('body')["scroll" + sizeName]; 22151 self.getEl('body')["scroll" + posName] = scrollStart + (e["delta" + deltaPosName] / ratio); 22152 }, 22153 22154 stop: function() { 22155 DomUtils.removeClass(DomUtils.get(axisId), prefix + 'active'); 22156 } 22157 }); 22158/* 22159 self.on('click', function(e) { 22160 if (e.target.id == self._id + '-scrollv') { 22161 22162 } 22163 });*/ 22164 } 22165 22166 self.addClass('scroll'); 22167 22168 addScrollAxis("v", "Top", "Height", "Y", "Width"); 22169 addScrollAxis("h", "Left", "Width", "X", "Height"); 22170 } 22171 22172 if (self.settings.autoScroll) { 22173 if (!self._hasScroll) { 22174 self._hasScroll = true; 22175 addScroll(); 22176 22177 self.on('wheel', function(e) { 22178 var bodyEl = self.getEl('body'); 22179 22180 bodyEl.scrollLeft += (e.deltaX || 0) * 10; 22181 bodyEl.scrollTop += e.deltaY * 10; 22182 22183 repaintScroll(); 22184 }); 22185 22186 DomUtils.on(self.getEl('body'), "scroll", repaintScroll); 22187 } 22188 22189 repaintScroll(); 22190 } 22191 } 22192 }; 22193}); 22194 22195// Included from: js/tinymce/classes/ui/Panel.js 22196 22197/**
22198 * Panel.js 22199 * 22200 * Copyright, Moxiecode Systems AB 22201 * Released under LGPL License. 22202 * 22203 * License: http://www.tinymce.com/license 22204 * Contributing: http://www.tinymce.com/contributing 22205 */ 22206 22207/** 22208 * Creates a new panel. 22209 * 22210 * @-x-less Panel.less 22211 * @class tinymce.ui.Panel 22212 * @extends tinymce.ui.Container 22213 * @mixes tinymce.ui.Scrollable 22214 */ 22215define("tinymce/ui/Panel", [ 22216 "tinymce/ui/Container", 22217 "tinymce/ui/Scrollable" 22218], function(Container, Scrollable) { 22219 "use strict"; 22220 22221 return Container.extend({ 22222 Defaults: { 22223 layout: 'fit', 22224 containerCls: 'panel' 22225 }, 22226 22227 Mixins: [Scrollable], 22228 22229 /** 22230 * Renders the control as a HTML string. 22231 * 22232 * @method renderHtml 22233 * @return {String} HTML representing the control. 22234 */ 22235 renderHtml: function() { 22236 var self = this, layout = self._layout, innerHtml = self.settings.html; 22237 22238 self.preRender(); 22239 layout.preRender(self); 22240 22241 if (typeof(innerHtml) == "undefined") { 22242 innerHtml = ( 22243 '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' + 22244 layout.renderHtml(self) + 22245 '</div>' 22246 ); 22247 } else { 22248 if (typeof(innerHtml) == 'function') { 22249 innerHtml = innerHtml.call(self); 22250 } 22251 22252 self._hasBody = false; 22253 } 22254 22255 return ( 22256 '<div id="' + self._id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1">' + 22257 (self._preBodyHtml || '') + 22258 innerHtml + 22259 '</div>' 22260 ); 22261 } 22262 }); 22263}); 22264 22265// Included from: js/tinymce/classes/ui/Movable.js 22266 22267/** 22268 * Movable.js 22269 * 22270 * Copyright, Moxiecode Systems AB 22271 * Released under LGPL License. 22272 * 22273 * License: http://www.tinymce.com/license 22274 * Contributing: http://www.tinymce.com/contributing 22275 */ 22276 22277/** 22278 * Movable mixin. Makes controls movable absolute and relative to other elements. 22279 * 22280 * @mixin tinymce.ui.Movable 22281 */ 22282define("tinymce/ui/Movable", [ 22283 "tinymce/ui/DomUtils" 22284], function(DomUtils) { 22285 "use strict"; 22286 22287 function calculateRelativePosition(ctrl, targetElm, rel) { 22288 var ctrlElm, pos, x, y, selfW, selfH, targetW, targetH, viewport, size; 22289 22290 viewport = DomUtils.getViewPort(); 22291 22292 // Get pos of target 22293 pos = DomUtils.getPos(targetElm); 22294 x = pos.x; 22295 y = pos.y; 22296 22297 if (ctrl._fixed) { 22298 x -= viewport.x; 22299 y -= viewport.y; 22300 } 22301 22302 // Get size of self 22303 ctrlElm = ctrl.getEl(); 22304 size = DomUtils.getSize(ctrlElm); 22305 selfW = size.width; 22306 selfH = size.height; 22307 22308 // Get size of target 22309 size = DomUtils.getSize(targetElm); 22310 targetW = size.width; 22311 targetH = size.height; 22312 22313 // Parse align string 22314 rel = (rel || '').split(''); 22315 22316 // Target corners 22317 if (rel[0] === 'b') { 22318 y += targetH; 22319 } 22320 22321 if (rel[1] === 'r') { 22322 x += targetW; 22323 } 22324 22325 if (rel[0] === 'c') { 22326 y += Math.round(targetH / 2); 22327 } 22328 22329 if (rel[1] === 'c') { 22330 x += Math.round(targetW / 2); 22331 } 22332 22333 // Self corners 22334 if (rel[3] === 'b') { 22335 y -= selfH; 22336 } 22337 22338 if (rel[4] === 'r') { 22339 x -= selfW; 22340 } 22341 22342 if (rel[3] === 'c') { 22343 y -= Math.round(selfH / 2); 22344 } 22345 22346 if (rel[4] === 'c') { 22347 x -= Math.round(selfW / 2); 22348 } 22349 22350 return { 22351 x: x, 22352 y: y, 22353 w: selfW, 22354 h: selfH 22355 }; 22356 } 22357 22358 return { 22359 /** 22360 * Tests various positions to get the most suitable one. 22361 * 22362 * @method testMoveRel 22363 * @param {DOMElement} elm Element to position against. 22364 * @param {Array} rels Array with relative positions. 22365 * @return {String} Best suitable relative position. 22366 */ 22367 testMoveRel: function(elm, rels) { 22368 var viewPortRect = DomUtils.getViewPort(); 22369 22370 for (var i = 0; i < rels.length; i++) { 22371 var pos = calculateRelativePosition(this, elm, rels[i]); 22372 22373 if (this._fixed) { 22374 if (pos.x > 0 && pos.x + pos.w < viewPortRect.w && pos.y > 0 && pos.y + pos.h < viewPortRect.h) { 22375 return rels[i]; 22376 } 22377 } else { 22378 if (pos.x > viewPortRect.x && pos.x + pos.w < viewPortRect.w + viewPortRect.x && 22379 pos.y > viewPortRect.y && pos.y + pos.h < viewPortRect.h + viewPortRect.y) { 22380 return rels[i]; 22381 } 22382 } 22383 } 22384 22385 return rels[0]; 22386 }, 22387 22388 /** 22389 * Move relative to the specified element. 22390 * 22391 * @method moveRel 22392 * @param {Element} elm Element to move relative to. 22393 * @param {String} rel Relative mode. For example: br-tl. 22394 * @return {tinymce.ui.Control} Current control instance. 22395 */ 22396 moveRel: function(elm, rel) { 22397 if (typeof(rel) != 'string') { 22398 rel = this.testMoveRel(elm, rel); 22399 } 22400 22401 var pos = calculateRelativePosition(this, elm, rel); 22402 return this.moveTo(pos.x, pos.y); 22403 }, 22404 22405 /** 22406 * Move by a relative x, y values. 22407 * 22408 * @method moveBy 22409 * @param {Number} dx Relative x position. 22410 * @param {Number} dy Relative y position. 22411 * @return {tinymce.ui.Control} Current control instance. 22412 */ 22413 moveBy: function(dx, dy) { 22414 var self = this, rect = self.layoutRect(); 22415 22416 self.moveTo(rect.x + dx, rect.y + dy); 22417 22418 return self; 22419 }, 22420 22421 /** 22422 * Move to absolute position. 22423 * 22424 * @method moveTo 22425 * @param {Number} x Absolute x position. 22426 * @param {Number} y Absolute y position. 22427 * @return {tinymce.ui.Control} Current control instance. 22428 */ 22429 moveTo: function(x, y) { 22430 var self = this; 22431 22432 // TODO: Move this to some global class 22433 function contrain(value, max, size) { 22434 if (value < 0) { 22435 return 0; 22436 } 22437 22438 if (value + size > max) { 22439 value = max - size; 22440 return value < 0 ? 0 : value; 22441 } 22442 22443 return value; 22444 } 22445 22446 if (self.settings.constrainToViewport) { 22447 var viewPortRect = DomUtils.getViewPort(window); 22448 var layoutRect = self.layoutRect(); 22449 22450 x = contrain(x, viewPortRect.w + viewPortRect.x, layoutRect.w); 22451 y = contrain(y, viewPortRect.h + viewPortRect.y, layoutRect.h); 22452 } 22453 22454 if (self._rendered) { 22455 self.layoutRect({x: x, y: y}).repaint(); 22456 } else { 22457 self.settings.x = x; 22458 self.settings.y = y; 22459 } 22460 22461 self.fire('move', {x: x, y: y}); 22462 22463 return self; 22464 } 22465 }; 22466}); 22467 22468// Included from: js/tinymce/classes/ui/Resizable.js 22469 22470/** 22471 * Resizable.js 22472 * 22473 * Copyright, Moxiecode Systems AB 22474 * Released under LGPL License. 22475 * 22476 * License: http://www.tinymce.com/license 22477 * Contributing: http://www.tinymce.com/contributing 22478 */ 22479 22480/** 22481 * Resizable mixin. Enables controls to be resized. 22482 * 22483 * @mixin tinymce.ui.Resizable 22484 */
22485define("tinymce/ui/Resizable", [ 22486 "tinymce/ui/DomUtils" 22487], function(DomUtils) { 22488 "use strict"; 22489 22490 return { 22491 /** 22492 * Resizes the control to contents. 22493 * 22494 * @method resizeToContent 22495 */ 22496 resizeToContent: function() { 22497 this._layoutRect.autoResize = true; 22498 this._lastRect = null; 22499 this.reflow(); 22500 }, 22501 22502 /** 22503 * Resizes the control to a specific width/height. 22504 * 22505 * @method resizeTo 22506 * @param {Number} w Control width. 22507 * @param {Number} h Control height. 22508 * @return {tinymce.ui.Control} Current control instance. 22509 */ 22510 resizeTo: function(w, h) { 22511 // TODO: Fix hack 22512 if (w <= 1 || h <= 1) { 22513 var rect = DomUtils.getWindowSize(); 22514 22515 w = w <= 1 ? w * rect.w : w; 22516 h = h <= 1 ? h * rect.h : h; 22517 } 22518 22519 this._layoutRect.autoResize = false; 22520 return this.layoutRect({minW: w, minH: h, w: w, h: h}).reflow(); 22521 }, 22522 22523 /** 22524 * Resizes the control to a specific relative width/height. 22525 * 22526 * @method resizeBy 22527 * @param {Number} dw Relative control width. 22528 * @param {Number} dh Relative control height. 22529 * @return {tinymce.ui.Control} Current control instance. 22530 */ 22531 resizeBy: function(dw, dh) { 22532 var self = this, rect = self.layoutRect(); 22533 22534 return self.resizeTo(rect.w + dw, rect.h + dh); 22535 } 22536 }; 22537}); 22538 22539// Included from: js/tinymce/classes/ui/FloatPanel.js 22540 22541/** 22542 * FloatPanel.js 22543 * 22544 * Copyright, Moxiecode Systems AB 22545 * Released under LGPL License. 22546 * 22547 * License: http://www.tinymce.com/license 22548 * Contributing: http://www.tinymce.com/contributing 22549 */ 22550 22551/** 22552 * This class creates a floating panel. 22553 * 22554 * @-x-less FloatPanel.less 22555 * @class tinymce.ui.FloatPanel 22556 * @extends tinymce.ui.Panel 22557 * @mixes tinymce.ui.Movable 22558 * @mixes tinymce.ui.Resizable 22559 */ 22560define("tinymce/ui/FloatPanel", [ 22561 "tinymce/ui/Panel", 22562 "tinymce/ui/Movable", 22563 "tinymce/ui/Resizable", 22564 "tinymce/ui/DomUtils" 22565], function(Panel, Movable, Resizable, DomUtils) { 22566 "use strict"; 22567 22568 var documentClickHandler, documentScrollHandler, visiblePanels = []; 22569 var zOrder = [], hasModal; 22570 22571 var FloatPanel = Panel.extend({ 22572 Mixins: [Movable, Resizable], 22573 22574 /** 22575 * Constructs a new control instance with the specified settings. 22576 * 22577 * @constructor 22578 * @param {Object} settings Name/value object with settings. 22579 * @setting {Boolean} autohide Automatically hide the panel. 22580 */ 22581 init: function(settings) { 22582 var self = this; 22583 22584 function reorder() { 22585 var i, zIndex = FloatPanel.zIndex || 0xFFFF, topModal; 22586 22587 if (zOrder.length) { 22588 for (i = 0; i < zOrder.length; i++) { 22589 if (zOrder[i].modal) { 22590 zIndex++; 22591 topModal = zOrder[i]; 22592 } 22593 22594 zOrder[i].getEl().style.zIndex = zIndex; 22595 zOrder[i].zIndex = zIndex; 22596 zIndex++; 22597 } 22598 } 22599 22600 var modalBlockEl = document.getElementById(self.classPrefix + 'modal-block'); 22601 22602 if (topModal) { 22603 DomUtils.css(modalBlockEl, 'z-index', topModal.zIndex - 1); 22604 } else if (modalBlockEl) { 22605 modalBlockEl.parentNode.removeChild(modalBlockEl); 22606 hasModal = false; 22607 } 22608 22609 FloatPanel.currentZIndex = zIndex; 22610 } 22611 22612 function isChildOf(ctrl, parent) { 22613 while (ctrl) { 22614 if (ctrl == parent) { 22615 return true; 22616 } 22617 22618 ctrl = ctrl.parent(); 22619 } 22620 } 22621 22622 /** 22623 * Repositions the panel to the top of page if the panel is outside of the visual viewport. It will 22624 * also reposition all child panels of the current panel. 22625 */ 22626 function repositionPanel(panel) { 22627 var scrollY = DomUtils.getViewPort().y; 22628 22629 function toggleFixedChildPanels(fixed, deltaY) { 22630 var parent; 22631 22632 for (var i = 0; i < visiblePanels.length; i++) { 22633 if (visiblePanels[i] != panel) { 22634 parent = visiblePanels[i].parent(); 22635 22636 while (parent && (parent = parent.parent())) { 22637 if (parent == panel) { 22638 visiblePanels[i].fixed(fixed).moveBy(0, deltaY).repaint(); 22639 } 22640 } 22641 } 22642 } 22643 } 22644 22645 if (panel.settings.autofix) { 22646 if (!panel._fixed) { 22647 panel._autoFixY = panel.layoutRect().y; 22648 22649 if (panel._autoFixY < scrollY) { 22650 panel.fixed(true).layoutRect({y: 0}).repaint(); 22651 toggleFixedChildPanels(true, scrollY - panel._autoFixY); 22652 } 22653 } else { 22654 if (panel._autoFixY > scrollY) { 22655 panel.fixed(false).layoutRect({y: panel._autoFixY}).repaint(); 22656 toggleFixedChildPanels(false, panel._autoFixY - scrollY); 22657 } 22658 } 22659 } 22660 } 22661 22662 self._super(settings); 22663 self._eventsRoot = self; 22664 22665 self.addClass('floatpanel'); 22666 22667 // Hide floatpanes on click out side the root button 22668 if (settings.autohide) { 22669 if (!documentClickHandler) { 22670 documentClickHandler = function(e) { 22671 var i, clickCtrl = self.getParentCtrl(e.target); 22672 22673 // Hide any float panel when a click is out side that float panel and the 22674 // float panels direct parent for example a click on a menu button 22675 i = visiblePanels.length; 22676 while (i--) { 22677 var panel = visiblePanels[i]; 22678 22679 if (panel.settings.autohide) { 22680 if (clickCtrl) { 22681 if (isChildOf(clickCtrl, panel) || panel.parent() === clickCtrl) { 22682 continue; 22683 } 22684 } 22685 22686 e = panel.fire('autohide', {target: e.target}); 22687 if (!e.isDefaultPrevented()) { 22688 panel.hide(); 22689 } 22690 } 22691 } 22692 }; 22693 22694 DomUtils.on(document, 'click', documentClickHandler); 22695 } 22696 22697 visiblePanels.push(self); 22698 } 22699 22700 if (settings.autofix) { 22701 if (!documentScrollHandler) { 22702 documentScrollHandler = function() { 22703 var i; 22704 22705 i = visiblePanels.length; 22706 while (i--) { 22707 repositionPanel(visiblePanels[i]); 22708 } 22709 }; 22710 22711 DomUtils.on(window, 'scroll', documentScrollHandler); 22712 } 22713 22714 self.on('move', function() { 22715 repositionPanel(this); 22716 }); 22717 } 22718 22719 self.on('postrender show', function(e) { 22720 if (e.control == self) { 22721 var modalBlockEl, prefix = self.classPrefix; 22722 22723 if (self.modal && !hasModal) { 22724 modalBlockEl = DomUtils.createFragment('<div id="' + prefix + 'modal-block" class="' + 22725 prefix + 'reset ' + prefix + 'fade"></div>'); 22726 modalBlockEl = modalBlockEl.firstChild; 22727 22728 self.getContainerElm().appendChild(modalBlockEl); 22729 22730 setTimeout(function() { 22731 DomUtils.addClass(modalBlockEl, prefix + 'in'); 22732 DomUtils.addClass(self.getEl(), prefix + 'in'); 22733 }, 0); 22734 22735 hasModal = true; 22736 } 22737 22738 zOrder.push(self); 22739 reorder(); 22740 } 22741 }); 22742 22743 self.on('close hide', function(e) { 22744 if (e.control == self) { 22745 var i = zOrder.length; 22746 22747 while (i--) { 22748 if (zOrder[i] === self) { 22749 zOrder.splice(i, 1); 22750 } 22751 } 22752 22753 reorder(); 22754 } 22755 }); 22756 22757 self.on('show', function() { 22758 self.parents().each(function(ctrl) { 22759 if (ctrl._fixed) { 22760 self.fixed(true); 22761 return false;
22762 } 22763 }); 22764 }); 22765 22766 if (settings.popover) { 22767 self._preBodyHtml = '<div class="' + self.classPrefix + 'arrow"></div>'; 22768 self.addClass('popover').addClass('bottom').addClass(self.isRtl() ? 'end' : 'start'); 22769 } 22770 }, 22771 22772 fixed: function(state) { 22773 var self = this; 22774 22775 if (self._fixed != state) { 22776 if (self._rendered) { 22777 var viewport = DomUtils.getViewPort(); 22778 22779 if (state) { 22780 self.layoutRect().y -= viewport.y; 22781 } else { 22782 self.layoutRect().y += viewport.y; 22783 } 22784 } 22785 22786 self.toggleClass('fixed', state); 22787 self._fixed = state; 22788 } 22789 22790 return self; 22791 }, 22792 22793 /** 22794 * Shows the current float panel. 22795 * 22796 * @method show 22797 * @return {tinymce.ui.FloatPanel} Current floatpanel instance. 22798 */ 22799 show: function() { 22800 var self = this, i, state = self._super(); 22801 22802 i = visiblePanels.length; 22803 while (i--) { 22804 if (visiblePanels[i] === self) { 22805 break; 22806 } 22807 } 22808 22809 if (i === -1) { 22810 visiblePanels.push(self); 22811 } 22812 22813 return state; 22814 }, 22815 22816 /** 22817 * Hides the current float panel. 22818 * 22819 * @method hide 22820 * @return {tinymce.ui.FloatPanel} Current floatpanel instance. 22821 */ 22822 hide: function() { 22823 removeVisiblePanel(this); 22824 return this._super(); 22825 }, 22826 22827 /** 22828 * Hides all visible the float panels. 22829 * 22830 * @method hideAll 22831 */ 22832 hideAll: function() { 22833 FloatPanel.hideAll(); 22834 }, 22835 22836 /** 22837 * Closes the float panel. This will remove the float panel from page and fire the close event. 22838 * 22839 * @method close 22840 */ 22841 close: function() { 22842 var self = this; 22843 22844 self.fire('close'); 22845 22846 return self.remove(); 22847 }, 22848 22849 /** 22850 * Removes the float panel from page. 22851 * 22852 * @method remove 22853 */ 22854 remove: function() { 22855 removeVisiblePanel(this); 22856 this._super(); 22857 } 22858 }); 22859 22860 /** 22861 * Hides all visible the float panels. 22862 * 22863 * @static 22864 * @method hideAll 22865 */ 22866 FloatPanel.hideAll = function() { 22867 var i = visiblePanels.length; 22868 22869 while (i--) { 22870 var panel = visiblePanels[i]; 22871 22872 if (panel.settings.autohide) { 22873 panel.fire('cancel', {}, false); 22874 panel.hide(); 22875 visiblePanels.splice(i, 1); 22876 } 22877 } 22878 }; 22879 22880 function removeVisiblePanel(panel) { 22881 var i; 22882 22883 i = visiblePanels.length; 22884 while (i--) { 22885 if (visiblePanels[i] === panel) { 22886 visiblePanels.splice(i, 1); 22887 } 22888 } 22889 22890 i = zOrder.length; 22891 while (i--) { 22892 if (zOrder[i] === panel) { 22893 zOrder.splice(i, 1); 22894 } 22895 } 22896 } 22897 22898 return FloatPanel; 22899}); 22900 22901// Included from: js/tinymce/classes/ui/KeyboardNavigation.js 22902 22903/** 22904 * KeyboardNavigation.js 22905 * 22906 * Copyright, Moxiecode Systems AB 22907 * Released under LGPL License. 22908 * 22909 * License: http://www.tinymce.com/license 22910 * Contributing: http://www.tinymce.com/contributing 22911 */ 22912 22913/** 22914 * This class handles keyboard navigation of controls and elements. 22915 * 22916 * @class tinymce.ui.KeyboardNavigation 22917 */ 22918define("tinymce/ui/KeyboardNavigation", [ 22919 "tinymce/ui/DomUtils" 22920], function(DomUtils) { 22921 "use strict"; 22922 22923 /** 22924 * Create a new KeyboardNavigation instance to handle the focus for a specific element. 22925 * 22926 * @constructor 22927 * @param {Object} settings the settings object to define how keyboard navigation works. 22928 * 22929 * @setting {tinymce.ui.Control} root the root control navigation focus movement is scoped to this root. 22930 * @setting {Array} items an array containing the items to move focus between. Every object in this array must have an 22931 * id attribute which maps to the actual DOM element and it must be able to have focus i.e. tabIndex=-1. 22932 * @setting {Function} onCancel the callback for when the user presses escape or otherwise indicates canceling. 22933 * @setting {Function} onAction (optional) the action handler to call when the user activates an item. 22934 * @setting {Boolean} enableLeftRight (optional, default) when true, the up/down arrows move through items. 22935 * @setting {Boolean} enableUpDown (optional) when true, the up/down arrows move through items. 22936 * Note for both up/down and left/right explicitly set both enableLeftRight and enableUpDown to true. 22937 */ 22938 return function(settings) { 22939 var root = settings.root, enableUpDown = settings.enableUpDown !== false; 22940 var enableLeftRight = settings.enableLeftRight !== false; 22941 var items = settings.items, focussedId; 22942 22943 /** 22944 * Initializes the items array if needed. This will collect items/elements 22945 * from the specified root control. 22946 */ 22947 function initItems() { 22948 if (!items) { 22949 items = []; 22950 22951 if (root.find) { 22952 // Root is a container then get child elements using the UI API 22953 root.find('*').each(function(ctrl) { 22954 if (ctrl.canFocus) { 22955 items.push(ctrl.getEl()); 22956 } 22957 }); 22958 } else { 22959 // Root is a control/widget then get the child elements of that control 22960 var elements = root.getEl().getElementsByTagName('*'); 22961 for (var i = 0; i < elements.length; i++) { 22962 if (elements[i].id && elements[i]) { 22963 items.push(elements[i]); 22964 } 22965 } 22966 } 22967 } 22968 } 22969 22970 /** 22971 * Returns the currently focused element. 22972 * 22973 * @private 22974 * @return {Element} Currently focused element. 22975 */ 22976 function getFocusElement() { 22977 return document.getElementById(focussedId); 22978 } 22979 22980 /** 22981 * Returns the currently focused elements wai aria role. 22982 * 22983 * @private 22984 * @param {Element} elm Optional element to get role from. 22985 * @return {String} Role of specified element. 22986 */ 22987 function getRole(elm) { 22988 elm = elm || getFocusElement(); 22989 22990 return elm && elm.getAttribute('role'); 22991 } 22992 22993 /** 22994 * Returns the role of the parent element. 22995 * 22996 * @private 22997 * @param {Element} elm Optional element to get parent role from. 22998 * @return {String} Role of the first parent that has a role. 22999 */ 23000 function getParentRole(elm) { 23001 var role, parent = elm || getFocusElement(); 23002 23003 while ((parent = parent.parentNode)) { 23004 if ((role = getRole(parent))) { 23005 return role; 23006 } 23007 } 23008 } 23009 23010 /** 23011 * Returns an wai aria property by name. 23012 * 23013 * @private 23014 * @param {String} name Name of the aria property to get for example "disabled". 23015 * @return {String} Aria property value. 23016 */ 23017 function getAriaProp(name) { 23018 var elm = document.getElementById(focussedId); 23019 23020 if (elm) { 23021 return elm.getAttribute('aria-' + name); 23022 } 23023 } 23024 23025 /** 23026 * Executes the onAction event callback. This is when the user presses enter/space. 23027 * 23028 * @private 23029 */ 23030 function action() { 23031 var focusElm = getFocusElement(); 23032 23033 if (focusElm && (focusElm.nodeName == "TEXTAREA" || focusElm.type == "text")) { 23034 return; 23035 } 23036 23037 if (settings.onAction) { 23038 settings.onAction(focussedId); 23039 } else { 23040 DomUtils.fire(getFocusElement(), 'click', {keyboard: true}); 23041 } 23042 23043 return true; 23044 } 23045 23046 /** 23047 * Cancels the current navigation. The same as pressing the Esc key. 23048 * 23049 * @method cancel 23050 */ 23051 function cancel() { 23052 var focusElm; 23053 23054 if (settings.onCancel) { 23055 if ((focusElm = getFocusElement())) { 23056 focusElm.blur(); 23057 } 23058 23059 settings.onCancel(); 23060 } else { 23061 settings.root.fire('cancel'); 23062 } 23063 } 23064 23065 /** 23066 * Moves the focus to the next or previous item. It will wrap to start/end if it can't move. 23067 * 23068 * @method moveFocus 23069 * @param {Number} dir Direction for move -1 or 1. 23070 */ 23071 function moveFocus(dir) { 23072 var idx = -1, focusElm, i; 23073 var visibleItems = []; 23074 23075 function isVisible(elm) { 23076 var rootElm = root ? root.getEl() : document.body; 23077 23078 while (elm && elm != rootElm) { 23079 if (elm.style.display == 'none') { 23080 return false;
23081 } 23082 23083 elm = elm.parentNode; 23084 } 23085 23086 return true; 23087 } 23088 23089 initItems(); 23090 23091 // TODO: Optimize this, will be slow on lots of items 23092 i = visibleItems.length; 23093 for (i = 0; i < items.length; i++) { 23094 if (isVisible(items[i])) { 23095 visibleItems.push(items[i]); 23096 } 23097 } 23098 23099 i = visibleItems.length; 23100 while (i--) { 23101 if (visibleItems[i].id === focussedId) { 23102 idx = i; 23103 break; 23104 } 23105 } 23106 23107 idx += dir; 23108 if (idx < 0) { 23109 idx = visibleItems.length - 1; 23110 } else if (idx >= visibleItems.length) { 23111 idx = 0; 23112 } 23113 23114 focusElm = visibleItems[idx]; 23115 focusElm.focus(); 23116 focussedId = focusElm.id; 23117 23118 if (settings.actOnFocus) { 23119 action(); 23120 } 23121 } 23122 23123 /** 23124 * Moves focus to the first item or the last focused item if root is a toolbar. 23125 * 23126 * @method focusFirst 23127 * @return {[type]} [description] 23128 */ 23129 function focusFirst() { 23130 var i, rootRole; 23131 23132 rootRole = getRole(settings.root.getEl()); 23133 initItems(); 23134 23135 i = items.length; 23136 while (i--) { 23137 if (rootRole == 'toolbar' && items[i].id === focussedId) { 23138 items[i].focus(); 23139 return; 23140 } 23141 } 23142 23143 items[0].focus(); 23144 } 23145 23146 // Handle accessible keys 23147 root.on('keydown', function(e) { 23148 var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40; 23149 var DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32, DOM_VK_TAB = 9; 23150 var preventDefault; 23151 23152 switch (e.keyCode) { 23153 case DOM_VK_LEFT: 23154 if (enableLeftRight) { 23155 if (settings.leftAction) { 23156 settings.leftAction(); 23157 } else { 23158 moveFocus(-1); 23159 } 23160 23161 preventDefault = true; 23162 } 23163 break; 23164 23165 case DOM_VK_RIGHT: 23166 if (enableLeftRight) { 23167 if (getRole() == 'menuitem' && getParentRole() == 'menu') { 23168 if (getAriaProp('haspopup')) { 23169 action(); 23170 } 23171 } else { 23172 moveFocus(1); 23173 } 23174 23175 preventDefault = true; 23176 } 23177 break; 23178 23179 case DOM_VK_UP: 23180 if (enableUpDown) { 23181 moveFocus(-1); 23182 preventDefault = true; 23183 } 23184 break; 23185 23186 case DOM_VK_DOWN: 23187 if (enableUpDown) { 23188 if (getRole() == 'menuitem' && getParentRole() == 'menubar') { 23189 action(); 23190 } else if (getRole() == 'button' && getAriaProp('haspopup')) { 23191 action(); 23192 } else { 23193 moveFocus(1); 23194 } 23195 23196 preventDefault = true; 23197 } 23198 break; 23199 23200 case DOM_VK_TAB: 23201 preventDefault = true; 23202 23203 if (e.shiftKey) { 23204 moveFocus(-1); 23205 } else { 23206 moveFocus(1); 23207 } 23208 break; 23209 23210 case DOM_VK_ESCAPE: 23211 preventDefault = true; 23212 cancel(); 23213 break; 23214 23215 case DOM_VK_ENTER: 23216 case DOM_VK_RETURN: 23217 case DOM_VK_SPACE: 23218 preventDefault = action(); 23219 break; 23220 } 23221 23222 if (preventDefault) { 23223 e.stopPropagation(); 23224 e.preventDefault(); 23225 } 23226 }); 23227 23228 // Init on focus in 23229 root.on('focusin', function(e) { 23230 initItems(); 23231 focussedId = e.target.id; 23232 }); 23233 23234 return { 23235 moveFocus: moveFocus, 23236 focusFirst: focusFirst, 23237 cancel: cancel 23238 }; 23239 }; 23240}); 23241 23242// Included from: js/tinymce/classes/ui/Window.js 23243 23244/** 23245 * Window.js 23246 * 23247 * Copyright, Moxiecode Systems AB 23248 * Released under LGPL License. 23249 * 23250 * License: http://www.tinymce.com/license 23251 * Contributing: http://www.tinymce.com/contributing 23252 */ 23253 23254/** 23255 * Creates a new window. 23256 * 23257 * @-x-less Window.less 23258 * @class tinymce.ui.Window 23259 * @extends tinymce.ui.FloatPanel 23260 */ 23261define("tinymce/ui/Window", [ 23262 "tinymce/ui/FloatPanel", 23263 "tinymce/ui/Panel", 23264 "tinymce/ui/DomUtils", 23265 "tinymce/ui/KeyboardNavigation", 23266 "tinymce/ui/DragHelper" 23267], function(FloatPanel, Panel, DomUtils, KeyboardNavigation, DragHelper) { 23268 "use strict"; 23269 23270 var Window = FloatPanel.extend({ 23271 modal: true, 23272 23273 Defaults: { 23274 border: 1, 23275 layout: 'flex', 23276 containerCls: 'panel', 23277 role: 'dialog', 23278 callbacks: { 23279 submit: function() { 23280 this.fire('submit', {data: this.toJSON()}); 23281 }, 23282 23283 close: function() { 23284 this.close(); 23285 } 23286 } 23287 }, 23288 23289 /** 23290 * Constructs a instance with the specified settings. 23291 * 23292 * @constructor 23293 * @param {Object} settings Name/value object with settings. 23294 */ 23295 init: function(settings) { 23296 var self = this; 23297 23298 self._super(settings); 23299 23300 if (self.isRtl()) { 23301 self.addClass('rtl'); 23302 } 23303 23304 self.addClass('window'); 23305 self._fixed = true; 23306 23307 // Create statusbar 23308 if (settings.buttons) { 23309 self.statusbar = new Panel({ 23310 layout: 'flex', 23311 border: '1 0 0 0', 23312 spacing: 3, 23313 padding: 10,
23314 align: 'center', 23315 pack: self.isRtl() ? 'start' : 'end', 23316 defaults: { 23317 type: 'button' 23318 }, 23319 items: settings.buttons 23320 }); 23321 23322 self.statusbar.addClass('foot'); 23323 self.statusbar.parent(self); 23324 } 23325 23326 self.on('click', function(e) { 23327 if (e.target.className.indexOf(self.classPrefix + 'close') != -1) { 23328 self.close(); 23329 } 23330 }); 23331 23332 self.aria('label', settings.title); 23333 self._fullscreen = false; 23334 }, 23335 23336 /** 23337 * Recalculates the positions of the controls in the current container. 23338 * This is invoked by the reflow method and shouldn't be called directly. 23339 * 23340 * @method recalc 23341 */ 23342 recalc: function() { 23343 var self = this, statusbar = self.statusbar, layoutRect, width, x, needsRecalc; 23344 23345 if (self._fullscreen) { 23346 self.layoutRect(DomUtils.getWindowSize()); 23347 self.layoutRect().contentH = self.layoutRect().innerH; 23348 } 23349 23350 self._super(); 23351 23352 layoutRect = self.layoutRect(); 23353 23354 // Resize window based on title width 23355 if (self.settings.title && !self._fullscreen) { 23356 width = layoutRect.headerW; 23357 if (width > layoutRect.w) { 23358 x = layoutRect.x - Math.max(0, width / 2); 23359 self.layoutRect({w: width, x: x}); 23360 needsRecalc = true; 23361 } 23362 } 23363 23364 // Resize window based on statusbar width 23365 if (statusbar) { 23366 statusbar.layoutRect({w: self.layoutRect().innerW}).recalc(); 23367 23368 width = statusbar.layoutRect().minW + layoutRect.deltaW; 23369 if (width > layoutRect.w) { 23370 x = layoutRect.x - Math.max(0, width - layoutRect.w); 23371 self.layoutRect({w: width, x: x}); 23372 needsRecalc = true; 23373 } 23374 } 23375 23376 // Recalc body and disable auto resize 23377 if (needsRecalc) { 23378 self.recalc(); 23379 } 23380 }, 23381 23382 /** 23383 * Initializes the current controls layout rect. 23384 * This will be executed by the layout managers to determine the 23385 * default minWidth/minHeight etc. 23386 * 23387 * @method initLayoutRect 23388 * @return {Object} Layout rect instance. 23389 */ 23390 initLayoutRect: function() { 23391 var self = this, layoutRect = self._super(), deltaH = 0, headEl; 23392 23393 // Reserve vertical space for title 23394 if (self.settings.title && !self._fullscreen) { 23395 headEl = self.getEl('head'); 23396 23397 var size = DomUtils.getSize(headEl); 23398 23399 layoutRect.headerW = size.width; 23400 layoutRect.headerH = size.height; 23401 23402 deltaH += layoutRect.headerH; 23403 } 23404 23405 // Reserve vertical space for statusbar 23406 if (self.statusbar) { 23407 deltaH += self.statusbar.layoutRect().h; 23408 } 23409 23410 layoutRect.deltaH += deltaH; 23411 layoutRect.minH += deltaH; 23412 //layoutRect.innerH -= deltaH; 23413 layoutRect.h += deltaH; 23414 23415 var rect = DomUtils.getWindowSize(); 23416 23417 layoutRect.x = Math.max(0, rect.w / 2 - layoutRect.w / 2); 23418 layoutRect.y = Math.max(0, rect.h / 2 - layoutRect.h / 2); 23419 23420 return layoutRect; 23421 }, 23422 23423 /** 23424 * Renders the control as a HTML string. 23425 * 23426 * @method renderHtml 23427 * @return {String} HTML representing the control. 23428 */ 23429 renderHtml: function() { 23430 var self = this, layout = self._layout, id = self._id, prefix = self.classPrefix; 23431 var settings = self.settings, headerHtml = '', footerHtml = '', html = settings.html; 23432 23433 self.preRender(); 23434 layout.preRender(self); 23435 23436 if (settings.title) { 23437 headerHtml = ( 23438 '<div id="' + id + '-head" class="' + prefix + 'window-head">' + 23439 '<div class="' + prefix + 'title">' + self.encode(settings.title) + '</div>' + 23440 '<button type="button" class="' + prefix + 'close" aria-hidden="true">×</button>' + 23441 '<div id="' + id + '-dragh" class="' + prefix + 'dragh"></div>' + 23442 '</div>' 23443 ); 23444 } 23445 23446 if (settings.url) { 23447 html = '<iframe src="' + settings.url + '" tabindex="-1"></iframe>'; 23448 } 23449 23450 if (typeof(html) == "undefined") { 23451 html = layout.renderHtml(self); 23452 } 23453 23454 if (self.statusbar) { 23455 footerHtml = self.statusbar.renderHtml(); 23456 } 23457 23458 return ( 23459 '<div id="' + id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1">' + 23460 headerHtml + 23461 '<div id="' + id + '-body" class="' + self.classes('body') + '">' + 23462 html + 23463 '</div>' + 23464 footerHtml + 23465 '</div>' 23466 ); 23467 }, 23468 23469 /** 23470 * Switches the window fullscreen mode. 23471 * 23472 * @method fullscreen 23473 * @param {Boolean} state True/false state. 23474 * @return {tinymce.ui.Window} Current window instance. 23475 */ 23476 fullscreen: function(state) { 23477 var self = this, documentElement = document.documentElement, slowRendering, prefix = self.classPrefix, layoutRect; 23478 23479 if (state != self._fullscreen) { 23480 DomUtils.on(window, 'resize', function() { 23481 var time; 23482 23483 if (self._fullscreen) { 23484 // Time the layout time if it's to slow use a timeout to not hog the CPU 23485 if (!slowRendering) { 23486 time = new Date().getTime(); 23487 23488 var rect = DomUtils.getWindowSize(); 23489 self.moveTo(0, 0).resizeTo(rect.w, rect.h); 23490 23491 if ((new Date().getTime()) - time > 50) { 23492 slowRendering = true; 23493 } 23494 } else { 23495 if (!self._timer) { 23496 self._timer = setTimeout(function() { 23497 var rect = DomUtils.getWindowSize(); 23498 self.moveTo(0, 0).resizeTo(rect.w, rect.h); 23499 23500 self._timer = 0; 23501 }, 50); 23502 } 23503 } 23504 } 23505 }); 23506 23507 layoutRect = self.layoutRect(); 23508 self._fullscreen = state; 23509 23510 if (!state) { 23511 self._borderBox = self.parseBox(self.settings.border); 23512 self.getEl('head').style.display = ''; 23513 layoutRect.deltaH += layoutRect.headerH; 23514 DomUtils.removeClass(documentElement, prefix + 'fullscreen'); 23515 DomUtils.removeClass(document.body, prefix + 'fullscreen'); 23516 self.removeClass('fullscreen'); 23517 self.moveTo(self._initial.x, self._initial.y).resizeTo(self._initial.w, self._initial.h); 23518 } else { 23519 self._initial = {x: layoutRect.x, y: layoutRect.y, w: layoutRect.w, h: layoutRect.h}; 23520 23521 self._borderBox = self.parseBox('0'); 23522 self.getEl('head').style.display = 'none'; 23523 layoutRect.deltaH -= layoutRect.headerH + 2; 23524 DomUtils.addClass(documentElement, prefix + 'fullscreen'); 23525 DomUtils.addClass(document.body, prefix + 'fullscreen'); 23526 self.addClass('fullscreen'); 23527 23528 var rect = DomUtils.getWindowSize(); 23529 self.moveTo(0, 0).resizeTo(rect.w, rect.h); 23530 } 23531 } 23532 23533 return self.reflow(); 23534 }, 23535 23536 /** 23537 * Called after the control has been rendered. 23538 * 23539 * @method postRender 23540 */ 23541 postRender: function() { 23542 var self = this, items = [], focusCtrl, autoFocusFound, startPos; 23543 23544 setTimeout(function() { 23545 self.addClass('in'); 23546 }, 0); 23547 23548 self.keyboardNavigation = new KeyboardNavigation({ 23549 root: self, 23550 enableLeftRight: false, 23551 enableUpDown: false, 23552 items: items, 23553 onCancel: function() { 23554 self.close(); 23555 } 23556 }); 23557 23558 self.find('*').each(function(ctrl) { 23559 if (ctrl.canFocus) { 23560 autoFocusFound = autoFocusFound || ctrl.settings.autofocus; 23561 focusCtrl = focusCtrl || ctrl; 23562 23563 // TODO: Figure out a better way 23564 if (ctrl.type == 'filepicker') { 23565 items.push(ctrl.getEl('inp')); 23566 23567 if (ctrl.getEl('open')) { 23568 items.push(ctrl.getEl('open')); 23569 } 23570 } else { 23571 items.push(ctrl.getEl()); 23572 } 23573 } 23574 }); 23575 23576 if (self.statusbar) { 23577 self.statusbar.find('*').each(function(ctrl) { 23578 if (ctrl.canFocus) { 23579 autoFocusFound = autoFocusFound || ctrl.settings.autofocus; 23580 focusCtrl = focusCtrl || ctrl; 23581 items.push(ctrl.getEl()); 23582 } 23583 }); 23584 } 23585 23586 self._super(); 23587 23588 if (self.statusbar) { 23589 self.statusbar.postRender(); 23590 } 23591 23592 if (!autoFocusFound && focusCtrl) { 23593 focusCtrl.focus(); 23594 } 23595 23596 this.dragHelper = new DragHelper(self._id + '-dragh', { 23597 start: function() { 23598 startPos = { 23599 x: self.layoutRect().x, 23600 y: self.layoutRect().y 23601 }; 23602 }, 23603 23604 drag: function(e) { 23605 self.moveTo(startPos.x + e.deltaX, startPos.y + e.deltaY); 23606 } 23607 }); 23608 23609 self.on('submit', function(e) { 23610 if (!e.isDefaultPrevented()) { 23611 self.close(); 23612 } 23613 }); 23614 }, 23615 23616 /** 23617 * Fires a submit event with the serialized form. 23618 * 23619 * @method submit 23620 * @return {Object} Event arguments object. 23621 */ 23622 submit: function() { 23623 return this.fire('submit', {data: this.toJSON()}); 23624 }, 23625 23626 /** 23627 * Removes the current control from DOM and from UI collections. 23628 * 23629 * @method remove 23630 * @return {tinymce.ui.Control} Current control instance. 23631 */ 23632 remove: function() { 23633 var self = this, prefix = self.classPrefix; 23634 23635 self.dragHelper.destroy(); 23636 self._super(); 23637 23638 if (self.statusbar) { 23639 this.statusbar.remove(); 23640 } 23641 23642 if (self._fullscreen) { 23643 DomUtils.removeClass(document.documentElement, prefix + 'fullscreen'); 23644 DomUtils.removeClass(document.body, prefix + 'fullscreen'); 23645 } 23646 } 23647 }); 23648 23649 return Window; 23650}); 23651 23652// Included from: js/tinymce/classes/ui/MessageBox.js 23653 23654/** 23655 * MessageBox.js 23656 * 23657 * Copyright, Moxiecode Systems AB 23658 * Released under LGPL License. 23659 * 23660 * License: http://www.tinymce.com/license 23661 * Contributing: http://www.tinymce.com/contributing 23662 */ 23663 23664/** 23665 * This class is used to create MessageBoxes like alerts/confirms etc. 23666 * 23667 * @class tinymce.ui.Window 23668 * @extends tinymce.ui.FloatPanel 23669 */
23670define("tinymce/ui/MessageBox", [ 23671 "tinymce/ui/Window" 23672], function(Window) { 23673 "use strict"; 23674 23675 var MessageBox = Window.extend({ 23676 /** 23677 * Constructs a instance with the specified settings. 23678 * 23679 * @constructor 23680 * @param {Object} settings Name/value object with settings. 23681 */ 23682 init: function(settings) { 23683 settings = { 23684 border: 1, 23685 padding: 20, 23686 layout: 'flex', 23687 pack: "center", 23688 align: "center", 23689 containerCls: 'panel', 23690 autoScroll: true, 23691 buttons: {type: "button", text: "Ok", action: "ok"}, 23692 items: { 23693 type: "label", 23694 multiline: true, 23695 maxWidth: 500, 23696 maxHeight: 200 23697 } 23698 }; 23699 23700 this._super(settings); 23701 }, 23702 23703 Statics: { 23704 /** 23705 * Ok buttons constant. 23706 * 23707 * @static 23708 * @final 23709 * @field {Number} OK 23710 */ 23711 OK: 1, 23712 23713 /** 23714 * Ok/cancel buttons constant. 23715 * 23716 * @static 23717 * @final 23718 * @field {Number} OK_CANCEL 23719 */ 23720 OK_CANCEL: 2, 23721 23722 /** 23723 * yes/no buttons constant. 23724 * 23725 * @static 23726 * @final 23727 * @field {Number} YES_NO 23728 */ 23729 YES_NO: 3, 23730 23731 /** 23732 * yes/no/cancel buttons constant. 23733 * 23734 * @static 23735 * @final 23736 * @field {Number} YES_NO_CANCEL 23737 */ 23738 YES_NO_CANCEL: 4, 23739 23740 /** 23741 * Constructs a new message box and renders it to the body element. 23742 * 23743 * @static 23744 * @method msgBox 23745 * @param {Object} settings Name/value object with settings. 23746 */ 23747 msgBox: function(settings) { 23748 var buttons, callback = settings.callback || function() {}; 23749 23750 switch (settings.buttons) { 23751 case MessageBox.OK_CANCEL: 23752 buttons = [ 23753 {type: "button", text: "Ok", subtype: "primary", onClick: function(e) { 23754 e.control.parents()[1].close(); 23755 callback(true); 23756 }}, 23757 23758 {type: "button", text: "Cancel", onClick: function(e) { 23759 e.control.parents()[1].close(); 23760 callback(false); 23761 }} 23762 ]; 23763 break; 23764 23765 case MessageBox.YES_NO: 23766 buttons = [ 23767 {type: "button", text: "Ok", subtype: "primary", onClick: function(e) { 23768 e.control.parents()[1].close(); 23769 callback(true); 23770 }} 23771 ]; 23772 break; 23773 23774 case MessageBox.YES_NO_CANCEL: 23775 buttons = [ 23776 {type: "button", text: "Ok", subtype: "primary", onClick: function(e) { 23777 e.control.parents()[1].close(); 23778 }} 23779 ]; 23780 break; 23781 23782 default: 23783 buttons = [ 23784 {type: "button", text: "Ok", subtype: "primary", onClick: function(e) { 23785 e.control.parents()[1].close(); 23786 callback(true); 23787 }} 23788 ]; 23789 break; 23790 } 23791 23792 return new Window({ 23793 padding: 20, 23794 x: settings.x, 23795 y: settings.y, 23796 minWidth: 300, 23797 minHeight: 100, 23798 layout: "flex", 23799 pack: "center", 23800 align: "center", 23801 buttons: buttons, 23802 title: settings.title, 23803 items: { 23804 type: "label", 23805 multiline: true, 23806 maxWidth: 500, 23807 maxHeight: 200, 23808 text: settings.text 23809 }, 23810 onClose: settings.onClose 23811 }).renderTo(document.body).reflow(); 23812 }, 23813 23814 /** 23815 * Creates a new alert dialog. 23816 * 23817 * @method alert 23818 * @param {Object} settings Settings for the alert dialog. 23819 * @param {function} [callback] Callback to execute when the user makes a choice. 23820 */ 23821 alert: function(settings, callback) {
23822 if (typeof(settings) == "string") { 23823 settings = {text: settings}; 23824 } 23825 23826 settings.callback = callback; 23827 return MessageBox.msgBox(settings); 23828 }, 23829 23830 /** 23831 * Creates a new confirm dialog. 23832 * 23833 * @method confirm 23834 * @param {Object} settings Settings for the confirm dialog. 23835 * @param {function} [callback] Callback to execute when the user makes a choice. 23836 */ 23837 confirm: function(settings, callback) { 23838 if (typeof(settings) == "string") { 23839 settings = {text: settings}; 23840 } 23841 23842 settings.callback = callback; 23843 settings.buttons = MessageBox.OK_CANCEL; 23844 23845 return MessageBox.msgBox(settings); 23846 } 23847 } 23848 }); 23849 23850 return MessageBox; 23851}); 23852 23853// Included from: js/tinymce/classes/WindowManager.js 23854 23855/** 23856 * WindowManager.js 23857 * 23858 * Copyright, Moxiecode Systems AB 23859 * Released under LGPL License. 23860 * 23861 * License: http://www.tinymce.com/license 23862 * Contributing: http://www.tinymce.com/contributing 23863 */ 23864 23865/** 23866 * This class handles the creation of native windows and dialogs. This class can be extended to provide for example inline dialogs. 23867 * 23868 * @class tinymce.WindowManager 23869 * @example 23870 * // Opens a new dialog with the file.htm file and the size 320x240 23871 * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog. 23872 * tinymce.activeEditor.windowManager.open({ 23873 * url: 'file.htm', 23874 * width: 320, 23875 * height: 240 23876 * }, { 23877 * custom_param: 1 23878 * }); 23879 * 23880 * // Displays an alert box using the active editors window manager instance 23881 * tinymce.activeEditor.windowManager.alert('Hello world!'); 23882 * 23883 * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm 23884 * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) { 23885 * if (s) 23886 * tinymce.activeEditor.windowManager.alert("Ok"); 23887 * else 23888 * tinymce.activeEditor.windowManager.alert("Cancel"); 23889 * }); 23890 */ 23891define("tinymce/WindowManager", [ 23892 "tinymce/ui/Window", 23893 "tinymce/ui/MessageBox" 23894], function(Window, MessageBox) { 23895 return function(editor) { 23896 var self = this, windows = []; 23897 23898 function getTopMostWindow() { 23899 if (windows.length) { 23900 return windows[windows.length - 1]; 23901 } 23902 } 23903 23904 self.windows = windows; 23905 23906 /** 23907 * Opens a new window. 23908 * 23909 * @method open 23910 * @param {Object} args Optional name/value settings collection contains things like width/height/url etc. 23911 * @option {String} title Window title. 23912 * @option {String} file URL of the file to open in the window. 23913 * @option {Number} width Width in pixels. 23914 * @option {Number} height Height in pixels. 23915 * @option {Boolean} resizable Specifies whether the popup window is resizable or not. 23916 * @option {Boolean} maximizable Specifies whether the popup window has a "maximize" button and can get maximized or not. 23917 * @option {String/bool} scrollbars Specifies whether the popup window can have scrollbars if required (i.e. content 23918 * larger than the popup size specified). 23919 */ 23920 self.open = function(args, params) { 23921 var win; 23922 23923 editor.editorManager.activeEditor = editor; 23924 23925 args.title = args.title || ' '; 23926 23927 // Handle URL 23928 args.url = args.url || args.file; // Legacy 23929 if (args.url) { 23930 args.width = parseInt(args.width || 320, 10); 23931 args.height = parseInt(args.height || 240, 10); 23932 } 23933 23934 // Handle body 23935 if (args.body) { 23936 args.items = { 23937 defaults: args.defaults, 23938 type: args.bodyType || 'form', 23939 items: args.body 23940 }; 23941 } 23942 23943 if (!args.url && !args.buttons) { 23944 args.buttons = [ 23945 {text: 'Ok', subtype: 'primary', onclick: function() { 23946 win.find('form')[0].submit(); 23947 win.close(); 23948 }}, 23949 23950 {text: 'Cancel', onclick: function() { 23951 win.close(); 23952 }} 23953 ]; 23954 } 23955 23956 win = new Window(args); 23957 windows.push(win); 23958 23959 win.on('close', function() { 23960 var i = windows.length; 23961 23962 while (i--) { 23963 if (windows[i] === win) { 23964 windows.splice(i, 1); 23965 } 23966 } 23967 23968 editor.focus(); 23969 }); 23970 23971 // Handle data 23972 if (args.data) { 23973 win.on('postRender', function() { 23974 this.find('*').each(function(ctrl) { 23975 var name = ctrl.name(); 23976 23977 if (name in args.data) { 23978 ctrl.value(args.data[name]); 23979 } 23980 }); 23981 }); 23982 } 23983 23984 // store args and parameters 23985 win.features = args || {}; 23986 win.params = params || {}; 23987 23988 // Takes a snapshot in the FocusManager of the selection before focus is lost to dialog 23989 editor.nodeChanged(); 23990 23991 return win.renderTo(document.body).reflow(); 23992 }; 23993 23994 /** 23995 * Creates a alert dialog. Please don't use the blocking behavior of this 23996 * native version use the callback method instead then it can be extended. 23997 * 23998 * @method alert 23999 * @param {String} message Text to display in the new alert dialog. 24000 * @param {function} callback Callback function to be executed after the user has selected ok. 24001 * @param {Object} scope Optional scope to execute the callback in. 24002 * @example 24003 * // Displays an alert box using the active editors window manager instance 24004 * tinymce.activeEditor.windowManager.alert('Hello world!'); 24005 */ 24006 self.alert = function(message, callback, scope) { 24007 MessageBox.alert(message, function() { 24008 if (callback) { 24009 callback.call(scope || this); 24010 } 24011 }); 24012 }; 24013 24014 /** 24015 * Creates a confirm dialog. Please don't use the blocking behavior of this 24016 * native version use the callback method instead then it can be extended. 24017 * 24018 * @method confirm 24019 * @param {String} messageText to display in the new confirm dialog. 24020 * @param {function} callback Callback function to be executed after the user has selected ok or cancel. 24021 * @param {Object} scope Optional scope to execute the callback in. 24022 * @example 24023 * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm 24024 * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) { 24025 * if (s) 24026 * tinymce.activeEditor.windowManager.alert("Ok"); 24027 * else 24028 * tinymce.activeEditor.windowManager.alert("Cancel"); 24029 * }); 24030 */ 24031 self.confirm = function(message, callback, scope) { 24032 MessageBox.confirm(message, function(state) { 24033 callback.call(scope || this, state); 24034 }); 24035 }; 24036 24037 /** 24038 * Closes the top most window. 24039 * 24040 * @method close 24041 */ 24042 self.close = function() {
24043 if (getTopMostWindow()) { 24044 getTopMostWindow().close(); 24045 } 24046 }; 24047 24048 /** 24049 * Returns the params of the last window open call. This can be used in iframe based 24050 * dialog to get params passed from the tinymce plugin. 24051 * 24052 * @example 24053 * var dialogArguments = top.tinymce.activeEditor.windowManager.getParams(); 24054 * 24055 * @method getParams 24056 * @return {Object} Name/value object with parameters passed from windowManager.open call. 24057 */ 24058 self.getParams = function() { 24059 return getTopMostWindow() ? getTopMostWindow().params : null; 24060 }; 24061 24062 /** 24063 * Sets the params of the last opened window. 24064 * 24065 * @method setParams 24066 * @param {Object} params Params object to set for the last opened window. 24067 */ 24068 self.setParams = function(params) { 24069 if (getTopMostWindow()) { 24070 getTopMostWindow().params = params; 24071 } 24072 }; 24073 }; 24074}); 24075 24076// Included from: js/tinymce/classes/util/Quirks.js 24077 24078/** 24079 * Quirks.js 24080 * 24081 * Copyright, Moxiecode Systems AB 24082 * Released under LGPL License. 24083 * 24084 * License: http://www.tinymce.com/license 24085 * Contributing: http://www.tinymce.com/contributing 24086 * 24087 * @ignore-file 24088 */ 24089 24090/** 24091 * This file includes fixes for various browser quirks it's made to make it easy to add/remove browser specific fixes. 24092 * 24093 * @class tinymce.util.Quirks 24094 */ 24095define("tinymce/util/Quirks", [ 24096 "tinymce/util/VK", 24097 "tinymce/dom/RangeUtils", 24098 "tinymce/html/Node", 24099 "tinymce/html/Entities", 24100 "tinymce/Env", 24101 "tinymce/util/Tools" 24102], function(VK, RangeUtils, Node, Entities, Env, Tools) { 24103 return function(editor) { 24104 var each = Tools.each; 24105 var BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, 24106 settings = editor.settings, parser = editor.parser, serializer = editor.serializer; 24107 var isGecko = Env.gecko, isIE = Env.ie, isWebKit = Env.webkit; 24108 24109 /** 24110 * Executes a command with a specific state this can be to enable/disable browser editing features. 24111 */ 24112 function setEditorCommandState(cmd, state) { 24113 try { 24114 editor.getDoc().execCommand(cmd, false, state); 24115 } catch (ex) { 24116 // Ignore 24117 } 24118 } 24119 24120 /** 24121 * Returns current IE document mode. 24122 */ 24123 function getDocumentMode() { 24124 var documentMode = editor.getDoc().documentMode; 24125 24126 return documentMode ? documentMode : 6; 24127 } 24128 24129 /** 24130 * Returns true/false if the event is prevented or not. 24131 * 24132 * @private 24133 * @param {Event} e Event object. 24134 * @return {Boolean} true/false if the event is prevented or not. 24135 */ 24136 function isDefaultPrevented(e) { 24137 return e.isDefaultPrevented(); 24138 } 24139 24140 /** 24141 * Fixes a WebKit bug when deleting contents using backspace or delete key. 24142 * WebKit will produce a span element if you delete across two block elements. 24143 * 24144 * Example: 24145 * <h1>a</h1><p>|b</p> 24146 * 24147 * Will produce this on backspace: 24148 * <h1>a<span style="<all runtime styles>">b</span></p> 24149 * 24150 * This fixes the backspace to produce: 24151 * <h1>a|b</p> 24152 * 24153 * See bug: https://bugs.webkit.org/show_bug.cgi?id=45784 24154 * 24155 * This fixes the following delete scenarios: 24156 * 1. Delete by pressing backspace key. 24157 * 2. Delete by pressing delete key. 24158 * 3. Delete by pressing backspace key with ctrl/cmd (Word delete). 24159 * 4. Delete by pressing delete key with ctrl/cmd (Word delete). 24160 * 5. Delete by drag/dropping contents inside the editor. 24161 * 6. Delete by using Cut Ctrl+X/Cmd+X. 24162 * 7. Delete by selecting contents and writing a character.' 24163 * 24164 * This code is a ugly hack since writing full custom delete logic for just this bug 24165 * fix seemed like a huge task. I hope we can remove this before the year 2030. 24166 */ 24167 function cleanupStylesWhenDeleting() { 24168 var doc = editor.getDoc(); 24169 24170 if (!window.MutationObserver) { 24171 return; 24172 } 24173 24174 function customDelete(isForward) { 24175 var mutationObserver = new MutationObserver(function() {}); 24176 24177 Tools.each(editor.getBody().getElementsByTagName('*'), function(elm) { 24178 // Mark existing spans 24179 if (elm.tagName == 'SPAN') { 24180 elm.setAttribute('mce-data-marked', 1); 24181 } 24182 24183 // Make sure all elements has a data-mce-style attribute 24184 if (!elm.hasAttribute('data-mce-style') && elm.hasAttribute('style')) { 24185 editor.dom.setAttrib(elm, 'style', elm.getAttribute('style')); 24186 } 24187 }); 24188
24189 // Observe added nodes and style attribute changes 24190 mutationObserver.observe(editor.getDoc(), { 24191 childList: true, 24192 attributes: true, 24193 subtree: true, 24194 attributeFilter: ['style'] 24195 }); 24196 24197 editor.getDoc().execCommand(isForward ? 'ForwardDelete' : 'Delete', false, null); 24198 24199 var rng = editor.selection.getRng(); 24200 var caretElement = rng.startContainer.parentNode; 24201 24202 Tools.each(mutationObserver.takeRecords(), function(record) { 24203 // Restore style attribute to previous value 24204 if (record.attributeName == "style") { 24205 var oldValue = record.target.getAttribute('data-mce-style'); 24206 24207 if (oldValue) { 24208 record.target.setAttribute("style", oldValue); 24209 } else { 24210 record.target.removeAttribute("style"); 24211 } 24212 } 24213 24214 // Remove all spans that isn't maked and retain selection 24215 Tools.each(record.addedNodes, function(node) { 24216 if (node.nodeName == "SPAN" && !node.getAttribute('mce-data-marked')) { 24217 var offset, container; 24218 24219 if (node == caretElement) { 24220 offset = rng.startOffset; 24221 container = node.firstChild; 24222 } 24223 24224 dom.remove(node, true); 24225 24226 if (container) { 24227 rng.setStart(container, offset); 24228 rng.setEnd(container, offset); 24229 editor.selection.setRng(rng); 24230 } 24231 } 24232 }); 24233 }); 24234 24235 mutationObserver.disconnect(); 24236 24237 // Remove any left over marks 24238 Tools.each(editor.dom.select('span[mce-data-marked]'), function(span) { 24239 span.removeAttribute('mce-data-marked'); 24240 }); 24241 } 24242 24243 editor.on('keydown', function(e) { 24244 var isForward = e.keyCode == DELETE, isMeta = VK.metaKeyPressed(e); 24245 24246 if (!isDefaultPrevented(e) && (isForward || e.keyCode == BACKSPACE)) { 24247 var rng = editor.selection.getRng(), container = rng.startContainer, offset = rng.startOffset; 24248 24249 // Ignore non meta delete in the where there is text before/after the caret 24250 if (!isMeta && rng.collapsed && container.nodeType == 3) { 24251 if (isForward ? offset < container.data.length : offset > 0) { 24252 return; 24253 } 24254 } 24255 24256 e.preventDefault(); 24257 24258 if (isMeta) { 24259 editor.selection.getSel().modify("extend", isForward ? "forward" : "backward", "word"); 24260 } 24261 24262 customDelete(isForward); 24263 } 24264 }); 24265 24266 editor.on('keypress', function(e) { 24267 if (!isDefaultPrevented(e) && !selection.isCollapsed() && e.charCode) { 24268 e.preventDefault(); 24269 customDelete(true); 24270 editor.selection.setContent(String.fromCharCode(e.charCode)); 24271 } 24272 }); 24273 24274 editor.addCommand('Delete', function() { 24275 customDelete(); 24276 }); 24277 24278 editor.addCommand('ForwardDelete', function() { 24279 customDelete(true); 24280 }); 24281 24282 editor.on('dragstart', function(e) { 24283 e.dataTransfer.setData('mce-internal', editor.selection.getContent()); 24284 }); 24285 24286 editor.on('drop', function(e) { 24287 if (!isDefaultPrevented(e)) { 24288 var internalContent = e.dataTransfer.getData('mce-internal'); 24289 24290 if (internalContent && doc.caretRangeFromPoint) { 24291 e.preventDefault(); 24292 customDelete(); 24293 editor.selection.setRng(doc.caretRangeFromPoint(e.x, e.y)); 24294 editor.insertContent(internalContent); 24295 } 24296 } 24297 }); 24298 24299 editor.on('cut', function(e) { 24300 if (!isDefaultPrevented(e) && e.clipboardData) { 24301 e.preventDefault(); 24302 e.clipboardData.clearData(); 24303 e.clipboardData.setData('text/html', editor.selection.getContent()); 24304 e.clipboardData.setData('text/plain', editor.selection.getContent({format: 'text'})); 24305 customDelete(true); 24306 } 24307 }); 24308 } 24309 24310 /** 24311 * Makes sure that the editor body becomes empty when backspace or delete is pressed in empty editors. 24312 * 24313 * For example: 24314 * <p><b>|</b></p> 24315 * 24316 * Or: 24317 * <h1>|</h1> 24318 * 24319 * Or: 24320 * [<h1></h1>] 24321 */ 24322 function emptyEditorWhenDeleting() { 24323 function serializeRng(rng) { 24324 var body = dom.create("body"); 24325 var contents = rng.cloneContents(); 24326 body.appendChild(contents); 24327 return selection.serializer.serialize(body, {format: 'html'}); 24328 } 24329 24330 function allContentsSelected(rng) { 24331 if (!rng.setStart) { 24332 if (rng.item) { 24333 return false; 24334 } 24335 24336 var bodyRng = rng.duplicate(); 24337 bodyRng.moveToElementText(editor.getBody()); 24338 return RangeUtils.compareRanges(rng, bodyRng); 24339 } 24340 24341 var selection = serializeRng(rng); 24342 24343 var allRng = dom.createRng(); 24344 allRng.selectNode(editor.getBody()); 24345 24346 var allSelection = serializeRng(allRng); 24347 return selection === allSelection; 24348 } 24349
24350 editor.on('keydown', function(e) { 24351 var keyCode = e.keyCode, isCollapsed, body; 24352 24353 // Empty the editor if it's needed for example backspace at <p><b>|</b></p> 24354 if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) { 24355 isCollapsed = editor.selection.isCollapsed(); 24356 body = editor.getBody(); 24357 24358 // Selection is collapsed but the editor isn't empty 24359 if (isCollapsed && !dom.isEmpty(body)) { 24360 return; 24361 } 24362 24363 // Selection isn't collapsed but not all the contents is selected 24364 if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) { 24365 return; 24366 } 24367 24368 // Manually empty the editor 24369 e.preventDefault(); 24370 editor.setContent(''); 24371 24372 if (body.firstChild && dom.isBlock(body.firstChild)) { 24373 editor.selection.setCursorLocation(body.firstChild, 0); 24374 } else { 24375 editor.selection.setCursorLocation(body, 0); 24376 } 24377 24378 editor.nodeChanged(); 24379 } 24380 }); 24381 } 24382 24383 /** 24384 * WebKit doesn't select all the nodes in the body when you press Ctrl+A. 24385 * IE selects more than the contents <body>[<p>a</p>]</body> instead of <body><p>[a]</p]</body> see bug #6438 24386 * This selects the whole body so that backspace/delete logic will delete everything 24387 */ 24388 function selectAll() { 24389 editor.on('keydown', function(e) { 24390 if (!isDefaultPrevented(e) && e.keyCode == 65 && VK.metaKeyPressed(e)) { 24391 e.preventDefault(); 24392 editor.execCommand('SelectAll'); 24393 } 24394 }); 24395 } 24396 24397 /** 24398 * WebKit has a weird issue where it some times fails to properly convert keypresses to input method keystrokes. 24399 * The IME on Mac doesn't initialize when it doesn't fire a proper focus event. 24400 * 24401 * This seems to happen when the user manages to click the documentElement element then the window doesn't get proper focus until 24402 * you enter a character into the editor. 24403 * 24404 * It also happens when the first focus in made to the body. 24405 * 24406 * See: https://bugs.webkit.org/show_bug.cgi?id=83566 24407 */ 24408 function inputMethodFocus() { 24409 if (!editor.settings.content_editable) { 24410 // Case 1 IME doesn't initialize if you focus the document 24411 dom.bind(editor.getDoc(), 'focusin', function() { 24412 selection.setRng(selection.getRng()); 24413 }); 24414 24415 // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event 24416 dom.bind(editor.getDoc(), 'mousedown', function(e) { 24417 if (e.target == editor.getDoc().documentElement) { 24418 editor.getWin().focus(); 24419 selection.setRng(selection.getRng()); 24420 } 24421 }); 24422 } 24423 } 24424 24425 /** 24426 * Backspacing in FireFox/IE from a paragraph into a horizontal rule results in a floating text node because the 24427 * browser just deletes the paragraph - the browser fails to merge the text node with a horizontal rule so it is 24428 * left there. TinyMCE sees a floating text node and wraps it in a paragraph on the key up event (ForceBlocks.js 24429 * addRootBlocks), meaning the action does nothing. With this code, FireFox/IE matche the behaviour of other 24430 * browsers. 24431 * 24432 * It also fixes a bug on Firefox where it's impossible to delete HR elements. 24433 */ 24434 function removeHrOnBackspace() { 24435 editor.on('keydown', function(e) { 24436 if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) { 24437 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { 24438 var node = selection.getNode(); 24439 var previousSibling = node.previousSibling; 24440 24441 if (node.nodeName == 'HR') { 24442 dom.remove(node); 24443 e.preventDefault(); 24444 return; 24445 } 24446 24447 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") { 24448 dom.remove(previousSibling); 24449 e.preventDefault(); 24450 } 24451 } 24452 } 24453 }); 24454 } 24455 24456 /** 24457 * Firefox 3.x has an issue where the body element won't get proper focus if you click out 24458 * side it's rectangle. 24459 */ 24460 function focusBody() { 24461 // Fix for a focus bug in FF 3.x where the body element 24462 // wouldn't get proper focus if the user clicked on the HTML element 24463 if (!window.Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4 24464 editor.on('mousedown', function(e) { 24465 if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") { 24466 var body = editor.getBody(); 24467
24468 // Blur the body it's focused but not correctly focused 24469 body.blur(); 24470 24471 // Refocus the body after a little while 24472 setTimeout(function() { 24473 body.focus(); 24474 }, 0); 24475 } 24476 }); 24477 } 24478 } 24479 24480 /** 24481 * WebKit has a bug where it isn't possible to select image, hr or anchor elements 24482 * by clicking on them so we need to fake that. 24483 */ 24484 function selectControlElements() { 24485 editor.on('click', function(e) { 24486 e = e.target; 24487 24488 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250 24489 // WebKit can't even do simple things like selecting an image 24490 // Needs tobe the setBaseAndExtend or it will fail to select floated images 24491 if (/^(IMG|HR)$/.test(e.nodeName)) { 24492 selection.getSel().setBaseAndExtent(e, 0, e, 1); 24493 } 24494 24495 if (e.nodeName == 'A' && dom.hasClass(e, 'mce-item-anchor')) { 24496 selection.select(e); 24497 } 24498 24499 editor.nodeChanged(); 24500 }); 24501 } 24502 24503 /** 24504 * Fixes a Gecko bug where the style attribute gets added to the wrong element when deleting between two block elements. 24505 * 24506 * Fixes do backspace/delete on this: 24507 * <p>bla[ck</p><p style="color:red">r]ed</p> 24508 * 24509 * Would become: 24510 * <p>bla|ed</p> 24511 * 24512 * Instead of: 24513 * <p style="color:red">bla|ed</p> 24514 */ 24515 function removeStylesWhenDeletingAcrossBlockElements() { 24516 function getAttributeApplyFunction() { 24517 var template = dom.getAttribs(selection.getStart().cloneNode(false)); 24518 24519 return function() { 24520 var target = selection.getStart(); 24521 24522 if (target !== editor.getBody()) { 24523 dom.setAttrib(target, "style", null); 24524 24525 each(template, function(attr) { 24526 target.setAttributeNode(attr.cloneNode(true)); 24527 }); 24528 } 24529 }; 24530 } 24531 24532 function isSelectionAcrossElements() { 24533 return !selection.isCollapsed() && 24534 dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock); 24535 } 24536 24537 editor.on('keypress', function(e) { 24538 var applyAttributes; 24539 24540 if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) { 24541 applyAttributes = getAttributeApplyFunction(); 24542 editor.getDoc().execCommand('delete', false, null); 24543 applyAttributes(); 24544 e.preventDefault(); 24545 return false; 24546 } 24547 }); 24548 24549 dom.bind(editor.getDoc(), 'cut', function(e) { 24550 var applyAttributes; 24551 24552 if (!isDefaultPrevented(e) && isSelectionAcrossElements()) { 24553 applyAttributes = getAttributeApplyFunction(); 24554 24555 setTimeout(function() { 24556 applyAttributes(); 24557 }, 0); 24558 } 24559 }); 24560 } 24561 24562 /** 24563 * Fire a nodeChanged when the selection is changed on WebKit this fixes selection issues on iOS5. It only fires the nodeChange 24564 * event every 50ms since it would other wise update the UI when you type and it hogs the CPU. 24565 */ 24566 function selectionChangeNodeChanged() { 24567 var lastRng, selectionTimer; 24568 24569 editor.on('selectionchange', function() { 24570 if (selectionTimer) { 24571 clearTimeout(selectionTimer); 24572 selectionTimer = 0; 24573 } 24574 24575 selectionTimer = window.setTimeout(function() { 24576 var rng = selection.getRng(); 24577 24578 // Compare the ranges to see if it was a real change or not 24579 if (!lastRng || !RangeUtils.compareRanges(rng, lastRng)) { 24580 editor.nodeChanged(); 24581 lastRng = rng; 24582 } 24583 }, 50); 24584 }); 24585 } 24586 24587 /** 24588 * Screen readers on IE needs to have the role application set on the body. 24589 */ 24590 function ensureBodyHasRoleApplication() { 24591 document.body.setAttribute("role", "application"); 24592 } 24593 24594 /** 24595 * Backspacing into a table behaves differently depending upon browser type. 24596 * Therefore, disable Backspace when cursor immediately follows a table. 24597 */ 24598 function disableBackspaceIntoATable() { 24599 editor.on('keydown', function(e) { 24600 if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) { 24601 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { 24602 var previousSibling = selection.getNode().previousSibling; 24603 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") { 24604 e.preventDefault(); 24605 return false; 24606 } 24607 } 24608 } 24609 }); 24610 } 24611 24612 /** 24613 * Old IE versions can't properly render BR elements in PRE tags white in contentEditable mode. So this 24614 * logic adds a \n before the BR so that it will get rendered. 24615 */
24616 function addNewLinesBeforeBrInPre() { 24617 // IE8+ rendering mode does the right thing with BR in PRE 24618 if (getDocumentMode() > 7) { 24619 return; 24620 } 24621 24622 // Enable display: none in area and add a specific class that hides all BR elements in PRE to 24623 // avoid the caret from getting stuck at the BR elements while pressing the right arrow key 24624 setEditorCommandState('RespectVisibilityInDesign', true); 24625 editor.contentStyles.push('.mceHideBrInPre pre br {display: none}'); 24626 dom.addClass(editor.getBody(), 'mceHideBrInPre'); 24627 24628 // Adds a \n before all BR elements in PRE to get them visual 24629 parser.addNodeFilter('pre', function(nodes) { 24630 var i = nodes.length, brNodes, j, brElm, sibling; 24631 24632 while (i--) { 24633 brNodes = nodes[i].getAll('br'); 24634 j = brNodes.length; 24635 while (j--) { 24636 brElm = brNodes[j]; 24637 24638 // Add \n before BR in PRE elements on older IE:s so the new lines get rendered 24639 sibling = brElm.prev; 24640 if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') { 24641 sibling.value += '\n'; 24642 } else { 24643 brElm.parent.insert(new Node('#text', 3), brElm, true).value = '\n'; 24644 } 24645 } 24646 } 24647 }); 24648 24649 // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible 24650 serializer.addNodeFilter('pre', function(nodes) { 24651 var i = nodes.length, brNodes, j, brElm, sibling; 24652 24653 while (i--) { 24654 brNodes = nodes[i].getAll('br'); 24655 j = brNodes.length; 24656 while (j--) { 24657 brElm = brNodes[j]; 24658 sibling = brElm.prev; 24659 if (sibling && sibling.type == 3) { 24660 sibling.value = sibling.value.replace(/\r?\n$/, ''); 24661 } 24662 } 24663 } 24664 }); 24665 } 24666 24667 /** 24668 * Moves style width/height to attribute width/height when the user resizes an image on IE. 24669 */ 24670 function removePreSerializedStylesWhenSelectingControls() { 24671 dom.bind(editor.getBody(), 'mouseup', function() { 24672 var value, node = selection.getNode(); 24673 24674 // Moved styles to attributes on IMG eements 24675 if (node.nodeName == 'IMG') { 24676 // Convert style width to width attribute 24677 if ((value = dom.getStyle(node, 'width'))) { 24678 dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, '')); 24679 dom.setStyle(node, 'width', ''); 24680 } 24681 24682 // Convert style height to height attribute 24683 if ((value = dom.getStyle(node, 'height'))) { 24684 dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, '')); 24685 dom.setStyle(node, 'height', ''); 24686 } 24687 } 24688 }); 24689 } 24690 24691 /** 24692 * Removes a blockquote when backspace is pressed at the beginning of it. 24693 * 24694 * For example: 24695 * <blockquote><p>|x</p></blockquote> 24696 * 24697 * Becomes: 24698 * <p>|x</p> 24699 */ 24700 function removeBlockQuoteOnBackSpace() { 24701 // Add block quote deletion handler 24702 editor.on('keydown', function(e) { 24703 var rng, container, offset, root, parent; 24704 24705 if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) { 24706 return; 24707 } 24708 24709 rng = selection.getRng(); 24710 container = rng.startContainer; 24711 offset = rng.startOffset; 24712 root = dom.getRoot(); 24713 parent = container; 24714 24715 if (!rng.collapsed || offset !== 0) { 24716 return; 24717 } 24718 24719 while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) { 24720 parent = parent.parentNode; 24721 } 24722 24723 // Is the cursor at the beginning of a blockquote? 24724 if (parent.tagName === 'BLOCKQUOTE') { 24725 // Remove the blockquote 24726 editor.formatter.toggle('blockquote', null, parent); 24727 24728 // Move the caret to the beginning of container 24729 rng = dom.createRng(); 24730 rng.setStart(container, 0); 24731 rng.setEnd(container, 0); 24732 selection.setRng(rng); 24733 } 24734 }); 24735 } 24736 24737 /** 24738 * Sets various Gecko editing options on mouse down and before a execCommand to disable inline table editing that is broken etc. 24739 */ 24740 function setGeckoEditingOptions() { 24741 function setOpts() { 24742 editor._refreshContentEditable(); 24743 24744 setEditorCommandState("StyleWithCSS", false); 24745 setEditorCommandState("enableInlineTableEditing", false); 24746 24747 if (!settings.object_resizing) { 24748 setEditorCommandState("enableObjectResizing", false); 24749 } 24750 } 24751 24752 if (!settings.readonly) { 24753 editor.on('BeforeExecCommand MouseDown', setOpts); 24754 } 24755 } 24756 24757 /** 24758 * Fixes a gecko link bug, when a link is placed at the end of block elements there is 24759 * no way to move the caret behind the link. This fix adds a bogus br element after the link. 24760 * 24761 * For example this: 24762 * <p><b><a href="#">x</a></b></p> 24763 * 24764 * Becomes this: 24765 * <p><b><a href="#">x</a></b><br></p> 24766 */ 24767 function addBrAfterLastLinks() { 24768 function fixLinks() { 24769 each(dom.select('a'), function(node) { 24770 var parentNode = node.parentNode, root = dom.getRoot(); 24771
24772 if (parentNode.lastChild === node) { 24773 while (parentNode && !dom.isBlock(parentNode)) { 24774 if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) { 24775 return; 24776 } 24777 24778 parentNode = parentNode.parentNode; 24779 } 24780 24781 dom.add(parentNode, 'br', {'data-mce-bogus': 1}); 24782 } 24783 }); 24784 } 24785 24786 editor.on('SetContent ExecCommand', function(e) { 24787 if (e.type == "setcontent" || e.command === 'mceInsertLink') { 24788 fixLinks(); 24789 } 24790 }); 24791 } 24792 24793 /** 24794 * WebKit will produce DIV elements here and there by default. But since TinyMCE uses paragraphs by 24795 * default we want to change that behavior. 24796 */ 24797 function setDefaultBlockType() { 24798 if (settings.forced_root_block) { 24799 editor.on('init', function() { 24800 setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block); 24801 }); 24802 } 24803 } 24804 24805 /** 24806 * Removes ghost selections from images/tables on Gecko. 24807 */ 24808 function removeGhostSelection() { 24809 editor.on('Undo Redo SetContent', function(e) { 24810 if (!e.initial) { 24811 editor.execCommand('mceRepaint'); 24812 } 24813 }); 24814 } 24815 24816 /** 24817 * Deletes the selected image on IE instead of navigating to previous page. 24818 */ 24819 function deleteControlItemOnBackSpace() { 24820 editor.on('keydown', function(e) { 24821 var rng; 24822 24823 if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) { 24824 rng = editor.getDoc().selection.createRange(); 24825 if (rng && rng.item) { 24826 e.preventDefault(); 24827 editor.undoManager.beforeChange(); 24828 dom.remove(rng.item(0)); 24829 editor.undoManager.add(); 24830 } 24831 } 24832 }); 24833 } 24834 24835 /** 24836 * IE10 doesn't properly render block elements with the right height until you add contents to them. 24837 * This fixes that by adding a padding-right to all empty text block elements. 24838 * See: https://connect.microsoft.com/IE/feedback/details/743881 24839 */ 24840 function renderEmptyBlocksFix() { 24841 var emptyBlocksCSS; 24842 24843 // IE10+ 24844 if (getDocumentMode() >= 10) { 24845 emptyBlocksCSS = ''; 24846 each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) { 24847 emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty'; 24848 }); 24849 24850 editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}'); 24851 } 24852 } 24853 24854 /** 24855 * Old IE versions can't retain contents within noscript elements so this logic will store the contents 24856 * as a attribute and the insert that value as it's raw text when the DOM is serialized. 24857 */ 24858 function keepNoScriptContents() { 24859 if (getDocumentMode() < 9) { 24860 parser.addNodeFilter('noscript', function(nodes) { 24861 var i = nodes.length, node, textNode; 24862 24863 while (i--) { 24864 node = nodes[i]; 24865 textNode = node.firstChild; 24866 24867 if (textNode) { 24868 node.attr('data-mce-innertext', textNode.value); 24869 } 24870 } 24871 }); 24872 24873 serializer.addNodeFilter('noscript', function(nodes) { 24874 var i = nodes.length, node, textNode, value; 24875 24876 while (i--) { 24877 node = nodes[i]; 24878 textNode = nodes[i].firstChild; 24879 24880 if (textNode) { 24881 textNode.value = Entities.decode(textNode.value); 24882 } else { 24883 // Old IE can't retain noscript value so an attribute is used to store it 24884 value = node.attributes.map['data-mce-innertext']; 24885 if (value) { 24886 node.attr('data-mce-innertext', null); 24887 textNode = new Node('#text', 3); 24888 textNode.value = value; 24889 textNode.raw = true; 24890 node.append(textNode); 24891 } 24892 } 24893 } 24894 }); 24895 } 24896 } 24897 24898 /** 24899 * IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode. 24900 */ 24901 function fixCaretSelectionOfDocumentElementOnIe() { 24902 var doc = dom.doc, body = doc.body, started, startRng, htmlElm; 24903 24904 // Return range from point or null if it failed 24905 function rngFromPoint(x, y) { 24906 var rng = body.createTextRange(); 24907 24908 try { 24909 rng.moveToPoint(x, y); 24910 } catch (ex) { 24911 // IE sometimes throws and exception, so lets just ignore it 24912 rng = null; 24913 } 24914 24915 return rng; 24916 } 24917 24918 // Fires while the selection is changing 24919 function selectionChange(e) { 24920 var pointRng; 24921 24922 // Check if the button is down or not 24923 if (e.button) { 24924 // Create range from mouse position 24925 pointRng = rngFromPoint(e.x, e.y); 24926 24927 if (pointRng) { 24928 // Check if pointRange is before/after selection then change the endPoint 24929 if (pointRng.compareEndPoints('StartToStart', startRng) > 0) { 24930 pointRng.setEndPoint('StartToStart', startRng); 24931 } else { 24932 pointRng.setEndPoint('EndToEnd', startRng); 24933 } 24934 24935 pointRng.select(); 24936 } 24937 } else { 24938 endSelection(); 24939 } 24940 } 24941 24942 // Removes listeners 24943 function endSelection() { 24944 var rng = doc.selection.createRange(); 24945 24946 // If the range is collapsed then use the last start range 24947 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) { 24948 startRng.select(); 24949 } 24950 24951 dom.unbind(doc, 'mouseup', endSelection); 24952 dom.unbind(doc, 'mousemove', selectionChange); 24953 startRng = started = 0; 24954 } 24955 24956 // Make HTML element unselectable since we are going to handle selection by hand 24957 doc.documentElement.unselectable = true; 24958 24959 // Detect when user selects outside BODY 24960 dom.bind(doc, 'mousedown contextmenu', function(e) { 24961 if (e.target.nodeName === 'HTML') { 24962 if (started) { 24963 endSelection(); 24964 } 24965 24966 // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML 24967 htmlElm = doc.documentElement; 24968 if (htmlElm.scrollHeight > htmlElm.clientHeight) { 24969 return; 24970 } 24971 24972 started = 1; 24973 // Setup start position 24974 startRng = rngFromPoint(e.x, e.y); 24975 if (startRng) { 24976 // Listen for selection change events 24977 dom.bind(doc, 'mouseup', endSelection); 24978 dom.bind(doc, 'mousemove', selectionChange); 24979 24980 dom.win.focus(); 24981 startRng.select(); 24982 } 24983 } 24984 }); 24985 } 24986 24987 /** 24988 * Fixes selection issues on Gecko where the caret can be placed between two inline elements like <b>a</b>|<b>b</b> 24989 * this fix will lean the caret right into the closest inline element. 24990 */ 24991 function normalizeSelection() { 24992 // 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
24993 editor.on('keyup focusin', function(e) { 24994 if (e.keyCode != 65 || !VK.metaKeyPressed(e)) { 24995 selection.normalize(); 24996 } 24997 }); 24998 } 24999 25000 /** 25001 * Forces Gecko to render a broken image icon if it fails to load an image. 25002 */ 25003 function showBrokenImageIcon() { 25004 editor.contentStyles.push( 25005 'img:-moz-broken {' + 25006 '-moz-force-broken-image-icon:1;' + 25007 'min-width:24px;' + 25008 'min-height:24px' + 25009 '}' 25010 ); 25011 } 25012 25013 /** 25014 * iOS has a bug where it's impossible to type if the document has a touchstart event 25015 * bound and the user touches the document while having the on screen keyboard visible. 25016 * 25017 * The touch event moves the focus to the parent document while having the caret inside the iframe 25018 * this fix moves the focus back into the iframe document. 25019 */ 25020 function restoreFocusOnKeyDown() { 25021 if (!editor.inline) { 25022 editor.on('keydown', function() { 25023 if (document.activeElement == document.body) { 25024 editor.getWin().focus(); 25025 } 25026 }); 25027 } 25028 } 25029 25030 /** 25031 * IE 11 has an annoying issue where you can't move focus into the editor 25032 * by clicking on the white area HTML element. We used to be able to to fix this with 25033 * the fixCaretSelectionOfDocumentElementOnIe fix. But since M$ removed the selection 25034 * object it's not possible anymore. So we need to hack in a ungly CSS to force the 25035 * body to be at least 150px. If the user clicks the HTML element out side this 150px region 25036 * we simply move the focus into the first paragraph. Not ideal since you loose the 25037 * positioning of the caret but goot enough for most cases. 25038 */ 25039 function bodyHeight() { 25040 if (!editor.inline) { 25041 editor.contentStyles.push('body {min-height: 150px}'); 25042 editor.on('click', function(e) { 25043 if (e.target.nodeName == 'HTML') { 25044 editor.execCommand('SelectAll'); 25045 editor.selection.collapse(true); 25046 editor.nodeChanged(); 25047 } 25048 }); 25049 } 25050 } 25051 25052 /** 25053 * Firefox on Mac OS will move the browser back to the previous page if you press CMD+Left arrow. 25054 * You might then loose all your work so we need to block that behavior and replace it with our own. 25055 */ 25056 function blockCmdArrowNavigation() { 25057 if (Env.mac) { 25058 editor.on('keydown', function(e) { 25059 if (VK.metaKeyPressed(e) && (e.keyCode == 37 || e.keyCode == 39)) { 25060 e.preventDefault(); 25061 editor.selection.getSel().modify('move', e.keyCode == 37 ? 'backward' : 'forward', 'word'); 25062 } 25063 }); 25064 } 25065 } 25066 25067 // All browsers 25068 disableBackspaceIntoATable(); 25069 removeBlockQuoteOnBackSpace(); 25070 emptyEditorWhenDeleting(); 25071 normalizeSelection(); 25072 25073 // WebKit 25074 if (isWebKit) { 25075 cleanupStylesWhenDeleting(); 25076 inputMethodFocus(); 25077 selectControlElements(); 25078 setDefaultBlockType(); 25079 25080 // iOS 25081 if (Env.iOS) { 25082 selectionChangeNodeChanged(); 25083 restoreFocusOnKeyDown(); 25084 bodyHeight(); 25085 } else { 25086 selectAll(); 25087 } 25088 } 25089 25090 // IE 25091 if (isIE && Env.ie < 11) { 25092 removeHrOnBackspace(); 25093 ensureBodyHasRoleApplication(); 25094 addNewLinesBeforeBrInPre(); 25095 removePreSerializedStylesWhenSelectingControls(); 25096 deleteControlItemOnBackSpace(); 25097 renderEmptyBlocksFix(); 25098 keepNoScriptContents(); 25099 fixCaretSelectionOfDocumentElementOnIe(); 25100 } 25101 25102 if (Env.ie >= 11) { 25103 bodyHeight(); 25104 } 25105 25106 if (Env.ie) { 25107 selectAll(); 25108 } 25109 25110 // Gecko 25111 if (isGecko) { 25112 removeHrOnBackspace(); 25113 focusBody(); 25114 removeStylesWhenDeletingAcrossBlockElements(); 25115 setGeckoEditingOptions(); 25116 addBrAfterLastLinks(); 25117 removeGhostSelection(); 25118 showBrokenImageIcon(); 25119 blockCmdArrowNavigation(); 25120 } 25121 }; 25122}); 25123 25124// Included from: js/tinymce/classes/util/Observable.js 25125 25126/** 25127 * Observable.js 25128 * 25129 * Copyright, Moxiecode Systems AB 25130 * Released under LGPL License. 25131 * 25132 * License: http://www.tinymce.com/license 25133 * Contributing: http://www.tinymce.com/contributing 25134 */ 25135 25136/** 25137 * This mixin will add event binding logic to classes. 25138 * 25139 * @mixin tinymce.util.Observable 25140 */ 25141define("tinymce/util/Observable", [ 25142 "tinymce/util/Tools" 25143], function(Tools) { 25144 var bindingsName = "__bindings"; 25145 var nativeEvents = Tools.makeMap( 25146 "focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange" + 25147 " mouseout mouseenter mouseleave keydown keypress keyup contextmenu dragstart dragend dragover draggesture dragdrop drop drag", ' ' 25148 ); 25149 25150 function returnFalse() { 25151 return false;
25152 } 25153 25154 function returnTrue() { 25155 return true; 25156 } 25157 25158 return { 25159 /** 25160 * Fires the specified event by name. 25161 * 25162 * @method fire 25163 * @param {String} name Name of the event to fire. 25164 * @param {tinymce.Event/Object?} args Event arguments. 25165 * @param {Boolean?} bubble True/false if the event is to be bubbled. 25166 * @return {tinymce.Event} Event instance passed in converted into tinymce.Event instance. 25167 * @example 25168 * instance.fire('event', {...}); 25169 */ 25170 fire: function(name, args, bubble) { 25171 var self = this, handlers, i, l, callback, parent; 25172 25173 name = name.toLowerCase(); 25174 args = args || {}; 25175 args.type = name; 25176 25177 // Setup target is there isn't one 25178 if (!args.target) { 25179 args.target = self; 25180 } 25181 25182 // Add event delegation methods if they are missing 25183 if (!args.preventDefault) { 25184 // Add preventDefault method 25185 args.preventDefault = function() { 25186 args.isDefaultPrevented = returnTrue; 25187 }; 25188 25189 // Add stopPropagation 25190 args.stopPropagation = function() { 25191 args.isPropagationStopped = returnTrue; 25192 }; 25193 25194 // Add stopImmediatePropagation 25195 args.stopImmediatePropagation = function() { 25196 args.isImmediatePropagationStopped = returnTrue; 25197 }; 25198 25199 // Add event delegation states 25200 args.isDefaultPrevented = returnFalse; 25201 args.isPropagationStopped = returnFalse; 25202 args.isImmediatePropagationStopped = returnFalse; 25203 } 25204 25205 //console.log(name, args); 25206 25207 if (self[bindingsName]) { 25208 handlers = self[bindingsName][name]; 25209 25210 if (handlers) { 25211 for (i = 0, l = handlers.length; i < l; i++) { 25212 handlers[i] = callback = handlers[i]; 25213 25214 // Stop immediate propagation if needed 25215 if (args.isImmediatePropagationStopped()) { 25216 break; 25217 } 25218 25219 // If callback returns false then prevent default and stop all propagation 25220 if (callback.call(self, args) === false) { 25221 args.preventDefault(); 25222 return args; 25223 } 25224 } 25225 } 25226 } 25227 25228 // Bubble event up to parents 25229 if (bubble !== false && self.parent) { 25230 parent = self.parent(); 25231 while (parent && !args.isPropagationStopped()) { 25232 parent.fire(name, args, false); 25233 parent = parent.parent(); 25234 } 25235 } 25236 25237 return args; 25238 }, 25239 25240 /** 25241 * Binds an event listener to a specific event by name. 25242 * 25243 * @method on 25244 * @param {String} name Event name or space separated list of events to bind. 25245 * @param {callback} callback Callback to be executed when the event occurs. 25246 * @return {Object} Current class instance. 25247 * @example 25248 * instance.on('event', function(e) { 25249 * // Callback logic 25250 * }); 25251 */ 25252 on: function(name, callback) { 25253 var self = this, bindings, handlers, names, i; 25254 25255 if (callback === false) { 25256 callback = function() { 25257 return false; 25258 }; 25259 } 25260 25261 if (callback) { 25262 names = name.toLowerCase().split(' '); 25263 i = names.length; 25264 while (i--) { 25265 name = names[i]; 25266 25267 bindings = self[bindingsName]; 25268 if (!bindings) { 25269 bindings = self[bindingsName] = {}; 25270 } 25271 25272 handlers = bindings[name]; 25273 if (!handlers) { 25274 handlers = bindings[name] = []; 25275 if (self.bindNative && nativeEvents[name]) { 25276 self.bindNative(name); 25277 } 25278 } 25279 25280 handlers.push(callback); 25281 } 25282 } 25283 25284 return self; 25285 }, 25286 25287 /** 25288 * Unbinds an event listener to a specific event by name. 25289 * 25290 * @method off 25291 * @param {String?} name Name of the event to unbind. 25292 * @param {callback?} callback Callback to unbind. 25293 * @return {Object} Current class instance. 25294 * @example 25295 * // Unbind specific callback 25296 * instance.off('event', handler); 25297 * 25298 * // Unbind all listeners by name 25299 * instance.off('event'); 25300 * 25301 * // Unbind all events 25302 * instance.off(); 25303 */ 25304 off: function(name, callback) { 25305 var self = this, i, bindings = self[bindingsName], handlers, bindingName, names, hi; 25306 25307 if (bindings) { 25308 if (name) { 25309 names = name.toLowerCase().split(' '); 25310 i = names.length; 25311 while (i--) { 25312 name = names[i]; 25313 handlers = bindings[name]; 25314 25315 // Unbind all handlers 25316 if (!name) { 25317 for (bindingName in bindings) { 25318 bindings[name].length = 0; 25319 } 25320 25321 return self; 25322 } 25323 25324 if (handlers) { 25325 // Unbind all by name 25326 if (!callback) { 25327 handlers.length = 0; 25328 } else { 25329 // Unbind specific ones 25330 hi = handlers.length; 25331 while (hi--) { 25332 if (handlers[hi] === callback) { 25333 handlers.splice(hi, 1); 25334 } 25335 } 25336 } 25337 25338 if (!handlers.length && self.unbindNative && nativeEvents[name]) { 25339 self.unbindNative(name); 25340 delete bindings[name]; 25341 } 25342 } 25343 } 25344 } else { 25345 if (self.unbindNative) { 25346 for (name in bindings) { 25347 self.unbindNative(name); 25348 } 25349 } 25350 25351 self[bindingsName] = []; 25352 } 25353 } 25354 25355 return self; 25356 }, 25357 25358 hasEventListeners: function(name) { 25359 var bindings = this[bindingsName]; 25360 25361 name = name.toLowerCase(); 25362 25363 return !(!bindings || !bindings[name] || bindings[name].length === 0); 25364 } 25365 }; 25366}); 25367 25368// Included from: js/tinymce/classes/Shortcuts.js 25369 25370/** 25371 * Shortcuts.js 25372 * 25373 * Copyright, Moxiecode Systems AB 25374 * Released under LGPL License. 25375 * 25376 * License: http://www.tinymce.com/license 25377 * Contributing: http://www.tinymce.com/contributing 25378 */ 25379 25380/** 25381 * Contains all logic for handling of keyboard shortcuts. 25382 */ 25383define("tinymce/Shortcuts", [ 25384 "tinymce/util/Tools", 25385 "tinymce/Env" 25386], function(Tools, Env) { 25387 var each = Tools.each, explode = Tools.explode; 25388 25389 var keyCodeLookup = { 25390 "f9": 120, 25391 "f10": 121, 25392 "f11": 122 25393 }; 25394 25395 return function(editor) { 25396 var self = this, shortcuts = {}; 25397
25398 editor.on('keyup keypress keydown', function(e) { 25399 if (e.altKey || e.ctrlKey || e.metaKey) { 25400 each(shortcuts, function(shortcut) { 25401 var ctrlKey = Env.mac ? e.metaKey : e.ctrlKey; 25402 25403 if (shortcut.ctrl != ctrlKey || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) { 25404 return; 25405 } 25406 25407 if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) { 25408 e.preventDefault(); 25409 25410 if (e.type == "keydown") { 25411 shortcut.func.call(shortcut.scope); 25412 } 25413 25414 return true; 25415 } 25416 }); 25417 } 25418 }); 25419 25420 /** 25421 * Adds a keyboard shortcut for some command or function. 25422 * 25423 * @method addShortcut 25424 * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o. 25425 * @param {String} desc Text description for the command. 25426 * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed. 25427 * @param {Object} sc Optional scope to execute the function in. 25428 * @return {Boolean} true/false state if the shortcut was added or not. 25429 */ 25430 self.add = function(pattern, desc, cmdFunc, scope) { 25431 var cmd; 25432 25433 cmd = cmdFunc; 25434 25435 if (typeof(cmdFunc) === 'string') { 25436 cmdFunc = function() { 25437 editor.execCommand(cmd, false, null); 25438 }; 25439 } else if (Tools.isArray(cmd)) { 25440 cmdFunc = function() { 25441 editor.execCommand(cmd[0], cmd[1], cmd[2]); 25442 }; 25443 } 25444 25445 each(explode(pattern.toLowerCase()), function(pattern) { 25446 var shortcut = { 25447 func: cmdFunc, 25448 scope: scope || editor, 25449 desc: editor.translate(desc), 25450 alt: false, 25451 ctrl: false, 25452 shift: false 25453 }; 25454 25455 each(explode(pattern, '+'), function(value) { 25456 switch (value) { 25457 case 'alt': 25458 case 'ctrl': 25459 case 'shift': 25460 shortcut[value] = true; 25461 break; 25462 25463 default: 25464 shortcut.charCode = value.charCodeAt(0); 25465 shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0); 25466 } 25467 }); 25468 25469 shortcuts[ 25470 (shortcut.ctrl ? 'ctrl' : '') + ',' + 25471 (shortcut.alt ? 'alt' : '') + ',' + 25472 (shortcut.shift ? 'shift' : '') + ',' + 25473 shortcut.keyCode 25474 ] = shortcut; 25475 }); 25476 25477 return true; 25478 }; 25479 }; 25480}); 25481 25482// Included from: js/tinymce/classes/Editor.js 25483 25484/** 25485 * Editor.js 25486 * 25487 * Copyright, Moxiecode Systems AB 25488 * Released under LGPL License. 25489 * 25490 * License: http://www.tinymce.com/license 25491 * Contributing: http://www.tinymce.com/contributing 25492 */ 25493 25494/*jshint scripturl:true */ 25495 25496/** 25497 * Include the base event class documentation. 25498 * 25499 * @include ../../../tools/docs/tinymce.Event.js 25500 */ 25501 25502/** 25503 * This class contains the core logic for a TinyMCE editor. 25504 * 25505 * @class tinymce.Editor 25506 * @mixes tinymce.util.Observable 25507 * @example 25508 * // Add a class to all paragraphs in the editor. 25509 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass'); 25510 * 25511 * // Gets the current editors selection as text 25512 * tinymce.activeEditor.selection.getContent({format: 'text'}); 25513 * 25514 * // Creates a new editor instance 25515 * var ed = new tinymce.Editor('textareaid', { 25516 * some_setting: 1 25517 * }, tinymce.EditorManager); 25518 * 25519 * // Select each item the user clicks on 25520 * ed.on('click', function(e) { 25521 * ed.selection.select(e.target); 25522 * }); 25523 * 25524 * ed.render(); 25525 */ 25526define("tinymce/Editor", [ 25527 "tinymce/dom/DOMUtils", 25528 "tinymce/AddOnManager", 25529 "tinymce/html/Node", 25530 "tinymce/dom/Serializer", 25531 "tinymce/html/Serializer", 25532 "tinymce/dom/Selection", 25533 "tinymce/Formatter", 25534 "tinymce/UndoManager", 25535 "tinymce/EnterKey", 25536 "tinymce/ForceBlocks", 25537 "tinymce/EditorCommands", 25538 "tinymce/util/URI", 25539 "tinymce/dom/ScriptLoader", 25540 "tinymce/dom/EventUtils", 25541 "tinymce/WindowManager", 25542 "tinymce/html/Schema", 25543 "tinymce/html/DomParser", 25544 "tinymce/util/Quirks", 25545 "tinymce/Env", 25546 "tinymce/util/Tools", 25547 "tinymce/util/Observable", 25548 "tinymce/Shortcuts" 25549], function( 25550 DOMUtils, AddOnManager, Node, DomSerializer, Serializer, 25551 Selection, Formatter, UndoManager, EnterKey, ForceBlocks, EditorCommands, 25552 URI, ScriptLoader, EventUtils, WindowManager, 25553 Schema, DomParser, Quirks, Env, Tools, Observable, Shortcuts 25554) { 25555 // Shorten these names 25556 var DOM = DOMUtils.DOM, ThemeManager = AddOnManager.ThemeManager, PluginManager = AddOnManager.PluginManager; 25557 var extend = Tools.extend, each = Tools.each, explode = Tools.explode; 25558 var inArray = Tools.inArray, trim = Tools.trim, resolve = Tools.resolve; 25559 var Event = EventUtils.Event; 25560 var isGecko = Env.gecko, ie = Env.ie; 25561 25562 function getEventTarget(editor, eventName) { 25563 if (eventName == 'selectionchange' || eventName == 'drop') { 25564 return editor.getDoc(); 25565 } 25566 25567 // Need to bind mousedown/mouseup etc to document not body in iframe mode 25568 // Since the user might click on the HTML element not the BODY 25569 if (!editor.inline && /^mouse|click|contextmenu/.test(eventName)) { 25570 return editor.getDoc(); 25571 } 25572 25573 return editor.getBody(); 25574 } 25575 25576 /** 25577 * Include documentation for all the events. 25578 * 25579 * @include ../../../tools/docs/tinymce.Editor.js 25580 */ 25581 25582 /** 25583 * Constructs a editor instance by id. 25584 * 25585 * @constructor 25586 * @method Editor 25587 * @param {String} id Unique id for the editor. 25588 * @param {Object} settings Settings for the editor. 25589 * @param {tinymce.EditorManager} editorManager EditorManager instance. 25590 * @author Moxiecode 25591 */ 25592 function Editor(id, settings, editorManager) { 25593 var self = this, documentBaseUrl, baseUri; 25594 25595 documentBaseUrl = self.documentBaseUrl = editorManager.documentBaseURL; 25596 baseUri = editorManager.baseURI; 25597 25598 /** 25599 * Name/value collection with editor settings. 25600 * 25601 * @property settings 25602 * @type Object 25603 * @example 25604 * // Get the value of the theme setting 25605 * tinymce.activeEditor.windowManager.alert("You are using the " + tinymce.activeEditor.settings.theme + " theme"); 25606 */ 25607 self.settings = settings = extend({ 25608 id: id, 25609 theme: 'modern', 25610 delta_width: 0, 25611 delta_height: 0, 25612 popup_css: '', 25613 plugins: '', 25614 document_base_url: documentBaseUrl, 25615 add_form_submit_trigger: true, 25616 submit_patch: true, 25617 add_unload_trigger: true, 25618 convert_urls: true, 25619 relative_urls: true, 25620 remove_script_host: true, 25621 object_resizing: true, 25622 doctype: '<!DOCTYPE html>', 25623 visual: true, 25624 font_size_style_values: 'xx-small,x-small,small,medium,large,x-large,xx-large', 25625 25626 // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size 25627 font_size_legacy_values: 'xx-small,small,medium,large,x-large,xx-large,300%', 25628 forced_root_block: 'p', 25629 hidden_input: true, 25630 padd_empty_editor: true, 25631 render_ui: true, 25632 indentation: '30px', 25633 inline_styles: true, 25634 convert_fonts_to_spans: true, 25635 indent: 'simple', 25636 indent_before: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,' +
25637 'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist', 25638 indent_after: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,' + 25639 'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist', 25640 validate: true, 25641 entity_encoding: 'named', 25642 url_converter: self.convertURL, 25643 url_converter_scope: self, 25644 ie7_compat: true 25645 }, settings); 25646 25647 AddOnManager.language = settings.language || 'en'; 25648 AddOnManager.languageLoad = settings.language_load; 25649 25650 AddOnManager.baseURL = editorManager.baseURL; 25651 25652 /** 25653 * Editor instance id, normally the same as the div/textarea that was replaced. 25654 * 25655 * @property id 25656 * @type String 25657 */ 25658 self.id = settings.id = id; 25659 25660 /** 25661 * State to force the editor to return false on a isDirty call. 25662 * 25663 * @property isNotDirty 25664 * @type Boolean 25665 * @example 25666 * function ajaxSave() { 25667 * var ed = tinymce.get('elm1'); 25668 * 25669 * // Save contents using some XHR call 25670 * alert(ed.getContent()); 25671 * 25672 * ed.isNotDirty = true; // Force not dirty state 25673 * } 25674 */ 25675 self.isNotDirty = true; 25676 25677 /** 25678 * Name/Value object containting plugin instances. 25679 * 25680 * @property plugins 25681 * @type Object 25682 * @example 25683 * // Execute a method inside a plugin directly 25684 * tinymce.activeEditor.plugins.someplugin.someMethod(); 25685 */ 25686 self.plugins = {}; 25687 25688 /** 25689 * URI object to document configured for the TinyMCE instance. 25690 * 25691 * @property documentBaseURI 25692 * @type tinymce.util.URI 25693 * @example 25694 * // Get relative URL from the location of document_base_url 25695 * tinymce.activeEditor.documentBaseURI.toRelative('/somedir/somefile.htm'); 25696 * 25697 * // Get absolute URL from the location of document_base_url 25698 * tinymce.activeEditor.documentBaseURI.toAbsolute('somefile.htm'); 25699 */ 25700 self.documentBaseURI = new URI(settings.document_base_url || documentBaseUrl, { 25701 base_uri: baseUri 25702 }); 25703 25704 /** 25705 * URI object to current document that holds the TinyMCE editor instance. 25706 * 25707 * @property baseURI 25708 * @type tinymce.util.URI 25709 * @example 25710 * // Get relative URL from the location of the API 25711 * tinymce.activeEditor.baseURI.toRelative('/somedir/somefile.htm'); 25712 * 25713 * // Get absolute URL from the location of the API 25714 * tinymce.activeEditor.baseURI.toAbsolute('somefile.htm'); 25715 */ 25716 self.baseURI = baseUri; 25717 25718 /** 25719 * Array with CSS files to load into the iframe. 25720 * 25721 * @property contentCSS 25722 * @type Array 25723 */ 25724 self.contentCSS = []; 25725 25726 /** 25727 * Array of CSS styles to add to head of document when the editor loads. 25728 * 25729 * @property contentStyles 25730 * @type Array 25731 */ 25732 self.contentStyles = []; 25733 25734 // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic 25735 self.shortcuts = new Shortcuts(self); 25736 25737 // Internal command handler objects 25738 self.execCommands = {}; 25739 self.queryStateCommands = {}; 25740 self.queryValueCommands = {}; 25741 self.loadedCSS = {}; 25742 25743 self.suffix = editorManager.suffix; 25744 self.editorManager = editorManager; 25745 self.inline = settings.inline; 25746 25747 // Call setup 25748 self.execCallback('setup', self); 25749 editorManager.fire('SetupEditor', self); 25750 } 25751 25752 Editor.prototype = { 25753 /** 25754 * Renderes the editor/adds it to the page. 25755 * 25756 * @method render 25757 */ 25758 render: function() { 25759 var self = this, settings = self.settings, id = self.id, suffix = self.suffix; 25760 25761 function readyHandler() { 25762 DOM.unbind(window, 'ready', readyHandler); 25763 self.render(); 25764 } 25765 25766 // Page is not loaded yet, wait for it 25767 if (!Event.domLoaded) { 25768 DOM.bind(window, 'ready', readyHandler); 25769 return; 25770 } 25771 25772 // Element not found, then skip initialization 25773 if (!self.getElement()) { 25774 return; 25775 } 25776 25777 // No editable support old iOS versions etc 25778 if (!Env.contentEditable) { 25779 return; 25780 } 25781 25782 // Hide target element early to prevent content flashing 25783 if (!settings.inline) { 25784 self.orgVisibility = self.getElement().style.visibility; 25785 self.getElement().style.visibility = 'hidden'; 25786 } else { 25787 self.inline = true; 25788 } 25789 25790 var form = self.getElement().form || DOM.getParent(id, 'form'); 25791 if (form) { 25792 self.formElement = form; 25793 25794 // Add hidden input for non input elements inside form elements 25795 if (settings.hidden_input && !/TEXTAREA|INPUT/i.test(self.getElement().nodeName)) { 25796 DOM.insertAfter(DOM.create('input', {type: 'hidden', name: id}), id); 25797 self.hasHiddenInput = true; 25798 } 25799 25800 // Pass submit/reset from form to editor instance 25801 self.formEventDelegate = function(e) { 25802 self.fire(e.type, e); 25803 }; 25804 25805 DOM.bind(form, 'submit reset', self.formEventDelegate); 25806 25807 // Reset contents in editor when the form is reset 25808 self.on('reset', function() { 25809 self.setContent(self.startContent, {format: 'raw'}); 25810 }); 25811 25812 // Check page uses id="submit" or name="submit" for it's submit button 25813 if (settings.submit_patch && !form.submit.nodeType && !form.submit.length && !form._mceOldSubmit) { 25814 form._mceOldSubmit = form.submit; 25815 form.submit = function() { 25816 self.editorManager.triggerSave(); 25817 self.isNotDirty = true; 25818 25819 return form._mceOldSubmit(form); 25820 }; 25821 } 25822 } 25823 25824 /** 25825 * Window manager reference, use this to open new windows and dialogs. 25826 * 25827 * @property windowManager 25828 * @type tinymce.WindowManager 25829 * @example 25830 * // Shows an alert message 25831 * tinymce.activeEditor.windowManager.alert('Hello world!'); 25832 * 25833 * // Opens a new dialog with the file.htm file and the size 320x240 25834 * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog. 25835 * tinymce.activeEditor.windowManager.open({ 25836 * url: 'file.htm', 25837 * width: 320, 25838 * height: 240 25839 * }, { 25840 * custom_param: 1 25841 * }); 25842 */ 25843 self.windowManager = new WindowManager(self); 25844 25845 if (settings.encoding == 'xml') { 25846 self.on('GetContent', function(e) { 25847 if (e.save) { 25848 e.content = DOM.encode(e.content); 25849 } 25850 }); 25851 } 25852 25853 if (settings.add_form_submit_trigger) { 25854 self.on('submit', function() { 25855 if (self.initialized) { 25856 self.save(); 25857 } 25858 }); 25859 } 25860 25861 if (settings.add_unload_trigger) { 25862 self._beforeUnload = function() { 25863 if (self.initialized && !self.destroyed && !self.isHidden()) { 25864 self.save({format: 'raw', no_events: true, set_dirty: false}); 25865 } 25866 }; 25867 25868 self.editorManager.on('BeforeUnload', self._beforeUnload); 25869 } 25870 25871 // Load scripts 25872 function loadScripts() { 25873 var scriptLoader = ScriptLoader.ScriptLoader; 25874 25875 if (settings.language && settings.language != 'en') { 25876 settings.language_url = self.editorManager.baseURL + '/langs/' + settings.language + '.js'; 25877 } 25878 25879 if (settings.language_url) { 25880 scriptLoader.add(settings.language_url); 25881 } 25882 25883 if (settings.theme && typeof settings.theme != "function" && 25884 settings.theme.charAt(0) != '-' && !ThemeManager.urls[settings.theme]) { 25885 var themeUrl = settings.theme_url; 25886 25887 if (themeUrl) { 25888 themeUrl = self.documentBaseURI.toAbsolute(themeUrl); 25889 } else { 25890 themeUrl = 'themes/' + settings.theme + '/theme' + suffix + '.js'; 25891 } 25892 25893 ThemeManager.load(settings.theme, themeUrl); 25894 } 25895 25896 if (Tools.isArray(settings.plugins)) { 25897 settings.plugins = settings.plugins.join(' '); 25898 } 25899 25900 each(settings.external_plugins, function(url, name) { 25901 PluginManager.load(name, url); 25902 settings.plugins += ' ' + name; 25903 }); 25904 25905 each(settings.plugins.split(/[ ,]/), function(plugin) { 25906 plugin = trim(plugin); 25907 25908 if (plugin && !PluginManager.urls[plugin]) { 25909 if (plugin.charAt(0) == '-') { 25910 plugin = plugin.substr(1, plugin.length); 25911 25912 var dependencies = PluginManager.dependencies(plugin); 25913 25914 each(dependencies, function(dep) { 25915 var defaultSettings = { 25916 prefix:'plugins/', 25917 resource: dep, 25918 suffix:'/plugin' + suffix + '.js?v=4.1.6.20161202' // DAJ 25919 }; 25920 25921 dep = PluginManager.createUrl(defaultSettings, dep); 25922 PluginManager.load(dep.resource, dep); 25923 }); 25924 } else { 25925 PluginManager.load(plugin, { 25926 prefix: 'plugins/', 25927 resource: plugin, 25928 suffix: '/plugin' + suffix + '.js?v=4.1.6.20161202' // DAJ 25929 }); 25930 } 25931 } 25932 }); 25933 25934 scriptLoader.loadQueue(function() { 25935 if (!self.removed) { 25936 self.init(); 25937 } 25938 }); 25939 } 25940 25941 loadScripts(); 25942 }, 25943 25944 /** 25945 * Initializes the editor this will be called automatically when 25946 * all plugins/themes and language packs are loaded by the rendered method. 25947 * This method will setup the iframe and create the theme and plugin instances. 25948 * 25949 * @method init 25950 */ 25951 init: function() { 25952 var self = this, settings = self.settings, elm = self.getElement(); 25953 var w, h, minHeight, n, o, url, bodyId, bodyClass, re, i, initializedPlugins = []; 25954 25955 self.rtl = this.editorManager.i18n.rtl; 25956 self.editorManager.add(self); 25957 25958 settings.aria_label = settings.aria_label || DOM.getAttrib(elm, 'aria-label', self.getLang('aria.rich_text_area')); 25959 25960 /** 25961 * Reference to the theme instance that was used to generate the UI. 25962 * 25963 * @property theme 25964 * @type tinymce.Theme 25965 * @example 25966 * // Executes a method on the theme directly 25967 * tinymce.activeEditor.theme.someMethod(); 25968 */ 25969 if (settings.theme) { 25970 if (typeof settings.theme != "function") { 25971 settings.theme = settings.theme.replace(/-/, ''); 25972 o = ThemeManager.get(settings.theme); 25973 self.theme = new o(self, ThemeManager.urls[settings.theme]); 25974 25975 if (self.theme.init) { 25976 self.theme.init(self, ThemeManager.urls[settings.theme] || self.documentBaseUrl.replace(/\/$/, '')); 25977 } 25978 } else { 25979 self.theme = settings.theme; 25980 } 25981 } 25982 25983 function initPlugin(plugin) { 25984 var constr = PluginManager.get(plugin), url, pluginInstance; 25985 25986 url = PluginManager.urls[plugin] || self.documentBaseUrl.replace(/\/$/, ''); 25987 plugin = trim(plugin); 25988 if (constr && inArray(initializedPlugins, plugin) === -1) { 25989 each(PluginManager.dependencies(plugin), function(dep){ 25990 initPlugin(dep); 25991 }); 25992 25993 pluginInstance = new constr(self, url); 25994 25995 self.plugins[plugin] = pluginInstance; 25996 25997 if (pluginInstance.init) { 25998 pluginInstance.init(self, url); 25999 initializedPlugins.push(plugin); 26000 } 26001 } 26002 } 26003 26004 // Create all plugins 26005 each(settings.plugins.replace(/\-/g, '').split(/[ ,]/), initPlugin); 26006 26007 // Measure box 26008 if (settings.render_ui && self.theme) { 26009 self.orgDisplay = elm.style.display; 26010 26011 if (typeof settings.theme != "function") { 26012 w = settings.width || elm.style.width || elm.offsetWidth; 26013 h = settings.height || elm.style.height || elm.offsetHeight; 26014 minHeight = settings.min_height || 100; 26015 re = /^[0-9\.]+(|px)$/i; 26016 26017 if (re.test('' + w)) { 26018 w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100); 26019 } 26020 26021 if (re.test('' + h)) {
26022 h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), minHeight); 26023 } 26024 26025 // Render UI 26026 o = self.theme.renderUI({ 26027 targetNode: elm, 26028 width: w, 26029 height: h, 26030 deltaWidth: settings.delta_width, 26031 deltaHeight: settings.delta_height 26032 }); 26033 26034 // Resize editor 26035 if (!settings.content_editable) { 26036 DOM.setStyles(o.sizeContainer || o.editorContainer, { 26037 wi2dth: w, 26038 // TODO: Fix this 26039 h2eight: h 26040 }); 26041 26042 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : ''); 26043 if (h < minHeight) { 26044 h = minHeight; 26045 } 26046 } 26047 } else { 26048 o = settings.theme(self, elm); 26049 26050 // Convert element type to id:s 26051 if (o.editorContainer.nodeType) { 26052 o.editorContainer = o.editorContainer.id = o.editorContainer.id || self.id + "_parent"; 26053 } 26054 26055 // Convert element type to id:s 26056 if (o.iframeContainer.nodeType) { 26057 o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || self.id + "_iframecontainer"; 26058 } 26059 26060 // Use specified iframe height or the targets offsetHeight 26061 h = o.iframeHeight || elm.offsetHeight; 26062 } 26063 26064 self.editorContainer = o.editorContainer; 26065 } 26066 26067 // Load specified content CSS last 26068 if (settings.content_css) { 26069 each(explode(settings.content_css), function(u) { 26070 self.contentCSS.push(self.documentBaseURI.toAbsolute(u)); 26071 }); 26072 } 26073 26074 // Load specified content CSS last 26075 if (settings.content_style) { 26076 self.contentStyles.push(settings.content_style); 26077 } 26078 26079 // Content editable mode ends here 26080 if (settings.content_editable) { 26081 elm = n = o = null; // Fix IE leak 26082 return self.initContentBody(); 26083 } 26084 26085 self.iframeHTML = settings.doctype + '<html><head>'; 26086 26087 // We only need to override paths if we have to 26088 // IE has a bug where it remove site absolute urls to relative ones if this is specified 26089 if (settings.document_base_url != self.documentBaseUrl) { 26090 self.iframeHTML += '<base href="' + self.documentBaseURI.getURI() + '" />'; 26091 } 26092 26093 // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode. 26094 if (!Env.caretAfter && settings.ie7_compat) { 26095 self.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />'; 26096 } 26097 26098 self.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />'; 26099 26100 // Load the CSS by injecting them into the HTML this will reduce "flicker" 26101 for (i = 0; i < self.contentCSS.length; i++) { 26102 var cssUrl = self.contentCSS[i]; 26103 self.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + cssUrl + '" />'; 26104 self.loadedCSS[cssUrl] = true; 26105 } 26106 26107 bodyId = settings.body_id || 'tinymce'; 26108 if (bodyId.indexOf('=') != -1) { 26109 bodyId = self.getParam('body_id', '', 'hash'); 26110 bodyId = bodyId[self.id] || bodyId; 26111 } 26112 26113 bodyClass = settings.body_class || ''; 26114 if (bodyClass.indexOf('=') != -1) { 26115 bodyClass = self.getParam('body_class', '', 'hash'); 26116 bodyClass = bodyClass[self.id] || ''; 26117 } 26118 26119 self.iframeHTML += '</head><body id="' + bodyId + '" class="mce-content-body ' + bodyClass + '" ' + 26120 'onload="window.parent.tinymce.get(\'' + self.id + '\').fire(\'load\');"><br></body></html>'; 26121 26122 var domainRelaxUrl = 'javascript:(function(){'+ 26123 'document.open();document.domain="' + document.domain + '";' + 26124 'var ed = window.parent.tinymce.get("' + self.id + '");document.write(ed.iframeHTML);' + 26125 'document.close();ed.initContentBody(true);})()'; 26126 26127 // Domain relaxing is required since the user has messed around with document.domain 26128 if (document.domain != location.hostname) { 26129 url = domainRelaxUrl; 26130 } 26131 26132 // Create iframe 26133 // TODO: ACC add the appropriate description on this. 26134 n = DOM.add(o.iframeContainer, 'iframe', { 26135 id: self.id + "_ifr", 26136 src: url || 'javascript:""', // Workaround for HTTPS warning in IE6/7 26137 frameBorder: '0', 26138 allowTransparency: "true", 26139 title: '', 26140 // MODIF TN :: Pas de Alt/Title sur le tinyMCE 26141 //self.editorManager.translate(
26142 //"Rich Text Area. Press ALT-F9 for menu. " + 26143 //"Press ALT-F10 for toolbar. Press ALT-0 for help" 26144 //), 26145 style: { 26146 width: '100%', 26147 height: h, 26148 display: 'block' // Important for Gecko to render the iframe correctly 26149 } 26150 }); 26151 26152 // Try accessing the document this will fail on IE when document.domain is set to the same as location.hostname 26153 // Then we have to force domain relaxing using the domainRelaxUrl approach very ugly!! 26154 if (ie) { 26155 try { 26156 self.getDoc(); 26157 } catch (e) { 26158 n.src = url = domainRelaxUrl; 26159 } 26160 } 26161 26162 self.contentAreaContainer = o.iframeContainer; 26163 26164 if (o.editorContainer) { 26165 DOM.get(o.editorContainer).style.display = self.orgDisplay; 26166 } 26167 26168 DOM.get(self.id).style.display = 'none'; 26169 DOM.setAttrib(self.id, 'aria-hidden', true); 26170 26171 if (!url) { 26172 self.initContentBody(); 26173 } 26174 26175 elm = n = o = null; // Cleanup 26176 }, 26177 26178 /** 26179 * This method get called by the init method ones the iframe is loaded. 26180 * It will fill the iframe with contents, setups DOM and selection objects for the iframe. 26181 * 26182 * @method initContentBody 26183 * @private 26184 */ 26185 initContentBody: function(skipWrite) { 26186 var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), body, contentCssText; 26187 26188 // Restore visibility on target element 26189 if (!settings.inline) { 26190 self.getElement().style.visibility = self.orgVisibility; 26191 } 26192 26193 // Setup iframe body 26194 if (!skipWrite && !settings.content_editable) { 26195 doc.open(); 26196 doc.write(self.iframeHTML); 26197 doc.close(); 26198 } 26199 26200 if (settings.content_editable) { 26201 self.on('remove', function() { 26202 var body = this.getBody(); 26203 26204 DOM.removeClass(body, 'mce-content-body'); 26205 DOM.removeClass(body, 'mce-edit-focus'); 26206 DOM.setAttrib(body, 'tabIndex', null); 26207 DOM.setAttrib(body, 'contentEditable', null); 26208 }); 26209 26210 DOM.addClass(targetElm, 'mce-content-body'); 26211 targetElm.tabIndex = -1; 26212 self.contentDocument = doc = settings.content_document || document; 26213 self.contentWindow = settings.content_window || window; 26214 self.bodyElement = targetElm; 26215 26216 // Prevent leak in IE 26217 settings.content_document = settings.content_window = null; 26218 26219 // TODO: Fix this 26220 settings.root_name = targetElm.nodeName.toLowerCase(); 26221 } 26222 26223 // It will not steal focus while setting contentEditable 26224 body = self.getBody(); 26225 body.disabled = true; 26226 26227 if (!settings.readonly) { 26228 if (self.inline && DOM.getStyle(body, 'position', true) == 'static') { 26229 body.style.position = 'relative'; 26230 } 26231 26232 body.contentEditable = self.getParam('content_editable_state', true); 26233 } 26234 26235 body.disabled = false; 26236 26237 /** 26238 * Schema instance, enables you to validate elements and it's children. 26239 * 26240 * @property schema 26241 * @type tinymce.html.Schema 26242 */ 26243 self.schema = new Schema(settings); 26244 26245 /** 26246 * DOM instance for the editor. 26247 * 26248 * @property dom 26249 * @type tinymce.dom.DOMUtils 26250 * @example 26251 * // Adds a class to all paragraphs within the editor 26252 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass'); 26253 */ 26254 self.dom = new DOMUtils(doc, { 26255 keep_values: true, 26256 url_converter: self.convertURL, 26257 url_converter_scope: self, 26258 hex_colors: settings.force_hex_style_colors, 26259 class_filter: settings.class_filter, 26260 update_styles: true, 26261 root_element: settings.content_editable ? self.id : null, 26262 collect: settings.content_editable, 26263 schema: self.schema, 26264 onSetAttrib: function(e) { 26265 self.fire('SetAttrib', e); 26266 } 26267 }); 26268 26269 /** 26270 * HTML parser will be used when contents is inserted into the editor. 26271 * 26272 * @property parser 26273 * @type tinymce.html.DomParser 26274 */ 26275 self.parser = new DomParser(settings, self.schema); 26276 26277 // Convert src and href into data-mce-src, data-mce-href and data-mce-style 26278 self.parser.addAttributeFilter('src,href,style', function(nodes, name) { 26279 var i = nodes.length, node, dom = self.dom, value, internalName; 26280 26281 while (i--) { 26282 node = nodes[i]; 26283 value = node.attr(name); 26284 internalName = 'data-mce-' + name; 26285 26286 // Add internal attribute if we need to we don't on a refresh of the document 26287 if (!node.attributes.map[internalName]) { 26288 if (name === "style") { 26289 node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name)); 26290 } else { 26291 node.attr(internalName, self.convertURL(value, name, node.name)); 26292 } 26293 } 26294 } 26295 }); 26296 26297 // Keep scripts from executing 26298 self.parser.addNodeFilter('script', function(nodes) { 26299 var i = nodes.length, node; 26300 26301 while (i--) { 26302 node = nodes[i]; 26303 node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript')); 26304 } 26305 }); 26306 26307 self.parser.addNodeFilter('#cdata', function(nodes) { 26308 var i = nodes.length, node; 26309 26310 while (i--) { 26311 node = nodes[i]; 26312 node.type = 8; 26313 node.name = '#comment'; 26314 node.value = '[CDATA[' + node.value + ']]'; 26315 } 26316 }); 26317 26318 self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes) { 26319 var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements(); 26320 26321 while (i--) { 26322 node = nodes[i]; 26323 26324 if (node.isEmpty(nonEmptyElements)) { 26325 node.empty().append(new Node('br', 1)).shortEnded = true; 26326 } 26327 } 26328 }); 26329 26330 /** 26331 * DOM serializer for the editor. Will be used when contents is extracted from the editor. 26332 * 26333 * @property serializer 26334 * @type tinymce.dom.Serializer 26335 * @example 26336 * // Serializes the first paragraph in the editor into a string 26337 * tinymce.activeEditor.serializer.serialize(tinymce.activeEditor.dom.select('p')[0]); 26338 */ 26339 self.serializer = new DomSerializer(settings, self); 26340 26341 /** 26342 * Selection instance for the editor. 26343 * 26344 * @property selection 26345 * @type tinymce.dom.Selection 26346 * @example 26347 * // Sets some contents to the current selection in the editor
26348 * tinymce.activeEditor.selection.setContent('Some contents'); 26349 * 26350 * // Gets the current selection 26351 * alert(tinymce.activeEditor.selection.getContent()); 26352 * 26353 * // Selects the first paragraph found 26354 * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]); 26355 */ 26356 self.selection = new Selection(self.dom, self.getWin(), self.serializer, self); 26357 26358 /** 26359 * Formatter instance. 26360 * 26361 * @property formatter 26362 * @type tinymce.Formatter 26363 */ 26364 self.formatter = new Formatter(self); 26365 26366 /** 26367 * Undo manager instance, responsible for handling undo levels. 26368 * 26369 * @property undoManager 26370 * @type tinymce.UndoManager 26371 * @example 26372 * // Undoes the last modification to the editor 26373 * tinymce.activeEditor.undoManager.undo(); 26374 */ 26375 self.undoManager = new UndoManager(self); 26376 26377 self.forceBlocks = new ForceBlocks(self); 26378 self.enterKey = new EnterKey(self); 26379 self.editorCommands = new EditorCommands(self); 26380 26381 self.fire('PreInit'); 26382 26383 if (!settings.browser_spellcheck && !settings.gecko_spellcheck) { 26384 doc.body.spellcheck = false; // Gecko 26385 DOM.setAttrib(body, "spellcheck", "false"); 26386 } 26387 26388 self.fire('PostRender'); 26389 26390 self.quirks = Quirks(self); 26391 26392 if (settings.directionality) { 26393 body.dir = settings.directionality; 26394 } 26395 26396 if (settings.nowrap) { 26397 body.style.whiteSpace = "nowrap"; 26398 } 26399 26400 if (settings.protect) { 26401 self.on('BeforeSetContent', function(e) { 26402 each(settings.protect, function(pattern) { 26403 e.content = e.content.replace(pattern, function(str) { 26404 return '<!--mce:protected ' + escape(str) + '-->'; 26405 }); 26406 }); 26407 }); 26408 } 26409 26410 self.on('SetContent', function() { 26411 self.addVisual(self.getBody()); 26412 }); 26413 26414 // Remove empty contents 26415 if (settings.padd_empty_editor) { 26416 self.on('PostProcess', function(e) { 26417 e.content = e.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, ''); 26418 }); 26419 } 26420 26421 self.load({initial: true, format: 'html'}); 26422 self.startContent = self.getContent({format: 'raw'}); 26423 26424 /** 26425 * Is set to true after the editor instance has been initialized 26426 * 26427 * @property initialized 26428 * @type Boolean 26429 * @example 26430 * function isEditorInitialized(editor) { 26431 * return editor && editor.initialized; 26432 * } 26433 */ 26434 self.initialized = true; 26435 26436 each(self._pendingNativeEvents, function(name) { 26437 self.dom.bind(getEventTarget(self, name), name, function(e) { 26438 self.fire(e.type, e); 26439 }); 26440 }); 26441 26442 self.fire('init'); 26443 self.focus(true); 26444 self.nodeChanged({initial: true}); 26445 self.execCallback('init_instance_callback', self); 26446 26447 // Add editor specific CSS styles 26448 if (self.contentStyles.length > 0) { 26449 contentCssText = ''; 26450 26451 each(self.contentStyles, function(style) { 26452 contentCssText += style + "\r\n"; 26453 }); 26454 26455 self.dom.addStyle(contentCssText); 26456 } 26457 26458 // Load specified content CSS last 26459 each(self.contentCSS, function(cssUrl) { 26460 if (!self.loadedCSS[cssUrl]) { 26461 self.dom.loadCSS(cssUrl); 26462 self.loadedCSS[cssUrl] = true; 26463 } 26464 }); 26465 26466 // Handle auto focus 26467 if (settings.auto_focus) { 26468 setTimeout(function () { 26469 var ed = self.editorManager.get(settings.auto_focus); 26470 26471 ed.selection.select(ed.getBody(), 1); 26472 ed.selection.collapse(1); 26473 ed.getBody().focus(); 26474 ed.getWin().focus(); 26475 }, 100); 26476 } 26477 26478 // Clean up references for IE 26479 targetElm = doc = body = null; 26480 }, 26481 26482 /** 26483 * Focuses/activates the editor. This will set this editor as the activeEditor in the tinymce collection 26484 * it will also place DOM focus inside the editor. 26485 * 26486 * @method focus 26487 * @param {Boolean} skip_focus Skip DOM focus. Just set is as the active editor. 26488 */ 26489 focus: function(skip_focus) { 26490 var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, rng; 26491 var controlElm, doc = self.getDoc(), body; 26492 26493 if (!skip_focus) { 26494 // Get selected control element 26495 rng = selection.getRng(); 26496 if (rng.item) { 26497 controlElm = rng.item(0); 26498 } 26499 26500 self._refreshContentEditable(); 26501 26502 // Focus the window iframe 26503 if (!contentEditable) { 26504 // WebKit needs this call to fire focusin event properly see #5948 26505 // But Opera pre Blink engine will produce an empty selection so skip Opera 26506 if (!Env.opera) { 26507 self.getBody().focus(); 26508 } 26509 26510 self.getWin().focus(); 26511 } 26512 26513 // Focus the body as well since it's contentEditable 26514 if (isGecko || contentEditable) { 26515 body = self.getBody(); 26516 26517 // Check for setActive since it doesn't scroll to the element 26518 if (body.setActive && Env.ie < 11) { 26519 body.setActive(); 26520 } else { 26521 body.focus(); 26522 } 26523 26524 if (contentEditable) { 26525 selection.normalize(); 26526 } 26527 } 26528 26529 // Restore selected control element 26530 // This is needed when for example an image is selected within a 26531 // layer a call to focus will then remove the control selection 26532 if (controlElm && controlElm.ownerDocument == doc) { 26533 rng = doc.body.createControlRange(); 26534 rng.addElement(controlElm); 26535 rng.select(); 26536 } 26537 } 26538 26539 if (self.editorManager.activeEditor != self) { 26540 if ((oed = self.editorManager.activeEditor)) { 26541 oed.fire('deactivate', {relatedTarget: self}); 26542 } 26543 26544 self.fire('activate', {relatedTarget: oed}); 26545 } 26546 26547 self.editorManager.activeEditor = self; 26548 }, 26549 26550 /** 26551 * Executes a legacy callback. This method is useful to call old 2.x option callbacks. 26552 * There new event model is a better way to add callback so this method might be removed in the future. 26553 * 26554 * @method execCallback 26555 * @param {String} name Name of the callback to execute. 26556 * @return {Object} Return value passed from callback function. 26557 */ 26558 execCallback: function(name) { 26559 var self = this, callback = self.settings[name], scope; 26560 26561 if (!callback) { 26562 return; 26563 } 26564 26565 // Look through lookup 26566 if (self.callbackLookup && (scope = self.callbackLookup[name])) { 26567 callback = scope.func; 26568 scope = scope.scope; 26569 } 26570 26571 if (typeof(callback) === 'string') { 26572 scope = callback.replace(/\.\w+$/, ''); 26573 scope = scope ? resolve(scope) : 0; 26574 callback = resolve(callback); 26575 self.callbackLookup = self.callbackLookup || {}; 26576 self.callbackLookup[name] = {func: callback, scope: scope}; 26577 } 26578 26579 return callback.apply(scope || self, Array.prototype.slice.call(arguments, 1)); 26580 }, 26581 26582 /** 26583 * Translates the specified string by replacing variables with language pack items it will also check if there is 26584 * a key mathcin the input. 26585 * 26586 * @method translate 26587 * @param {String} text String to translate by the language pack data. 26588 * @return {String} Translated string. 26589 */ 26590 translate: function(text) { 26591 var lang = this.settings.language || 'en', i18n = this.editorManager.i18n; 26592 26593 if (!text) { 26594 return ''; 26595 } 26596 26597 return i18n.data[lang + '.' + text] || text.replace(/\{\#([^\}]+)\}/g, function(a, b) { 26598 return i18n.data[lang + '.' + b] || '{#' + b + '}'; 26599 }); 26600 }, 26601 26602 /** 26603 * Returns a language pack item by name/key. 26604 * 26605 * @method getLang 26606 * @param {String} name Name/key to get from the language pack. 26607 * @param {String} defaultVal Optional default value to retrive. 26608 */ 26609 getLang: function(name, defaultVal) { 26610 return ( 26611 this.editorManager.i18n.data[(this.settings.language || 'en') + '.' + name] || 26612 (defaultVal !== undefined ? defaultVal : '{#' + name + '}') 26613 ); 26614 }, 26615 26616 /** 26617 * Returns a configuration parameter by name. 26618 * 26619 * @method getParam 26620 * @param {String} name Configruation parameter to retrive. 26621 * @param {String} defaultVal Optional default value to return. 26622 * @param {String} type Optional type parameter. 26623 * @return {String} Configuration parameter value or default value. 26624 * @example 26625 * // Returns a specific config value from the currently active editor 26626 * var someval = tinymce.activeEditor.getParam('myvalue'); 26627 * 26628 * // Returns a specific config value from a specific editor instance by id 26629 * var someval2 = tinymce.get('my_editor').getParam('myvalue'); 26630 */ 26631 getParam: function(name, defaultVal, type) { 26632 var value = name in this.settings ? this.settings[name] : defaultVal, output; 26633 26634 if (type === 'hash') { 26635 output = {}; 26636 26637 if (typeof(value) === 'string') { 26638 each(value.indexOf('=') > 0 ? value.split(/[;,](?![^=;,]*(?:[;,]|$))/) : value.split(','), function(value) { 26639 value = value.split('='); 26640 26641 if (value.length > 1) { 26642 output[trim(value[0])] = trim(value[1]); 26643 } else { 26644 output[trim(value[0])] = trim(value); 26645 } 26646 }); 26647 } else { 26648 output = value; 26649 } 26650 26651 return output; 26652 } 26653 26654 return value; 26655 }, 26656 26657 /** 26658 * Distpaches out a onNodeChange event to all observers. This method should be called when you 26659 * need to update the UI states or element path etc. 26660 * 26661 * @method nodeChanged 26662 */ 26663 nodeChanged: function() { 26664 var self = this, selection = self.selection, node, parents, root; 26665 26666 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading 26667 if (self.initialized && !self.settings.disable_nodechange) { 26668 // Get start node 26669 root = self.getBody(); 26670 node = selection.getStart() || root; 26671 node = ie && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state 26672 26673 // Edge case for <p>|<img></p> 26674 if (node.nodeName == 'IMG' && selection.isCollapsed()) { 26675 node = node.parentNode; 26676 } 26677 26678 // Get parents and add them to object 26679 parents = []; 26680 self.dom.getParent(node, function(node) { 26681 if (node === root) { 26682 return true; 26683 } 26684 26685 parents.push(node); 26686 }); 26687 26688 self.fire('NodeChange', {element: node, parents: parents}); 26689 } 26690 }, 26691 26692 /**
26693 * Adds a button that later gets created by the ControlManager. This is a shorter and easier method 26694 * of adding buttons without the need to deal with the ControlManager directly. But it's also less 26695 * powerfull if you need more control use the ControlManagers factory methods instead. 26696 * 26697 * @method addButton 26698 * @param {String} name Button name to add. 26699 * @param {Object} settings Settings object with title, cmd etc. 26700 * @example 26701 * // Adds a custom button to the editor that inserts contents when clicked 26702 * tinymce.init({ 26703 * ... 26704 * 26705 * toolbar: 'example' 26706 * 26707 * setup: function(ed) { 26708 * ed.addButton('example', { 26709 * title: 'My title', 26710 * image: '../js/tinymce/plugins/example/img/example.gif', 26711 * onclick: function() { 26712 * ed.insertContent('Hello world!!'); 26713 * } 26714 * }); 26715 * } 26716 * }); 26717 */ 26718 addButton: function(name, settings) { 26719 var self = this; 26720 26721 if (settings.cmd) { 26722 settings.onclick = function() { 26723 self.execCommand(settings.cmd); 26724 }; 26725 } 26726 26727 if (!settings.text && !settings.icon) { 26728 settings.icon = name; 26729 } 26730 26731 self.buttons = self.buttons || {}; 26732 settings.tooltip = settings.tooltip || settings.title; 26733 self.buttons[name] = settings; 26734 }, 26735 26736 /** 26737 * Adds a menu item to be used in the menus of the modern theme. 26738 * 26739 * @method addMenuItem 26740 * @param {String} name Menu item name to add. 26741 * @param {Object} settings Settings object with title, cmd etc. 26742 * @example 26743 * // Adds a custom menu item to the editor that inserts contents when clicked 26744 * // The context option allows you to add the menu item to an existing default menu 26745 * tinymce.init({ 26746 * ... 26747 * 26748 * setup: function(ed) { 26749 * ed.addMenuItem('example', { 26750 * title: 'My menu item', 26751 * context: 'tools', 26752 * onclick: function() { 26753 * ed.insertContent('Hello world!!'); 26754 * } 26755 * }); 26756 * } 26757 * }); 26758 */ 26759 addMenuItem: function(name, settings) { 26760 var self = this; 26761 26762 if (settings.cmd) { 26763 settings.onclick = function() { 26764 self.execCommand(settings.cmd); 26765 }; 26766 } 26767 26768 self.menuItems = self.menuItems || {}; 26769 self.menuItems[name] = settings; 26770 }, 26771 26772 /** 26773 * Adds a custom command to the editor, you can also override existing commands with this method. 26774 * The command that you add can be executed with execCommand. 26775 * 26776 * @method addCommand 26777 * @param {String} name Command name to add/override. 26778 * @param {addCommandCallback} callback Function to execute when the command occurs. 26779 * @param {Object} scope Optional scope to execute the function in. 26780 * @example 26781 * // Adds a custom command that later can be executed using execCommand 26782 * tinymce.init({ 26783 * ... 26784 * 26785 * setup: function(ed) { 26786 * // Register example command 26787 * ed.addCommand('mycommand', function(ui, v) { 26788 * ed.windowManager.alert('Hello world!! Selection: ' + ed.selection.getContent({format: 'text'})); 26789 * }); 26790 * } 26791 * }); 26792 */ 26793 addCommand: function(name, callback, scope) { 26794 /** 26795 * Callback function that gets called when a command is executed. 26796 * 26797 * @callback addCommandCallback 26798 * @param {Boolean} ui Display UI state true/false. 26799 * @param {Object} value Optional value for command. 26800 * @return {Boolean} True/false state if the command was handled or not. 26801 */ 26802 this.execCommands[name] = {func: callback, scope: scope || this}; 26803 }, 26804 26805 /** 26806 * Adds a custom query state command to the editor, you can also override existing commands with this method. 26807 * The command that you add can be executed with queryCommandState function. 26808 * 26809 * @method addQueryStateHandler 26810 * @param {String} name Command name to add/override. 26811 * @param {addQueryStateHandlerCallback} callback Function to execute when the command state retrival occurs. 26812 * @param {Object} scope Optional scope to execute the function in. 26813 */ 26814 addQueryStateHandler: function(name, callback, scope) { 26815 /** 26816 * Callback function that gets called when a queryCommandState is executed. 26817 * 26818 * @callback addQueryStateHandlerCallback 26819 * @return {Boolean} True/false state if the command is enabled or not like is it bold. 26820 */ 26821 this.queryStateCommands[name] = {func: callback, scope: scope || this}; 26822 }, 26823 26824 /** 26825 * Adds a custom query value command to the editor, you can also override existing commands with this method. 26826 * The command that you add can be executed with queryCommandValue function. 26827 * 26828 * @method addQueryValueHandler 26829 * @param {String} name Command name to add/override. 26830 * @param {addQueryValueHandlerCallback} callback Function to execute when the command value retrival occurs. 26831 * @param {Object} scope Optional scope to execute the function in. 26832 */ 26833 addQueryValueHandler: function(name, callback, scope) { 26834 /** 26835 * Callback function that gets called when a queryCommandValue is executed. 26836 * 26837 * @callback addQueryValueHandlerCallback 26838 * @return {Object} Value of the command or undefined. 26839 */ 26840 this.queryValueCommands[name] = {func: callback, scope: scope || this}; 26841 }, 26842 26843 /** 26844 * Adds a keyboard shortcut for some command or function. 26845 * 26846 * @method addShortcut 26847 * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o. 26848 * @param {String} desc Text description for the command. 26849 * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed. 26850 * @param {Object} sc Optional scope to execute the function in. 26851 * @return {Boolean} true/false state if the shortcut was added or not. 26852 */ 26853 addShortcut: function(pattern, desc, cmdFunc, scope) { 26854 this.shortcuts.add(pattern, desc, cmdFunc, scope); 26855 }, 26856 26857 /**
26858 * Executes a command on the current instance. These commands can be TinyMCE internal commands prefixed with "mce" or 26859 * they can be build in browser commands such as "Bold". A compleate list of browser commands is available on MSDN or Mozilla.org. 26860 * This function will dispatch the execCommand function on each plugin, theme or the execcommand_callback option if none of these 26861 * return true it will handle the command as a internal browser command. 26862 * 26863 * @method execCommand 26864 * @param {String} cmd Command name to execute, for example mceLink or Bold. 26865 * @param {Boolean} ui True/false state if a UI (dialog) should be presented or not. 26866 * @param {mixed} value Optional command value, this can be anything. 26867 * @param {Object} a Optional arguments object. 26868 */ 26869 execCommand: function(cmd, ui, value, args) { 26870 var self = this, state = 0, cmdItem; 26871 26872 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(cmd) && (!args || !args.skip_focus)) { 26873 self.focus(); 26874 } 26875 26876 args = extend({}, args); 26877 args = self.fire('BeforeExecCommand', {command: cmd, ui: ui, value: value}); 26878 if (args.isDefaultPrevented()) { 26879 return false; 26880 } 26881 26882 // Registred commands 26883 if ((cmdItem = self.execCommands[cmd])) { 26884 // Fall through on true 26885 if (cmdItem.func.call(cmdItem.scope, ui, value) !== true) { 26886 self.fire('ExecCommand', {command: cmd, ui: ui, value: value}); 26887 return true; 26888 } 26889 } 26890 26891 // Plugin commands 26892 each(self.plugins, function(p) { 26893 if (p.execCommand && p.execCommand(cmd, ui, value)) { 26894 self.fire('ExecCommand', {command: cmd, ui: ui, value: value}); 26895 state = true; 26896 return false; 26897 } 26898 }); 26899 26900 if (state) { 26901 return state; 26902 } 26903 26904 // Theme commands 26905 if (self.theme && self.theme.execCommand && self.theme.execCommand(cmd, ui, value)) { 26906 self.fire('ExecCommand', {command: cmd, ui: ui, value: value}); 26907 return true; 26908 } 26909 26910 // Editor commands 26911 if (self.editorCommands.execCommand(cmd, ui, value)) { 26912 self.fire('ExecCommand', {command: cmd, ui: ui, value: value}); 26913 return true; 26914 } 26915 26916 // Browser commands 26917 self.getDoc().execCommand(cmd, ui, value); 26918 self.fire('ExecCommand', {command: cmd, ui: ui, value: value}); 26919 }, 26920 26921 /** 26922 * Returns a command specific state, for example if bold is enabled or not. 26923 * 26924 * @method queryCommandState 26925 * @param {string} cmd Command to query state from. 26926 * @return {Boolean} Command specific state, for example if bold is enabled or not. 26927 */ 26928 queryCommandState: function(cmd) { 26929 var self = this, queryItem, returnVal; 26930 26931 // Is hidden then return undefined 26932 if (self._isHidden()) { 26933 return; 26934 } 26935 26936 // Registred commands 26937 if ((queryItem = self.queryStateCommands[cmd])) { 26938 returnVal = queryItem.func.call(queryItem.scope); 26939 26940 // Fall though on true 26941 if (returnVal !== true) { 26942 return returnVal; 26943 } 26944 } 26945 26946 // Editor commands 26947 returnVal = self.editorCommands.queryCommandState(cmd); 26948 if (returnVal !== -1) { 26949 return returnVal; 26950 } 26951 26952 // Browser commands 26953 try { 26954 return self.getDoc().queryCommandState(cmd); 26955 } catch (ex) { 26956 // Fails sometimes see bug: 1896577 26957 } 26958 }, 26959 26960 /** 26961 * Returns a command specific value, for example the current font size. 26962 * 26963 * @method queryCommandValue 26964 * @param {string} cmd Command to query value from. 26965 * @return {Object} Command specific value, for example the current font size. 26966 */ 26967 queryCommandValue: function(cmd) { 26968 var self = this, queryItem, returnVal; 26969 26970 // Is hidden then return undefined 26971 if (self._isHidden()) { 26972 return; 26973 } 26974 26975 // Registred commands 26976 if ((queryItem = self.queryValueCommands[cmd])) { 26977 returnVal = queryItem.func.call(queryItem.scope); 26978 26979 // Fall though on true 26980 if (returnVal !== true) { 26981 return returnVal; 26982 } 26983 } 26984 26985 // Editor commands 26986 returnVal = self.editorCommands.queryCommandValue(cmd); 26987 if (returnVal !== undefined) { 26988 return returnVal; 26989 } 26990 26991 // Browser commands 26992 try { 26993 return self.getDoc().queryCommandValue(cmd); 26994 } catch (ex) { 26995 // Fails sometimes see bug: 1896577 26996 } 26997 }, 26998 26999 /** 27000 * Shows the editor and hides any textarea/div that the editor is supposed to replace. 27001 * 27002 * @method show 27003 */ 27004 show: function() { 27005 var self = this; 27006 27007 DOM.show(self.getContainer()); 27008 DOM.hide(self.id); 27009 self.load(); 27010 self.fire('show'); 27011 }, 27012 27013 /** 27014 * Hides the editor and shows any textarea/div that the editor is supposed to replace. 27015 * 27016 * @method hide 27017 */ 27018 hide: function() { 27019 var self = this, doc = self.getDoc(); 27020 27021 // Fixed bug where IE has a blinking cursor left from the editor
27022 if (ie && doc && !self.inline) { 27023 doc.execCommand('SelectAll'); 27024 } 27025 27026 // We must save before we hide so Safari doesn't crash 27027 self.save(); 27028 27029 // defer the call to hide to prevent an IE9 crash #4921 27030 DOM.hide(self.getContainer()); 27031 DOM.setStyle(self.id, 'display', self.orgDisplay); 27032 self.fire('hide'); 27033 }, 27034 27035 /** 27036 * Returns true/false if the editor is hidden or not. 27037 * 27038 * @method isHidden 27039 * @return {Boolean} True/false if the editor is hidden or not. 27040 */ 27041 isHidden: function() { 27042 return !DOM.isHidden(this.id); 27043 }, 27044 27045 /** 27046 * Sets the progress state, this will display a throbber/progess for the editor. 27047 * This is ideal for asycronous operations like an AJAX save call. 27048 * 27049 * @method setProgressState 27050 * @param {Boolean} state Boolean state if the progress should be shown or hidden. 27051 * @param {Number} time Optional time to wait before the progress gets shown. 27052 * @return {Boolean} Same as the input state. 27053 * @example 27054 * // Show progress for the active editor 27055 * tinymce.activeEditor.setProgressState(true); 27056 * 27057 * // Hide progress for the active editor 27058 * tinymce.activeEditor.setProgressState(false); 27059 * 27060 * // Show progress after 3 seconds 27061 * tinymce.activeEditor.setProgressState(true, 3000); 27062 */ 27063 setProgressState: function(state, time) { 27064 this.fire('ProgressState', {state: state, time: time}); 27065 }, 27066 27067 /** 27068 * Loads contents from the textarea or div element that got converted into an editor instance. 27069 * This method will move the contents from that textarea or div into the editor by using setContent 27070 * so all events etc that method has will get dispatched as well. 27071 * 27072 * @method load 27073 * @param {Object} args Optional content object, this gets passed around through the whole load process. 27074 * @return {String} HTML string that got set into the editor. 27075 */ 27076 load: function(args) { 27077 var self = this, elm = self.getElement(), html; 27078 27079 if (elm) { 27080 args = args || {}; 27081 args.load = true; 27082 27083 html = self.setContent(elm.value !== undefined ? elm.value : elm.innerHTML, args); 27084 args.element = elm; 27085 27086 if (!args.no_events) { 27087 self.fire('LoadContent', args); 27088 } 27089 27090 args.element = elm = null; 27091 27092 return html; 27093 } 27094 }, 27095 27096 /** 27097 * Saves the contents from a editor out to the textarea or div element that got converted into an editor instance. 27098 * This method will move the HTML contents from the editor into that textarea or div by getContent 27099 * so all events etc that method has will get dispatched as well. 27100 * 27101 * @method save 27102 * @param {Object} args Optional content object, this gets passed around through the whole save process. 27103 * @return {String} HTML string that got set into the textarea/div. 27104 */ 27105 save: function(args) { 27106 var self = this, elm = self.getElement(), html, form; 27107 27108 if (!elm || !self.initialized) { 27109 return; 27110 } 27111 27112 args = args || {}; 27113 args.save = true; 27114 27115 args.element = elm; 27116 html = args.content = self.getContent(args); 27117 27118 if (!args.no_events) { 27119 self.fire('SaveContent', args); 27120 } 27121 27122 html = args.content; 27123 27124 if (!/TEXTAREA|INPUT/i.test(elm.nodeName)) { 27125 elm.innerHTML = html; 27126 27127 // Update hidden form element 27128 if ((form = DOM.getParent(self.id, 'form'))) { 27129 each(form.elements, function(elm) { 27130 if (elm.name == self.id) { 27131 elm.value = html; 27132 return false; 27133 } 27134 }); 27135 } 27136 } else { 27137 elm.value = html; 27138 } 27139 27140 args.element = elm = null; 27141 27142 if (args.set_dirty !== false) { 27143 self.isNotDirty = true; 27144 } 27145 27146 return html; 27147 }, 27148 27149 /** 27150 * Sets the specified content to the editor instance, this will cleanup the content before it gets set using 27151 * the different cleanup rules options. 27152 * 27153 * @method setContent 27154 * @param {String} content Content to set to editor, normally HTML contents but can be other formats as well. 27155 * @param {Object} args Optional content object, this gets passed around through the whole set process. 27156 * @return {String} HTML string that got set into the editor. 27157 * @example 27158 * // Sets the HTML contents of the activeEditor editor 27159 * tinymce.activeEditor.setContent('<span>some</span> html'); 27160 *
27161 * // Sets the raw contents of the activeEditor editor 27162 * tinymce.activeEditor.setContent('<span>some</span> html', {format: 'raw'}); 27163 * 27164 * // Sets the content of a specific editor (my_editor in this example) 27165 * tinymce.get('my_editor').setContent(data); 27166 * 27167 * // Sets the bbcode contents of the activeEditor editor if the bbcode plugin was added 27168 * tinymce.activeEditor.setContent('[b]some[/b] html', {format: 'bbcode'}); 27169 */ 27170 setContent: function(content, args) { 27171 var self = this, body = self.getBody(), forcedRootBlockName; 27172 27173 // Setup args object 27174 args = args || {}; 27175 args.format = args.format || 'html'; 27176 args.set = true; 27177 args.content = content; 27178 27179 // Do preprocessing 27180 if (!args.no_events) { 27181 self.fire('BeforeSetContent', args); 27182 } 27183 27184 content = args.content; 27185 27186 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content 27187 // It will also be impossible to place the caret in the editor unless there is a BR element present 27188 if (content.length === 0 || /^\s+$/.test(content)) { 27189 forcedRootBlockName = self.settings.forced_root_block; 27190 27191 // Check if forcedRootBlock is configured and that the block is a valid child of the body 27192 if (forcedRootBlockName && self.schema.isValidChild(body.nodeName.toLowerCase(), forcedRootBlockName.toLowerCase())) { 27193 // Padd with bogus BR elements on modern browsers and IE 7 and 8 since they don't render empty P tags properly 27194 content = ie && ie < 11 ? '' : '<br data-mce-bogus="1">'; 27195 content = self.dom.createHTML(forcedRootBlockName, self.settings.forced_root_block_attrs, content); 27196 } else if (!ie || ie < 11) { 27197 // We need to add a BR when forced_root_block is disabled on non IE browsers to place the caret 27198 content = '<br data-mce-bogus="1">'; 27199 } 27200 27201 body.innerHTML = content; 27202 27203 self.fire('SetContent', args); 27204 } else { 27205 // Parse and serialize the html 27206 if (args.format !== 'raw') { 27207 content = new Serializer({}, self.schema).serialize( 27208 self.parser.parse(content, {isRootContent: true}) 27209 ); 27210 } 27211 27212 // Set the new cleaned contents to the editor 27213 args.content = trim(content); 27214 self.dom.setHTML(body, args.content); 27215 27216 // Do post processing 27217 if (!args.no_events) { 27218 self.fire('SetContent', args); 27219 } 27220 27221 // Don't normalize selection if the focused element isn't the body in 27222 // content editable mode since it will steal focus otherwise 27223 /*if (!self.settings.content_editable || document.activeElement === self.getBody()) { 27224 self.selection.normalize(); 27225 }*/ 27226 } 27227 27228 return args.content; 27229 }, 27230 27231 /** 27232 * Gets the content from the editor instance, this will cleanup the content before it gets returned using 27233 * the different cleanup rules options. 27234 * 27235 * @method getContent 27236 * @param {Object} args Optional content object, this gets passed around through the whole get process. 27237 * @return {String} Cleaned content string, normally HTML contents. 27238 * @example 27239 * // Get the HTML contents of the currently active editor 27240 * console.debug(tinymce.activeEditor.getContent()); 27241 * 27242 * // Get the raw contents of the currently active editor 27243 * tinymce.activeEditor.getContent({format: 'raw'}); 27244 * 27245 * // Get content of a specific editor: 27246 * tinymce.get('content id').getContent() 27247 */ 27248 getContent: function(args) { 27249 var self = this, content, body = self.getBody(); 27250 27251 // Setup args object 27252 args = args || {}; 27253 args.format = args.format || 'html'; 27254 args.get = true; 27255 args.getInner = true; 27256 27257 // Do preprocessing 27258 if (!args.no_events) { 27259 self.fire('BeforeGetContent', args); 27260 } 27261 27262 // Get raw contents or by default the cleaned contents 27263 if (args.format == 'raw') { 27264 content = body.innerHTML; 27265 } else if (args.format == 'text') { 27266 content = body.innerText || body.textContent; 27267 } else { 27268 content = self.serializer.serialize(body, args); 27269 } 27270 27271 // Trim whitespace in beginning/end of HTML 27272 if (args.format != 'text') { 27273 args.content = trim(content); 27274 } else { 27275 args.content = content; 27276 } 27277 27278 // Do post processing 27279 if (!args.no_events) { 27280 self.fire('GetContent', args); 27281 } 27282 27283 return args.content; 27284 }, 27285 27286 /** 27287 * Inserts content at caret position. 27288 * 27289 * @method insertContent 27290 * @param {String} content Content to insert. 27291 */ 27292 insertContent: function(content) { 27293 this.execCommand('mceInsertContent', false, content); 27294 }, 27295 27296 /** 27297 * Returns true/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents. 27298 * 27299 * @method isDirty 27300 * @return {Boolean} True/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents. 27301 * @example 27302 * if (tinymce.activeEditor.isDirty()) 27303 * alert("You must save your contents."); 27304 */ 27305 isDirty: function() { 27306 return !this.isNotDirty; 27307 }, 27308 27309 /** 27310 * Returns the editors container element. The container element wrappes in 27311 * all the elements added to the page for the editor. Such as UI, iframe etc. 27312 * 27313 * @method getContainer 27314 * @return {Element} HTML DOM element for the editor container. 27315 */ 27316 getContainer: function() { 27317 var self = this; 27318 27319 if (!self.container) { 27320 self.container = DOM.get(self.editorContainer || self.id + '_parent'); 27321 } 27322 27323 return self.container; 27324 }, 27325 27326 /** 27327 * Returns the editors content area container element. The this element is the one who 27328 * holds the iframe or the editable element. 27329 * 27330 * @method getContentAreaContainer 27331 * @return {Element} HTML DOM element for the editor area container. 27332 */ 27333 getContentAreaContainer: function() { 27334 return this.contentAreaContainer; 27335 }, 27336 27337 /** 27338 * Returns the target element/textarea that got replaced with a TinyMCE editor instance. 27339 * 27340 * @method getElement 27341 * @return {Element} HTML DOM element for the replaced element. 27342 */ 27343 getElement: function() { 27344 return DOM.get(this.settings.content_element || this.id); 27345 }, 27346 27347 /** 27348 * Returns the iframes window object. 27349 * 27350 * @method getWin 27351 * @return {Window} Iframe DOM window object. 27352 */ 27353 getWin: function() { 27354 var self = this, elm; 27355 27356 if (!self.contentWindow) { 27357 elm = DOM.get(self.id + "_ifr"); 27358 27359 if (elm) { 27360 self.contentWindow = elm.contentWindow; 27361 } 27362 } 27363 27364 return self.contentWindow; 27365 }, 27366 27367 /** 27368 * Returns the iframes document object. 27369 * 27370 * @method getDoc 27371 * @return {Document} Iframe DOM document object. 27372 */ 27373 getDoc: function() { 27374 var self = this, win; 27375 27376 if (!self.contentDocument) { 27377 win = self.getWin(); 27378 27379 if (win) { 27380 self.contentDocument = win.document; 27381 } 27382 } 27383 27384 return self.contentDocument; 27385 }, 27386 27387 /** 27388 * Returns the iframes body element. 27389 * 27390 * @method getBody 27391 * @return {Element} Iframe body element. 27392 */ 27393 getBody: function() { 27394 return this.bodyElement || this.getDoc().body; 27395 }, 27396 27397 /** 27398 * URL converter function this gets executed each time a user adds an img, a or 27399 * any other element that has a URL in it. This will be called both by the DOM and HTML 27400 * manipulation functions. 27401 * 27402 * @method convertURL 27403 * @param {string} url URL to convert. 27404 * @param {string} name Attribute name src, href etc. 27405 * @param {string/HTMLElement} elm Tag name or HTML DOM element depending on HTML or DOM insert. 27406 * @return {string} Converted URL string. 27407 */ 27408 convertURL: function(url, name, elm) { 27409 var self = this, settings = self.settings; 27410 27411 // Use callback instead 27412 if (settings.urlconverter_callback) { 27413 return self.execCallback('urlconverter_callback', url, elm, true, name); 27414 } 27415 27416 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs 27417 if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0 || url.length === 0) { 27418 return url; 27419 } 27420 27421 // Convert to relative 27422 if (settings.relative_urls) { 27423 return self.documentBaseURI.toRelative(url); 27424 } 27425 27426 // Convert to absolute 27427 url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host); 27428 27429 return url; 27430 }, 27431 27432 /** 27433 * Adds visual aid for tables, anchors etc so they can be more easily edited inside the editor. 27434 * 27435 * @method addVisual 27436 * @param {Element} elm Optional root element to loop though to find tables etc that needs the visual aid. 27437 */ 27438 addVisual: function(elm) { 27439 var self = this, settings = self.settings, dom = self.dom, cls; 27440 27441 elm = elm || self.getBody(); 27442 27443 if (self.hasVisual === undefined) { 27444 self.hasVisual = settings.visual; 27445 } 27446 27447 each(dom.select('table,a', elm), function(elm) { 27448 var value; 27449 27450 switch (elm.nodeName) { 27451 case 'TABLE': 27452 cls = settings.visual_table_class || 'mce-item-table'; 27453 value = dom.getAttrib(elm, 'border'); 27454 27455 if (!value || value == '0') { 27456 if (self.hasVisual) { 27457 dom.addClass(elm, cls); 27458 } else { 27459 dom.removeClass(elm, cls); 27460 } 27461 } 27462 27463 return; 27464 27465 case 'A': 27466 if (!dom.getAttrib(elm, 'href', false)) { 27467 value = dom.getAttrib(elm, 'name') || elm.id; 27468 cls = 'mce-item-anchor'; 27469 27470 if (value) { 27471 if (self.hasVisual) { 27472 dom.addClass(elm, cls); 27473 } else { 27474 dom.removeClass(elm, cls); 27475 } 27476 } 27477 } 27478 27479 return; 27480 } 27481 }); 27482 27483 self.fire('VisualAid', {element: elm, hasVisual: self.hasVisual}); 27484 }, 27485 27486 /** 27487 * Removes the editor from the dom and tinymce collection. 27488 * 27489 * @method remove 27490 */ 27491 remove: function() { 27492 var self = this; 27493 27494 if (!self.removed) { 27495 self.removed = 1; // Cancels post remove event execution 27496 27497 // Remove any hidden input 27498 if (self.hasHiddenInput) { 27499 DOM.remove(self.getElement().nextSibling); 27500 } 27501 27502 // Fixed bug where IE has a blinking cursor left from the editor
27503 var doc = self.getDoc(); 27504 if (ie && doc && !self.inline) { 27505 doc.execCommand('SelectAll'); 27506 } 27507 27508 // We must save before we hide so Safari doesn't crash 27509 self.save(); 27510 27511 DOM.setStyle(self.id, 'display', self.orgDisplay); 27512 27513 // Don't clear the window or document if content editable 27514 // is enabled since other instances might still be present 27515 if (!self.settings.content_editable) { 27516 Event.unbind(self.getWin()); 27517 Event.unbind(self.getDoc()); 27518 } 27519 27520 var elm = self.getContainer(); 27521 Event.unbind(self.getBody()); 27522 Event.unbind(elm); 27523 27524 self.fire('remove'); 27525 27526 self.editorManager.remove(self); 27527 DOM.remove(elm); 27528 self.destroy(); 27529 } 27530 }, 27531 27532 bindNative: function(name) { 27533 var self = this; 27534 27535 if (self.settings.readonly) { 27536 return; 27537 } 27538 27539 if (self.initialized) { 27540 self.dom.bind(getEventTarget(self, name), name, function(e) { 27541 self.fire(name, e); 27542 }); 27543 } else { 27544 if (!self._pendingNativeEvents) { 27545 self._pendingNativeEvents = [name]; 27546 } else { 27547 self._pendingNativeEvents.push(name); 27548 } 27549 } 27550 }, 27551 27552 unbindNative: function(name) { 27553 var self = this; 27554 27555 if (self.initialized) { 27556 self.dom.unbind(name); 27557 } 27558 }, 27559 27560 /** 27561 * Destroys the editor instance by removing all events, element references or other resources 27562 * that could leak memory. This method will be called automatically when the page is unloaded 27563 * but you can also call it directly if you know what you are doing. 27564 * 27565 * @method destroy 27566 * @param {Boolean} automatic Optional state if the destroy is an automatic destroy or user called one. 27567 */ 27568 destroy: function(automatic) { 27569 var self = this, form; 27570 27571 // One time is enough 27572 if (self.destroyed) { 27573 return; 27574 } 27575 27576 // If user manually calls destroy and not remove 27577 // Users seems to have logic that calls destroy instead of remove 27578 if (!automatic && !self.removed) { 27579 self.remove(); 27580 return; 27581 } 27582 27583 // We must unbind on Gecko since it would otherwise produce the pesky "attempt 27584 // to run compile-and-go script on a cleared scope" message 27585 if (automatic && isGecko) { 27586 Event.unbind(self.getDoc()); 27587 Event.unbind(self.getWin()); 27588 Event.unbind(self.getBody()); 27589 } 27590 27591 if (!automatic) { 27592 self.editorManager.off('beforeunload', self._beforeUnload); 27593 27594 // Manual destroy 27595 if (self.theme && self.theme.destroy) { 27596 self.theme.destroy(); 27597 } 27598 27599 // Destroy controls, selection and dom 27600 self.selection.destroy(); 27601 self.dom.destroy(); 27602 } 27603 27604 form = self.formElement; 27605 if (form) { 27606 if (form._mceOldSubmit) { 27607 form.submit = form._mceOldSubmit; 27608 form._mceOldSubmit = null; 27609 } 27610 27611 DOM.unbind(form, 'submit reset', self.formEventDelegate); 27612 } 27613 27614 self.contentAreaContainer = self.formElement = self.container = null; 27615 self.settings.content_element = self.bodyElement = self.contentDocument = self.contentWindow = null; 27616 27617 if (self.selection) { 27618 self.selection = self.selection.win = self.selection.dom = self.selection.dom.doc = null; 27619 } 27620 27621 self.destroyed = 1; 27622 }, 27623 27624 // Internal functions 27625 27626 _refreshContentEditable: function() { 27627 var self = this, body, parent; 27628 27629 // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again 27630 if (self._isHidden()) { 27631 body = self.getBody(); 27632 parent = body.parentNode; 27633 27634 parent.removeChild(body); 27635 parent.appendChild(body); 27636 27637 body.focus(); 27638 } 27639 }, 27640 27641 _isHidden: function() { 27642 var sel; 27643 27644 if (!isGecko) { 27645 return 0; 27646 } 27647 27648 // Weird, wheres that cursor selection? 27649 sel = this.selection.getSel(); 27650 return (!sel || !sel.rangeCount || sel.rangeCount === 0); 27651 } 27652 }; 27653 27654 extend(Editor.prototype, Observable); 27655 27656 return Editor; 27657}); 27658 27659// Included from: js/tinymce/classes/util/I18n.js 27660 27661/** 27662 * I18n.js 27663 * 27664 * Copyright, Moxiecode Systems AB 27665 * Released under LGPL License. 27666 * 27667 * License: http://www.tinymce.com/license 27668 * Contributing: http://www.tinymce.com/contributing 27669 */ 27670 27671/** 27672 * I18n class that handles translation of TinyMCE UI. 27673 * Uses po style with csharp style parameters. 27674 * 27675 * @class tinymce.util.I18n 27676 */ 27677define("tinymce/util/I18n", [], function() { 27678 "use strict"; 27679 27680 var data = {}; 27681 27682 return { 27683 /** 27684 * Property gets set to true if a RTL language pack was loaded. 27685 * 27686 * @property rtl 27687 * @type {Boolean} 27688 */ 27689 rtl: false, 27690 27691 /** 27692 * Adds translations for a specific language code. 27693 * 27694 * @method add 27695 * @param {String} code Language code like sv_SE. 27696 * @param {Array} items Name/value array with English en_US to sv_SE. 27697 */ 27698 add: function(code, items) { 27699 for (var name in items) { 27700 data[name] = items[name]; 27701 } 27702 27703 this.rtl = this.rtl || data._dir === 'rtl'; 27704 }, 27705 27706 /** 27707 * Translates the specified text. 27708 * 27709 * It has a few formats: 27710 * I18n.translate("Text"); 27711 * I18n.translate(["Text {0}/{1}", 0, 1]); 27712 * I18n.translate({raw: "Raw string"}); 27713 * 27714 * @method translate 27715 * @param {String/Object/Array} text Text to translate. 27716 * @return {String} String that got translated. 27717 */ 27718 translate: function(text) { 27719 if (typeof(text) == "undefined") { 27720 return text; 27721 } 27722 27723 if (typeof(text) != "string" && text.raw) { 27724 return text.raw; 27725 } 27726 27727 if (text.push) { 27728 var values = text.slice(1); 27729 27730 text = (data[text[0]] || text[0]).replace(/\{([^\}]+)\}/g, function(match1, match2) { 27731 return values[match2]; 27732 }); 27733 } 27734 27735 return data[text] || text; 27736 }, 27737 27738 data: data 27739 }; 27740}); 27741 27742// Included from: js/tinymce/classes/FocusManager.js 27743 27744/** 27745 * FocusManager.js 27746 * 27747 * Copyright, Moxiecode Systems AB 27748 * Released under LGPL License. 27749 * 27750 * License: http://www.tinymce.com/license 27751 * Contributing: http://www.tinymce.com/contributing 27752 */ 27753 27754/** 27755 * This class manages the focus/blur state of the editor. This class is needed since some
27756 * browsers fire false focus/blur states when the selection is moved to a UI dialog or similar. 27757 * 27758 * This class will fire two events focus and blur on the editor instances that got affected. 27759 * It will also handle the restore of selection when the focus is lost and returned. 27760 * 27761 * @class tinymce.FocusManager 27762 */ 27763define("tinymce/FocusManager", [ 27764 "tinymce/dom/DOMUtils", 27765 "tinymce/Env" 27766], function(DOMUtils, Env) { 27767 /** 27768 * Constructs a new focus manager instance. 27769 * 27770 * @constructor FocusManager 27771 * @param {tinymce.EditorManager} editorManager Editor manager instance to handle focus for. 27772 */ 27773 function FocusManager(editorManager) { 27774 function getActiveElement() { 27775 try { 27776 return document.activeElement; 27777 } catch (ex) { 27778 // IE sometimes fails to get the activeElement when resizing table 27779 // TODO: Investigate this 27780 return document.body; 27781 } 27782 } 27783 27784 // We can't store a real range on IE 11 since it gets mutated so we need to use a bookmark object 27785 // TODO: Move this to a separate range utils class since it's it's logic is present in Selection as well. 27786 function createBookmark(rng) { 27787 if (rng && rng.startContainer) { 27788 return { 27789 startContainer: rng.startContainer, 27790 startOffset: rng.startOffset, 27791 endContainer: rng.endContainer, 27792 endOffset: rng.endOffset 27793 }; 27794 } 27795 27796 return rng; 27797 } 27798 27799 function bookmarkToRng(editor, bookmark) { 27800 var rng; 27801 27802 if (bookmark.startContainer) { 27803 rng = editor.getDoc().createRange(); 27804 rng.setStart(bookmark.startContainer, bookmark.startOffset); 27805 rng.setEnd(bookmark.endContainer, bookmark.endOffset); 27806 } else { 27807 rng = bookmark; 27808 } 27809 27810 return rng; 27811 } 27812 27813 function isUIElement(elm) { 27814 return !!DOMUtils.DOM.getParent(elm, FocusManager.isEditorUIElement); 27815 } 27816 27817 function isNodeInBodyOfEditor(node, editor) { 27818 var body = editor.getBody(); 27819 27820 while (node) { 27821 if (node == body) { 27822 return true; 27823 } 27824 27825 node = node.parentNode; 27826 } 27827 } 27828 27829 function registerEvents(e) { 27830 var editor = e.editor, selectionChangeHandler; 27831 27832 editor.on('init', function() { 27833 // On IE take selection snapshot onbeforedeactivate 27834 if ("onbeforedeactivate" in document && Env.ie < 11) { 27835 editor.dom.bind(editor.getBody(), 'beforedeactivate', function() { 27836 try { 27837 editor.lastRng = editor.selection.getRng(); 27838 } catch (ex) { 27839 // IE throws "Unexcpected call to method or property access" some times so lets ignore it 27840 } 27841 27842 editor.selection.lastFocusBookmark = createBookmark(editor.lastRng); 27843 }); 27844 } else if (editor.inline || Env.ie > 10) { 27845 // On other browsers take snapshot on nodechange in inline mode since they have Ghost selections for iframes 27846 editor.on('nodechange keyup', function() { 27847 var node = document.activeElement; 27848 27849 // IE 11 reports active element as iframe not body of iframe 27850 if (node && node.id == editor.id + '_ifr') { 27851 node = editor.getBody(); 27852 } 27853 27854 if (isNodeInBodyOfEditor(node, editor)) { 27855 editor.lastRng = editor.selection.getRng(); 27856 } 27857 }); 27858 27859 // Handles the issue with WebKit not retaining selection within inline document 27860 // If the user releases the mouse out side the body while selecting a nodeChange won't 27861 // fire and there for the selection snapshot won't be stored 27862 // TODO: Optimize this since we only need to bind these on the active editor 27863 if (Env.webkit) { 27864 selectionChangeHandler = function() { 27865 var rng = editor.selection.getRng(); 27866 27867 // Store when it's non collapsed 27868 if (!rng.collapsed) { 27869 editor.lastRng = rng; 27870 } 27871 }; 27872 27873 // Bind selection handler 27874 DOMUtils.DOM.bind(document, 'selectionchange', selectionChangeHandler); 27875 27876 editor.on('remove', function() { 27877 DOMUtils.DOM.unbind(document, 'selectionchange', selectionChangeHandler); 27878 }); 27879 } 27880 } 27881 }); 27882 27883 editor.on('setcontent', function() { 27884 editor.lastRng = null; 27885 }); 27886 27887 // Remove last selection bookmark on mousedown see #6305 27888 editor.on('mousedown', function() { 27889 editor.selection.lastFocusBookmark = null; 27890 }); 27891 27892 editor.on('focusin', function() { 27893 var focusedEditor = editorManager.focusedEditor; 27894 27895 if (editor.selection.lastFocusBookmark) { 27896 editor.selection.setRng(bookmarkToRng(editor, editor.selection.lastFocusBookmark)); 27897 editor.selection.lastFocusBookmark = null; 27898 } 27899 27900 if (focusedEditor != editor) { 27901 if (focusedEditor) { 27902 focusedEditor.fire('blur', {focusedEditor: editor}); 27903 } 27904 27905 editorManager.activeEditor = editor; 27906 editorManager.focusedEditor = editor; 27907 editor.fire('focus', {blurredEditor: focusedEditor}); 27908 editor.focus(false); 27909 } 27910 27911 editor.lastRng = null; 27912 }); 27913 27914 editor.on('focusout', function() { 27915 window.setTimeout(function() { 27916 var focusedEditor = editorManager.focusedEditor; 27917 27918 // Still the same editor the the blur was outside any editor UI 27919 if (!isUIElement(getActiveElement()) && focusedEditor == editor) { 27920 editor.fire('blur', {focusedEditor: null}); 27921 editorManager.focusedEditor = null; 27922 editor.selection.lastFocusBookmark = null; 27923 } 27924 }, 0); 27925 }); 27926 } 27927 27928 // Check if focus is moved to an element outside the active editor by checking if the target node 27929 // isn't within the body of the activeEditor nor a UI element such as a dialog child control 27930 DOMUtils.DOM.bind(document, 'focusin', function(e) { 27931 var activeEditor = editorManager.activeEditor; 27932
27933 if (activeEditor && e.target.ownerDocument == document) { 27934 activeEditor.selection.lastFocusBookmark = createBookmark(activeEditor.lastRng); 27935 27936 // Fire a blur event if the element isn't a UI element 27937 if (!isUIElement(e.target) && editorManager.focusedEditor == activeEditor) { 27938 activeEditor.fire('blur', {focusedEditor: null}); 27939 editorManager.focusedEditor = null; 27940 } 27941 } 27942 }); 27943 27944 editorManager.on('AddEditor', registerEvents); 27945 } 27946 27947 /** 27948 * Returns true if the specified element is part of the UI for example an button or text input. 27949 * 27950 * @method isEditorUIElement 27951 * @param {Element} elm Element to check if it's part of the UI or not. 27952 * @return {Boolean} True/false state if the element is part of the UI or not. 27953 */ 27954 FocusManager.isEditorUIElement = function(elm) { 27955 // MODIF TN :: Test que ClassName est une chaine avant le indexOf pour eviter le form.className sur la propagation qui renvoi le input id="className" 27956 return ((typeof elm.className == "string")&&(elm.className.indexOf('mce-') !== -1)); 27957 //return elm.className.indexOf('mce-') !== -1; 27958 }; 27959 27960 return FocusManager; 27961}); 27962 27963// Included from: js/tinymce/classes/EditorManager.js 27964 27965/** 27966 * EditorManager.js 27967 * 27968 * Copyright, Moxiecode Systems AB 27969 * Released under LGPL License. 27970 * 27971 * License: http://www.tinymce.com/license 27972 * Contributing: http://www.tinymce.com/contributing 27973 */ 27974 27975/** 27976 * This class used as a factory for manager for tinymce.Editor instances. 27977 * 27978 * @example 27979 * tinymce.EditorManager.init({}); 27980 * 27981 * @class tinymce.EditorManager 27982 * @mixes tinymce.util.Observable 27983 * @static 27984 */ 27985define("tinymce/EditorManager", [ 27986 "tinymce/Editor", 27987 "tinymce/dom/DOMUtils", 27988 "tinymce/util/URI", 27989 "tinymce/Env", 27990 "tinymce/util/Tools", 27991 "tinymce/util/Observable", 27992 "tinymce/util/I18n", 27993 "tinymce/FocusManager" 27994], function(Editor, DOMUtils, URI, Env, Tools, Observable, I18n, FocusManager) { 27995 var DOM = DOMUtils.DOM; 27996 var explode = Tools.explode, each = Tools.each, extend = Tools.extend; 27997 var instanceCounter = 0, beforeUnloadDelegate; 27998 27999 var EditorManager = { 28000 /** 28001 * Major version of TinyMCE build. 28002 * 28003 * @property majorVersion
28004 * @type String 28005 */ 28006 majorVersion : '4', 28007 28008 /** 28009 * Minor version of TinyMCE build. 28010 * 28011 * @property minorVersion 28012 * @type String 28013 */ 28014 minorVersion : '0.12', 28015 28016 /** 28017 * Release date of TinyMCE build. 28018 * 28019 * @property releaseDate 28020 * @type String 28021 */ 28022 releaseDate: '2013-12-18', 28023 28024 /** 28025 * Collection of editor instances. 28026 * 28027 * @property editors 28028 * @type Object 28029 * @example 28030 * for (edId in tinymce.editors) 28031 * tinymce.editors[edId].save(); 28032 */ 28033 editors: [], 28034 28035 /** 28036 * Collection of language pack data. 28037 * 28038 * @property i18n 28039 * @type Object 28040 */ 28041 i18n: I18n, 28042 28043 /** 28044 * Currently active editor instance. 28045 * 28046 * @property activeEditor 28047 * @type tinymce.Editor 28048 * @example 28049 * tinyMCE.activeEditor.selection.getContent(); 28050 * tinymce.EditorManager.activeEditor.selection.getContent(); 28051 */ 28052 activeEditor: null, 28053 28054 setup: function() { 28055 var self = this, baseURL, documentBaseURL, suffix = "", preInit; 28056 28057 // Get base URL for the current document 28058 documentBaseURL = document.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); 28059 if (!/[\/\\]$/.test(documentBaseURL)) { 28060 documentBaseURL += '/'; 28061 } 28062 28063 // If tinymce is defined and has a base use that or use the old tinyMCEPreInit 28064 preInit = window.tinymce || window.tinyMCEPreInit; 28065 if (preInit) { 28066 baseURL = preInit.base || preInit.baseURL; 28067 suffix = preInit.suffix; 28068 } else { 28069 // Get base where the tinymce script is located 28070 var scripts = document.getElementsByTagName('script'); 28071 for (var i = 0; i < scripts.length; i++) { 28072 var src = scripts[i].src; 28073 28074 // Script types supported: 28075 // tinymce.js tinymce.min.js tinymce.dev.js 28076 // tinymce.jquery.js tinymce.jquery.min.js tinymce.jquery.dev.js 28077 // tinymce.full.js tinymce.full.min.js tinymce.full.dev.js 28078 if (/tinymce(\.full|\.jquery|)(\.min|\.dev|)\.js/.test(src)) { 28079 if (src.indexOf('.min') != -1) { 28080 suffix = '.min'; 28081 } 28082 28083 baseURL = src.substring(0, src.lastIndexOf('/')); 28084 break; 28085 } 28086 } 28087 } 28088 28089 /** 28090 * Base URL where the root directory if TinyMCE is located. 28091 * 28092 * @property baseURL
28093 * @type String 28094 */ 28095 self.baseURL = new URI(documentBaseURL).toAbsolute(baseURL); 28096 28097 /** 28098 * Document base URL where the current document is located. 28099 * 28100 * @property documentBaseURL 28101 * @type String 28102 */ 28103 self.documentBaseURL = documentBaseURL; 28104 28105 /** 28106 * Absolute baseURI for the installation path of TinyMCE. 28107 * 28108 * @property baseURI 28109 * @type tinymce.util.URI 28110 */ 28111 self.baseURI = new URI(self.baseURL); 28112 28113 /** 28114 * Current suffix to add to each plugin/theme that gets loaded for example ".min". 28115 * 28116 * @property suffix 28117 * @type String 28118 */ 28119 self.suffix = suffix; 28120 28121 self.focusManager = new FocusManager(self); 28122 }, 28123 28124 /** 28125 * Initializes a set of editors. This method will create editors based on various settings. 28126 * 28127 * @method init 28128 * @param {Object} settings Settings object to be passed to each editor instance. 28129 * @example 28130 * // Initializes a editor using the longer method 28131 * tinymce.EditorManager.init({ 28132 * some_settings : 'some value' 28133 * }); 28134 * 28135 * // Initializes a editor instance using the shorter version 28136 * tinyMCE.init({ 28137 * some_settings : 'some value' 28138 * }); 28139 */ 28140 init: function(settings) { 28141 var self = this, editors = [], editor; 28142 28143 function createId(elm) { 28144 var id = elm.id; 28145 28146 // Use element id, or unique name or generate a unique id 28147 if (!id) { 28148 id = elm.name; 28149 28150 if (id && !DOM.get(id)) { 28151 id = elm.name; 28152 } else { 28153 // Generate unique name 28154 id = DOM.uniqueId(); 28155 } 28156 28157 elm.setAttribute('id', id); 28158 } 28159 28160 return id; 28161 } 28162 28163 function execCallback(se, n, s) { 28164 var f = se[n]; 28165 28166 if (!f) { 28167 return; 28168 } 28169 28170 return f.apply(s || this, Array.prototype.slice.call(arguments, 2)); 28171 } 28172 28173 function hasClass(n, c) { 28174 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c); 28175 } 28176 28177 function readyHandler() { 28178 var l, co; 28179 28180 DOM.unbind(window, 'ready', readyHandler); 28181 28182 execCallback(settings, 'onpageload'); 28183 28184 if (settings.types) { 28185 // Process type specific selector 28186 each(settings.types, function(type) { 28187 each(DOM.select(type.selector), function(elm) { 28188 var editor = new Editor(createId(elm), extend({}, settings, type), self); 28189 editors.push(editor); 28190 editor.render(1); 28191 }); 28192 }); 28193 28194 return; 28195 } else if (settings.selector) { 28196 // Process global selector 28197 each(DOM.select(settings.selector), function(elm) { 28198 var editor = new Editor(createId(elm), settings, self); 28199 editors.push(editor); 28200 editor.render(1); 28201 }); 28202 28203 return; 28204 } 28205 28206 // Fallback to old setting 28207 switch (settings.mode) { 28208 case "exact": 28209 l = settings.elements || ''; 28210 28211 if(l.length > 0) { 28212 each(explode(l), function(v) { 28213 if (DOM.get(v)) { 28214 editor = new Editor(v, settings, self); 28215 editors.push(editor); 28216 editor.render(true); 28217 } else { 28218 each(document.forms, function(f) { 28219 each(f.elements, function(e) { 28220 if (e.name === v) { 28221 v = 'mce_editor_' + instanceCounter++; 28222 DOM.setAttrib(e, 'id', v); 28223 28224 editor = new Editor(v, settings, self); 28225 editors.push(editor); 28226 editor.render(1); 28227 } 28228 }); 28229 }); 28230 } 28231 }); 28232 } 28233 break; 28234 28235 case "textareas": 28236 case "specific_textareas": 28237 each(DOM.select('textarea'), function(elm) { 28238 if (settings.editor_deselector && hasClass(elm, settings.editor_deselector)) { 28239 return; 28240 } 28241 28242 if (!settings.editor_selector || hasClass(elm, settings.editor_selector)) { 28243 editor = new Editor(createId(elm), settings, self); 28244 editors.push(editor); 28245 editor.render(true); 28246 } 28247 }); 28248 break; 28249 } 28250 28251 // Call onInit when all editors are initialized 28252 if (settings.oninit) { 28253 l = co = 0; 28254 28255 each(editors, function(ed) { 28256 co++; 28257 28258 if (!ed.initialized) { 28259 // Wait for it 28260 ed.on('init', function() { 28261 l++; 28262 28263 // All done 28264 if (l == co) { 28265 execCallback(settings, 'oninit'); 28266 } 28267 }); 28268 } else { 28269 l++; 28270 } 28271 28272 // All done 28273 if (l == co) { 28274 execCallback(settings, 'oninit'); 28275 } 28276 }); 28277 } 28278 } 28279 28280 self.settings = settings; 28281 28282 DOM.bind(window, 'ready', readyHandler); 28283 }, 28284 28285 /** 28286 * Returns a editor instance by id. 28287 * 28288 * @method get 28289 * @param {String/Number} id Editor instance id or index to return. 28290 * @return {tinymce.Editor} Editor instance to return. 28291 * @example 28292 * // Adds an onclick event to an editor by id (shorter version) 28293 * tinymce.get('mytextbox').on('click', function(e) { 28294 * ed.windowManager.alert('Hello world!'); 28295 * }); 28296 * 28297 * // Adds an onclick event to an editor by id (longer version) 28298 * tinymce.EditorManager.get('mytextbox').on('click', function(e) { 28299 * ed.windowManager.alert('Hello world!'); 28300 * }); 28301 */ 28302 get: function(id) { 28303 if (id === undefined) { 28304 return this.editors; 28305 } 28306 28307 return this.editors[id]; 28308 }, 28309 28310 /** 28311 * Adds an editor instance to the editor collection. This will also set it as the active editor. 28312 * 28313 * @method add 28314 * @param {tinymce.Editor} editor Editor instance to add to the collection. 28315 * @return {tinymce.Editor} The same instance that got passed in. 28316 */ 28317 add: function(editor) { 28318 var self = this, editors = self.editors; 28319 28320 // Add named and index editor instance 28321 editors[editor.id] = editor; 28322 editors.push(editor); 28323 28324 self.activeEditor = editor; 28325 28326 /** 28327 * Fires when an editor is added to the EditorManager collection. 28328 * 28329 * @event AddEditor 28330 * @param {Object} e Event arguments. 28331 */ 28332 self.fire('AddEditor', {editor: editor}); 28333 28334 if (!beforeUnloadDelegate) { 28335 beforeUnloadDelegate = function() { 28336 self.fire('BeforeUnload'); 28337 }; 28338 28339 DOM.bind(window, 'beforeunload', beforeUnloadDelegate); 28340 } 28341 28342 return editor; 28343 }, 28344 28345 /** 28346 * Creates an editor instance and adds it to the EditorManager collection. 28347 * 28348 * @method createEditor 28349 * @param {String} id Instance id to use for editor. 28350 * @param {Object} settings Editor instance settings. 28351 * @return {tinymce.Editor} Editor instance that got created. 28352 */ 28353 createEditor: function(id, settings) { 28354 return this.add(new Editor(id, settings, this)); 28355 }, 28356 28357 /** 28358 * Removes a editor or editors form page. 28359 * 28360 * @example 28361 * // Remove all editors bound to divs 28362 * tinymce.remove('div'); 28363 * 28364 * // Remove all editors bound to textareas 28365 * tinymce.remove('textarea'); 28366 * 28367 * // Remove all editors 28368 * tinymce.remove(); 28369 * 28370 * // Remove specific instance by id 28371 * tinymce.remove('#id'); 28372 * 28373 * @method remove 28374 * @param {tinymce.Editor/String/Object}
28374 [selector] CSS selector or editor instance to remove. 28375 * @return {tinymce.Editor} The editor that got passed in will be return if it was found otherwise null. 28376 */ 28377 remove: function(selector) { 28378 var self = this, i, editors = self.editors, editor, removedFromList; 28379 28380 // Remove all editors 28381 if (!selector) { 28382 for (i = editors.length - 1; i >= 0; i--) { 28383 self.remove(editors[i]); 28384 } 28385 28386 return; 28387 } 28388 28389 // Remove editors by selector 28390 if (typeof(selector) == "string") { 28391 selector = selector.selector || selector; 28392 28393 each(DOM.select(selector), function(elm) { 28394 self.remove(editors[elm.id]); 28395 }); 28396 28397 return; 28398 } 28399 28400 // Remove specific editor 28401 editor = selector; 28402 28403 // Not in the collection 28404 if (!editors[editor.id]) { 28405 return null; 28406 } 28407 28408 delete editors[editor.id]; 28409 28410 for (i = 0; i < editors.length; i++) { 28411 if (editors[i] == editor) { 28412 editors.splice(i, 1); 28413 removedFromList = true; 28414 break; 28415 } 28416 } 28417 28418 // Select another editor since the active one was removed 28419 if (self.activeEditor == editor) { 28420 self.activeEditor = editors[0]; 28421 } 28422 28423 /** 28424 * Fires when an editor is removed from EditorManager collection. 28425 * 28426 * @event RemoveEditor 28427 * @param {Object} e Event arguments. 28428 */ 28429 if (removedFromList) { 28430 self.fire('RemoveEditor', {editor: editor}); 28431 } 28432 28433 if (!editors.length) { 28434 DOM.unbind(window, 'beforeunload', beforeUnloadDelegate); 28435 } 28436 28437 editor.remove(); 28438 28439 return editor; 28440 }, 28441 28442 /** 28443 * Executes a specific command on the currently active editor. 28444 * 28445 * @method execCommand 28446 * @param {String} c Command to perform for example Bold. 28447 * @param {Boolean} u Optional boolean state if a UI should be presented for the command or not. 28448 * @param {String} v Optional value parameter like for example an URL to a link. 28449 * @return {Boolean} true/false if the command was executed or not. 28450 */ 28451 execCommand: function(cmd, ui, value) { 28452 var self = this, editor = self.get(value); 28453 28454 // Manager commands 28455 switch (cmd) { 28456 case "mceAddEditor": 28457 if (!self.get(value)) { 28458 new Editor(value, self.settings, self).render(); 28459 } 28460 28461 return true; 28462 28463 case "mceRemoveEditor": 28464 if (editor) { 28465 editor.remove(); 28466 } 28467 28468 return true; 28469 28470 case 'mceToggleEditor': 28471 if (!editor) { 28472 self.execCommand('mceAddEditor', 0, value); 28473 return true; 28474 } 28475 28476 if (editor.isHidden()) { 28477 editor.show(); 28478 } else { 28479 editor.hide(); 28480 } 28481 28482 return true; 28483 } 28484 28485 // Run command on active editor 28486 if (self.activeEditor) { 28487 return self.activeEditor.execCommand(cmd, ui, value); 28488 } 28489 28490 return false; 28491 }, 28492 28493 /** 28494 * Calls the save method on all editor instances in the collection. This can be useful when a form is to be submitted. 28495 * 28496 * @method triggerSave 28497 * @example 28498 * // Saves all contents 28499 * tinyMCE.triggerSave(); 28500 */ 28501 triggerSave: function() { 28502 each(this.editors, function(editor) { 28503 editor.save(); 28504 }); 28505 }, 28506 28507 /** 28508 * Adds a language pack, this gets called by the loaded language files like en.js. 28509 * 28510 * @method addI18n 28511 * @param {String} code Optional language code. 28512 * @param {Object} items Name/value object with translations. 28513 */ 28514 addI18n: function(code, items) { 28515 I18n.add(code, items); 28516 }, 28517 28518 /** 28519 * Translates the specified string using the language pack items. 28520 * 28521 * @method translate 28522 * @param {String/Array/Object} text String to translate 28523 * @return {String} Translated string. 28524 */ 28525 translate: function(text) { 28526 return I18n.translate(text); 28527 } 28528 }; 28529 28530 extend(EditorManager, Observable); 28531 28532 EditorManager.setup(); 28533 28534 // Export EditorManager as tinymce/tinymce in global namespace 28535 window.tinymce = window.tinyMCE = EditorManager; 28536 28537 return EditorManager; 28538}); 28539 28540// Included from: js/tinymce/classes/LegacyInput.js 28541 28542/** 28543 * LegacyInput.js 28544 * 28545 * Copyright, Moxiecode Systems AB 28546 * Released under LGPL License. 28547 * 28548 * License: http://www.tinymce.com/license 28549 * Contributing: http://www.tinymce.com/contributing 28550 */ 28551 28552define("tinymce/LegacyInput", [ 28553 "tinymce/EditorManager", 28554 "tinymce/util/Tools" 28555], function(EditorManager, Tools) { 28556 var each = Tools.each, explode = Tools.explode; 28557 28558 EditorManager.on('AddEditor', function(e) { 28559 var editor = e.editor; 28560 28561 editor.on('preInit', function() { 28562 var filters, fontSizes, dom, settings = editor.settings; 28563 28564 function replaceWithSpan(node, styles) { 28565 each(styles, function(value, name) { 28566 if (value) { 28567 dom.setStyle(node, name, value); 28568 } 28569 }); 28570 28571 dom.rename(node, 'span'); 28572 } 28573 28574 function convert(e) { 28575 dom = editor.dom; 28576 28577 if (settings.convert_fonts_to_spans) { 28578 each(dom.select('font,u,strike', e.node), function(node) { 28579 filters[node.nodeName.toLowerCase()](dom, node); 28580 }); 28581 } 28582 } 28583 28584 if (settings.inline_styles) { 28585 fontSizes = explode(settings.font_size_legacy_values); 28586 28587 filters = { 28588 font: function(dom, node) { 28589 replaceWithSpan(node, { 28590 backgroundColor: node.style.backgroundColor, 28591 color: node.color, 28592 fontFamily: node.face, 28593 fontSize: fontSizes[parseInt(node.size, 10) - 1] 28594 }); 28595 }, 28596 28597 u: function(dom, node) { 28598 replaceWithSpan(node, { 28599 textDecoration: 'underline' 28600 }); 28601 }, 28602 28603 strike: function(dom, node) { 28604 replaceWithSpan(node, { 28605 textDecoration: 'line-through' 28606 }); 28607 } 28608 }; 28609 28610 editor.on('PreProcess SetContent', convert); 28611 } 28612 }); 28613 }); 28614}); 28615 28616// Included from: js/tinymce/classes/util/XHR.js 28617 28618/** 28619 * XHR.js 28620 * 28621 * Copyright, Moxiecode Systems AB 28622 * Released under LGPL License. 28623 * 28624 * License: http://www.tinymce.com/license 28625 * Contributing: http://www.tinymce.com/contributing 28626 */ 28627 28628/** 28629 * This class enables you to send XMLHTTPRequests cross browser. 28630 * @class tinymce.util.XHR 28631 * @static 28632 * @example 28633 * // Sends a low level Ajax request 28634 * tinymce.util.XHR.send({ 28635 * url: 'someurl', 28636 * success: function(text) { 28637 * console.debug(text); 28638 * } 28639 * }); 28640 */ 28641define("tinymce/util/XHR", [], function() { 28642 return { 28643 /** 28644 * Sends a XMLHTTPRequest. 28645 * Consult the Wiki for details on what settings this method takes. 28646 * 28647 * @method send 28648 * @param {Object} settings Object will target URL, callbacks and other info needed to make the request. 28649 */ 28650 send: function(settings) { 28651 var xhr, count = 0; 28652 28653 function ready() { 28654 if (!settings.async || xhr.readyState == 4 || count++ > 10000) { 28655 if (settings.success && count < 10000 && xhr.status == 200) { 28656 settings.success.call(settings.success_scope, '' + xhr.responseText, xhr, settings); 28657 } else if (settings.error) { 28658 settings.error.call(settings.error_scope, count > 10000 ? 'TIMED_OUT' : 'GENERAL', xhr, settings); 28659 } 28660 28661 xhr = null; 28662 } else { 28663 setTimeout(ready, 10); 28664 } 28665 } 28666 28667 // Default settings 28668 settings.scope = settings.scope || this; 28669 settings.success_scope = settings.success_scope || settings.scope; 28670 settings.error_scope = settings.error_scope || settings.scope; 28671 settings.async = settings.async === false ? false : true; 28672 settings.data = settings.data || ''; 28673 28674 xhr = new XMLHttpRequest(); 28675 28676 if (xhr) { 28677 if (xhr.overrideMimeType) { 28678 xhr.overrideMimeType(settings.content_type); 28679 } 28680
28681 xhr.open(settings.type || (settings.data ? 'POST' : 'GET'), settings.url, settings.async); 28682 28683 if (settings.content_type) { 28684 xhr.setRequestHeader('Content-Type', settings.content_type); 28685 } 28686 28687 xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 28688 28689 xhr.send(settings.data); 28690 28691 // Syncronous request 28692 if (!settings.async) { 28693 return ready(); 28694 } 28695 28696 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE 28697 setTimeout(ready, 10); 28698 } 28699 } 28700 }; 28701}); 28702 28703// Included from: js/tinymce/classes/util/JSON.js 28704 28705/** 28706 * JSON.js 28707 * 28708 * Copyright, Moxiecode Systems AB 28709 * Released under LGPL License. 28710 * 28711 * License: http://www.tinymce.com/license 28712 * Contributing: http://www.tinymce.com/contributing 28713 */ 28714 28715/** 28716 * JSON parser and serializer class. 28717 * 28718 * @class tinymce.util.JSON 28719 * @static 28720 * @example 28721 * // JSON parse a string into an object 28722 * var obj = tinymce.util.JSON.parse(somestring); 28723 * 28724 * // JSON serialize a object into an string 28725 * var str = tinymce.util.JSON.serialize(obj); 28726 */ 28727define("tinymce/util/JSON", [], function() { 28728 function serialize(o, quote) { 28729 var i, v, t, name; 28730 28731 quote = quote || '"'; 28732 28733 if (o === null) { 28734 return 'null'; 28735 } 28736 28737 t = typeof o; 28738 28739 if (t == 'string') { 28740 v = '\bb\tt\nn\ff\rr\""\'\'\\\\'; 28741 28742 return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) { 28743 // Make sure single quotes never get encoded inside double quotes for JSON compatibility 28744 if (quote === '"' && a === "'") { 28745 return a; 28746 } 28747 28748 i = v.indexOf(b); 28749 28750 if (i + 1) { 28751 return '\\' + v.charAt(i + 1); 28752 } 28753 28754 a = b.charCodeAt().toString(16); 28755 28756 return '\\u' + '0000'.substring(a.length) + a; 28757 }) + quote; 28758 } 28759 28760 if (t == 'object') { 28761 if (o.hasOwnProperty && Object.prototype.toString.call(o) === '[object Array]') { 28762 for (i=0, v = '['; i<o.length; i++) { 28763 v += (i > 0 ? ',' : '') + serialize(o[i], quote); 28764 } 28765 28766 return v + ']'; 28767 } 28768 28769 v = '{'; 28770 28771 for (name in o) { 28772 if (o.hasOwnProperty(name)) { 28773 v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + 28774 quote +':' + serialize(o[name], quote) : ''; 28775 } 28776 } 28777 28778 return v + '}'; 28779 } 28780 28781 return '' + o; 28782 } 28783 28784 return { 28785 /** 28786 * Serializes the specified object as a JSON string. 28787 * 28788 * @method serialize 28789 * @param {Object} obj Object to serialize as a JSON string. 28790 * @param {String} quote Optional quote string defaults to ". 28791 * @return {string} JSON string serialized from input. 28792 */ 28793 serialize: serialize, 28794 28795 /** 28796 * Unserializes/parses the specified JSON string into a object. 28797 * 28798 * @method parse 28799 * @param {string} s JSON String to parse into a JavaScript object. 28800 * @return {Object} Object from input JSON string or undefined if it failed. 28801 */ 28802 parse: function(text) { 28803 try { 28804 // Trick uglify JS 28805 return window[String.fromCharCode(101) + 'val']('(' + text + ')'); 28806 } catch (ex) { 28807 // Ignore 28808 } 28809 } 28810 28811 /**#@-*/ 28812 }; 28813}); 28814 28815// Included from: js/tinymce/classes/util/JSONRequest.js 28816 28817/** 28818 * JSONRequest.js 28819 * 28820 * Copyright, Moxiecode Systems AB 28821 * Released under LGPL License. 28822 * 28823 * License: http://www.tinymce.com/license 28824 * Contributing: http://www.tinymce.com/contributing 28825 */ 28826 28827/** 28828 * This class enables you to use JSON-RPC to call backend methods. 28829 * 28830 * @class tinymce.util.JSONRequest 28831 * @example 28832 * var json = new tinymce.util.JSONRequest({ 28833 * url: 'somebackend.php' 28834 * }); 28835 * 28836 * // Send RPC call 1 28837 * json.send({ 28838 * method: 'someMethod1', 28839 * params: ['a', 'b'], 28840 * success: function(result) { 28841 * console.dir(result); 28842 * } 28843 * }); 28844 * 28845 * // Send RPC call 2 28846 * json.send({ 28847 * method: 'someMethod2', 28848 * params: ['a', 'b'], 28849 * success: function(result) { 28850 * console.dir(result); 28851 * } 28852 * }); 28853 */ 28854define("tinymce/util/JSONRequest", [ 28855 "tinymce/util/JSON", 28856 "tinymce/util/XHR", 28857 "tinymce/util/Tools" 28858], function(JSON, XHR, Tools) { 28859 var extend = Tools.extend; 28860 28861 function JSONRequest(settings) { 28862 this.settings = extend({}, settings); 28863 this.count = 0; 28864 } 28865 28866 /** 28867 * Simple helper function to send a JSON-RPC request without the need to initialize an object. 28868 * Consult the Wiki API documentation for more details on what you can pass to this function. 28869 * 28870 * @method sendRPC 28871 * @static 28872 * @param {Object} o Call object where there are three field id, method and params this object should also contain callbacks etc. 28873 */ 28874 JSONRequest.sendRPC = function(o) { 28875 return new JSONRequest().send(o); 28876 }; 28877 28878 JSONRequest.prototype = { 28879 /** 28880 * Sends a JSON-RPC call. Consult the Wiki API documentation for more details on what you can pass to this function. 28881 * 28882 * @method send 28883 * @param {Object} args Call object where there are three field id, method and params this object should also contain callbacks etc. 28884 */ 28885 send: function(args) { 28886 var ecb = args.error, scb = args.success; 28887 28888 args = extend(this.settings, args); 28889 28890 args.success = function(c, x) { 28891 c = JSON.parse(c); 28892 28893 if (typeof(c) == 'undefined') { 28894 c = { 28895 error : 'JSON Parse error.' 28896 }; 28897 } 28898 28899 if (c.error) { 28900 ecb.call(args.error_scope || args.scope, c.error, x); 28901 } else { 28902 scb.call(args.success_scope || args.scope, c.result); 28903 } 28904 }; 28905 28906 args.error = function(ty, x) { 28907 if (ecb) { 28908 ecb.call(args.error_scope || args.scope, ty, x); 28909 } 28910 }; 28911 28912 args.data = JSON.serialize({ 28913 id: args.id || 'c' + (this.count++), 28914 method: args.method, 28915 params: args.params 28916 }); 28917 28918 // JSON content type for Ruby on rails. Bug: #1883287 28919 args.content_type = 'application/json'; 28920 28921 XHR.send(args); 28922 } 28923 }; 28924 28925 return JSONRequest; 28926}); 28927 28928// Included from: js/tinymce/classes/util/JSONP.js 28929 28930/** 28931 * JSONP.js 28932 * 28933 * Copyright, Moxiecode Systems AB 28934 * Released under LGPL License. 28935 * 28936 * License: http://www.tinymce.com/license 28937 * Contributing: http://www.tinymce.com/contributing 28938 */ 28939 28940define("tinymce/util/JSONP", [ 28941 "tinymce/dom/DOMUtils" 28942], function(DOMUtils) { 28943 return { 28944 callbacks: {}, 28945 count: 0, 28946 28947 send: function(settings) { 28948 var self = this, dom = DOMUtils.DOM, count = settings.count !== undefined ? settings.count : self.count; 28949 var id = 'tinymce_jsonp_' + count; 28950 28951 self.callbacks[count] = function(json) { 28952 dom.remove(id); 28953 delete self.callbacks[count]; 28954 28955 settings.callback(json); 28956 }; 28957 28958 dom.add(dom.doc.body, 'script', { 28959 id: id, 28960 src: settings.url, 28961 type: 'text/javascript' 28962 }); 28963 28964 self.count++; 28965 } 28966 }; 28967}); 28968 28969// Included from: js/tinymce/classes/util/LocalStorage.js 28970 28971/** 28972 * LocalStorage.js 28973 * 28974 * Copyright, Moxiecode Systems AB 28975 * Released under LGPL License. 28976 * 28977 * License: http://www.tinymce.com/license 28978 * Contributing: http://www.tinymce.com/contributing 28979 */ 28980 28981/** 28982 * This class will simulate LocalStorage on IE 7 and return the native version on modern browsers. 28983 * Storage is done using userData on IE 7 and a special serialization format. The format is designed 28984 * to be as small as possible by making sure that the keys and values doesn't need to be encoded. This 28985 * makes it possible to store for example HTML data. 28986 * 28987 * Storage format for userData: 28988 * <base 32 key length>,<key string>,<base 32 value length>,<value>,... 28989 * 28990 * For example this data key1=value1,key2=value2 would be: 28991 * 4,key1,6,value1,4,key2,6,value2 28992 * 28993 * @class tinymce.util.LocalStorage
28994 * @static 28995 * @version 4.0 28996 * @example 28997 * tinymce.util.LocalStorage.setItem('key', 'value'); 28998 * var value = tinymce.util.LocalStorage.getItem('key'); 28999 */ 29000define("tinymce/util/LocalStorage", [], function() { 29001 var LocalStorage, storageElm, items, keys, userDataKey, hasOldIEDataSupport; 29002 29003 // Check for native support 29004 try { 29005 if (window.localStorage) { 29006 return localStorage; 29007 } 29008 } catch (ex) { 29009 // Ignore 29010 } 29011 29012 userDataKey = "tinymce"; 29013 storageElm = document.documentElement; 29014 hasOldIEDataSupport = !!storageElm.addBehavior; 29015 29016 if (hasOldIEDataSupport) { 29017 storageElm.addBehavior('#default#userData'); 29018 } 29019 29020 /** 29021 * Gets the keys names and updates LocalStorage.length property. Since IE7 doesn't have any getters/setters. 29022 */ 29023 function updateKeys() { 29024 keys = []; 29025 29026 for (var key in items) { 29027 keys.push(key); 29028 } 29029 29030 LocalStorage.length = keys.length; 29031 } 29032 29033 /** 29034 * Loads the userData string and parses it into the items structure. 29035 */ 29036 function load() { 29037 var key, data, value, pos = 0; 29038 29039 items = {}; 29040 29041 // localStorage can be disabled on WebKit/Gecko s
29041o make a dummy storage 29042 if (!hasOldIEDataSupport) { 29043 return; 29044 } 29045 29046 function next(end) { 29047 var value, nextPos; 29048 29049 nextPos = end !== undefined ? pos + end : data.indexOf(',', pos); 29050 if (nextPos === -1 || nextPos > data.length) { 29051 return null; 29052 } 29053 29054 value = data.substring(pos, nextPos); 29055 pos = nextPos + 1; 29056 29057 return value; 29058 } 29059 29060 storageElm.load(userDataKey); 29061 data = storageElm.getAttribute(userDataKey) || ''; 29062 29063 do { 29064 var offset = next(); 29065 if (offset === null) { 29066 break; 29067 } 29068 29069 key = next(parseInt(offset, 32) || 0); 29070 if (key !== null) { 29071 offset = next(); 29072 if (offset === null) { 29073 break; 29074 } 29075 29076 value = next(parseInt(offset, 32) || 0); 29077 29078 if (key) { 29079 items[key] = value; 29080 } 29081 } 29082 } while (key !== null); 29083 29084 updateKeys(); 29085 } 29086 29087 /** 29088 * Saves the items structure into a the userData format. 29089 */ 29090 function save() { 29091 var value, data = ''; 29092 29093 // localStorage can be disabled on WebKit/Gecko so make a dummy storage 29094 if (!hasOldIEDataSupport) { 29095 return; 29096 } 29097 29098 for (var key in items) { 29099 value = items[key]; 29100 data += (data ? ',' : '') + key.length.toString(32) + ',' + key + ',' + value.length.toString(32) + ',' + value; 29101 } 29102 29103 storageElm.setAttribute(userDataKey, data); 29104 29105 try { 29106 storageElm.save(userDataKey); 29107 } catch (ex) { 29108 // Ignore disk full 29109 } 29110 29111 updateKeys(); 29112 } 29113 29114 LocalStorage = { 29115 /** 29116 * Length of the number of items in storage. 29117 * 29118 * @property length 29119 * @type Number 29120 * @return {Number} Number of items in storage. 29121 */ 29122 //length:0, 29123 29124 /** 29125 * Returns the key name by index. 29126 * 29127 * @method key 29128 * @param {Number} index Index of key to return. 29129 * @return {String} Key value or null if it wasn't found. 29130 */ 29131 key: function(index) { 29132 return keys[index]; 29133 }, 29134 29135 /** 29136 * Returns the value if the specified key or null if it wasn't found. 29137 * 29138 * @method getItem 29139 * @param {String} key Key of item to retrive. 29140 * @return {String} Value of the specified item or null if it wasn't found. 29141 */ 29142 getItem: function(key) { 29143 return key in items ? items[key] : null; 29144 }, 29145 29146 /** 29147 * Sets the value of the specified item by it's key. 29148 * 29149 * @method setItem 29150 * @param {String} key Key of the item to set. 29151 * @param {String} value Value of the item to set. 29152 */ 29153 setItem: function(key, value) { 29154 items[key] = "" + value; 29155 save(); 29156 }, 29157 29158 /** 29159 * Removes the specified item by key. 29160 * 29161 * @method removeItem 29162 * @param {String} key Key of item to remove. 29163 */ 29164 removeItem: function(key) { 29165 delete items[key]; 29166 save(); 29167 }, 29168 29169 /** 29170 * Removes all items. 29171 * 29172 * @method clear 29173 */ 29174 clear: function() { 29175 items = {}; 29176 save(); 29177 } 29178 }; 29179 29180 load(); 29181 29182 return LocalStorage; 29183}); 29184 29185// Included from: js/tinymce/classes/Compat.js 29186 29187/** 29188 * Compat.js 29189 * 29190 * Copyright, Moxiecode Systems AB 29191 * Released under LGPL License. 29192 * 29193 * License: http://www.tinymce.com/license 29194 * Contributing: http://www.tinymce.com/contributing 29195 */ 29196 29197/** 29198 * TinyMCE core class. 29199 * 29200 * @static 29201 * @class tinymce 29202 * @borrow-members tinymce.EditorManager 29203 * @borrow-members tinymce.util.Tools 29204 */ 29205define("tinymce/Compat", [ 29206 "tinymce/dom/DOMUtils", 29207 "tinymce/dom/EventUtils", 29208 "tinymce/dom/ScriptLoader", 29209 "tinymce/AddOnManager", 29210 "tinymce/util/Tools", 29211 "tinymce/Env" 29212], function(DOMUtils, EventUtils, ScriptLoader, AddOnManager, Tools, Env) { 29213 var tinymce = window.tinymce; 29214 29215 /** 29216 * @property {tinymce.dom.DOMUtils} DOM Global DOM instance. 29217 * @property {tinymce.dom.ScriptLoader} ScriptLoader Global ScriptLoader instance. 29218 * @property {tinymce.AddOnManager} PluginManager Global PluginManager instance. 29219 * @property {tinymce.AddOnManager} ThemeManager Global ThemeManager instance. 29220 */ 29221 tinymce.DOM = DOMUtils.DOM; 29222 tinymce.ScriptLoader = ScriptLoader.ScriptLoader; 29223 tinymce.PluginManager = AddOnManager.PluginManager; 29224 tinymce.ThemeManager = AddOnManager.ThemeManager; 29225 29226 tinymce.dom = tinymce.dom || {}; 29227 tinymce.dom.Event = EventUtils.Event; 29228 29229 Tools.each(Tools, function(func, key) { 29230 tinymce[key] = func; 29231 }); 29232 29233 Tools.each('isOpera isWebKit isIE isGecko isMac'.split(' '), function(name) { 29234 tinymce[name] = Env[name.substr(2).toLowerCase()]; 29235 }); 29236 29237 return {}; 29238}); 29239
29240// Describe the different namespaces 29241 29242/** 29243 * Root level namespace this contains classes directly releated to the TinyMCE editor. 29244 * 29245 * @namespace tinymce 29246 */ 29247 29248/** 29249 * Contains classes for handling the browsers DOM. 29250 * 29251 * @namespace tinymce.dom 29252 */ 29253 29254/** 29255 * Contains html parser and serializer logic. 29256 * 29257 * @namespace tinymce.html 29258 */ 29259 29260/** 29261 * Contains the different UI types such as buttons, listboxes etc. 29262 * 29263 * @namespace tinymce.ui 29264 */ 29265 29266/** 29267 * Contains various utility classes such as json parser, cookies etc. 29268 * 29269 * @namespace tinymce.util 29270 */ 29271 29272// Included from: js/tinymce/classes/ui/Layout.js 29273 29274/** 29275 * Layout.js 29276 * 29277 * Copyright, Moxiecode Systems AB 29278 * Released under LGPL License. 29279 * 29280 * License: http://www.tinymce.com/license 29281 * Contributing: http://www.tinymce.com/contributing 29282 */ 29283 29284/** 29285 * Base layout manager class. 29286 * 29287 * @class tinymce.ui.Layout 29288 */ 29289define("tinymce/ui/Layout", [ 29290 "tinymce/util/Class", 29291 "tinymce/util/Tools" 29292], function(Class, Tools) { 29293 "use strict"; 29294 29295 return Class.extend({ 29296 Defaults: { 29297 firstControlClass: 'first', 29298 lastControlClass: 'last' 29299 }, 29300 29301 /** 29302 * Constructs a layout instance with the specified settings. 29303 * 29304 * @constructor 29305 * @param {Object} settings Name/value object with settings. 29306 */ 29307 init: function(settings) { 29308 this.settings = Tools.extend({}, this.Defaults, settings); 29309 }, 29310 29311 /** 29312 * This method gets invoked before the layout renders the controls. 29313 * 29314 * @method preRender 29315 * @param {tinymce.ui.Container} container Container instance to preRender. 29316 */ 29317 preRender: function(container) { 29318 container.addClass(this.settings.containerClass, 'body'); 29319 }, 29320 29321 /** 29322 * Applies layout classes to the container. 29323 * 29324 * @private 29325 */ 29326 applyClasses: function(container) { 29327 var self = this, settings = self.settings, items, firstClass, lastClass; 29328 29329 items = container.items().filter(':visible'); 29330 firstClass = settings.firstControlClass; 29331 lastClass = settings.lastControlClass; 29332 29333 items.each(function(item) { 29334 item.removeClass(firstClass).removeClass(lastClass); 29335 29336 if (settings.controlClass) { 29337 item.addClass(settings.controlClass); 29338 } 29339 }); 29340 29341 items.eq(0).addClass(firstClass); 29342 items.eq(-1).addClass(lastClass); 29343 }, 29344 29345 /** 29346 * Renders the specified container and any layout specific HTML. 29347 * 29348 * @method renderHtml 29349 * @param {tinymce.ui.Container} container Container to render HTML for. 29350 */ 29351 renderHtml: function(container) { 29352 var self = this, settings = self.settings, items, html = ''; 29353 29354 items = container.items(); 29355 items.eq(0).addClass(settings.firstControlClass); 29356 items.eq(-1).addClass(settings.lastControlClass); 29357 29358 items.each(function(item) { 29359 if (settings.controlClass) { 29360 item.addClass(settings.controlClass); 29361 } 29362 29363 html += item.renderHtml(); 29364 }); 29365 29366 return html; 29367 }, 29368 29369 /** 29370 * Recalculates the positions of the controls in the specified container. 29371 * 29372 * @method recalc 29373 * @param {tinymce.ui.Container} container Container instance to recalc. 29374 */ 29375 recalc: function() { 29376 }, 29377 29378 /** 29379 * This method gets invoked after the layout renders the controls. 29380 * 29381 * @method postRender 29382 * @param {tinymce.ui.Container} container Container instance to postRender. 29383 */ 29384 postRender: function() { 29385 } 29386 }); 29387}); 29388 29389// Included from: js/tinymce/classes/ui/AbsoluteLayout.js 29390 29391/** 29392 * AbsoluteLayout.js 29393 * 29394 * Copyright, Moxiecode Systems AB 29395 * Released under LGPL License. 29396 * 29397 * License: http://www.tinymce.com/license 29398 * Contributing: http://www.tinymce.com/contributing 29399 */ 29400 29401/** 29402 * LayoutManager for absolute positioning. This layout manager is more of 29403 * a base class for other layouts but can be created and used directly. 29404 * 29405 * @-x-less AbsoluteLayout.less 29406 * @class tinymce.ui.AbsoluteLayout 29407 * @extends tinymce.ui.Layout 29408 */ 29409define("tinymce/ui/AbsoluteLayout", [ 29410 "tinymce/ui/Layout" 29411], function(Layout) { 29412 "use strict"; 29413 29414 return Layout.extend({ 29415 Defaults: { 29416 containerClass: 'abs-layout', 29417 controlClass: 'abs-layout-item' 29418 }, 29419 29420 /** 29421 * Recalculates the positions of the controls in the specified container. 29422 * 29423 * @method recalc 29424 * @param {tinymce.ui.Container} container Container instance to recalc. 29425 */ 29426 recalc: function(container) { 29427 container.items().filter(':visible').each(function(ctrl) { 29428 var settings = ctrl.settings; 29429 29430 ctrl.layoutRect({ 29431 x: settings.x, 29432 y: settings.y, 29433 w: settings.w, 29434 h: settings.h 29435 }); 29436 29437 if (ctrl.recalc) { 29438 ctrl.recalc(); 29439 } 29440 }); 29441 }, 29442 29443 /** 29444 * Renders the specified container and any layout specific HTML. 29445 * 29446 * @method renderHtml 29447 * @param {tinymce.ui.Container} container Container to render HTML for. 29448 */ 29449 renderHtml: function(container) { 29450 return '<div id="' + container._id + '-absend" class="' + container.classPrefix + 'abs-end"></div>' + this._super(container); 29451 } 29452 }); 29453}); 29454 29455// Included from: js/tinymce/classes/ui/Tooltip.js 29456 29457/** 29458 * Tooltip.js 29459 * 29460 * Copyright, Moxiecode Systems AB 29461 * Released under LGPL License. 29462 * 29463 * License: http://www.tinymce.com/license 29464 * Contributing: http://www.tinymce.com/contributing 29465 */ 29466 29467/** 29468 * Creates a tooltip instance. 29469 * 29470 * @-x-less ToolTip.less 29471 * @class tinymce.ui.ToolTip 29472 * @extends tinymce.ui.Control 29473 * @mixes tinymce.ui.Movable 29474 */
29475define("tinymce/ui/Tooltip", [ 29476 "tinymce/ui/Control", 29477 "tinymce/ui/Movable" 29478], function(Control, Movable) { 29479 return Control.extend({ 29480 Mixins: [Movable], 29481 29482 Defaults: { 29483 classes: 'widget tooltip tooltip-n' 29484 }, 29485 29486 /** 29487 * Sets/gets the current label text. 29488 * 29489 * @method text 29490 * @param {String} [text] New label text. 29491 * @return {String|tinymce.ui.Tooltip} Current text or current label instance. 29492 */ 29493 text: function(value) { 29494 var self = this; 29495 29496 if (typeof(value) != "undefined") { 29497 self._value = value; 29498 29499 if (self._rendered) { 29500 self.getEl().lastChild.innerHTML = self.encode(value); 29501 } 29502 29503 return self; 29504 } 29505 29506 return self._value; 29507 }, 29508 29509 /** 29510 * Renders the control as a HTML string. 29511 * 29512 * @method renderHtml 29513 * @return {String} HTML representing the control. 29514 */ 29515 renderHtml: function() { 29516 var self = this, prefix = self.classPrefix; 29517 29518 return ( 29519 '<div id="' + self._id + '" class="' + self.classes() + '" role="presentation">' + 29520 '<div class="' + prefix + 'tooltip-arrow"></div>' + 29521 '<div class="' + prefix + 'tooltip-inner">' + self.encode(self._text) + '</div>' + 29522 '</div>' 29523 ); 29524 }, 29525 29526 /** 29527 * Repaints the control after a layout operation. 29528 * 29529 * @method repaint 29530 */ 29531 repaint: function() { 29532 var self = this, style, rect; 29533 29534 style = self.getEl().style; 29535 rect = self._layoutRect; 29536 29537 style.left = rect.x + 'px'; 29538 style.top = rect.y + 'px'; 29539 style.zIndex = 0xFFFF + 0xFFFF; 29540 } 29541 }); 29542}); 29543 29544// Included from: js/tinymce/classes/ui/Widget.js 29545 29546/** 29547 * Widget.js 29548 * 29549 * Copyright, Moxiecode Systems AB 29550 * Released under LGPL License. 29551 * 29552 * License: http://www.tinymce.com/license 29553 * Contributing: http://www.tinymce.com/contributing 29554 */ 29555 29556/** 29557 * Widget base class a widget is a control that has a tooltip and s
29557ome basic states. 29558 * 29559 * @class tinymce.ui.Widget 29560 * @extends tinymce.ui.Control 29561 */ 29562define("tinymce/ui/Widget", [ 29563 "tinymce/ui/Control", 29564 "tinymce/ui/Tooltip" 29565], function(Control, Tooltip) { 29566 "use strict"; 29567 29568 var tooltip; 29569 29570 var Widget = Control.extend({ 29571 /** 29572 * Constructs a instance with the specified settings. 29573 * 29574 * @constructor 29575 * @param {Object} settings Name/value object with settings. 29576 * @setting {String} tooltip Tooltip text to display when hovering. 29577 * @setting {Boolean} autofocus True if the control should be focused when rendered. 29578 * @setting {String} text Text to display inside widget. 29579 */ 29580 init: function(settings) { 29581 var self = this; 29582 29583 self._super(settings); 29584 self.canFocus = true; 29585 29586 if (settings.tooltip && Widget.tooltips !== false) { 29587 self.on('mouseenter', function(e) { 29588 var tooltip = self.tooltip().moveTo(-0xFFFF); 29589 29590 if (e.control == self) { 29591 var rel = tooltip.text(settings.tooltip).show().testMoveRel(self.getEl(), ['bc-tc', 'bc-tl', 'bc-tr']); 29592 29593 tooltip.toggleClass('tooltip-n', rel == 'bc-tc'); 29594 tooltip.toggleClass('tooltip-nw', rel == 'bc-tl'); 29595 tooltip.toggleClass('tooltip-ne', rel == 'bc-tr'); 29596 29597 tooltip.moveRel(self.getEl(), rel); 29598 } else { 29599 tooltip.hide(); 29600 } 29601 }); 29602 29603 self.on('mouseleave mousedown click', function() { 29604 self.tooltip().hide(); 29605 }); 29606 29607 } 29608 29609 self.aria('label', settings.tooltip); 29610 }, 29611 29612 /** 29613 * Returns the current tooltip instance. 29614 * 29615 * @method tooltip 29616 * @return {tinymce.ui.Tooltip} Tooltip instance. 29617 */ 29618 tooltip: function() { 29619 var self = this; 29620 29621 if (!tooltip) { 29622 tooltip = new Tooltip({type: 'tooltip'}); 29623 tooltip.renderTo(self.getContainerElm()); 29624 } 29625 29626 return tooltip; 29627 }, 29628 29629 /** 29630 * Sets/gets the active state of the widget. 29631 * 29632 * @method active 29633 * @param {Boolean} [state] State if the control is active. 29634 * @return {Boolean|tinymce.ui.Widget} True/false or current widget instance. 29635 */ 29636 active: function(state) { 29637 var self = this, undef; 29638 29639 if (state !== undef) { 29640 self.aria('pressed', state); 29641 self.toggleClass('active', state); 29642 } 29643 29644 return self._super(state); 29645 }, 29646 29647 /** 29648 * Sets/gets the disabled state of the widget. 29649 * 29650 * @method disabled 29651 * @param {Boolean} [state] State if the control is disabled. 29652 * @return {Boolean|tinymce.ui.Widget} True/false or current widget instance. 29653 */ 29654 disabled: function(state) { 29655 var self = this, undef; 29656 29657 if (state !== undef) { 29658 self.aria('disabled', state); 29659 self.toggleClass('disabled', state); 29660 } 29661 29662 return self._super(state); 29663 }, 29664 29665 /** 29666 * Called after the control has been rendered. 29667 * 29668 * @method postRender 29669 */ 29670 postRender: function() { 29671 var self = this, settings = self.settings; 29672 29673 self._rendered = true; 29674 29675 self._super(); 29676 29677 if (!self.parent() && (settings.width || settings.height)) { 29678 self.initLayoutRect(); 29679 self.repaint(); 29680 } 29681 29682 if (settings.autofocus) { 29683 setTimeout(function() { 29684 self.focus(); 29685 }, 0); 29686 } 29687 }, 29688 29689 /** 29690 * Removes the current control from DOM and from UI collections. 29691 * 29692 * @method remove 29693 * @return {tinymce.ui.Control} Current control instance. 29694 */ 29695 remove: function() { 29696 this._super(); 29697 29698 if (tooltip) { 29699 tooltip.remove(); 29700 tooltip = null; 29701 } 29702 } 29703 }); 29704 29705 return Widget; 29706}); 29707 29708// Included from: js/tinymce/classes/ui/Button.js 29709 29710/** 29711 * Button.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 29720/** 29721 * This class is used to create buttons. You can create them directly or through the Factory. 29722 * 29723 * @example 29724 * // Create and render a button to the body element 29725 * tinymce.ui.Factory.create({ 29726 * type: 'button', 29727 * text: 'My button' 29728 * }).renderTo(document.body); 29729 * 29730 * @-x-less Button.less 29731 * @class tinymce.ui.Button 29732 * @extends tinymce.ui.Widget 29733 */ 29734define("tinymce/ui/Button", [ 29735 "tinymce/ui/Widget" 29736], function(Widget) { 29737 "use strict"; 29738 29739 return Widget.extend({ 29740 Defaults: { 29741 classes: "widget btn", 29742 role: "button" 29743 }, 29744 29745 /** 29746 * Constructs a new button instance with the specified settings. 29747 * 29748 * @constructor 29749 * @param {Object} settings Name/value object with settings.
29750 * @setting {String} size Size of the button small|medium|large. 29751 * @setting {String} image Image to use for icon. 29752 * @setting {String} icon Icon to use for button. 29753 */ 29754 init: function(settings) { 29755 var self = this, size; 29756 29757 self.on('click mousedown', function(e) { 29758 e.preventDefault(); 29759 }); 29760 29761 self._super(settings); 29762 size = settings.size; 29763 29764 if (settings.subtype) { 29765 self.addClass(settings.subtype); 29766 } 29767 29768 if (size) { 29769 self.addClass('btn-' + size); 29770 } 29771 }, 29772 29773 /** 29774 * Sets/gets the current button icon. 29775 * 29776 * @method icon 29777 * @param {String} [icon] New icon identifier. 29778 * @return {String|tinymce.ui.MenuButton} Current icon or current MenuButton instance. 29779 */ 29780 icon: function(icon) { 29781 var self = this, prefix = self.classPrefix; 29782 29783 if (typeof(icon) == 'undefined') { 29784 return self.settings.icon; 29785 } 29786 29787 self.settings.icon = icon; 29788 icon = icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : ''; 29789 29790 if (self._rendered) { 29791 var btnElm = self.getEl().firstChild, iconElm = btnElm.getElementsByTagName('i')[0]; 29792 29793 if (icon) { 29794 if (!iconElm || iconElm != btnElm.firstChild) { 29795 iconElm = document.createElement('i'); 29796 btnElm.insertBefore(iconElm, btnElm.firstChild); 29797 } 29798 29799 iconElm.className = icon; 29800 } else if (iconElm) { 29801 btnElm.removeChild(iconElm); 29802 } 29803 29804 self.text(self._text); // Set text again to fix whitespace between icon + text 29805 } 29806 29807 return self; 29808 }, 29809 29810 /** 29811 * Repaints the button for example after it's been resizes by a layout engine. 29812 * 29813 * @method repaint 29814 */ 29815 repaint: function() { 29816 var btnStyle = this.getEl().firstChild.style; 29817 29818 btnStyle.width = btnStyle.height = "100%"; 29819 29820 this._super(); 29821 }, 29822 29823 /** 29824 * Renders the control as a HTML string. 29825 * 29826 * @method renderHtml 29827 * @return {String} HTML representing the control. 29828 */ 29829 renderHtml: function() { 29830 var self = this, id = self._id, prefix = self.classPrefix; 29831 var icon = self.settings.icon, image = ''; 29832 29833 if (self.settings.image) { 29834 icon = 'none'; 29835 image = ' style="background-image: url(\'' + self.settings.image + '\')"'; 29836 } 29837 29838 icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : ''; 29839 29840 return ( 29841 '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' + 29842 '<button role="presentation" type="button" tabindex="-1">' + 29843 (icon ? '<i class="' + icon + '"' + image + '></i>' : '') + 29844 (self._text ? (icon ? '\u00a0' : '') + self.encode(self._text) : '') + 29845 '</button>' + 29846 '</div>' 29847 ); 29848 } 29849 }); 29850}); 29851 29852// Included from: js/tinymce/classes/ui/ButtonGroup.js 29853 29854/** 29855 * ButtonGroup.js 29856 * 29857 * Copyright, Moxiecode Systems AB 29858 * Released under LGPL License. 29859 * 29860 * License: http://www.tinymce.com/license 29861 * Contributing: http://www.tinymce.com/contributing 29862 */ 29863 29864/** 29865 * This control enables you to put multiple buttons into a group. This is 29866 * useful when you want to combine similar toolbar buttons into a group. 29867 * 29868 * @example 29869 * // Create and render a buttongroup with two buttons to the body element 29870 * tinymce.ui.Factory.create({ 29871 * type: 'buttongroup', 29872 * items: [ 29873 * {text: 'Button A'}, 29874 * {text: 'Button B'} 29875 * ] 29876 * }).renderTo(document.body); 29877 * 29878 * @-x-less ButtonGroup.less 29879 * @class tinymce.ui.ButtonGroup 29880 * @extends tinymce.ui.Container 29881 */ 29882define("tinymce/ui/ButtonGroup", [ 29883 "tinymce/ui/Container" 29884], function(Container) { 29885 "use strict"; 29886 29887 return Container.extend({ 29888 Defaults: { 29889 defaultType: 'button', 29890 role: 'toolbar' 29891 }, 29892 29893 /** 29894 * Renders the control as a HTML string. 29895 * 29896 * @method renderHtml 29897 * @return {String} HTML representing the control. 29898 */ 29899 renderHtml: function() { 29900 var self = this, layout = self._layout; 29901 29902 self.addClass('btn-group'); 29903 self.preRender(); 29904 layout.preRender(self); 29905 29906 return ( 29907 '<div id="' + self._id + '" class="' + self.classes() + '">'+ 29908 '<div id="' + self._id + '-body">'+ 29909 (self.settings.html || '') + layout.renderHtml(self) + 29910 '</div>' + 29911 '</div>' 29912 ); 29913 } 29914 }); 29915}); 29916 29917// Included from: js/tinymce/classes/ui/Checkbox.js 29918 29919/** 29920 * Checkbox.js 29921 * 29922 * Copyright, Moxiecode Systems AB 29923 * Released under LGPL License. 29924 * 29925 * License: http://www.tinymce.com/license 29926 * Contributing: http://www.tinymce.com/contributing 29927 */ 29928 29929/** 29930 * This control creates a custom checkbox. 29931 * 29932 * @example 29933 * // Create and render a checkbox to the body element 29934 * tinymce.ui.Factory.create({ 29935 * type: 'checkbox', 29936 * checked: true, 29937 * text: 'My checkbox' 29938 * }).renderTo(document.body); 29939 * 29940 * @-x-less Checkbox.less 29941 * @class tinymce.ui.Checkbox 29942 * @extends tinymce.ui.Widget 29943 */
29944define("tinymce/ui/Checkbox", [ 29945 "tinymce/ui/Widget" 29946], function(Widget) { 29947 "use strict"; 29948 29949 return Widget.extend({ 29950 Defaults: { 29951 classes: "checkbox", 29952 role: "checkbox", 29953 checked: false 29954 }, 29955 29956 /** 29957 * Constructs a new Checkbox instance with the specified settings. 29958 * 29959 * @constructor 29960 * @param {Object} settings Name/value object with settings. 29961 * @setting {Boolean} checked True if the checkbox should be checked by default. 29962 */ 29963 init: function(settings) { 29964 var self = this; 29965 29966 self._super(settings); 29967 29968 self.on('click mousedown', function(e) { 29969 e.preventDefault(); 29970 }); 29971 29972 self.on('click', function(e) { 29973 e.preventDefault(); 29974 29975 if (!self.disabled()) { 29976 self.checked(!self.checked()); 29977 } 29978 }); 29979 29980 self.checked(self.settings.checked); 29981 }, 29982 29983 /** 29984 * Getter/setter function for the checked state. 29985 * 29986 * @method checked 29987 * @param {Boolean} [state] State to be set. 29988 * @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation. 29989 */ 29990 checked: function(state) { 29991 var self = this; 29992 29993 if (typeof state != "undefined") { 29994 if (state) { 29995 self.addClass('checked'); 29996 } else { 29997 self.removeClass('checked'); 29998 } 29999 30000 self._checked = state; 30001 self.aria('checked', state); 30002 30003 return self; 30004 } 30005 30006 return self._checked; 30007 }, 30008 30009 /** 30010 * Getter/setter function for the value state. 30011 * 30012 * @method value 30013 * @param {Boolean} [state] State to be set. 30014 * @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation. 30015 */ 30016 value: function(state) { 30017 return this.checked(state); 30018 }, 30019 30020 /** 30021 * Renders the control as a HTML string. 30022 * 30023 * @method renderHtml 30024 * @return {String} HTML representing the control. 30025 */ 30026 renderHtml: function() { 30027 var self = this, id = self._id, prefix = self.classPrefix; 30028 30029 return ( 30030 '<div id="' + id + '" class="' + self.classes() + '" unselectable="on" aria-labeledby="' + id + '-al" tabindex="-1">' + 30031 '<i class="' + prefix + 'ico ' + prefix + 'i-checkbox"></i>' + 30032 '<span id="' + id +'-al" class="' + prefix + 'label">' + self.encode(self._text) + '</span>' + 30033 '</div>' 30034 ); 30035 } 30036 }); 30037}); 30038 30039// Included from: js/tinymce/classes/ui/PanelButton.js 30040 30041/** 30042 * PanelButton.js 30043 * 30044 * Copyright, Moxiecode Systems AB 30045 * Released under LGPL License. 30046 * 30047 * License: http://www.tinymce.com/license 30048 * Contributing: http://www.tinymce.com/contributing 30049 */ 30050 30051/** 30052 * Creates a new panel button. 30053 * 30054 * @class tinymce.ui.PanelButton 30055 * @extends tinymce.ui.Button 30056 */ 30057define("tinymce/ui/PanelButton", [ 30058 "tinymce/ui/Button", 30059 "tinymce/ui/FloatPanel" 30060], function(Button, FloatPanel) { 30061 "use strict"; 30062 30063 return Button.extend({ 30064 /** 30065 * Shows the panel for the button. 30066 * 30067 * @method showPanel 30068 */ 30069 showPanel: function() { 30070 var self = this, settings = self.settings; 30071 30072 self.active(true); 30073 30074 if (!self.panel) { 30075 var panelSettings = settings.panel; 30076 30077 // Wrap panel in grid layout if type if specified 30078 // This makes it possible to add forms or other containers directly in the panel option 30079 if (panelSettings.type) { 30080 panelSettings = { 30081 layout: 'grid', 30082 items: panelSettings 30083 }; 30084 } 30085 30086 panelSettings.popover = true; 30087 panelSettings.autohide = true; 30088 30089 self.panel = new FloatPanel(panelSettings).on('hide', function() { 30090 self.active(false); 30091 }).parent(self).renderTo(self.getContainerElm()); 30092 self.panel.fire('show'); 30093 self.panel.reflow(); 30094 } else { 30095 self.panel.show(); 30096 } 30097 30098 self.panel.moveRel(self.getEl(), settings.popoverAlign || (self.isRtl() ? ['bc-tr', 'bc-tc'] : ['bc-tl', 'bc-tc'])); 30099 }, 30100 30101 /** 30102 * Hides the panel for the button. 30103 * 30104 * @method hidePanel 30105 */ 30106 hidePanel: function() { 30107 var self = this; 30108 30109 if (self.panel) { 30110 self.panel.hide(); 30111 } 30112 }, 30113 30114 /** 30115 * Called after the control has been rendered. 30116 * 30117 * @method postRender 30118 */ 30119 postRender: function() { 30120 var self = this; 30121 30122 self.on('click', function(e) { 30123 if (e.control === self) { 30124 if (self.panel && self.panel.visible()) { 30125 self.hidePanel(); 30126 } else { 30127 self.showPanel(); 30128 } 30129 } 30130 }); 30131 30132 return self._super(); 30133 } 30134 }); 30135}); 30136 30137// Included from: js/tinymce/classes/ui/ColorButton.js 30138 30139/** 30140 * ColorButton.js 30141 * 30142 * Copyright, Moxiecode Systems AB 30143 * Released under LGPL License. 30144 * 30145 * License: http://www.tinymce.com/license 30146 * Contributing: http://www.tinymce.com/contributing 30147 */ 30148 30149/** 30150 * This class creates a color button control. This is a split button in which the main 30151 * button has a visual representation of the currently selected color. When clicked 30152 * the caret button displays a color picker, allowing the user to select a new color. 30153 * 30154 * @-x-less ColorButton.less 30155 * @class tinymce.ui.ColorButton 30156 * @extends tinymce.ui.PanelButton 30157 */
30158define("tinymce/ui/ColorButton", [ 30159 "tinymce/ui/PanelButton", 30160 "tinymce/dom/DOMUtils" 30161], function(PanelButton, DomUtils) { 30162 "use strict"; 30163 30164 var DOM = DomUtils.DOM; 30165 30166 return PanelButton.extend({ 30167 /** 30168 * Constructs a new ColorButton instance with the specified settings. 30169 * 30170 * @constructor 30171 * @param {Object} settings Name/value object with settings. 30172 */ 30173 init: function(settings) { 30174 this._super(settings); 30175 this.addClass('colorbutton'); 30176 }, 30177 30178 /** 30179 * Getter/setter for the current color. 30180 * 30181 * @method color 30182 * @param {String} [color] Color to set. 30183 * @return {String|tinymce.ui.ColorButton} Current color or current instance. 30184 */ 30185 color: function(color) { 30186 if (color) { 30187 this._color = color; 30188 this.getEl('preview').style.backgroundColor = color; 30189 return this; 30190 } 30191 30192 return this._color; 30193 }, 30194 30195 /** 30196 * Renders the control as a HTML string. 30197 * 30198 * @method renderHtml 30199 * @return {String} HTML representing the control. 30200 */ 30201 renderHtml: function() { 30202 var self = this, id = self._id, prefix = self.classPrefix; 30203 var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : ''; 30204 var image = self.settings.image ? ' style="background-image: url(\'' + self.settings.image + '\')"' : ''; 30205 30206 return ( 30207 '<div id="' + id + '" class="' + self.classes() + '">' + 30208 '<button role="presentation" hidefocus type="button" tabindex="-1">' + 30209 (icon ? '<i class="' + icon + '"' + image + '></i>' : '') + 30210 '<span id="' + id + '-preview" class="' + prefix + 'preview"></span>' + 30211 (self._text ? (icon ? ' ' : '') + (self._text) : '') + 30212 '</button>' + 30213 '<button type="button" class="' + prefix + 'open" hidefocus tabindex="-1">' + 30214 ' <i class="' + prefix + 'caret"></i>' + 30215 '</button>' + 30216 '</div>' 30217 ); 30218 }, 30219 30220 /** 30221 * Called after the control has been rendered. 30222 * 30223 * @method postRender 30224 */ 30225 postRender: function() { 30226 var self = this, onClickHandler = self.settings.onclick; 30227 30228 self.on('click', function(e) { 30229 if (e.control == self && !DOM.getParent(e.target, '.' + self.classPrefix + 'open')) { 30230 e.stopImmediatePropagation(); 30231 onClickHandler.call(self, e); 30232 } 30233 }); 30234 30235 delete self.settings.onclick; 30236 30237 return self._super(); 30238 } 30239 30240 }); 30241}); 30242 30243// Included from: js/tinymce/classes/ui/ComboBox.js 30244 30245/** 30246 * ComboBox.js 30247 * 30248 * Copyright, Moxiecode Systems AB 30249 * Released under LGPL License. 30250 * 30251 * License: http://www.tinymce.com/license 30252 * Contributing: http://www.tinymce.com/contributing 30253 */ 30254 30255/** 30256 * This class creates a combobox control. Select box that you select a value from or 30257 * type a value into. 30258 * 30259 * @-x-less ComboBox.less 30260 * @class tinymce.ui.ComboBox 30261 * @extends tinymce.ui.Widget 30262 */ 30263define("tinymce/ui/ComboBox", [ 30264 "tinymce/ui/Widget", 30265 "tinymce/ui/DomUtils" 30266], function(Widget, DomUtils) { 30267 "use strict"; 30268 30269 return Widget.extend({ 30270 /** 30271 * Constructs a new control instance with the specified settings. 30272 * 30273 * @constructor 30274 * @param {Object} settings Name/value object with settings. 30275 * @setting {String} placeholder Placeholder text to display. 30276 */ 30277 init: function(settings) { 30278 var self = this; 30279 30280 self._super(settings); 30281 self.addClass('combobox'); 30282 30283 self.on('click', function(e) { 30284 var elm = e.target; 30285 30286 while (elm) { 30287 if (elm.id && elm.id.indexOf('-open') != -1) { 30288 self.fire('action'); 30289 } 30290 30291 elm = elm.parentNode; 30292 } 30293 }); 30294 30295 // TODO: Rework this 30296 self.on('keydown', function(e) { 30297 if (e.target.nodeName == "INPUT" && e.keyCode == 13) { 30298 self.parents().reverse().each(function(ctrl) { 30299 e.preventDefault(); 30300 self.fire('change'); 30301 30302 if (ctrl.hasEventListeners('submit') && ctrl.toJSON) { 30303 ctrl.fire('submit', {data: ctrl.toJSON()}); 30304 return false; 30305 } 30306 }); 30307 } 30308 }); 30309 30310 if (settings.placeholder) { 30311 self.addClass('placeholder'); 30312 30313 self.on('focusin', function() { 30314 if (!self._hasOnChange) { 30315 DomUtils.on(self.getEl('inp'), 'change', function() { 30316 self.fire('change'); 30317 }); 30318 30319 self._hasOnChange = true; 30320 } 30321 30322 if (self.hasClass('placeholder')) { 30323 self.getEl('inp').value = ''; 30324 self.removeClass('placeholder'); 30325 } 30326 }); 30327 30328 self.on('focusout', function() { 30329 if (self.value().length === 0) { 30330 self.getEl('inp').value = settings.placeholder; 30331 self.addClass('placeholder'); 30332 } 30333 }); 30334 } 30335 }, 30336 30337 /** 30338 * Getter/setter function for the control value. 30339 * 30340 * @method value 30341 * @param {String} [value] Value to be set. 30342 * @return {String|tinymce.ui.ComboBox} Value or self if it's a set operation. 30343 */ 30344 value: function(value) { 30345 var self = this; 30346 30347 if (typeof(value) != "undefined") { 30348 self._value = value; 30349 self.removeClass('placeholder'); 30350 30351 if (self._rendered) { 30352 self.getEl('inp').value = value; 30353 } 30354 30355 return self; 30356 } 30357 30358 if (self._rendered) { 30359 value = self.getEl('inp').value; 30360 30361 if (value != self.settings.placeholder) { 30362 return value; 30363 } 30364 30365 return ''; 30366 } 30367 30368 return self._value; 30369 }, 30370 30371 /** 30372 * Getter/setter function for the disabled state. 30373 * 30374 * @method value 30375 * @param {Boolean} [state] State to be set.
30376 * @return {Boolean|tinymce.ui.ComboBox} True/false or self if it's a set operation. 30377 */ 30378 disabled: function(state) { 30379 var self = this; 30380 30381 if (self._rendered && typeof(state) != 'undefined') { 30382 self.getEl('inp').disabled = state; 30383 } 30384 30385 return self._super(state); 30386 }, 30387 30388 /** 30389 * Focuses the input area of the control. 30390 * 30391 * @method focus 30392 */ 30393 focus: function() { 30394 this.getEl('inp').focus(); 30395 }, 30396 30397 /** 30398 * Repaints the control after a layout operation. 30399 * 30400 * @method repaint 30401 */ 30402 repaint: function() { 30403 var self = this, elm = self.getEl(), openElm = self.getEl('open'), rect = self.layoutRect(); 30404 var width, lineHeight; 30405 30406 if (openElm) { 30407 width = rect.w - DomUtils.getSize(openElm).width - 10; 30408 } else { 30409 width = rect.w - 10; 30410 } 30411 30412 // Detect old IE 7+8 add lineHeight to align caret vertically in the middle 30413 var doc = document; 30414 if (doc.all && (!doc.documentMode || doc.documentMode <= 8)) { 30415 lineHeight = (self.layoutRect().h - 2) + 'px'; 30416 } 30417 30418 DomUtils.css(elm.firstChild, { 30419 width: width, 30420 lineHeight: lineHeight 30421 }); 30422 30423 self._super(); 30424 30425 return self; 30426 }, 30427 30428 /** 30429 * Post render method. Called after the control has been rendered to the target. 30430 * 30431 * @method postRender 30432 * @return {tinymce.ui.ComboBox} Current combobox instance. 30433 */ 30434 postRender: function() { 30435 var self = this; 30436 30437 DomUtils.on(this.getEl('inp'), 'change', function() { 30438 self.fire('change'); 30439 }); 30440 30441 return self._super(); 30442 }, 30443 30444 remove: function() { 30445 DomUtils.off(this.getEl('inp')); 30446 this._super(); 30447 }, 30448 30449 /** 30450 * Renders the control as a HTML string. 30451 * 30452 * @method renderHtml 30453 * @return {String} HTML representing the control. 30454 */ 30455 renderHtml: function() { 30456 var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix; 30457 var value = settings.value || settings.placeholder || ''; 30458 var icon, text, openBtnHtml = ''; 30459 30460 icon = settings.icon ? prefix + 'ico ' + prefix + 'i-' + settings.icon : ''; 30461 text = self._text; 30462 30463 if (icon || text) { 30464 openBtnHtml = ( 30465 '<div id="' + id + '-open" class="' + prefix + 'btn ' + prefix + 'open" tabIndex="-1">' + 30466 '<button id="' + id + '-action" type="button" hidefocus tabindex="-1">' + 30467 (icon ? '<i class="' + icon + '"></i>' : '<i class="' + prefix + 'caret"></i>') + 30468 (text ? (icon ? ' ' : '') + text : '') + 30469 '</button>' + 30470 '</div>' 30471 ); 30472 30473 self.addClass('has-open'); 30474 } 30475 30476 return ( 30477 '<div id="' + id + '" class="' + self.classes() + '">' + 30478 '<input id="' + id + '-inp" class="' + prefix + 'textbox ' + prefix + 'placeholder" value="' + 30479 value + '" hidefocus="true"' + (self.disabled() ? ' disabled="disabled"' : '') + '>' + 30480 openBtnHtml + 30481 '</div>' 30482 ); 30483 } 30484 }); 30485}); 30486 30487// Included from: js/tinymce/classes/ui/Path.js 30488 30489/** 30490 * Path.js 30491 * 30492 * Copyright, Moxiecode Systems AB 30493 * Released under LGPL License. 30494 * 30495 * License: http://www.tinymce.com/license 30496 * Contributing: http://www.tinymce.com/contributing 30497 */ 30498 30499/** 30500 * Creates a new path control. 30501 * 30502 * @-x-less Path.less 30503 * @class tinymce.ui.Path 30504 * @extends tinymce.ui.Widget 30505 */ 30506define("tinymce/ui/Path", [ 30507 "tinymce/ui/Widget", 30508 "tinymce/ui/KeyboardNavigation" 30509], function(Widget, KeyboardNavigation) { 30510 "use strict"; 30511 30512 return Widget.extend({ 30513 /** 30514 * Constructs a instance with the specified settings. 30515 * 30516 * @constructor 30517 * @param {Object} settings Name/value object with settings. 30518 * @setting {String} delimiter Delimiter to display between items in path. 30519 */ 30520 init: function(settings) { 30521 var self = this; 30522 30523 if (!settings.delimiter) { 30524 settings.delimiter = '\u00BB'; 30525 } 30526 30527 self._super(settings); 30528 self.addClass('path'); 30529 self.canFocus = true; 30530 30531 self.on('click', function(e) { 30532 var index, target = e.target; 30533 30534 if ((index = target.getAttribute('data-index'))) { 30535 self.fire('select', {value: self.data()[index], index: index}); 30536 } 30537 }); 30538 }, 30539 30540 /** 30541 * Focuses the current control. 30542 * 30543 * @method focus 30544 * @return {tinymce.ui.Control} Current control instance. 30545 */ 30546 focus: function() { 30547 var self = this; 30548 30549 self.keyNav = new KeyboardNavigation({ 30550 root: self, 30551 enableLeftRight: true 30552 }); 30553
30554 self.keyNav.focusFirst(); 30555 30556 return self; 30557 }, 30558 30559 /** 30560 * Sets/gets the data to be used for the path. 30561 * 30562 * @method data 30563 * @param {Array} data Array with items name is rendered to path. 30564 */ 30565 data: function(data) { 30566 var self = this; 30567 30568 if (typeof(data) !== "undefined") { 30569 self._data = data; 30570 self.update(); 30571 30572 return self; 30573 } 30574 30575 return self._data; 30576 }, 30577 30578 /** 30579 * Updated the path. 30580 * 30581 * @private 30582 */ 30583 update: function() { 30584 this.innerHtml(this._getPathHtml()); 30585 }, 30586 30587 /** 30588 * Called after the control has been rendered. 30589 * 30590 * @method postRender 30591 */ 30592 postRender: function() { 30593 var self = this; 30594 30595 self._super(); 30596 30597 self.data(self.settings.data); 30598 }, 30599 30600 /** 30601 * Renders the control as a HTML string. 30602 * 30603 * @method renderHtml 30604 * @return {String} HTML representing the control. 30605 */ 30606 renderHtml: function() { 30607 var self = this; 30608 30609 return ( 30610 '<div id="' + self._id + '" class="' + self.classes() + '">' + 30611 self._getPathHtml() + 30612 '</div>' 30613 ); 30614 }, 30615 30616 _getPathHtml: function() { 30617 var self = this, parts = self._data || [], i, l, html = '', prefix = self.classPrefix; 30618 30619 for (i = 0, l = parts.length; i < l; i++) { 30620 html += ( 30621 (i > 0 ? '<div class="'+ prefix + 'divider" aria-hidden="true"> ' + self.settings.delimiter + ' </div>' : '') + 30622 '<div role="button" class="' + prefix + 'path-item' + (i == l - 1 ? ' ' + prefix + 'last' : '') + '" data-index="' + 30623 i + '" tabindex="-1" id="' + self._id + '-' + i +'">' + parts[i].name + '</div>' 30624 ); 30625 } 30626 30627 if (!html) { 30628 html = '<div class="' + prefix + 'path-item"> </div>'; 30629 } 30630 30631 return html; 30632 } 30633 }); 30634}); 30635 30636// Included from: js/tinymce/classes/ui/ElementPath.js 30637 30638/** 30639 * ElementPath.js 30640 * 30641 * Copyright, Moxiecode Systems AB 30642 * Released under LGPL License. 30643 * 30644 * License: http://www.tinymce.com/license 30645 * Contributing: http://www.tinymce.com/contributing 30646 */ 30647 30648/** 30649 * This control creates an path for the current selections parent elements in TinyMCE. 30650 * 30651 * @class tinymce.ui.ElementPath 30652 * @extends tinymce.ui.Path 30653 */ 30654define("tinymce/ui/ElementPath", [ 30655 "tinymce/ui/Path", 30656 "tinymce/EditorManager" 30657], function(Path, EditorManager) { 30658 return Path.extend({ 30659 /** 30660 * Post render method. Called after the control has been rendered to the target. 30661 * 30662 * @method postRender 30663 * @return {tinymce.ui.ElementPath} Current combobox instance. 30664 */ 30665 postRender: function() { 30666 var self = this, editor = EditorManager.activeEditor; 30667 30668 function isHidden(elm) { 30669 if (elm.nodeType === 1) { 30670 if (elm.nodeName == "BR" || !!elm.getAttribute('data-mce-bogus')) { 30671 return true; 30672 } 30673 30674 if (elm.getAttribute('data-mce-type') === 'bookmark') { 30675 return true; 30676 } 30677 } 30678 30679 return false; 30680 } 30681 30682 self.on('select', function(e) { 30683 var parents = [], node, body = editor.getBody(); 30684 30685 editor.focus(); 30686 30687 node = editor.selection.getStart(); 30688 while (node && node != body) { 30689 if (!isHidden(node)) { 30690 parents.push(node); 30691 } 30692 30693 node = node.parentNode; 30694 } 30695 30696 editor.selection.select(parents[parents.length - 1 - e.index]); 30697 editor.nodeChanged(); 30698 }); 30699 30700 editor.on('nodeChange', function(e) { 30701 var parents = [], selectionParents = e.parents, i = selectionParents.length; 30702 30703 while (i--) { 30704 if (selectionParents[i].nodeType == 1 && !isHidden(selectionParents[i])) { 30705 var args = editor.fire('ResolveName', { 30706 name: selectionParents[i].nodeName.toLowerCase(), 30707 target: selectionParents[i] 30708 }); 30709 30710 parents.push({name: args.name}); 30711 } 30712 } 30713 30714 self.data(parents); 30715 }); 30716 30717 return self._super(); 30718 } 30719 }); 30720}); 30721 30722// Included from: js/tinymce/classes/ui/FormItem.js 30723 30724/** 30725 * FormItem.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 class is a container created by the form element with 30736 * a label and control item. 30737 * 30738 * @class tinymce.ui.FormItem 30739 * @extends tinymce.ui.Container 30740 * @setting {String} label Label to display for the form item. 30741 */
30742define("tinymce/ui/FormItem", [ 30743 "tinymce/ui/Container" 30744], function(Container) { 30745 "use strict"; 30746 30747 return Container.extend({ 30748 Defaults: { 30749 layout: 'flex', 30750 align: 'center', 30751 defaults: { 30752 flex: 1 30753 } 30754 }, 30755 30756 /** 30757 * Renders the control as a HTML string. 30758 * 30759 * @method renderHtml 30760 * @return {String} HTML representing the control. 30761 */ 30762 renderHtml: function() { 30763 var self = this, layout = self._layout, prefix = self.classPrefix; 30764 30765 self.addClass('formitem'); 30766 layout.preRender(self); 30767 30768 return ( 30769 '<div id="' + self._id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1">' + 30770 (self.settings.title ? ('<div id="' + self._id + '-title" class="' + prefix + 'title">' + 30771 self.settings.title + '</div>') : '') + 30772 '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' + 30773 (self.settings.html || '') + layout.renderHtml(self) + 30774 '</div>' + 30775 '</div>' 30776 ); 30777 } 30778 }); 30779}); 30780 30781// Included from: js/tinymce/classes/ui/Form.js 30782 30783/** 30784 * Form.js 30785 * 30786 * Copyright, Moxiecode Systems AB 30787 * Released under LGPL License. 30788 * 30789 * License: http://www.tinymce.com/license 30790 * Contributing: http://www.tinymce.com/contributing 30791 */ 30792 30793/** 30794 * This class creates a form container. A form container has the ability 30795 * to automatically wrap items in tinymce.ui.FormItem instances. 30796 * 30797 * Each FormItem instance is a container for the label and the item. 30798 * 30799 * @example 30800 * tinymce.ui.Factory.create({ 30801 * type: 'form', 30802 * items: [ 30803 * {type: 'textbox', label: 'My text box'} 30804 * ] 30805 * }).renderTo(document.body); 30806 * 30807 * @class tinymce.ui.Form 30808 * @extends tinymce.ui.Container 30809 */ 30810define("tinymce/ui/Form", [ 30811 "tinymce/ui/Container", 30812 "tinymce/ui/FormItem" 30813], function(Container, FormItem) { 30814 "use strict"; 30815 30816 return Container.extend({ 30817 Defaults: { 30818 containerCls: 'form', 30819 layout: 'flex', 30820 direction: 'column', 30821 align: 'stretch', 30822 flex: 1, 30823 padding: 20, 30824 labelGap: 30, 30825 spacing: 10, 30826 callbacks: { 30827 submit: function() { 30828 this.submit(); 30829 } 30830 } 30831 }, 30832 30833 /** 30834 * This method gets invoked before the control is rendered. 30835 * 30836 * @method preRender 30837 */ 30838 preRender: function() { 30839 var self = this, items = self.items(); 30840 30841 // Wrap any labeled items in FormItems 30842 items.each(function(ctrl) { 30843 var formItem, label = ctrl.settings.label; 30844 30845 if (label) { 30846 formItem = new FormItem({ 30847 layout: 'flex', 30848 autoResize: "overflow", 30849 defaults: {flex: 1}, 30850 items: [ 30851 {type: 'label', text: label, flex: 0, forId: ctrl._id} 30852 ] 30853 }); 30854 30855 formItem.type = 'formitem'; 30856 30857 if (typeof(ctrl.settings.flex) == "undefined") { 30858 ctrl.settings.flex = 1; 30859 } 30860 30861 self.replace(ctrl, formItem); 30862 formItem.add(ctrl); 30863 } 30864 }); 30865 }, 30866 30867 /** 30868 * Recalcs label widths. 30869 * 30870 * @private 30871 */ 30872 recalcLabels: function() { 30873 var self = this, maxLabelWidth = 0, labels = [], i, labelGap; 30874 30875 if (self.settings.labelGapCalc === false) { 30876 return; 30877 } 30878 30879 self.items().filter('formitem').each(function(item) { 30880 var labelCtrl = item.items()[0], labelWidth = labelCtrl.getEl().clientWidth; 30881 30882 maxLabelWidth = labelWidth > maxLabelWidth ? labelWidth : maxLabelWidth; 30883 labels.push(labelCtrl); 30884 }); 30885 30886 labelGap = self.settings.labelGap || 0; 30887 30888 i = labels.length; 30889 while (i--) { 30890 labels[i].settings.minWidth = maxLabelWidth + labelGap; 30891 } 30892 }, 30893 30894 /** 30895 * Getter/setter for the visibility state. 30896 * 30897 * @method visible 30898 * @param {Boolean} [state] True/false state to show/hide. 30899 * @return {tinymce.ui.Form|Boolean} True/false state or current control. 30900 */ 30901 visible: function(state) { 30902 var val = this._super(state); 30903 30904 if (state === true && this._rendered) { 30905 this.recalcLabels(); 30906 } 30907 30908 return val; 30909 }, 30910 30911 /** 30912 * Fires a submit event with the serialized form. 30913 * 30914 * @method submit 30915 * @return {Object} Event arguments object. 30916 */ 30917 submit: function() { 30918 return this.fire('submit', {data: this.toJSON()}); 30919 }, 30920 30921 /** 30922 * Post render method. Called after the control has been rendered to the target. 30923 * 30924 * @method postRender 30925 * @return {tinymce.ui.ComboBox} Current combobox instance. 30926 */ 30927 postRender: function() { 30928 var self = this; 30929 30930 self._super(); 30931 self.recalcLabels(); 30932 self.fromJSON(self.settings.data); 30933 } 30934 }); 30935}); 30936 30937// Included from: js/tinymce/classes/ui/FieldSet.js 30938 30939/** 30940 * FieldSet.js 30941 * 30942 * Copyright, Moxiecode Systems AB 30943 * Released under LGPL License. 30944 * 30945 * License: http://www.tinymce.com/license 30946 * Contributing: http://www.tinymce.com/contributing 30947 */ 30948 30949/** 30950 * This class creates fieldset containers. 30951 * 30952 * @-x-less FieldSet.less 30953 * @class tinymce.ui.FieldSet 30954 * @extends tinymce.ui.Form 30955 */
30956define("tinymce/ui/FieldSet", [ 30957 "tinymce/ui/Form" 30958], function(Form) { 30959 "use strict"; 30960 30961 return Form.extend({ 30962 Defaults: { 30963 containerCls: 'fieldset', 30964 layout: 'flex', 30965 direction: 'column', 30966 align: 'stretch', 30967 flex: 1, 30968 padding: "25 15 5 15", 30969 labelGap: 30, 30970 spacing: 10, 30971 border: 1 30972 }, 30973 30974 /** 30975 * Renders the control as a HTML string. 30976 * 30977 * @method renderHtml 30978 * @return {String} HTML representing the control. 30979 */ 30980 renderHtml: function() { 30981 var self = this, layout = self._layout, prefix = self.classPrefix; 30982 30983 self.preRender(); 30984 layout.preRender(self); 30985 30986 return ( 30987 '<fieldset id="' + self._id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1">' + 30988 (self.settings.title ? ('<legend id="' + self._id + '-title" class="' + prefix + 'fieldset-title">' + 30989 self.settings.title + '</legend>') : '') + 30990 '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' + 30991 (self.settings.html || '') + layout.renderHtml(self) + 30992 '</div>' + 30993 '</fieldset>' 30994 ); 30995 } 30996 }); 30997}); 30998 30999// Included from: js/tinymce/classes/ui/FilePicker.js 31000 31001/** 31002 * FilePicker.js 31003 * 31004 * Copyright, Moxiecode Systems AB 31005 * Released under LGPL License. 31006 * 31007 * License: http://www.tinymce.com/license 31008 * Contributing: http://www.tinymce.com/contributing 31009 */ 31010 31011/*global tinymce:true */ 31012 31013/** 31014 * This class creates a file picker control. 31015 * 31016 * @class tinymce.ui.FilePicker 31017 * @extends tinymce.ui.ComboBox 31018 */ 31019define("tinymce/ui/FilePicker", [ 31020 "tinymce/ui/ComboBox" 31021], function(ComboBox) { 31022 "use strict"; 31023 31024 return ComboBox.extend({ 31025 /** 31026 * Constructs a new control instance with the specified settings. 31027 * 31028 * @constructor 31029 * @param {Object} settings Name/value object with settings. 31030 */ 31031 init: function(settings) { 31032 var self = this, editor = tinymce.activeEditor, fileBrowserCallback; 31033 31034 settings.spellcheck = false; 31035 31036 fileBrowserCallback = editor.settings.file_browser_callback; 31037 if (fileBrowserCallback) { 31038 settings.icon = 'browse'; 31039 31040 settings.onaction = function() { 31041 fileBrowserCallback( 31042 self.getEl('inp').id, 31043 self.getEl('inp').value, 31044 settings.filetype, 31045 window 31046 ); 31047 }; 31048 } 31049 31050 self._super(settings); 31051 } 31052 }); 31053}); 31054 31055// Included from: js/tinymce/classes/ui/FitLayout.js 31056 31057/** 31058 * FitLayout.js 31059 * 31060 * Copyright, Moxiecode Systems AB 31061 * Released under LGPL License. 31062 * 31063 * License: http://www.tinymce.com/license 31064 * Contributing: http://www.tinymce.com/contributing 31065 */ 31066 31067/** 31068 * This layout manager will resize the control to be the size of it's parent container. 31069 * In other words width: 100% and height: 100%. 31070 * 31071 * @-x-less FitLayout.less 31072 * @class tinymce.ui.FitLayout 31073 * @extends tinymce.ui.AbsoluteLayout 31074 */ 31075define("tinymce/ui/FitLayout", [ 31076 "tinymce/ui/AbsoluteLayout" 31077], function(AbsoluteLayout) { 31078 "use strict"; 31079 31080 return AbsoluteLayout.extend({ 31081 /** 31082 * Recalculates the positions of the controls in the specified container. 31083 * 31084 * @method recalc 31085 * @param {tinymce.ui.Container} container Container instance to recalc. 31086 */ 31087 recalc: function(container) { 31088 var contLayoutRect = container.layoutRect(), paddingBox = container.paddingBox(); 31089 31090 container.items().filter(':visible').each(function(ctrl) { 31091 ctrl.layoutRect({ 31092 x: paddingBox.left, 31093 y: paddingBox.top, 31094 w: contLayoutRect.innerW - paddingBox.right - paddingBox.left, 31095 h: contLayoutRect.innerH - paddingBox.top - paddingBox.bottom 31096 }); 31097 31098 if (ctrl.recalc) { 31099 ctrl.recalc(); 31100 } 31101 }); 31102 } 31103 }); 31104}); 31105 31106// Included from: js/tinymce/classes/ui/FlexLayout.js 31107 31108/** 31109 * FlexLayout.js 31110 * 31111 * Copyright, Moxiecode Systems AB 31112 * Released under LGPL License. 31113 * 31114 * License: http://www.tinymce.com/license 31115 * Contributing: http://www.tinymce.com/contributing 31116 */ 31117 31118/** 31119 * This layout manager works similar to the CSS flex box. 31120 * 31121 * @setting {String} direction row|row-reverse|column|column-reverse 31122 * @setting {Number} flex A positive-number to flex by. 31123 * @setting {String} align start|end|center|stretch 31124 * @setting {String} pack start|end|justify 31125 * 31126 * @class tinymce.ui.FlexLayout 31127 * @extends tinymce.ui.AbsoluteLayout 31128 */
31129define("tinymce/ui/FlexLayout", [ 31130 "tinymce/ui/AbsoluteLayout" 31131], function(AbsoluteLayout) { 31132 "use strict"; 31133 31134 return AbsoluteLayout.extend({ 31135 /** 31136 * Recalculates the positions of the controls in the specified container. 31137 * 31138 * @method recalc 31139 * @param {tinymce.ui.Container} container Container instance to recalc. 31140 */ 31141 recalc: function(container) { 31142 // A ton of variables, needs to be in the same scope for performance 31143 var i, l, items, contLayoutRect, contPaddingBox, contSettings, align, pack, spacing, totalFlex, availableSpace, direction; 31144 var ctrl, ctrlLayoutRect, ctrlSettings, flex, maxSizeItems = [], size, maxSize, ratio, rect, pos, maxAlignEndPos; 31145 var sizeName, minSizeName, posName, maxSizeName, beforeName, innerSizeName, afterName, deltaSizeName, contentSizeName; 31146 var alignAxisName, alignInnerSizeName, alignSizeName, alignMinSizeName, alignMaxSizeName, alignBeforeName, alignAfterName; 31147 var alignDeltaSizeName, alignContentSizeName; 31148 var max = Math.max, min = Math.min; 31149 31150 // Get container items, properties and settings 31151 items = container.items().filter(':visible'); 31152 contLayoutRect = container.layoutRect(); 31153 contPaddingBox = container._paddingBox; 31154 contSettings = container.settings; 31155 direction = container.isRtl() ? (contSettings.direction || 'row-reversed') : contSettings.direction; 31156 align = contSettings.align; 31157 pack = container.isRtl() ? (contSettings.pack || 'end') : contSettings.pack; 31158 spacing = contSettings.spacing || 0; 31159 31160 if (direction == "row-reversed" || direction == "column-reverse") { 31161 items = items.set(items.toArray().reverse()); 31162 direction = direction.split('-')[0]; 31163 } 31164 31165 // Setup axis variable name for row/column direction since the calculations is the same 31166 if (direction == "column") { 31167 posName = "y"; 31168 sizeName = "h"; 31169 minSizeName = "minH"; 31170 maxSizeName = "maxH"; 31171 innerSizeName = "innerH"; 31172 beforeName = 'top'; 31173 afterName = 'bottom'; 31174 deltaSizeName = "deltaH"; 31175 contentSizeName = "contentH"; 31176 31177 alignBeforeName = "left"; 31178 alignSizeName = "w"; 31179 alignAxisName = "x"; 31180 alignInnerSizeName = "innerW"; 31181 alignMinSizeName = "minW"; 31182 alignMaxSizeName = "maxW"; 31183 alignAfterName = "right"; 31184 alignDeltaSizeName = "deltaW"; 31185 alignContentSizeName = "contentW"; 31186 } else { 31187 posName = "x"; 31188 sizeName = "w"; 31189 minSizeName = "minW"; 31190 maxSizeName = "maxW"; 31191 innerSizeName = "innerW"; 31192 beforeName = 'left'; 31193 afterName = 'right'; 31194 deltaSizeName = "deltaW"; 31195 contentSizeName = "contentW"; 31196 31197 alignBeforeName = "top"; 31198 alignSizeName = "h"; 31199 alignAxisName = "y"; 31200 alignInnerSizeName = "innerH"; 31201 alignMinSizeName = "minH"; 31202 alignMaxSizeName = "maxH"; 31203 alignAfterName = "bottom"; 31204 alignDeltaSizeName = "deltaH"; 31205 alignContentSizeName = "contentH"; 31206 } 31207 31208 // Figure out total flex, availableSpace and collect any max size elements 31209 availableSpace = contLayoutRect[innerSizeName] - contPaddingBox[beforeName] - contPaddingBox[beforeName]; 31210 maxAlignEndPos = totalFlex = 0; 31211 for (i = 0, l = items.length; i < l; i++) { 31212 ctrl = items[i]; 31213 ctrlLayoutRect = ctrl.layoutRect(); 31214 ctrlSettings = ctrl.settings; 31215 flex = ctrlSettings.flex; 31216 availableSpace -= (i < l - 1 ? spacing : 0); 31217 31218 if (flex > 0) { 31219 totalFlex += flex; 31220 31221 // Flexed item has a max size then we need to check if we will hit that size 31222 if (ctrlLayoutRect[maxSizeName]) { 31223 maxSizeItems.push(ctrl); 31224 } 31225 31226 ctrlLayoutRect.flex = flex; 31227 } 31228 31229 availableSpace -= ctrlLayoutRect[minSizeName]; 31230 31231 // Calculate the align end position to be used to check for overflow/underflow 31232 size = contPaddingBox[alignBeforeName] + ctrlLayoutRect[alignMinSizeName] + contPaddingBox[alignAfterName]; 31233 if (size > maxAlignEndPos) { 31234 maxAlignEndPos = size; 31235 } 31236 } 31237 31238 // Calculate minW/minH 31239 rect = {}; 31240 if (availableSpace < 0) { 31241 rect[minSizeName] = contLayoutRect[minSizeName] - availableSpace + contLayoutRect[deltaSizeName]; 31242 } else { 31243 rect[minSizeName] = contLayoutRect[innerSizeName] - availableSpace + contLayoutRect[deltaSizeName]; 31244 } 31245 31246 rect[alignMinSizeName] = maxAlignEndPos + contLayoutRect[alignDeltaSizeName]; 31247 31248 rect[contentSizeName] = contLayoutRect[innerSizeName] - availableSpace; 31249 rect[alignContentSizeName] = maxAlignEndPos; 31250 rect.minW = min(rect.minW, contLayoutRect.maxW); 31251 rect.minH = min(rect.minH, contLayoutRect.maxH); 31252 rect.minW = max(rect.minW, contLayoutRect.startMinWidth); 31253 rect.minH = max(rect.minH, contLayoutRect.startMinHeight); 31254 31255 // Resize container container if minSize was changed 31256 if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) { 31257 rect.w = rect.minW; 31258 rect.h = rect.minH; 31259 31260 container.layoutRect(rect); 31261 this.recalc(container); 31262 31263 // Forced recalc for example if items are hidden/shown 31264 if (container._lastRect === null) { 31265 var parentCtrl = container.parent(); 31266 if (parentCtrl) { 31267 parentCtrl._lastRect = null; 31268 parentCtrl.recalc(); 31269 } 31270 } 31271 31272 return; 31273 } 31274 31275 // Handle max size elements, check if they will become to wide with current options 31276 ratio = availableSpace / totalFlex; 31277 for (i = 0, l = maxSizeItems.length; i < l; i++) { 31278 ctrl = maxSizeItems[i]; 31279 ctrlLayoutRect = ctrl.layoutRect(); 31280 maxSize = ctrlLayoutRect[maxSizeName]; 31281 size = ctrlLayoutRect[minSizeName] + ctrlLayoutRect.flex * ratio; 31282 31283 if (size > maxSize) { 31284 availableSpace -= (ctrlLayoutRect[maxSizeName] - ctrlLayoutRect[minSizeName]); 31285 totalFlex -= ctrlLayoutRect.flex; 31286 ctrlLayoutRect.flex = 0; 31287 ctrlLayoutRect.maxFlexSize = maxSize; 31288 } else { 31289 ctrlLayoutRect.maxFlexSize = 0; 31290 } 31291 } 31292 31293 // Setup new ratio, target layout rect, start position 31294 ratio = availableSpace / totalFlex; 31295 pos = contPaddingBox[beforeName]; 31296 rect = {}; 31297 31298 // Handle pack setting moves the start position to end, center 31299 if (totalFlex === 0) { 31300 if (pack == "end") { 31301 pos = availableSpace + contPaddingBox[beforeName]; 31302 } else if (pack == "center") { 31303 pos = Math.round( 31304 (contLayoutRect[innerSizeName] / 2) - ((contLayoutRect[innerSizeName] - availableSpace) / 2) 31305 ) + contPaddingBox[beforeName]; 31306 31307 if (pos < 0) { 31308 pos = contPaddingBox[beforeName]; 31309 } 31310 } else if (pack == "justify") { 31311 pos = contPaddingBox[beforeName]; 31312 spacing = Math.floor(availableSpace / (items.length - 1)); 31313 } 31314 } 31315 31316 // Default aligning (start) the other ones needs to be calculated while doing the layout 31317 rect[alignAxisName] = contPaddingBox[alignBeforeName]; 31318 31319 // Start laying out controls 31320 for (i = 0, l = items.length; i < l; i++) { 31321 ctrl = items[i]; 31322 ctrlLayoutRect = ctrl.layoutRect(); 31323 size = ctrlLayoutRect.maxFlexSize || ctrlLayoutRect[minSizeName]; 31324 31325 // Align the control on the other axis 31326 if (align === "center") { 31327 rect[alignAxisName] = Math.round((contLayoutRect[alignInnerSizeName] / 2) - (ctrlLayoutRect[alignSizeName] / 2)); 31328 } else if (align === "stretch") { 31329 rect[alignSizeName] = max( 31330 ctrlLayoutRect[alignMinSizeName] || 0, 31331 contLayoutRect[alignInnerSizeName] - contPaddingBox[alignBeforeName] - contPaddingBox[alignAfterName] 31332 ); 31333 rect[alignAxisName] = contPaddingBox[alignBeforeName]; 31334 } else if (align === "end") { 31335 rect[alignAxisName] = contLayoutRect[alignInnerSizeName] - ctrlLayoutRect[alignSizeName] - contPaddingBox.top; 31336 } 31337 31338 // Calculate new size based on flex 31339 if (ctrlLayoutRect.flex > 0) { 31340 size += ctrlLayoutRect.flex * ratio; 31341 } 31342 31343 rect[sizeName] = size; 31344 rect[posName] = pos; 31345 ctrl.layoutRect(rect); 31346 31347 // Recalculate containers 31348 if (ctrl.recalc) { 31349 ctrl.recalc(); 31350 } 31351 31352 // Move x/y position 31353 pos += size + spacing; 31354 } 31355 } 31356 }); 31357}); 31358 31359// Included from: js/tinymce/classes/ui/FlowLayout.js 31360 31361/** 31362 * FlowLayout.js 31363 * 31364 * Copyright, Moxiecode Systems AB 31365 * Released under LGPL License. 31366 * 31367 * License: http://www.tinymce.com/license 31368 * Contributing: http://www.tinymce.com/contributing 31369 */ 31370 31371/** 31372 * This layout manager will place the controls by using the browsers native layout. 31373 * 31374 * @-x-less FlowLayout.less 31375 * @class tinymce.ui.FlowLayout 31376 * @extends tinymce.ui.Layout 31377 */
31378define("tinymce/ui/FlowLayout", [ 31379 "tinymce/ui/Layout" 31380], function(Layout) { 31381 return Layout.extend({ 31382 Defaults: { 31383 containerClass: 'flow-layout', 31384 controlClass: 'flow-layout-item', 31385 endClass : 'break' 31386 }, 31387 31388 /** 31389 * Recalculates the positions of the controls in the specified container. 31390 * 31391 * @method recalc 31392 * @param {tinymce.ui.Container} container Container instance to recalc. 31393 */ 31394 recalc: function(container) { 31395 container.items().filter(':visible').each(function(ctrl) { 31396 if (ctrl.recalc) { 31397 ctrl.recalc(); 31398 } 31399 }); 31400 } 31401 }); 31402}); 31403 31404// Included from: js/tinymce/classes/ui/FormatControls.js 31405 31406/** 31407 * FormatControls.js 31408 * 31409 * Copyright, Moxiecode Systems AB 31410 * Released under LGPL License. 31411 * 31412 * License: http://www.tinymce.com/license 31413 * Contributing: http://www.tinymce.com/contributing 31414 */ 31415 31416/** 31417 * Internal class containing all TinyMCE specific control types such as 31418 * format listboxes, fontlist boxes, toolbar buttons etc. 31419 * 31420 * @class tinymce.ui.FormatControls 31421 */ 31422define("tinymce/ui/FormatControls", [ 31423 "tinymce/ui/Control", 31424 "tinymce/ui/Widget", 31425 "tinymce/ui/FloatPanel", 31426 "tinymce/util/Tools", 31427 "tinymce/EditorManager", 31428 "tinymce/Env" 31429], function(Control, Widget, FloatPanel, Tools, EditorManager, Env) { 31430 var each = Tools.each; 31431 31432 EditorManager.on('AddEditor', function(e) { 31433 if (e.editor.rtl) { 31434 Control.rtl = true; 31435 } 31436 31437 registerControls(e.editor); 31438 }); 31439 31440 Control.translate = function(text) { 31441 return EditorManager.translate(text); 31442 }; 31443 31444 Widget.tooltips = !Env.iOS; 31445 31446 function registerControls(editor) { 31447 var formatMenu; 31448 31449 // Generates a preview for a format 31450 function getPreviewCss(format) { 31451 var name, previewElm, dom = editor.dom; 31452 var previewCss = '', parentFontSize, previewStyles; 31453 31454 previewStyles = editor.settings.preview_styles; 31455 31456 // No preview forced 31457 if (previewStyles === false) { 31458 return ''; 31459 } 31460 31461 // Default preview 31462 if (!previewStyles) { 31463 previewStyles = 'font-family font-size font-weight font-style text-decoration ' + 31464 'text-transform color background-color border border-radius'; 31465 } 31466 31467 // Removes any variables since these can't be previewed 31468 function removeVars(val) { 31469 return val.replace(/%(\w+)/g, ''); 31470 } 31471 31472 // Create block/inline element to use for preview 31473 format = editor.formatter.get(format); 31474 if (!format) { 31475 return; 31476 } 31477 31478 format = format[0]; 31479 name = format.block || format.inline || 'span'; 31480 previewElm = dom.create(name); 31481 31482 // Add format styles to preview element 31483 each(format.styles, function(value, name) { 31484 value = removeVars(value); 31485 31486 if (value) { 31487 dom.setStyle(previewElm, name, value); 31488 } 31489 }); 31490 31491 // Add attributes to preview element 31492 each(format.attributes, function(value, name) { 31493 value = removeVars(value); 31494 31495 if (value) { 31496 dom.setAttrib(previewElm, name, value); 31497 } 31498 }); 31499 31500 // Add classes to preview element 31501 each(format.classes, function(value) { 31502 value = removeVars(value); 31503 31504 if (!dom.hasClass(previewElm, value)) { 31505 dom.addClass(previewElm, value); 31506 } 31507 }); 31508 31509 editor.fire('PreviewFormats'); 31510 31511 // Add the previewElm outside the visual area 31512 dom.setStyles(previewElm, {position: 'absolute', left: -0xFFFF}); 31513 editor.getBody().appendChild(previewElm); 31514 31515 // Get parent container font size so we can compute px values out of em/% for older IE:s 31516 parentFontSize = dom.getStyle(editor.getBody(), 'fontSize', true); 31517 parentFontSize = /px$/.test(parentFontSize) ? parseInt(parentFontSize, 10) : 0; 31518 31519 each(previewStyles.split(' '), function(name) { 31520 var value = dom.getStyle(previewElm, name, true); 31521 31522 // If background is transparent then check if the body has a background color we can use 31523 if (name == 'background-color' && /transparent|rgba\s*\([^)]+,\s*0\)/.test(value)) { 31524 value = dom.getStyle(editor.getBody(), name, true); 31525 31526 // Ignore white since it's the default color, not the nicest fix 31527 // TODO: Fix this by detecting runtime style 31528 if (dom.toHex(value).toLowerCase() == '#ffffff') { 31529 return; 31530 } 31531 } 31532 31533 if (name == 'color') { 31534 // Ignore black since it's the default color, not the nicest fix 31535 // TODO: Fix this by detecting runtime style 31536 if (dom.toHex(value).toLowerCase() == '#000000') { 31537 return; 31538 } 31539 } 31540 31541 // Old IE won't calculate the font size so we need to do that manually 31542 if (name == 'font-size') { 31543 if (/em|%$/.test(value)) { 31544 if (parentFontSize === 0) { 31545 return; 31546 } 31547 31548 // Convert font size from em/% to px 31549 value = parseFloat(value, 10) / (/%$/.test(value) ? 100 : 1); 31550 value = (value * parentFontSize) + 'px'; 31551 } 31552 } 31553 31554 if (name == "border" && value) { 31555 previewCss += 'padding:0 2px;'; 31556 } 31557 31558 previewCss += name + ':' + value + ';'; 31559 }); 31560 31561 editor.fire('AfterPreviewFormats'); 31562 31563 //previewCss += 'line-height:normal'; 31564 31565 dom.remove(previewElm); 31566 31567 return previewCss; 31568 } 31569 31570 function createListBoxChangeHandler(items, formatName) { 31571 return function() { 31572 var self = this; 31573 31574 editor.on('nodeChange', function(e) { 31575 var formatter = editor.formatter; 31576 var value = null; 31577 31578 each(e.parents, function(node) { 31579 each(items, function(item) { 31580 if (formatName) { 31581 if (formatter.matchNode(node, formatName, {value: item.value})) { 31582 value = item.value; 31583 } 31584 } else { 31585 if (formatter.matchNode(node, item.value)) { 31586 value = item.value; 31587 } 31588 } 31589 31590 if (value) { 31591 return false;
31592 } 31593 }); 31594 31595 if (value) { 31596 return false; 31597 } 31598 }); 31599 31600 self.value(value); 31601 }); 31602 }; 31603 } 31604 31605 function createFormats(formats) { 31606 formats = formats.split(';'); 31607 31608 var i = formats.length; 31609 while (i--) { 31610 formats[i] = formats[i].split('='); 31611 } 31612 31613 return formats; 31614 } 31615 31616 function createFormatMenu() { 31617 var count = 0, newFormats = []; 31618 31619 var defaultStyleFormats = [ 31620 {title: 'Headers', items: [ 31621 {title: 'Header 1', format: 'h1'}, 31622 {title: 'Header 2', format: 'h2'}, 31623 {title: 'Header 3', format: 'h3'}, 31624 {title: 'Header 4', format: 'h4'}, 31625 {title: 'Header 5', format: 'h5'}, 31626 {title: 'Header 6', format: 'h6'} 31627 ]}, 31628 31629 {title: 'Inline', items: [ 31630 {title: 'Bold', icon: 'bold', format: 'bold'}, 31631 {title: 'Italic', icon: 'italic', format: 'italic'}, 31632 {title: 'Underline', icon: 'underline', format: 'underline'}, 31633 {title: 'Strikethrough', icon: 'strikethrough', format: 'strikethrough'}, 31634 {title: 'Superscript', icon: 'superscript', format: 'superscript'}, 31635 {title: 'Subscript', icon: 'subscript', format: 'subscript'}, 31636 {title: 'Code', icon: 'code', format: 'code'} 31637 ]}, 31638 31639 {title: 'Blocks', items: [ 31640 {title: 'Paragraph', format: 'p'}, 31641 {title: 'Blockquote', format: 'blockquote'}, 31642 {title: 'Div', format: 'div'}, 31643 {title: 'Pre', format: 'pre'} 31644 ]}, 31645 31646 {title: 'Alignment', items: [ 31647 {title: 'Left', icon: 'alignleft', format: 'alignleft'}, 31648 {title: 'Center', icon: 'aligncenter', format: 'aligncenter'}, 31649 {title: 'Right', icon: 'alignright', format: 'alignright'}, 31650 {title: 'Justify', icon: 'alignjustify', format: 'alignjustify'} 31651 ]} 31652 ]; 31653 31654 function createMenu(formats) { 31655 var menu = []; 31656 31657 if (!formats) { 31658 return; 31659 } 31660 31661 each(formats, function(format) { 31662 var menuItem = { 31663 text: format.title, 31664 icon: format.icon 31665 }; 31666 31667 if (format.items) { 31668 menuItem.menu = createMenu(format.items); 31669 } else { 31670 var formatName = format.format || "custom" + count++; 31671 31672 if (!format.format) { 31673 format.name = formatName; 31674 newFormats.push(format); 31675 } 31676 31677 menuItem.format = formatName; 31678 } 31679 31680 menu.push(menuItem); 31681 }); 31682 31683 return menu; 31684 } 31685 31686 editor.on('init', function() { 31687 each(newFormats, function(format) { 31688 editor.formatter.register(format.name, format); 31689 }); 31690 }); 31691 31692 var menu = createMenu(editor.settings.style_formats || defaultStyleFormats); 31693 31694 menu = { 31695 type: 'menu', 31696 items: menu, 31697 onPostRender: function(e) { 31698 editor.fire('renderFormatsMenu', {control: e.control}); 31699 }, 31700 itemDefaults: { 31701 preview: true, 31702 31703 textStyle: function() { 31704 if (this.settings.format) { 31705 return getPreviewCss(this.settings.format); 31706 } 31707 }, 31708 31709 onPostRender: function() { 31710 var self = this, formatName = this.settings.format; 31711 31712 if (formatName) { 31713 self.parent().on('show', function() { 31714 self.disabled(!editor.formatter.canApply(formatName)); 31715 self.active(editor.formatter.match(formatName)); 31716 }); 31717 } 31718 }, 31719 31720 onclick: function() { 31721 if (this.settings.format) { 31722 toggleFormat(this.settings.format); 31723 } 31724 } 31725 } 31726 }; 31727 31728 return menu; 31729 } 31730 31731 formatMenu = createFormatMenu(); 31732 31733 // Simple format controls <control/format>:<UI text> 31734 each({ 31735 bold: 'Bold', 31736 italic: 'Italic', 31737 underline: 'Underline', 31738 strikethrough: 'Strikethrough', 31739 subscript: 'Subscript', 31740 superscript: 'Superscript' 31741 }, function(text, name) { 31742 editor.addButton(name, { 31743 tooltip: text, 31744 onPostRender: function() { 31745 var self = this; 31746 31747 // TODO: Fix this 31748 if (editor.formatter) { 31749 editor.formatter.formatChanged(name, function(state) { 31750 self.active(state); 31751 }); 31752 } else { 31753 editor.on('init', function() { 31754 editor.formatter.formatChanged(name, function(state) { 31755 self.active(state); 31756 }); 31757 }); 31758 } 31759 }, 31760 onclick: function() { 31761 toggleFormat(name); 31762 } 31763 }); 31764 }); 31765 31766 // Simple command controls <control>:[<UI text>,<Command>] 31767 each({ 31768 outdent: ['Decrease indent', 'Outdent'], 31769 indent: ['Increase indent', 'Indent'], 31770 cut: ['Cut', 'Cut'], 31771 copy: ['Copy', 'Copy'], 31772 paste: ['Paste', 'Paste'], 31773 help: ['Help', 'mceHelp'], 31774 selectall: ['Select all', 'SelectAll'], 31775 hr: ['Insert horizontal rule', 'InsertHorizontalRule'],
31776 removeformat: ['Clear formatting', 'RemoveFormat'], 31777 visualaid: ['Visual aids', 'mceToggleVisualAid'], 31778 newdocument: ['New document', 'mceNewDocument'] 31779 }, function(item, name) { 31780 editor.addButton(name, { 31781 tooltip: item[0], 31782 cmd: item[1] 31783 }); 31784 }); 31785 31786 // Simple command controls with format state 31787 each({ 31788 blockquote: ['Toggle blockquote', 'mceBlockQuote'], 31789 numlist: ['Numbered list', 'InsertOrderedList'], 31790 bullist: ['Bullet list', 'InsertUnorderedList'], 31791 subscript: ['Subscript', 'Subscript'], 31792 superscript: ['Superscript', 'Superscript'], 31793 alignleft: ['Align left', 'JustifyLeft'], 31794 aligncenter: ['Align center', 'JustifyCenter'], 31795 alignright: ['Align right', 'JustifyRight'], 31796 alignjustify: ['Justify', 'JustifyFull'] 31797 }, function(item, name) { 31798 editor.addButton(name, { 31799 tooltip: item[0], 31800 cmd: item[1], 31801 onPostRender: function() { 31802 var self = this; 31803 31804 // TODO: Fix this 31805 if (editor.formatter) { 31806 editor.formatter.formatChanged(name, function(state) { 31807 self.active(state); 31808 }); 31809 } else { 31810 editor.on('init', function() { 31811 editor.formatter.formatChanged(name, function(state) { 31812 self.active(state); 31813 }); 31814 }); 31815 } 31816 } 31817 }); 31818 }); 31819 31820 function hasUndo() { 31821 return editor.undoManager ? editor.undoManager.hasUndo() : false; 31822 } 31823 31824 function hasRedo() { 31825 return editor.undoManager ? editor.undoManager.hasRedo() : false; 31826 } 31827 31828 function toggleUndoState() { 31829 var self = this; 31830 31831 self.disabled(!hasUndo()); 31832 editor.on('Undo Redo AddUndo TypingUndo', function() { 31833 self.disabled(!hasUndo()); 31834 }); 31835 } 31836 31837 function toggleRedoState() { 31838 var self = this; 31839 31840 self.disabled(!hasRedo()); 31841 editor.on('Undo Redo AddUndo TypingUndo', function() { 31842 self.disabled(!hasRedo()); 31843 }); 31844 } 31845 31846 function toggleVisualAidState() { 31847 var self = this; 31848 31849 editor.on('VisualAid', function(e) { 31850 self.active(e.hasVisual); 31851 }); 31852 31853 self.active(editor.hasVisual); 31854 } 31855 31856 editor.addButton('undo', { 31857 tooltip: 'Undo', 31858 onPostRender: toggleUndoState, 31859 cmd: 'undo' 31860 }); 31861 31862 editor.addButton('redo', { 31863 tooltip: 'Redo', 31864 onPostRender: toggleRedoState, 31865 cmd: 'redo' 31866 }); 31867 31868 editor.addMenuItem('newdocument', { 31869 text: 'New document', 31870 shortcut: 'Ctrl+N', 31871 icon: 'newdocument', 31872 cmd: 'mceNewDocument' 31873 }); 31874 31875 editor.addMenuItem('undo', { 31876 text: 'Undo', 31877 icon: 'undo', 31878 shortcut: 'Ctrl+Z', 31879 onPostRender: toggleUndoState, 31880 cmd: 'undo' 31881 }); 31882 31883 editor.addMenuItem('redo', { 31884 text: 'Redo', 31885 icon: 'redo', 31886 shortcut: 'Ctrl+Y', 31887 onPostRender: toggleRedoState, 31888 cmd: 'redo' 31889 }); 31890 31891 editor.addMenuItem('visualaid', { 31892 text: 'Visual aids', 31893 selectable: true, 31894 onPostRender: toggleVisualAidState, 31895 cmd: 'mceToggleVisualAid' 31896 }); 31897 31898 each({ 31899 cut: ['Cut', 'Cut', 'Ctrl+X'], 31900 copy: ['Copy', 'Copy', 'Ctrl+C'], 31901 paste: ['Paste', 'Paste', 'Ctrl+V'], 31902 selectall: ['Select all', 'SelectAll', 'Ctrl+A'], 31903 bold: ['Bold', 'Bold', 'Ctrl+B'], 31904 italic: ['Italic', 'Italic', 'Ctrl+I'], 31905 underline: ['Underline', 'Underline'], 31906 strikethrough: ['Strikethrough', 'Strikethrough'], 31907 subscript: ['Subscript', 'Subscript'], 31908 superscript: ['Superscript', 'Superscript'], 31909 removeformat: ['Clear formatting', 'RemoveFormat'] 31910 }, function(item, name) { 31911 editor.addMenuItem(name, { 31912 text: item[0], 31913 icon: name, 31914 shortcut: item[2], 31915 cmd: item[1] 31916 }); 31917 }); 31918 31919 editor.on('mousedown', function() { 31920 FloatPanel.hideAll(); 31921 }); 31922 31923 function toggleFormat(fmt) { 31924 if (fmt.control) { 31925 fmt = fmt.control.value(); 31926 } 31927 31928 if (fmt) { 31929 editor.execCommand('mceToggleFormat', false, fmt); 31930 } 31931 } 31932 31933 editor.addButton('styleselect', { 31934 type: 'menubutton', 31935 text: 'Formats', 31936 menu: formatMenu 31937 }); 31938 31939 editor.addButton('formatselect', function() { 31940 var items = [], blocks = createFormats(editor.settings.block_formats || 31941 'Paragraph=p;' + 31942 'Address=address;' + 31943 'Pre=pre;' + 31944 'Header 1=h1;' + 31945 'Header 2=h2;' + 31946 'Header 3=h3;' + 31947 'Header 4=h4;' + 31948 'Header 5=h5;' + 31949 'Header 6=h6' 31950 ); 31951 31952 each(blocks, function(block) { 31953 items.push({ 31954 text: block[0], 31955 value: block[1], 31956 textStyle: function() { 31957 return getPreviewCss(block[1]); 31958 } 31959 }); 31960 }); 31961 31962 return { 31963 type: 'listbox', 31964 text: blocks[0][0], 31965 values: items, 31966 fixedWidth: true, 31967 onselect: toggleFormat, 31968 onPostRender: createListBoxChangeHandler(items) 31969 }; 31970 }); 31971 31972 editor.addButton('fontselect', function() { 31973 var defaultFontsFormats =
31974 'Andale Mono=andale mono,times;' + 31975 'Arial=arial,helvetica,sans-serif;' + 31976 'Arial Black=arial black,avant garde;' + 31977 'Book Antiqua=book antiqua,palatino;' + 31978 'Comic Sans MS=comic sans ms,sans-serif;' + 31979 'Courier New=courier new,courier;' + 31980 'Georgia=georgia,palatino;' + 31981 'Helvetica=helvetica;' + 31982 'Impact=impact,chicago;' + 31983 'Symbol=symbol;' + 31984 'Tahoma=tahoma,arial,helvetica,sans-serif;' + 31985 'Terminal=terminal,monaco;' + 31986 'Times New Roman=times new roman,times;' + 31987 'Trebuchet MS=trebuchet ms,geneva;' + 31988 'Verdana=verdana,geneva;' + 31989 'Webdings=webdings;' + 31990 'Wingdings=wingdings,zapf dingbats'; 31991 31992 var items = [], fonts = createFormats(editor.settings.font_formats || defaultFontsFormats); 31993 31994 each(fonts, function(font) { 31995 items.push({ 31996 text: {raw: font[0]}, 31997 value: font[1], 31998 textStyle: font[1].indexOf('dings') == -1 ? 'font-family:' + font[1] : '' 31999 }); 32000 }); 32001 32002 return { 32003 type: 'listbox', 32004 text: 'Font Family', 32005 tooltip: 'Font Family', 32006 values: items, 32007 fixedWidth: true, 32008 onPostRender: createListBoxChangeHandler(items, 'fontname'), 32009 onselect: function(e) { 32010 if (e.control.settings.value) { 32011 editor.execCommand('FontName', false, e.control.settings.value); 32012 } 32013 } 32014 }; 32015 }); 32016 32017 editor.addButton('fontsizeselect', function() { 32018 var items = [], defaultFontsizeFormats = '8pt 10pt 12pt 14pt 18pt 24pt 36pt'; 32019 var fontsize_formats = editor.settings.fontsize_formats || defaultFontsizeFormats; 32020 32021 each(fontsize_formats.split(' '), function(item) { 32022 items.push({text: item, value: item}); 32023 }); 32024 32025 return { 32026 type: 'listbox', 32027 text: 'Font Sizes', 32028 tooltip: 'Font Sizes', 32029 values: items, 32030 fixedWidth: true, 32031 onPostRender: createListBoxChangeHandler(items, 'fontsize'), 32032 onclick: function(e) { 32033 if (e.control.settings.value) { 32034 editor.execCommand('FontSize', false, e.control.settings.value); 32035 } 32036 } 32037 }; 32038 }); 32039 32040 editor.addMenuItem('formats', { 32041 text: 'Formats', 32042 menu: formatMenu 32043 }); 32044 } 32045}); 32046 32047// Included from: js/tinymce/classes/ui/GridLayout.js 32048 32049/** 32050 * GridLayout.js 32051 * 32052 * Copyright, Moxiecode Systems AB 32053 * Released under LGPL License. 32054 * 32055 * License: http://www.tinymce.com/license 32056 * Contributing: http://www.tinymce.com/contributing 32057 */ 32058 32059/** 32060 * This layout manager places controls in a grid. 32061 * 32062 * @setting {Number} spacing Spacing between controls. 32063 * @setting {Number} spacingH Horizontal spacing between controls. 32064 * @setting {Number} spacingV Vertical spacing between controls. 32065 * @setting {Number} columns Number of columns to use. 32066 * @setting {String/Array} alignH start|end|center|stretch or array of values for each column. 32067 * @setting {String/Array} alignV start|end|center|stretch or array of values for each column. 32068 * @setting {String} pack start|end 32069 * 32070 * @class tinymce.ui.GridLayout 32071 * @extends tinymce.ui.AbsoluteLayout 32072 */ 32073define("tinymce/ui/GridLayout", [ 32074 "tinymce/ui/AbsoluteLayout" 32075], function(AbsoluteLayout) { 32076 "use strict"; 32077 32078 return AbsoluteLayout.extend({ 32079 /** 32080 * Recalculates the positions of the controls in the specified container. 32081 * 32082 * @method recalc 32083 * @param {tinymce.ui.Container} container Container instance to recalc. 32084 */ 32085 recalc: function(container) { 32086 var settings = container.settings, rows, cols, items, contLayoutRect, width, height, rect, 32087 ctrlLayoutRect, ctrl, x, y, posX, posY, ctrlSettings, contPaddingBox, align, spacingH, spacingV, alignH, alignV, maxX, maxY, 32088 colWidths = [], rowHeights = [], ctrlMinWidth, ctrlMinHeight, alignX, alignY, availableWidth, availableHeight; 32089 32090 // Get layout settings 32091 settings = container.settings; 32092 items = container.items().filter(':visible'); 32093 contLayoutRect = container.layoutRect(); 32094 cols = settings.columns || Math.ceil(Math.sqrt(items.length)); 32095 rows = Math.ceil(items.length / cols); 32096 spacingH = settings.spacingH || settings.spacing || 0; 32097 spacingV = settings.spacingV || settings.spacing || 0; 32098 alignH = settings.alignH || settings.align; 32099 alignV = settings.alignV || settings.align; 32100 contPaddingBox = container._paddingBox; 32101
32102 if (alignH && typeof(alignH) == "string") { 32103 alignH = [alignH]; 32104 } 32105 32106 if (alignV && typeof(alignV) == "string") { 32107 alignV = [alignV]; 32108 } 32109 32110 // Zero padd columnWidths 32111 for (x = 0; x < cols; x++) { 32112 colWidths.push(0); 32113 } 32114 32115 // Zero padd rowHeights 32116 for (y = 0; y < rows; y++) { 32117 rowHeights.push(0); 32118 } 32119 32120 // Calculate columnWidths and rowHeights 32121 for (y = 0; y < rows; y++) { 32122 for (x = 0; x < cols; x++) { 32123 ctrl = items[y * cols + x]; 32124 32125 // Out of bounds 32126 if (!ctrl) { 32127 break; 32128 } 32129 32130 ctrlLayoutRect = ctrl.layoutRect(); 32131 ctrlMinWidth = ctrlLayoutRect.minW; 32132 ctrlMinHeight = ctrlLayoutRect.minH; 32133 32134 colWidths[x] = ctrlMinWidth > colWidths[x] ? ctrlMinWidth : colWidths[x]; 32135 rowHeights[y] = ctrlMinHeight > rowHeights[y] ? ctrlMinHeight : rowHeights[y]; 32136 } 32137 } 32138 32139 // Calculate maxX 32140 availableWidth = contLayoutRect.innerW - contPaddingBox.left - contPaddingBox.right; 32141 for (maxX = 0, x = 0; x < cols; x++) { 32142 maxX += colWidths[x] + (x > 0 ? spacingH : 0); 32143 availableWidth -= (x > 0 ? spacingH : 0) + colWidths[x]; 32144 } 32145 32146 // Calculate maxY 32147 availableHeight = contLayoutRect.innerH - contPaddingBox.top - contPaddingBox.bottom; 32148 for (maxY = 0, y = 0; y < rows; y++) { 32149 maxY += rowHeights[y] + (y > 0 ? spacingV : 0); 32150 availableHeight -= (y > 0 ? spacingV : 0) + rowHeights[y]; 32151 } 32152 32153 maxX += contPaddingBox.left + contPaddingBox.right; 32154 maxY += contPaddingBox.top + contPaddingBox.bottom; 32155 32156 // Calculate minW/minH 32157 rect = {}; 32158 rect.minW = maxX + (contLayoutRect.w - contLayoutRect.innerW); 32159 rect.minH = maxY + (contLayoutRect.h - contLayoutRect.innerH); 32160 32161 rect.contentW = rect.minW - contLayoutRect.deltaW; 32162 rect.contentH = rect.minH - contLayoutRect.deltaH; 32163 rect.minW = Math.min(rect.minW, contLayoutRect.maxW); 32164 rect.minH = Math.min(rect.minH, contLayoutRect.maxH); 32165 rect.minW = Math.max(rect.minW, contLayoutRect.startMinWidth); 32166 rect.minH = Math.max(rect.minH, contLayoutRect.startMinHeight); 32167 32168 // Resize container container if minSize was changed 32169 if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) { 32170 rect.w = rect.minW; 32171 rect.h = rect.minH; 32172 32173 container.layoutRect(rect); 32174 this.recalc(container); 32175 32176 // Forced recalc for example if items are hidden/shown 32177 if (container._lastRect === null) { 32178 var parentCtrl = container.parent(); 32179 if (parentCtrl) { 32180 parentCtrl._lastRect = null; 32181 parentCtrl.recalc(); 32182 } 32183 } 32184 32185 return; 32186 } 32187 32188 // Update contentW/contentH so absEnd moves correctly 32189 if (contLayoutRect.autoResize) { 32190 rect = container.layoutRect(rect); 32191 rect.contentW = rect.minW - contLayoutRect.deltaW; 32192 rect.contentH = rect.minH - contLayoutRect.deltaH; 32193 } 32194 32195 var flexV; 32196 32197 if (settings.packV == 'start') { 32198 flexV = 0; 32199 } else { 32200 flexV = availableHeight > 0 ? Math.floor(availableHeight / rows) : 0; 32201 } 32202 32203 // Calculate totalFlex 32204 var totalFlex = 0; 32205 var flexWidths = settings.flexWidths; 32206 if (flexWidths) { 32207 for (x = 0; x < flexWidths.length; x++) { 32208 totalFlex += flexWidths[x]; 32209 } 32210 } else { 32211 totalFlex = cols; 32212 } 32213 32214 // Calculate new column widths based on flex values 32215 var ratio = availableWidth / totalFlex; 32216 for (x = 0; x < cols; x++) { 32217 colWidths[x] += flexWidths ? flexWidths[x] * ratio : ratio; 32218 } 32219 32220 // Move/resize controls 32221 posY = contPaddingBox.top; 32222 for (y = 0; y < rows; y++) { 32223 posX = contPaddingBox.left; 32224 height = rowHeights[y] + flexV; 32225 32226 for (x = 0; x < cols; x++) { 32227 ctrl = items[y * cols + x]; 32228 32229 // No more controls to render then break 32230 if (!ctrl) { 32231 break; 32232 } 32233 32234 // Get control settings and calculate x, y 32235 ctrlSettings = ctrl.settings; 32236 ctrlLayoutRect = ctrl.layoutRect(); 32237 width = Math.max(colWidths[x], ctrlLayoutRect.startMinWidth); 32238 alignX = alignY = 0; 32239 ctrlLayoutRect.x = posX; 32240 ctrlLayoutRect.y = posY; 32241 32242 // Align control horizontal 32243 align = ctrlSettings.alignH || (alignH ? (alignH[x] || alignH[0]) : null); 32244 if (align == "center") { 32245 ctrlLayoutRect.x = posX + (width / 2) - (ctrlLayoutRect.w / 2); 32246 } else if (align == "right") { 32247 ctrlLayoutRect.x = posX + width - ctrlLayoutRect.w; 32248 } else if (align == "stretch") { 32249 ctrlLayoutRect.w = width; 32250 } 32251 32252 // Align control vertical
32253 align = ctrlSettings.alignV || (alignV ? (alignV[x] || alignV[0]) : null); 32254 if (align == "center") { 32255 ctrlLayoutRect.y = posY + (height / 2) - (ctrlLayoutRect.h / 2); 32256 } else if (align == "bottom") { 32257 ctrlLayoutRect.y = posY + height - ctrlLayoutRect.h; 32258 } else if (align == "stretch") { 32259 ctrlLayoutRect.h = height; 32260 } 32261 32262 ctrl.layoutRect(ctrlLayoutRect); 32263 32264 posX += width + spacingH; 32265 32266 if (ctrl.recalc) { 32267 ctrl.recalc(); 32268 } 32269 } 32270 32271 posY += height + spacingV; 32272 } 32273 } 32274 }); 32275}); 32276 32277// Included from: js/tinymce/classes/ui/Iframe.js 32278 32279/** 32280 * Iframe.js 32281 * 32282 * Copyright, Moxiecode Systems AB 32283 * Released under LGPL License. 32284 * 32285 * License: http://www.tinymce.com/license 32286 * Contributing: http://www.tinymce.com/contributing 32287 */ 32288 32289/*jshint scripturl:true */ 32290 32291/** 32292 * This class creates an iframe. 32293 * 32294 * @setting {String} url Url to open in the iframe. 32295 * 32296 * @-x-less Iframe.less 32297 * @class tinymce.ui.Iframe 32298 * @extends tinymce.ui.Widget 32299 */ 32300define("tinymce/ui/Iframe", [ 32301 "tinymce/ui/Widget" 32302], function(Widget) { 32303 "use strict"; 32304 32305 return Widget.extend({ 32306 /** 32307 * Renders the control as a HTML string. 32308 * 32309 * @method renderHtml 32310 * @return {String} HTML representing the control. 32311 */ 32312 renderHtml: function() { 32313 var self = this; 32314 32315 self.addClass('iframe'); 32316 self.canFocus = false; 32317 32318 return ( 32319 '<iframe id="' + self._id + '" class="' + self.classes() + '" tabindex="-1" src="' + 32320 (self.settings.url || "javascript:\'\'") + '" frameborder="0"></iframe>' 32321 ); 32322 }, 32323 32324 /** 32325 * Setter for the iframe source. 32326 * 32327 * @method src 32328 * @param {String} src Source URL for iframe. 32329 */ 32330 src: function(src) { 32331 this.getEl().src = src; 32332 }, 32333 32334 /** 32335 * Inner HTML for the iframe. 32336 * 32337 * @method html 32338 * @param {String} html HTML string to set as HTML inside the iframe. 32339 * @param {function} callback Optional callback to execute when the iframe body is filled with contents. 32340 * @return {tinymce.ui.Iframe} Current iframe control. 32341 */ 32342 html: function(html, callback) { 32343 var self = this, body = this.getEl().contentWindow.document.body; 32344 32345 // Wait for iframe to initialize IE 10 takes time 32346 if (!body) { 32347 setTimeout(function() { 32348 self.html(html); 32349 }, 0); 32350 } else { 32351 body.innerHTML = html; 32352 32353 if (callback) { 32354 callback(); 32355 } 32356 } 32357 32358 return this; 32359 } 32360 }); 32361}); 32362 32363// Included from: js/tinymce/classes/ui/Label.js 32364 32365/** 32366 * Label.js 32367 * 32368 * Copyright, Moxiecode Systems AB 32369 * Released under LGPL License. 32370 * 32371 * License: http://www.tinymce.com/license 32372 * Contributing: http://www.tinymce.com/contributing 32373 */ 32374 32375/** 32376 * This class creates a label element. A label is a simple text control 32377 * that can be bound to other controls. 32378 * 32379 * @-x-less Label.less 32380 * @class tinymce.ui.Label 32381 * @extends tinymce.ui.Widget 32382 */ 32383define("tinymce/ui/Label", [ 32384 "tinymce/ui/Widget", 32385 "tinymce/ui/DomUtils" 32386], function(Widget, DomUtils) { 32387 "use strict"; 32388 32389 return Widget.extend({ 32390 /** 32391 * Constructs a instance with the specified settings. 32392 * 32393 * @constructor 32394 * @param {Object} settings Name/value object with settings. 32395 * @param {Boolean} multiline Multiline label. 32396 */ 32397 init: function(settings) { 32398 var self = this; 32399 32400 self._super(settings); 32401 self.addClass('widget'); 32402 self.addClass('label'); 32403 self.canFocus = false; 32404 32405 if (settings.multiline) { 32406 self.addClass('autoscroll'); 32407 } 32408 32409 if (settings.strong) { 32410 self.addClass('strong'); 32411 } 32412 }, 32413 32414 /** 32415 * Initializes the current controls layout rect. 32416 * This will be executed by the layout managers to determine the 32417 * default minWidth/minHeight etc. 32418 * 32419 * @method initLayoutRect 32420 * @return {Object} Layout rect instance. 32421 */ 32422 initLayoutRect: function() { 32423 var self = this, layoutRect = self._super(); 32424 32425 if (self.settings.multiline) { 32426 var size = DomUtils.getSize(self.getEl()); 32427 32428 // Check if the text fits within maxW if not then try word wrapping it 32429 if (size.width > layoutRect.maxW) { 32430 layoutRect.minW = layoutRect.maxW; 32431 self.addClass('multiline'); 32432 } 32433 32434 self.getEl().style.width = layoutRect.minW + 'px'; 32435 layoutRect.startMinH = layoutRect.h = layoutRect.minH = Math.min(layoutRect.maxH, DomUtils.getSize(self.getEl()).height); 32436 } 32437 32438 return layoutRect; 32439 }, 32440 32441 /** 32442 * Repaints the control after a layout operation. 32443 * 32444 * @method repaint 32445 */ 32446 repaint: function() { 32447 var self = this; 32448 32449 if (!self.settings.multiline) { 32450 self.getEl().style.lineHeight = self.layoutRect().h + 'px'; 32451 } 32452 32453 return self._super(); 32454 }, 32455 32456 /** 32457 * Sets/gets the current label text. 32458 * 32459 * @method text 32460 * @param {String} [text] New label text. 32461 * @return {String|tinymce.ui.Label} Current text or current label instance. 32462 */ 32463 text: function(text) { 32464 var self = this; 32465 32466 if (self._rendered && text) { 32467 this.innerHtml(self.encode(text)); 32468 } 32469 32470 return self._super(text); 32471 }, 32472 32473 /** 32474 * Renders the control as a HTML string. 32475 * 32476 * @method renderHtml 32477 * @return {String} HTML representing the control. 32478 */ 32479 renderHtml: function() { 32480 var self = this, forId = self.settings.forId; 32481 32482 return ( 32483 '<label id="' + self._id + '" class="' + self.classes() + '"' + (forId ? ' for="' + forId : '') + '">' + 32484 self.encode(self._text) + 32485 '</label>' 32486 ); 32487 } 32488 }); 32489}); 32490 32491// Included from: js/tinymce/classes/ui/Toolbar.js 32492 32493/** 32494 * Toolbar.js 32495 * 32496 * Copyright, Moxiecode Systems AB 32497 * Released under LGPL License. 32498 * 32499 * License: http://www.tinymce.com/license 32500 * Contributing: http://www.tinymce.com/contributing 32501 */ 32502 32503/** 32504 * Creates a new toolbar. 32505 * 32506 * @class tinymce.ui.Toolbar 32507 * @extends tinymce.ui.Container 32508 */
32509define("tinymce/ui/Toolbar", [ 32510 "tinymce/ui/Container", 32511 "tinymce/ui/KeyboardNavigation" 32512], function(Container, KeyboardNavigation) { 32513 "use strict"; 32514 32515 return Container.extend({ 32516 Defaults: { 32517 role: 'toolbar', 32518 layout: 'flow' 32519 }, 32520 32521 /** 32522 * Constructs a instance with the specified settings. 32523 * 32524 * @constructor 32525 * @param {Object} settings Name/value object with settings. 32526 */ 32527 init: function(settings) { 32528 var self = this; 32529 32530 self._super(settings); 32531 self.addClass('toolbar'); 32532 }, 32533 32534 /** 32535 * Called after the control has been rendered. 32536 * 32537 * @method postRender 32538 */ 32539 postRender: function() { 32540 var self = this; 32541 32542 self.items().addClass('toolbar-item'); 32543 32544 self.keyNav = new KeyboardNavigation({ 32545 root: self, 32546 enableLeftRight: true 32547 }); 32548 32549 return self._super(); 32550 } 32551 }); 32552}); 32553 32554// Included from: js/tinymce/classes/ui/MenuBar.js 32555 32556/** 32557 * MenuBar.js 32558 * 32559 * Copyright, Moxiecode Systems AB 32560 * Released under LGPL License. 32561 * 32562 * License: http://www.tinymce.com/license 32563 * Contributing: http://www.tinymce.com/contributing 32564 */ 32565 32566/** 32567 * Creates a new menubar. 32568 * 32569 * @-x-less MenuBar.less 32570 * @class tinymce.ui.MenuBar 32571 * @extends tinymce.ui.Container 32572 */ 32573define("tinymce/ui/MenuBar", [ 32574 "tinymce/ui/Toolbar" 32575], function(Toolbar) { 32576 "use strict"; 32577 32578 return Toolbar.extend({ 32579 Defaults: { 32580 role: 'menubar', 32581 containerCls: 'menubar', 32582 defaults: { 32583 type: 'menubutton' 32584 } 32585 } 32586 }); 32587}); 32588 32589// Included from: js/tinymce/classes/ui/MenuButton.js 32590 32591/** 32592 * MenuButton.js 32593 * 32594 * Copyright, Moxiecode Systems AB 32595 * Released under LGPL License. 32596 * 32597 * License: http://www.tinymce.com/license 32598 * Contributing: http://www.tinymce.com/contributing 32599 */ 32600 32601/** 32602 * Creates a new menu button. 32603 * 32604 * @-x-less MenuButton.less 32605 * @class tinymce.ui.MenuButton 32606 * @extends tinymce.ui.Button 32607 */ 32608define("tinymce/ui/MenuButton", [ 32609 "tinymce/ui/Button", 32610 "tinymce/ui/Factory", 32611 "tinymce/ui/MenuBar" 32612], function(Button, Factory, MenuBar) { 32613 "use strict"; 32614 32615 // TODO: Maybe add as some global function 32616 function isChildOf(node, parent) { 32617 while (node) { 32618 if (parent === node) { 32619 return true; 32620 } 32621 32622 node = node.parentNode; 32623 } 32624 32625 return false; 32626 } 32627 32628 var MenuButton = Button.extend({ 32629 /** 32630 * Constructs a instance with the specified settings. 32631 * 32632 * @constructor 32633 * @param {Object} settings Name/value object with settings. 32634 */ 32635 init: function(settings) { 32636 var self = this; 32637 32638 self._renderOpen = true; 32639 self._super(settings); 32640 32641 self.addClass('menubtn'); 32642 32643 if (settings.fixedWidth) { 32644 self.addClass('fixed-width'); 32645 } 32646 32647 self.aria('haspopup', true); 32648 self.hasPopup = true; 32649 }, 32650 32651 /** 32652 * Shows the menu for the button. 32653 * 32654 * @method showMenu 32655 */ 32656 showMenu: function() { 32657 var self = this, settings = self.settings, menu; 32658 32659 if (self.menu && self.menu.visible()) { 32660 return self.hideMenu(); 32661 } 32662 32663 if (!self.menu) { 32664 menu = settings.menu || []; 32665 32666 // Is menu array then auto constuct menu control 32667 if (menu.length) { 32668 menu = { 32669 type: 'menu', 32670 items: menu 32671 }; 32672 } else { 32673 menu.type = menu.type || 'menu'; 32674 } 32675 32676 self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm()); 32677 self.fire('createmenu'); 32678 self.menu.reflow(); 32679 self.menu.on('cancel', function(e) { 32680 if (e.control === self.menu) { 32681 self.focus(); 32682 } 32683 }); 32684 32685 self.menu.on('show hide', function(e) { 32686 if (e.control == self.menu) { 32687 self.activeMenu(e.type == 'show'); 32688 } 32689 }).fire('show'); 32690 32691 self.aria('expanded', true); 32692 } 32693 32694 self.menu.show(); 32695 self.menu.layoutRect({w: self.layoutRect().w}); 32696 self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']); 32697 }, 32698 32699 /** 32700 * Hides the menu for the button. 32701 * 32702 * @method hideMenu 32703 */ 32704 hideMenu: function() { 32705 var self = this; 32706 32707 if (self.menu) { 32708 self.menu.items().each(function(item) { 32709 if (item.hideMenu) { 32710 item.hideMenu(); 32711 } 32712 }); 32713 32714 self.menu.hide(); 32715 self.aria('expanded', false); 32716 } 32717 }, 32718 32719 /** 32720 * Sets the active menu state. 32721 * 32722 * @private 32723 */ 32724 activeMenu: function(state) { 32725 this.toggleClass('active', state); 32726 }, 32727 32728 /** 32729 * Renders the control as a HTML string. 32730 * 32731 * @method renderHtml 32732 * @return {String} HTML representing the control. 32733 */ 32734 renderHtml: function() { 32735 var self = this, id = self._id, prefix = self.classPrefix; 32736 var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : ''; 32737 32738 self.aria('role', self.parent() instanceof MenuBar ? 'menuitem' : 'button'); 32739 32740 return ( 32741 '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' + 32742 '<button id="' + id + '-open" role="presentation" type="button" tabindex="-1">' + 32743 (icon ? '<i class="' + icon + '"></i>' : '') + 32744 '<span>' + (self._text ? (icon ? '\u00a0' : '') + self.encode(self._text) : '') + '</span>' + 32745 ' <i class="' + prefix + 'caret"></i>' + 32746 '</button>' + 32747 '</div>' 32748 ); 32749 }, 32750 32751 /** 32752 * Gets invoked after the control has been rendered. 32753 * 32754 * @method postRender 32755 */ 32756 postRender: function() { 32757 var self = this; 32758 32759 self.on('click', function(e) { 32760 if (e.control === self && isChildOf(e.target, self.getEl())) { 32761 self.showMenu(); 32762 32763 if (e.keyboard) { 32764 self.menu.items()[0].focus(); 32765 } 32766 } 32767 }); 32768 32769 self.on('mouseenter', function(e) { 32770 var overCtrl = e.control, parent = self.parent(), hasVisibleSiblingMenu; 32771 32772 if (overCtrl && parent && overCtrl instanceof MenuButton && overCtrl.parent() == parent) { 32773 parent.items().filter('MenuButton').each(function(ctrl) { 32774 if (ctrl.hideMenu && ctrl != overCtrl) { 32775 if (ctrl.menu && ctrl.menu.visible()) { 32776 hasVisibleSiblingMenu = true; 32777 } 32778 32779 ctrl.hideMenu(); 32780 } 32781 }); 32782 32783 if (hasVisibleSiblingMenu) { 32784 overCtrl.focus(); // Fix for: #5887 32785 overCtrl.showMenu(); 32786 } 32787 } 32788 }); 32789 32790 return self._super(); 32791 }, 32792 32793 /** 32794 * Sets/gets the current button text. 32795 * 32796 * @method text 32797 * @param {String} [text] New button text. 32798 * @return {String|tinymce.ui.MenuButton} Current text or current MenuButton instance. 32799 */ 32800 text: function(text) { 32801 var self = this, i, children; 32802 32803 if (self._rendered) { 32804 children = self.getEl('open').getElementsByTagName('span'); 32805 for (i = 0; i < children.length; i++) { 32806 children[i].innerHTML = (self.settings.icon && text ? '\u00a0' : '') + self.encode(text); 32807 } 32808 } 32809 32810 return this._super(text); 32811 }, 32812 32813 /** 32814 * Removes the control and it's menus. 32815 * 32816 * @method remove 32817 */ 32818 remove: function() { 32819 this._super(); 32820 32821 if (this.menu) { 32822 this.menu.remove(); 32823 } 32824 } 32825 }); 32826
32827 return MenuButton; 32828}); 32829 32830// Included from: js/tinymce/classes/ui/ListBox.js 32831 32832/** 32833 * ListBox.js 32834 * 32835 * Copyright, Moxiecode Systems AB 32836 * Released under LGPL License. 32837 * 32838 * License: http://www.tinymce.com/license 32839 * Contributing: http://www.tinymce.com/contributing 32840 */ 32841 32842/** 32843 * Creates a new list box control. 32844 * 32845 * @-x-less ListBox.less 32846 * @class tinymce.ui.ListBox 32847 * @extends tinymce.ui.MenuButton 32848 */ 32849define("tinymce/ui/ListBox", [ 32850 "tinymce/ui/MenuButton" 32851], function(MenuButton) { 32852 "use strict"; 32853 32854 return MenuButton.extend({ 32855 /** 32856 * Constructs a instance with the specified settings. 32857 * 32858 * @constructor 32859 * @param {Object} settings Name/value object with settings. 32860 * @setting {Array} values Array with values to add to list box. 32861 */ 32862 init: function(settings) { 32863 var self = this, values, i, selected, selectedText, lastItemCtrl; 32864 32865 self._values = values = settings.values; 32866 if (values) { 32867 for (i = 0; i < values.length; i++) { 32868 selected = values[i].selected || settings.value === values[i].value; 32869 32870 if (selected) { 32871 selectedText = selectedText || values[i].text; 32872 self._value = values[i].value; 32873 } 32874 } 32875 32876 settings.menu = values; 32877 } 32878 32879 settings.text = settings.text || selectedText || values[0].text; 32880 32881 self._super(settings); 32882 self.addClass('listbox'); 32883 32884 self.on('select', function(e) { 32885 var ctrl = e.control; 32886 32887 if (lastItemCtrl) { 32888 e.lastControl = lastItemCtrl; 32889 } 32890 32891 if (settings.multiple) { 32892 ctrl.active(!ctrl.active()); 32893 } else { 32894 self.value(e.control.settings.value); 32895 } 32896 32897 lastItemCtrl = ctrl; 32898 }); 32899 }, 32900 32901 /** 32902 * Getter/setter function for the control value. 32903 * 32904 * @method value 32905 * @param {String} [value] Value to be set. 32906 * @return {Boolean/tinymce.ui.ListBox} Value or self if it's a set operation. 32907 */ 32908 value: function(value) { 32909 var self = this, active, selectedText, menu, i; 32910 32911 function activateByValue(menu, value) { 32912 menu.items().each(function(ctrl) { 32913 active = ctrl.value() === value; 32914 32915 if (active) { 32916 selectedText = selectedText || ctrl.text(); 32917 } 32918 32919 ctrl.active(active); 32920 32921 if (ctrl.menu) { 32922 activateByValue(ctrl.menu, value); 32923 } 32924 }); 32925 } 32926 32927 if (typeof(value) != "undefined") { 32928 if (self.menu) { 32929 activateByValue(self.menu, value); 32930 } else { 32931 menu = self.settings.menu; 32932 for (i = 0; i < menu.length; i++) { 32933 active = menu[i].value == value; 32934 32935 if (active) { 32936 selectedText = selectedText || menu[i].text; 32937 } 32938 32939 menu[i].active = active; 32940 } 32941 } 32942 32943 self.text(selectedText || this.settings.text); 32944 } 32945 32946 return self._super(value); 32947 } 32948 }); 32949}); 32950 32951// Included from: js/tinymce/classes/ui/MenuItem.js 32952 32953/** 32954 * MenuItem.js 32955 * 32956 * Copyright, Moxiecode Systems AB 32957 * Released under LGPL License. 32958 * 32959 * License: http://www.tinymce.com/license 32960 * Contributing: http://www.tinymce.com/contributing 32961 */ 32962 32963/** 32964 * Creates a new menu item. 32965 * 32966 * @-x-less MenuItem.less 32967 * @class tinymce.ui.MenuItem 32968 * @extends tinymce.ui.Control 32969 */ 32970define("tinymce/ui/MenuItem", [ 32971 "tinymce/ui/Widget", 32972 "tinymce/ui/Factory", 32973 "tinymce/Env" 32974], function(Widget, Factory, Env) { 32975 "use strict"; 32976 32977 return Widget.extend({ 32978 Defaults: { 32979 border: 0, 32980 role: 'menuitem' 32981 }, 32982 32983 /** 32984 * Constructs a instance with the specified settings. 32985 * 32986 * @constructor 32987 * @param {Object} settings Name/value object with settings. 32988 * @setting {Boolean} selectable Selectable menu. 32989 * @setting {Array} menu Submenu array with items. 32990 * @setting {String} shortcut Shortcut to display for menu item. Example: Ctrl+X 32991 */ 32992 init: function(settings) { 32993 var self = this; 32994 32995 self.hasPopup = true; 32996 32997 self._super(settings); 32998 32999 settings = self.settings; 33000 33001 self.addClass('menu-item'); 33002 33003 if (settings.menu) { 33004 self.addClass('menu-item-expand'); 33005 } 33006 33007 if (settings.preview) { 33008 self.addClass('menu-item-preview'); 33009 } 33010 33011 if (self._text === '-' || self._text === '|') { 33012 self.addClass('menu-item-sep'); 33013 self.aria('role', 'separator'); 33014 self.canFocus = false; 33015 self._text = '-'; 33016 } 33017 33018 if (settings.selectable) { 33019 self.aria('role', 'menuitemcheckbox'); 33020 self.aria('checked', true); 33021 self.addClass('menu-item-checkbox'); 33022 settings.icon = 'selected'; 33023 } 33024
33025 if (!settings.preview && !settings.selectable) { 33026 self.addClass('menu-item-normal'); 33027 } 33028 33029 self.on('mousedown', function(e) { 33030 e.preventDefault(); 33031 }); 33032 33033 self.on('mouseenter click', function(e) { 33034 if (e.control === self) { 33035 if (!settings.menu && e.type === 'click') { 33036 self.parent().hideAll(); 33037 self.fire('cancel'); 33038 self.fire('select'); 33039 } else { 33040 self.showMenu(); 33041 33042 if (e.keyboard) { 33043 setTimeout(function() { 33044 self.menu.items()[0].focus(); 33045 }, 0); 33046 } 33047 } 33048 } 33049 }); 33050 33051 if (settings.menu) { 33052 self.aria('haspopup', true); 33053 } 33054 }, 33055 33056 /** 33057 * Returns true/false if the menuitem has sub menu. 33058 * 33059 * @method hasMenus 33060 * @return {Boolean} True/false state if it has submenu. 33061 */ 33062 hasMenus: function() { 33063 return !!this.settings.menu; 33064 }, 33065 33066 /** 33067 * Shows the menu for the menu item. 33068 * 33069 * @method showMenu 33070 */ 33071 showMenu: function() { 33072 var self = this, settings = self.settings, menu, parent = self.parent(); 33073 33074 parent.items().each(function(ctrl) { 33075 if (ctrl !== self) { 33076 ctrl.hideMenu(); 33077 } 33078 }); 33079 33080 if (settings.menu) { 33081 menu = self.menu; 33082 33083 if (!menu) { 33084 menu = settings.menu; 33085 33086 // Is menu array then auto constuct menu control 33087 if (menu.length) { 33088 menu = { 33089 type: 'menu', 33090 items: menu 33091 }; 33092 } else { 33093 menu.type = menu.type || 'menu'; 33094 } 33095 33096 if (parent.settings.itemDefaults) { 33097 menu.itemDefaults = parent.settings.itemDefaults; 33098 } 33099 33100 menu = self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm()); 33101 menu.reflow(); 33102 menu.fire('show'); 33103 menu.on('cancel', function() { 33104 self.focus(); 33105 }); 33106 33107 menu.on('hide', function(e) { 33108 if (e.control === menu) { 33109 self.removeClass('selected'); 33110 } 33111 }); 33112 } else { 33113 menu.show(); 33114 } 33115 33116 menu._parentMenu = parent; 33117 33118 menu.addClass('menu-sub'); 33119 33120 var rel = menu.testMoveRel( 33121 self.getEl(), 33122 self.isRtl() ? ['tl-tr', 'bl-br', 'tr-tl', 'br-bl'] : ['tr-tl', 'br-bl', 'tl-tr', 'bl-br'] 33123 ); 33124 33125 menu.moveRel(self.getEl(), rel); 33126 menu.rel = rel; 33127 33128 rel = 'menu-sub-' + rel; 33129 menu.removeClass(menu._lastRel); 33130 menu.addClass(rel); 33131 menu._lastRel = rel; 33132 33133 self.addClass('selected'); 33134 self.aria('expanded', true); 33135 } 33136 }, 33137 33138 /** 33139 * Hides the menu for the menu item. 33140 * 33141 * @method hideMenu 33142 */ 33143 hideMenu: function() { 33144 var self = this; 33145 33146 if (self.menu) { 33147 self.menu.items().each(function(item) { 33148 if (item.hideMenu) { 33149 item.hideMenu(); 33150 } 33151 }); 33152 33153 self.menu.hide(); 33154 self.aria('expanded', false); 33155 } 33156 33157 return self; 33158 }, 33159 33160 /** 33161 * Renders the control as a HTML string. 33162 * 33163 * @method renderHtml 33164 * @return {String} HTML representing the control. 33165 */ 33166 renderHtml: function() { 33167 var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix, text = self.encode(self._text); 33168 var icon = self.settings.icon, image = '', shortcut = settings.shortcut; 33169 33170 if (icon) { 33171 self.parent().addClass('menu-has-icons'); 33172 } 33173 33174 if (settings.image) { 33175 icon = 'none'; 33176 image = ' style="background-image: url(\'' + settings.image + '\')"'; 33177 } 33178 33179 if (shortcut && Env.mac) { 33180 // format shortcut for Mac 33181 shortcut = shortcut.replace(/ctrl\+alt\+/i, '⌥⌘'); // ctrl+cmd 33182 shortcut = shortcut.replace(/ctrl\+/i, '⌘'); // ctrl symbol 33183 shortcut = shortcut.replace(/alt\+/i, '⌥'); // cmd symbol 33184 shortcut = shortcut.replace(/shift\+/i, '⇧'); // shift symbol 33185 } 33186 33187 icon = prefix + 'ico ' + prefix + 'i-' + (self.settings.icon || 'none'); 33188 33189 return ( 33190 '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' + 33191 (text !== '-' ? '<i class="' + icon + '"' + image + '></i> ' : '') + 33192 (text !== '-' ? '<span id="' + id + '-text" class="' + prefix + 'text">' + text + '</span>' : '') + 33193 (shortcut ? '<div id="' + id + '-shortcut" class="' + prefix + 'menu-shortcut">' + 33194 shortcut + '</div>' : '') + 33195 (settings.menu ? '<div class="' + prefix + 'caret"></div>' : '') + 33196 '</div>' 33197 ); 33198 }, 33199 33200 /** 33201 * Gets invoked after the control has been rendered. 33202 * 33203 * @method postRender 33204 */ 33205 postRender: function() { 33206 var self = this, settings = self.settings; 33207 33208 var textStyle = settings.textStyle; 33209 if (typeof(textStyle) == "function") { 33210 textStyle = textStyle.call(this); 33211 } 33212 33213 if (textStyle) { 33214 var textElm = self.getEl('text'); 33215 if (textElm) { 33216 textElm.setAttribute('style', textStyle); 33217 } 33218 } 33219 33220 return self._super(); 33221 }, 33222 33223 /** 33224 * Removes the control and it's menus. 33225 * 33226 * @method remove 33227 */ 33228 remove: function() { 33229 this._super(); 33230 33231 if (this.menu) { 33232 this.menu.remove(); 33233 } 33234 } 33235 }); 33236}); 33237 33238// Included from: js/tinymce/classes/ui/Menu.js 33239 33240/** 33241 * Menu.js 33242 * 33243 * Copyright, Moxiecode Systems AB 33244 * Released under LGPL License. 33245 * 33246 * License: http://www.tinymce.com/license 33247 * Contributing: http://www.tinymce.com/contributing 33248 */ 33249 33250/** 33251 * Creates a new menu. 33252 * 33253 * @-x-less Menu.less 33254 * @class tinymce.ui.Menu 33255 * @extends tinymce.ui.FloatPanel 33256 */
33257define("tinymce/ui/Menu", [ 33258 "tinymce/ui/FloatPanel", 33259 "tinymce/ui/KeyboardNavigation", 33260 "tinymce/ui/MenuItem", 33261 "tinymce/util/Tools" 33262], function(FloatPanel, KeyboardNavigation, MenuItem, Tools) { 33263 "use strict"; 33264 33265 var Menu = FloatPanel.extend({ 33266 Defaults: { 33267 defaultType: 'menuitem', 33268 border: 1, 33269 layout: 'stack', 33270 role: 'menu' 33271 }, 33272 33273 /** 33274 * Constructs a instance with the specified settings. 33275 * 33276 * @constructor 33277 * @param {Object} settings Name/value object with settings. 33278 */ 33279 init: function(settings) { 33280 var self = this; 33281 33282 settings.autohide = true; 33283 settings.constrainToViewport = true; 33284 33285 if (settings.itemDefaults) { 33286 var items = settings.items, i = items.length; 33287 33288 while (i--) { 33289 items[i] = Tools.extend({}, settings.itemDefaults, items[i]); 33290 } 33291 } 33292 33293 self._super(settings); 33294 self.addClass('menu'); 33295 33296 self.keyNav = new KeyboardNavigation({ 33297 root: self, 33298 enableUpDown: true, 33299 enableLeftRight: true, 33300 33301 leftAction: function() { 33302 if (self.parent() instanceof MenuItem) { 33303 self.keyNav.cancel(); 33304 } 33305 }, 33306 33307 onCancel: function() { 33308 self.fire('cancel', {}, false); 33309 self.hide(); 33310 } 33311 }); 33312 }, 33313 33314 /** 33315 * Repaints the control after a layout operation. 33316 * 33317 * @method repaint 33318 */ 33319 repaint: function() { 33320 this.toggleClass('menu-align', true); 33321 33322 this._super(); 33323 33324 this.getEl().style.height = ''; 33325 this.getEl('body').style.height = ''; 33326 33327 return this; 33328 }, 33329 33330 /** 33331 * Hides/closes the menu. 33332 * 33333 * @method cancel 33334 */ 33335 cancel: function() { 33336 var self = this; 33337 33338 self.hideAll(); 33339 self.fire('cancel'); 33340 self.fire('select'); 33341 }, 33342 33343 /** 33344 * Hide menu and all sub menus. 33345 * 33346 * @method hideAll 33347 */ 33348 hideAll: function() { 33349 var self = this; 33350 33351 this.find('menuitem').exec('hideMenu'); 33352 33353 return self._super(); 33354 }, 33355 33356 /** 33357 * Invoked before the menu is rendered. 33358 * 33359 * @method preRender 33360 */ 33361 preRender: function() { 33362 var self = this; 33363 33364 self.items().each(function(ctrl) { 33365 var settings = ctrl.settings; 33366 33367 if (settings.icon || settings.selectable) { 33368 self._hasIcons = true; 33369 return false; 33370 } 33371 }); 33372 33373 return self._super(); 33374 } 33375 }); 33376 33377 return Menu; 33378}); 33379 33380// Included from: js/tinymce/classes/ui/Radio.js 33381 33382/** 33383 * Radio.js 33384 * 33385 * Copyright, Moxiecode Systems AB 33386 * Released under LGPL License. 33387 * 33388 * License: http://www.tinymce.com/license 33389 * Contributing: http://www.tinymce.com/contributing 33390 */ 33391 33392/** 33393 * Creates a new radio button. 33394 * 33395 * @-x-less Radio.less 33396 * @class tinymce.ui.Radio 33397 * @extends tinymce.ui.Checkbox 33398 */ 33399define("tinymce/ui/Radio", [ 33400 "tinymce/ui/Checkbox" 33401], function(Checkbox) { 33402 "use strict"; 33403 33404 return Checkbox.extend({ 33405 Defaults: { 33406 classes: "radio", 33407 role: "radio" 33408 } 33409 }); 33410}); 33411 33412// Included from: js/tinymce/classes/ui/ResizeHandle.js 33413 33414/** 33415 * ResizeHandle.js 33416 * 33417 * Copyright, Moxiecode Systems AB 33418 * Released under LGPL License. 33419 * 33420 * License: http://www.tinymce.com/license 33421 * Contributing: http://www.tinymce.com/contributing 33422 */ 33423 33424/** 33425 * Renders a resize handle that fires ResizeStart, Resize and ResizeEnd events. 33426 * 33427 * @-x-less ResizeHandle.less 33428 * @class tinymce.ui.ResizeHandle 33429 * @extends tinymce.ui.Widget 33430 */ 33431define("tinymce/ui/ResizeHandle", [ 33432 "tinymce/ui/Widget", 33433 "tinymce/ui/DragHelper" 33434], function(Widget, DragHelper) { 33435 "use strict"; 33436 33437 return Widget.extend({ 33438 /** 33439 * Renders the control as a HTML string. 33440 * 33441 * @method renderHtml 33442 * @return {String} HTML representing the control. 33443 */ 33444 renderHtml: function() { 33445 var self = this, prefix = self.classPrefix; 33446 33447 self.addClass('resizehandle'); 33448 33449 if (self.settings.direction == "both") { 33450 self.addClass('resizehandle-both'); 33451 } 33452 33453 self.canFocus = false; 33454 33455 return ( 33456 '<div id="' + self._id + '" class="' + self.classes() + '">' + 33457 '<i class="' + prefix + 'ico ' + prefix + 'i-resize"></i>' + 33458 '</div>' 33459 ); 33460 }, 33461 33462 /** 33463 * Called after the control has been rendered. 33464 * 33465 * @method postRender 33466 */ 33467 postRender: function() { 33468 var self = this; 33469 33470 self._super(); 33471 33472 self.resizeDragHelper = new DragHelper(this._id, { 33473 start: function() { 33474 self.fire('ResizeStart'); 33475 }, 33476 33477 drag: function(e) { 33478 if (self.settings.direction != "both") { 33479 e.deltaX = 0; 33480 } 33481 33482 self.fire('Resize', e); 33483 }, 33484 33485 end: function() { 33486 self.fire('ResizeEnd'); 33487 } 33488 }); 33489 }, 33490 33491 remove: function() { 33492 if (this.resizeDragHelper) { 33493 this.resizeDragHelper.destroy(); 33494 } 33495 33496 return this._super(); 33497 } 33498 }); 33499}); 33500 33501// Included from: js/tinymce/classes/ui/Spacer.js 33502 33503/** 33504 * Spacer.js 33505 * 33506 * Copyright, Moxiecode Systems AB 33507 * Released under LGPL License. 33508 * 33509 * License: http://www.tinymce.com/license 33510 * Contributing: http://www.tinymce.com/contributing 33511 */ 33512 33513/** 33514 * Creates a spacer. This control is used in flex layouts for example. 33515 * 33516 * @-x-less Spacer.less 33517 * @class tinymce.ui.Spacer 33518 * @extends tinymce.ui.Widget 33519 */
33520define("tinymce/ui/Spacer", [ 33521 "tinymce/ui/Widget" 33522], function(Widget) { 33523 "use strict"; 33524 33525 return Widget.extend({ 33526 /** 33527 * Renders the control as a HTML string. 33528 * 33529 * @method renderHtml 33530 * @return {String} HTML representing the control. 33531 */ 33532 renderHtml: function() { 33533 var self = this; 33534 33535 self.addClass('spacer'); 33536 self.canFocus = false; 33537 33538 return '<div id="' + self._id + '" class="' + self.classes() + '"></div>'; 33539 } 33540 }); 33541}); 33542 33543// Included from: js/tinymce/classes/ui/SplitButton.js 33544 33545/** 33546 * SplitButton.js 33547 * 33548 * Copyright, Moxiecode Systems AB 33549 * Released under LGPL License. 33550 * 33551 * License: http://www.tinymce.com/license 33552 * Contributing: http://www.tinymce.com/contributing 33553 */ 33554 33555/** 33556 * Creates a split button. 33557 * 33558 * @-x-less SplitButton.less 33559 * @class tinymce.ui.SplitButton 33560 * @extends tinymce.ui.Button 33561 */ 33562define("tinymce/ui/SplitButton", [ 33563 "tinymce/ui/MenuButton", 33564 "tinymce/ui/DomUtils" 33565], function(MenuButton, DomUtils) { 33566 return MenuButton.extend({ 33567 Defaults: { 33568 classes: "widget btn splitbtn", 33569 role: "splitbutton" 33570 }, 33571 33572 /** 33573 * Repaints the control after a layout operation. 33574 * 33575 * @method repaint 33576 */ 33577 repaint: function() { 33578 var self = this, elm = self.getEl(), rect = self.layoutRect(), mainButtonElm, menuButtonElm; 33579 33580 self._super(); 33581 33582 mainButtonElm = elm.firstChild; 33583 menuButtonElm = elm.lastChild; 33584 33585 DomUtils.css(mainButtonElm, { 33586 width: rect.w - DomUtils.getSize(menuButtonElm).width, 33587 height: rect.h - 2 33588 }); 33589 33590 DomUtils.css(menuButtonElm, { 33591 height: rect.h - 2 33592 }); 33593 33594 return self; 33595 }, 33596 33597 /** 33598 * Sets the active menu state. 33599 * 33600 * @private 33601 */ 33602 activeMenu: function(state) { 33603 var self = this; 33604 33605 DomUtils.toggleClass(self.getEl().lastChild, self.classPrefix + 'active', state); 33606 }, 33607 33608 /** 33609 * Renders the control as a HTML string. 33610 * 33611 * @method renderHtml 33612 * @return {String} HTML representing the control. 33613 */ 33614 renderHtml: function() { 33615 var self = this, id = self._id, prefix = self.classPrefix; 33616 var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : ''; 33617 33618 return ( 33619 '<div id="' + id + '" class="' + self.classes() + '">' + 33620 '<button type="button" hidefocus tabindex="-1">' + 33621 (icon ? '<i class="' + icon + '"></i>' : '') + 33622 (self._text ? (icon ? ' ' : '') + self._text : '') + 33623 '</button>' + 33624 '<button type="button" class="' + prefix + 'open" hidefocus tabindex="-1">' + 33625 //(icon ? '<i class="' + icon + '"></i>' : '') + 33626 (self._menuBtnText ? (icon ? '\u00a0' : '') + self._menuBtnText : '') + 33627 ' <i class="' + prefix + 'caret"></i>' + 33628 '</button>' + 33629 '</div>' 33630 ); 33631 }, 33632 33633 /** 33634 * Called after the control has been rendered. 33635 * 33636 * @method postRender 33637 */ 33638 postRender: function() { 33639 var self = this, onClickHandler = self.settings.onclick; 33640 33641 self.on('click', function(e) { 33642 var node = e.target; 33643 33644 if (e.control == this) { 33645 // Find clicks that is on the main button 33646 while (node) { 33647 if (node.nodeName == 'BUTTON' && node.className.indexOf('open') == -1) { 33648 e.stopImmediatePropagation(); 33649 onClickHandler.call(this, e); 33650 return; 33651 } 33652 33653 node = node.parentNode; 33654 } 33655 } 33656 }); 33657 33658 delete self.settings.onclick; 33659 33660 return self._super(); 33661 } 33662 }); 33663}); 33664 33665// Included from: js/tinymce/classes/ui/StackLayout.js 33666 33667/** 33668 * StackLayout.js 33669 * 33670 * Copyright, Moxiecode Systems AB 33671 * Released under LGPL License. 33672 * 33673 * License: http://www.tinymce.com/license 33674 * Contributing: http://www.tinymce.com/contributing 33675 */ 33676 33677/** 33678 * This layout uses the browsers layout when the items are blocks. 33679 * 33680 * @-x-less StackLayout.less 33681 * @class tinymce.ui.StackLayout 33682 * @extends tinymce.ui.FlowLayout 33683 */ 33684define("tinymce/ui/StackLayout", [ 33685 "tinymce/ui/FlowLayout" 33686], function(FlowLayout) { 33687 "use strict"; 33688 33689 return FlowLayout.extend({ 33690 Defaults: { 33691 containerClass: 'stack-layout', 33692 controlClass: 'stack-layout-item', 33693 endClass : 'break' 33694 } 33695 }); 33696}); 33697 33698// Included from: js/tinymce/classes/ui/TabPanel.js 33699 33700/** 33701 * TabPanel.js 33702 * 33703 * Copyright, Moxiecode Systems AB 33704 * Released under LGPL License. 33705 * 33706 * License: http://www.tinymce.com/license 33707 * Contributing: http://www.tinymce.com/contributing 33708 */ 33709 33710/** 33711 * Creates a tab panel control. 33712 * 33713 * @-x-less TabPanel.less 33714 * @class tinymce.ui.TabPanel 33715 * @extends tinymce.ui.Panel 33716 * 33717 * @setting {Number} activeTab Active tab index. 33718 */
33719define("tinymce/ui/TabPanel", [ 33720 "tinymce/ui/Panel", 33721 "tinymce/ui/DomUtils" 33722], function(Panel, DomUtils) { 33723 "use strict"; 33724 33725 return Panel.extend({ 33726 lastIdx: 0, 33727 33728 Defaults: { 33729 layout: 'absolute', 33730 defaults: { 33731 type: 'panel' 33732 } 33733 }, 33734 33735 /** 33736 * Activates the specified tab by index. 33737 * 33738 * @method activateTab 33739 * @param {Number} idx Index of the tab to activate. 33740 */ 33741 activateTab: function(idx) { 33742 if (this.activeTabId) { 33743 DomUtils.removeClass(this.getEl(this.activeTabId), this.classPrefix + 'active'); 33744 } 33745 33746 this.activeTabId = 't' + idx; 33747 33748 DomUtils.addClass(this.getEl('t' + idx), this.classPrefix + 'active'); 33749 33750 if (idx != this.lastIdx) { 33751 this.items()[this.lastIdx].hide(); 33752 this.lastIdx = idx; 33753 } 33754 33755 this.items()[idx].show().fire('showtab'); 33756 this.reflow(); 33757 }, 33758 33759 /** 33760 * Renders the control as a HTML string. 33761 * 33762 * @method renderHtml 33763 * @return {String} HTML representing the control. 33764 */ 33765 renderHtml: function() { 33766 var self = this, layout = self._layout, tabsHtml = '', prefix = self.classPrefix; 33767 33768 self.preRender(); 33769 layout.preRender(self); 33770 33771 self.items().each(function(ctrl, i) { 33772 tabsHtml += ( 33773 '<div id="' + self._id + '-t' + i + '" class="' + prefix + 'tab" unselectable="on">' + 33774 self.encode(ctrl.settings.title) + 33775 '</div>' 33776 ); 33777 }); 33778 33779 return ( 33780 '<div id="' + self._id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1">' + 33781 '<div id="' + self._id + '-head" class="' + prefix + 'tabs">' + 33782 tabsHtml + 33783 '</div>' + 33784 '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' + 33785 layout.renderHtml(self) + 33786 '</div>' + 33787 '</div>' 33788 ); 33789 }, 33790 33791 /** 33792 * Called after the control has been rendered. 33793 * 33794 * @method postRender 33795 */ 33796 postRender: function() { 33797 var self = this; 33798 33799 self._super(); 33800 33801 self.settings.activeTab = self.settings.activeTab || 0; 33802 self.activateTab(self.settings.activeTab); 33803 33804 this.on('click', function(e) { 33805 var targetParent = e.target.parentNode; 33806 33807 if (e.target.parentNode.id == self._id + '-head') { 33808 var i = targetParent.childNodes.length; 33809 33810 while (i--) { 33811 if (targetParent.childNodes[i] == e.target) { 33812 self.activateTab(i); 33813 } 33814 } 33815 } 33816 }); 33817 }, 33818 33819 /** 33820 * Initializes the current controls layout rect. 33821 * This will be executed by the layout managers to determine the 33822 * default minWidth/minHeight etc. 33823 * 33824 * @method initLayoutRect 33825 * @return {Object} Layout rect instance. 33826 */ 33827 initLayoutRect: function() { 33828 var self = this, rect, minW, minH; 33829 33830 minW = DomUtils.getSize(self.getEl('head')).width; 33831 minW = minW < 0 ? 0 : minW; 33832 minH = 0; 33833 self.items().each(function(item, i) { 33834 minW = Math.max(minW, item.layoutRect().minW); 33835 minH = Math.max(minH, item.layoutRect().minH); 33836 if (self.settings.activeTab != i) { 33837 item.hide(); 33838 } 33839 }); 33840 33841 self.items().each(function(ctrl) { 33842 ctrl.settings.x = 0; 33843 ctrl.settings.y = 0; 33844 ctrl.settings.w = minW; 33845 ctrl.settings.h = minH; 33846 33847 ctrl.layoutRect({ 33848 x: 0, 33849 y: 0, 33850 w: minW, 33851 h: minH 33852 }); 33853 }); 33854 33855 var headH = DomUtils.getSize(self.getEl('head')).height; 33856 33857 self.settings.minWidth = minW; 33858 self.settings.minHeight = minH + headH; 33859 33860 rect = self._super(); 33861 rect.deltaH += headH; 33862 rect.innerH = rect.h - rect.deltaH; 33863 33864 return rect; 33865 } 33866 }); 33867}); 33868 33869// Included from: js/tinymce/classes/ui/TextBox.js 33870 33871/** 33872 * TextBox.js 33873 * 33874 * Copyright, Moxiecode Systems AB 33875 * Released under LGPL License. 33876 * 33877 * License: http://www.tinymce.com/license 33878 * Contributing: http://www.tinymce.com/contributing 33879 */ 33880 33881/** 33882 * Creates a new textbox. 33883 * 33884 * @-x-less TextBox.less 33885 * @class tinymce.ui.TextBox 33886 * @extends tinymce.ui.Widget 33887 */ 33888define("tinymce/ui/TextBox", [ 33889 "tinymce/ui/Widget", 33890 "tinymce/ui/DomUtils" 33891], function(Widget, DomUtils) { 33892 "use strict"; 33893 33894 return Widget.extend({ 33895 /** 33896 * Constructs a instance with the specified settings. 33897 * 33898 * @constructor 33899 * @param {Object} settings Name/value object with settings. 33900 * @setting {Boolean} multiline True if the textbox is a multiline control. 33901 * @setting {Number} maxLength Max length for the textbox. 33902 * @setting {Number} size Size of the textbox in characters. 33903 */ 33904 init: function(settings) { 33905 var self = this; 33906 33907 self._super(settings); 33908 33909 self._value = settings.value || ''; 33910 self.addClass('textbox'); 33911 33912 if (settings.multiline) { 33913 self.addClass('multiline'); 33914 } else { 33915 // TODO: Rework this 33916 self.on('keydown', function(e) { 33917 if (e.keyCode == 13) { 33918 self.parents().reverse().each(function(ctrl) { 33919 e.preventDefault(); 33920 33921 if (ctrl.hasEventListeners('submit') && ctrl.toJSON) { 33922 ctrl.fire('submit', {data: ctrl.toJSON()}); 33923 return false;
33924 } 33925 }); 33926 } 33927 }); 33928 } 33929 }, 33930 33931 /** 33932 * Getter/setter function for the disabled state. 33933 * 33934 * @method value 33935 * @param {Boolean} [state] State to be set. 33936 * @return {Boolean|tinymce.ui.ComboBox} True/false or self if it's a set operation. 33937 */ 33938 disabled: function(state) { 33939 var self = this; 33940 33941 if (self._rendered && typeof(state) != 'undefined') { 33942 self.getEl().disabled = state; 33943 } 33944 33945 return self._super(state); 33946 }, 33947 33948 /** 33949 * Getter/setter function for the control value. 33950 * 33951 * @method value 33952 * @param {String} [value] Value to be set. 33953 * @return {String|tinymce.ui.ComboBox} Value or self if it's a set operation. 33954 */ 33955 value: function(value) { 33956 var self = this; 33957 33958 if (typeof(value) != "undefined") { 33959 self._value = value; 33960 33961 if (self._rendered) { 33962 self.getEl().value = value; 33963 } 33964 33965 return self; 33966 } 33967 33968 if (self._rendered) { 33969 return self.getEl().value; 33970 } 33971 33972 return self._value; 33973 }, 33974 33975 /** 33976 * Repaints the control after a layout operation. 33977 * 33978 * @method repaint 33979 */ 33980 repaint: function() { 33981 var self = this, style, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect; 33982 33983 style = self.getEl().style; 33984 rect = self._layoutRect; 33985 lastRepaintRect = self._lastRepaintRect || {}; 33986 33987 // Detect old IE 7+8 add lineHeight to align caret vertically in the middle 33988 var doc = document; 33989 if (!self.settings.multiline && doc.all && (!doc.documentMode || doc.documentMode <= 8)) { 33990 style.lineHeight = (rect.h - borderH) + 'px'; 33991 } 33992 33993 borderBox = self._borderBox; 33994 borderW = borderBox.left + borderBox.right + 8; 33995 borderH = borderBox.top + borderBox.bottom + (self.settings.multiline ? 8 : 0); 33996 33997 if (rect.x !== lastRepaintRect.x) { 33998 style.left = rect.x + 'px'; 33999 lastRepaintRect.x = rect.x; 34000 } 34001 34002 if (rect.y !== lastRepaintRect.y) { 34003 style.top = rect.y + 'px'; 34004 lastRepaintRect.y = rect.y; 34005 } 34006 34007 if (rect.w !== lastRepaintRect.w) { 34008 style.width = (rect.w - borderW) + 'px'; 34009 lastRepaintRect.w = rect.w; 34010 } 34011 34012 if (rect.h !== lastRepaintRect.h) { 34013 style.height = (rect.h - borderH) + 'px'; 34014 lastRepaintRect.h = rect.h; 34015 } 34016 34017 self._lastRepaintRect = lastRepaintRect; 34018 self.fire('repaint', {}, false); 34019 34020 return self; 34021 }, 34022 34023 /** 34024 * Renders the control as a HTML string. 34025 * 34026 * @method renderHtml 34027 * @return {String} HTML representing the control. 34028 */ 34029 renderHtml: function() { 34030 var self = this, id = self._id, settings = self.settings, value = self.encode(self._value, false), extraAttrs = ''; 34031 34032 if ("spellcheck" in settings) { 34033 extraAttrs += ' spellcheck="' + settings.spellcheck + '"'; 34034 } 34035 34036 if (settings.maxLength) { 34037 extraAttrs += ' maxlength="' + settings.maxLength + '"'; 34038 } 34039 34040 if (settings.size) { 34041 extraAttrs += ' size="' + settings.size + '"'; 34042 } 34043 34044 if (settings.subtype) { 34045 extraAttrs += ' type="' + settings.subtype + '"'; 34046 } 34047 34048 if (self.disabled()) { 34049 extraAttrs += ' disabled="disabled"'; 34050 } 34051 34052 if (settings.multiline) { 34053 return ( 34054 '<textarea id="' + id + '" class="' + self.classes() + '" ' + 34055 (settings.rows ? ' rows="' + settings.rows + '"' : '') + 34056 ' hidefocus="true"' + extraAttrs + '>' + value + 34057 '</textarea>' 34058 ); 34059 } 34060 34061 return '<input id="' + id + '" class="' + self.classes() + '" value="' + value + '" hidefocus="true"' + extraAttrs + '>'; 34062 }, 34063 34064 /** 34065 * Called after the control has been rendered. 34066 * 34067 * @method postRender 34068 */ 34069 postRender: function() { 34070 var self = this; 34071 34072 DomUtils.on(self.getEl(), 'change', function(e) { 34073 self.fire('change', e); 34074 }); 34075 34076 return self._super(); 34077 }, 34078 34079 remove: function() { 34080 DomUtils.off(this.getEl()); 34081 this._super(); 34082 } 34083 }); 34084}); 34085 34086// Included from: js/tinymce/classes/ui/Throbber.js 34087 34088/** 34089 * Throbber.js 34090 * 34091 * Copyright, Moxiecode Systems AB 34092 * Released under LGPL License. 34093 * 34094 * License: http://www.tinymce.com/license 34095 * Contributing: http://www.tinymce.com/contributing 34096 */ 34097 34098/** 34099 * This class enables you to display a Throbber for any element. 34100 * 34101 * @-x-less Throbber.less 34102 * @class tinymce.ui.Throbber 34103 */
34104define("tinymce/ui/Throbber", [ 34105 "tinymce/ui/DomUtils" 34106], function(DomUtils) { 34107 "use strict"; 34108 34109 /** 34110 * Constructs a new throbber. 34111 * 34112 * @constructor 34113 * @param {Element} elm DOM Html element to display throbber in. 34114 */ 34115 return function(elm) { 34116 var self = this, state; 34117 34118 /** 34119 * Shows the throbber. 34120 * 34121 * @method show 34122 * @param {Number} [time] Time to wait before showing. 34123 * @return {tinymce.ui.Throbber} Current throbber instance. 34124 */ 34125 self.show = function(time) { 34126 self.hide(); 34127 34128 state = true; 34129 34130 window.setTimeout(function() { 34131 if (state) { 34132 elm.appendChild(DomUtils.createFragment('<div class="mce-throbber"></div>')); 34133 } 34134 }, time || 0); 34135 34136 return self; 34137 }; 34138 34139 /** 34140 * Hides the throbber. 34141 * 34142 * @method hide 34143 * @return {tinymce.ui.Throbber} Current throbber instance. 34144 */ 34145 self.hide = function() { 34146 var child = elm.lastChild; 34147 34148 if (child && child.className.indexOf('throbber') != -1) { 34149 child.parentNode.removeChild(child); 34150 } 34151 34152 state = false; 34153 34154 return self; 34155 }; 34156 }; 34157}); 34158 34159expose(["tinymce/dom/EventUtils","tinymce/dom/Sizzle","tinymce/dom/DomQuery","tinymce/html/Styles","tinymce/dom/TreeWalker","tinymce/util/Tools","tinymce/dom/Range","tinymce/html/Entities","tinymce/Env","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/Selection","tinymce/dom/RangeUtils","tinymce/Formatter","tinymce/UndoManager","tinymce/EnterKey","tinymce/ForceBlocks","tinymce/EditorCommands","tinymce/util/URI","tinymce/util/Class","tinymce/ui/Selector","tinymce/ui/Collection","tinymce/ui/DomUtils","tinymce/ui/Control","tinymce/ui/Factory","tinymce/ui/Container","tinymce/ui/DragHelper","tinymce/ui/Scrollable","tinymce/ui/Panel","tinymce/ui/Movable","tinymce/ui/Resizable","tinymce/ui/FloatPanel","tinymce/ui/KeyboardNavigation","tinymce/ui/Window","tinymce/ui/MessageBox","tinymce/WindowManager","tinymce/util/Quirks","tinymce/util/Observable","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/PanelButton","tinymce/ui/ColorButton","tinymce/ui/ComboBox","tinymce/ui/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"]); 34160})(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.